DevTools:从零构建全栈开发者工具站

开发者日常工作中经常需要各种小工具:格式化 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 跨域请求控制

扩展新工具

添加新工具只需三步:

  1. 创建 Vue 组件 frontend/src/views/NewTool.vue
  2. 添加路由配置(包含 meta 信息)
  3. (可选)添加后端 API

菜单会自动生成,无需额外配置。

总结

DevTools 展示了一个完整的全栈项目应该具备的特性:

  • 现代前端:Vue 3 组合式 API + 响应式设计
  • 高效后端:Go 高并发 + SQLite 轻量存储
  • 实时通讯:WebSocket 双向通信
  • 安全机制:多层防护确保服务稳定
  • 容器部署:Docker 一键启动

这个项目既可作为日常工具使用,也是学习全栈开发的优秀参考。


项目地址GitHub 在线演示t.jaxiu.cn