开发者日常工作中经常需要各种小工具:格式化 JSON、对比文本差异、转换时间戳、测试正则表达式……与其每次都搜索在线工具,不如自己搭建一个集成平台。
本文介绍 DevTools —— 一个功能完整的开发者工具站,包含 16+ 种常用工具,支持实时聊天室和短链服务。我们将深入分析其技术架构和实现细节。
项目概览
技术栈:
- 前端:Vue 3 + Vite + Element Plus + TailwindCSS
- 后端:Go (Gin) + SQLite
- 部署:Docker 多阶段构建
核心功能:
| 工具 | 功能描述 |
|---|---|
| JSON 工具 | 格式化、压缩、校验、JSONPath 查询 |
| Diff 对比 | 字符/单词/行级差异对比 |
| Markdown | 实时预览、导出 HTML/PDF |
| 共享粘贴板 | 临时分享代码片段 |
| Base64 | 文本/图片编解码 |
| 时间戳 | 时间戳转换与计算 |
| 正则测试 | 正则表达式在线测试 |
| Mermaid | 流程图/时序图绘制 |
| 聊天室 | WebSocket 实时通讯 |
| 短链服务 | URL 缩短与统计 |
架构设计
整体架构采用前后端分离:
graph TB
subgraph Frontend["前端 (Vue 3)"]
Router[Vue Router]
Views[16个工具组件]
UI[Element Plus + Tailwind]
end
subgraph Backend["后端 (Go Gin)"]
API[REST API]
WS[WebSocket]
MW[中间件层]
end
subgraph Storage["存储"]
SQLite[(SQLite)]
FS[文件系统]
end
Frontend --> Backend
API --> SQLite
WS --> SQLite
API --> FS
前端实现
路由与代码分割
使用动态导入实现按需加载,优化首屏性能:
// router/index.js
const routes = [
{
path: '/json',
name: 'JsonTool',
component: () => import('../views/JsonTool.vue'),
meta: { title: 'JSON 工具', icon: 'Document' }
},
// ... 其他路由
]
菜单从路由元数据自动生成,添加新工具只需新增路由配置:
// App.vue
const menuRoutes = computed(() =>
routes.filter(r => r.meta?.title && !r.path.includes(':'))
)
响应式布局
采用 768px 作为分界点,PC 端显示可折叠侧边栏,移动端使用抽屉菜单:
<template>
<!-- PC 端侧边栏 -->
<el-aside v-if="!isMobile" :width="isCollapsed ? '64px' : '200px'">
<el-menu :collapse="isCollapsed">
<!-- 菜单项 -->
</el-menu>
</el-aside>
<!-- 移动端抽屉 -->
<el-drawer v-if="isMobile" v-model="drawerVisible">
<el-menu><!-- 菜单项 --></el-menu>
</el-drawer>
</template>
Keep-alive 缓存
使用 keep-alive 保持组件状态,切换工具时不丢失输入数据:
<router-view v-slot="{ Component }">
<keep-alive>
<component :is="Component" />
</keep-alive>
</router-view>
后端实现
API 路由设计
采用 RESTful 风格,按功能模块分组:
// main.go
func setupRoutes(r *gin.Engine) {
api := r.Group("/api")
// 粘贴板
paste := api.Group("/paste")
paste.POST("", rateLimiter.Middleware(), pasteHandler.Create)
paste.GET("/:id", pasteHandler.Get)
paste.GET("/:id/info", pasteHandler.GetInfo)
// 聊天室
chat := api.Group("/chat")
chat.POST("/room", rateLimiter.Middleware(), chatHandler.CreateRoom)
chat.GET("/room/:id/ws", chatHandler.WebSocket)
// 短链
api.POST("/shorturl", rateLimiter.Middleware(), shortHandler.Create)
r.GET("/s/:id", shortHandler.Redirect)
}
限流中间件
基于 IP 的滑动窗口限流:
// middleware/ratelimit.go
type RateLimiter struct {
requests map[string][]time.Time
limit int
window time.Duration
mu sync.RWMutex
}
func (rl *RateLimiter) Allow(ip string) bool {
rl.mu.Lock()
defer rl.mu.Unlock()
now := time.Now()
windowStart := now.Add(-rl.window)
// 清理过期请求
valid := make([]time.Time, 0)
for _, t := range rl.requests[ip] {
if t.After(windowStart) {
valid = append(valid, t)
}
}
if len(valid) >= rl.limit {
return false
}
rl.requests[ip] = append(valid, now)
return true
}
WebSocket 聊天室
实现实时通讯的核心架构:
// handlers/chat.go
type ChatHandler struct {
rooms map[string]*Room
mu sync.RWMutex
}
type Room struct {
clients map[*Client]bool
mu sync.RWMutex
}
type Client struct {
conn *websocket.Conn
nickname string
send chan []byte
room *Room
}
// 消息广播
func (r *Room) broadcast(msg []byte) {
r.mu.RLock()
defer r.mu.RUnlock()
for client := range r.clients {
select {
case client.send <- msg:
default:
// 发送失败,关闭连接
close(client.send)
delete(r.clients, client)
}
}
}
每个客户端启动两个 Goroutine:
readPump:从 WebSocket 读取消息并广播writePump:从发送队列写入 WebSocket
func (c *Client) readPump() {
defer func() {
c.room.unregister(c)
c.conn.Close()
}()
for {
_, message, err := c.conn.ReadMessage()
if err != nil {
break
}
c.room.broadcast(message)
}
}
func (c *Client) writePump() {
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
for {
select {
case message := <-c.send:
c.conn.WriteMessage(websocket.TextMessage, message)
case <-ticker.C:
c.conn.WriteMessage(websocket.PingMessage, nil)
}
}
}
文件上传安全
通过魔数检测确保文件类型安全:
func detectFileType(data []byte) string {
signatures := map[string][]byte{
"image/jpeg": {0xFF, 0xD8, 0xFF},
"image/png": {0x89, 0x50, 0x4E, 0x47},
"image/gif": {0x47, 0x49, 0x46},
"video/mp4": {0x66, 0x74, 0x79, 0x70}, // ftyp
}
for mimeType, sig := range signatures {
if bytes.HasPrefix(data, sig) {
return mimeType
}
}
return ""
}
数据库设计
使用 SQLite 作为存储,简单高效:
-- 粘贴板
CREATE TABLE pastes (
id TEXT PRIMARY KEY,
content TEXT NOT NULL,
title TEXT DEFAULT '',
language TEXT DEFAULT 'text',
password TEXT DEFAULT '',
expires_at DATETIME,
max_views INTEGER DEFAULT 100,
views INTEGER DEFAULT 0,
created_at DATETIME,
creator_ip TEXT
);
-- 短链
CREATE TABLE short_urls (
id TEXT PRIMARY KEY,
original_url TEXT NOT NULL,
expires_at DATETIME,
max_clicks INTEGER DEFAULT 1000,
clicks INTEGER DEFAULT 0,
created_at DATETIME,
creator_ip TEXT
);
自动清理机制
后台 Goroutine 每小时清理过期数据:
func startCleanupTask(db *models.DB) {
ticker := time.NewTicker(time.Hour)
for range ticker.C {
db.CleanExpiredPastes()
db.CleanExpiredShortURLs()
db.CleanInactiveRooms(7 * 24 * time.Hour)
utils.CleanExpiredUploads("./data/uploads", 7)
}
}
Docker 部署
多阶段构建优化镜像体积:
# 前端构建
FROM node:20-alpine AS frontend
WORKDIR /app/frontend
COPY frontend/package*.json ./
RUN npm install
COPY frontend/ ./
RUN npm run build
# 后端构建
FROM golang:1.21-alpine AS backend
WORKDIR /app
RUN apk add --no-cache gcc musl-dev sqlite-dev
COPY backend/ ./
RUN CGO_ENABLED=1 go build -o server .
# 生产镜像
FROM alpine:latest
WORKDIR /app
COPY --from=backend /app/server .
COPY --from=frontend /app/frontend/dist ./dist
EXPOSE 8082
CMD ["./server"]
最终镜像不到 100MB,包含完整的前后端应用。
安全机制总结
| 层面 | 措施 |
|---|---|
| 请求限流 | IP 维度滑动窗口限流 |
| 内容限制 | 文本 100KB,图片 30MB |
| 密码保护 | SHA256 哈希存储 |
| 文件验证 | 魔数检测 + 扩展名白名单 |
| 数据清理 | 定时清理过期数据 |
| CORS | 跨域请求控制 |
扩展新工具
添加新工具只需三步:
- 创建 Vue 组件
frontend/src/views/NewTool.vue - 添加路由配置(包含 meta 信息)
- (可选)添加后端 API
菜单会自动生成,无需额外配置。
总结
DevTools 展示了一个完整的全栈项目应该具备的特性:
- 现代前端:Vue 3 组合式 API + 响应式设计
- 高效后端:Go 高并发 + SQLite 轻量存储
- 实时通讯:WebSocket 双向通信
- 安全机制:多层防护确保服务稳定
- 容器部署:Docker 一键启动
这个项目既可作为日常工具使用,也是学习全栈开发的优秀参考。
项目地址:GitHub 在线演示:t.jaxiu.cn