Files
ticket-workbench/docs/05-代码审查/代码审查报告.md
绝尘 633ba27710 优化: 文档整理到 docs 目录
参考 u-desk 项目结构,按编号分类:
- 01-设计文档/ (功能设计 + 数据库设计)
- 05-代码审查/ (代码审查报告)
- 07-项目管理/ (工作报告)
- INDEX.md 索引入口
2026-05-14 01:05:25 +08:00

13 KiB
Raw Permalink Blame History

审查报告: ticket-workbench (Go + Vue)

语言: Go 1.24 / Vue 3 + Arco Design | 后端 19 文件 | 前端 14 文件


🔴 必须修复 (10)

① [detail.vue:7,17,22,117] 前端字段名与后端 API 返回不匹配 — 页面无法正常渲染

改什么: 后端返回 ticketid/contactname/contactphone/createtime (snake_case),前端用 ticket.id/ticket.contactName/ticket.contactPhone/ticket.createdAt (camelCase)。同样 note 用 note.id/note.createdAt 而后端返回 noteid/createtime

怎么改:

- <a-descriptions-item label="工单编号">{{ ticket.id }}</a-descriptions-item>
+ <a-descriptions-item label="工单编号">{{ ticket.ticketid }}</a-descriptions-item>

- <a-descriptions-item label="联系人">{{ ticket.contactName }}</a-descriptions-item>
+ <a-descriptions-item label="联系人">{{ ticket.contactname }}</a-descriptions-item>

- <a-descriptions-item label="联系电话">{{ ticket.contactPhone || '-' }}</a-descriptions-item>
+ <a-descriptions-item label="联系电话">{{ ticket.contactphone || '-' }}</a-descriptions-item>

- <a-descriptions-item label="创建时间">{{ ticket.createdAt }}</a-descriptions-item>
+ <a-descriptions-item label="创建时间">{{ ticket.createtime }}</a-descriptions-item>

- <a-timeline-item v-for="note in notes" :key="note.id">
+ <a-timeline-item v-for="note in notes" :key="note.noteid">

- <div class="note-time">{{ note.createdAt }}</div>
+ <div class="note-time">{{ note.createtime }}</div>

② [ticket_service.go:23,29] 工单列表默认过滤 bug — 不传 status/priority 时只查 status=0 且 priority=0 的记录

改什么: int16 零值是 0if status >= 0 对零值成立。用户不传筛选参数时SQL 自动追加 WHERE status=0 AND priority=0,查不到其他数据。

怎么改: DTO 改为指针类型service 判 nil

// dto/ticket.go
  type TicketListQuery struct {
-     Status   int16  `form:"status"`
+     Status   *int16 `form:"status"`
      Category string `form:"category"`
-     Priority int16  `form:"priority"`
+     Priority *int16 `form:"priority"`
      Keyword  string `form:"keyword"`
      Page     int    `form:"page"`
      PageSize int    `form:"pageSize"`
  }

// ticket_service.go
- if status >= 0 {
-     query = query.Where("status = ?", status)
+ if status != nil {
+     query = query.Where("status = ?", *status)
  }
- if priority >= 0 {
-     query = query.Where("priority = ?", priority)
+ if priority != nil {
+     query = query.Where("priority = ?", *priority)
  }

handler 传参也要同步调整:

// ticket_handler.go
- result, err := service.ListTickets(db, query.Status, query.Category, query.Priority, query.Keyword, query.Page, query.PageSize)
+ result, err := service.ListTickets(db, query.Status, query.Category, query.Priority, query.Keyword, query.Page, query.PageSize)

③ [auth_service.go:13] session map 无并发保护 — 并发登录/请求会 panic

改什么: sessions 是裸 mapgin 并发读写会触发 fatal error: concurrent map writes

怎么改: 加 sync.RWMutex

+ import "sync"

- var sessions = make(map[string]*model.TicketUser)
+ var (
+     sessions  = make(map[string]*model.TicketUser)
+     sessionMu sync.RWMutex
+ )

  // Login
+ sessionMu.Lock()
  sessions[sessionID] = &user
+ sessionMu.Unlock()

  // Logout
+ sessionMu.Lock()
  delete(sessions, sessionID)
+ sessionMu.Unlock()

  // GetUserBySession
+ sessionMu.RLock()
+ defer sessionMu.RUnlock()
  return sessions[sessionID]

④ [config.yaml:4-11] 数据库密码和 AI API Key 硬编码在配置文件中

改什么: Lake@2019 和 GLM API Key 明文写在版本控制的 config.yaml 中。

怎么改: config.yaml 加入 .gitignore,创建 config.example.yaml 作为模板。程序支持环境变量覆盖:

// config.go Load 函数中加入
v.AutomaticEnv()
v.SetEnvPrefix("TKT")

⑤ [detail.vue:238-241] 状态更新调错 API — 用了 updateTicket 而非 updateTicketStatus

改什么: handleUpdateStatus 调用 updateTicket(id, { status }),对应 PUT /tickets/:id,但该接口不处理 status 字段变更。

怎么改:

  async function handleUpdateStatus(status: number) {
    try {
-     await updateTicket(ticketId, { status })
+     await updateTicketStatus(ticketId, status)
      Message.success('状态更新成功')
      fetchDetail()

⑥ [detail.vue:214-236] AI 确认调错 API — 用 updateTicket 替代了 confirmAnalysis

改什么: handleConfirmAnalysis 调用 updateTicketaiAnalysissuggestedRole 字段,但后端 UpdateTicket 不处理这些字段。

怎么改:

  async function handleConfirmAnalysis() {
    confirming.value = true
    try {
-     const aiAnalysis = JSON.stringify({
-       category: analysisForm.category,
-       priority: analysisForm.priority,
-       summary: analysisForm.summary,
-       suggestedRole: analysisForm.suggestedRole
-     })
-     await updateTicket(ticketId, {
-       category: analysisForm.category,
-       priority: analysisForm.priority,
-       aiAnalysis,
-       suggestedRole: analysisForm.suggestedRole
-     })
+     await confirmAnalysis(ticketId, {
+       category: analysisForm.category,
+       priority: analysisForm.priority,
+       summary: analysisForm.summary
+     })
      Message.success('确认成功')
      fetchDetail()

同时需要在 import 中引入 confirmAnalysis

  import {
    getTicketDetail,
-   updateTicket,
    analyzeTicket,
+   confirmAnalysis,
    getTicketNotes,
    createNote
  } from '@/api/ticket'

⑦ [auth_handler.go:52] /auth/me 返回空 account — middleware 未设置 account 到 context

改什么: c.GetString("account") 取不到值Auth 中间件只设置了 userid/username/role/team,没有 account

怎么改:

// middleware/auth.go
  c.Set("userid", user.Userid)
  c.Set("username", user.Username)
+ c.Set("account", user.Account)
  c.Set("role", user.Role)
  c.Set("team", user.Team)

⑧ [detail.vue:178,180] ticketData.assigneeticketData.aiAnalysis 字段不存在

改什么: Ticket model 没有 assigneeaiAnalysis 字段,访问 undefined 对象导致运行时问题。AI 分析需通过 getAnalysis(ticketId) 单独获取。

怎么改:

  async function fetchDetail() {
    loading.value = true
    try {
-     const [ticketData, notesData] = await Promise.all([
+     const [ticketData, notesData, analysisData] = await Promise.all([
        getTicketDetail(ticketId),
-       getTicketNotes(ticketId)
+       getTicketNotes(ticketId),
+       getAnalysis(ticketId).catch(() => [])
      ])
      ticket.value = ticketData
      notes.value = notesData
-     assignee.value = ticketData.assignee || ''

-     if (ticketData.aiAnalysis) {
-       try {
-         const analysis = JSON.parse(ticketData.aiAnalysis)
+     if (analysisData && analysisData.length > 0) {
+       const latest = analysisData[0]
          analysisForm.category = latest.category || ''
          analysisForm.priority = latest.priority || 0
          analysisForm.summary = latest.summary || ''
-         analysisForm.suggestedRole = analysis.suggested_role || analysis.suggestedRole || ''
+         analysisForm.suggestedRole = latest.suggestrole || ''
-       } catch {
-         analysisForm.summary = ticketData.aiAnalysis
-       }
      }

同时需要在 import 中引入 getAnalysis

  import {
    getTicketDetail,
    updateTicketStatus,
    analyzeTicket,
+   getAnalysis,
+   confirmAnalysis,
    getTicketNotes,
    createNote
  } from '@/api/ticket'

⑨ [analysis_service.go:93] AI 返回 JSON 解析过于脆弱 — 没处理 markdown 代码块包裹

改什么: AI 模型经常返回 ```json {...} ``` 格式,直接 json.Unmarshal 会失败。

怎么改:

+ import "strings"

  content := glmResp.Choices[0].Message.Content
+ // 去除 markdown 代码块包裹
+ content = strings.TrimSpace(content)
+ if strings.HasPrefix(content, "```") {
+     if idx := strings.Index(content, "\n"); idx >= 0 {
+         content = content[idx+1:]
+     }
+     if idx := strings.LastIndex(content, "```"); idx >= 0 {
+         content = content[:idx]
+     }
+     content = strings.TrimSpace(content)
+ }

  var result AnalysisResult
- if err := json.Unmarshal([]byte(glmResp.Choices[0].Message.Content), &result); err != nil {
+ if err := json.Unmarshal([]byte(content), &result); err != nil {

⑩ [main.go] 缺少 AutoMigrate — 首次启动表不存在会报错

改什么: 没有 db.AutoMigrate(...) 调用,数据库表需手动创建。

怎么改:

  sqlDB.SetMaxOpenConns(100)

+ if err := db.AutoMigrate(
+     &model.TicketUser{},
+     &model.TicketInfo{},
+     &model.TicketAiAnalysis{},
+     &model.TicketNote{},
+     &model.TicketOperationLog{},
+ ); err != nil {
+     log.Fatalf("Failed to auto migrate: %v", err)
+ }

  gin.SetMode(gin.ReleaseMode)

🟡 建议改进 (7)

⑪ [auth_service.go:15-18] MD5 哈希密码不安全

说明: MD5 可快速碰撞/彩虹表。生产环境应使用 bcrypt。


⑫ [analysis_service.go:41-54] AI prompt 注入风险

说明: 用户提交的工单内容直接拼入 prompt恶意用户可构造内容操控 AI 输出格式/内容。应对用户输入做转义或用 structured prompt 模式。


⑬ [ticket_service.go:111] UpdateTicket 不检查记录是否存在

说明: 传入不存在的 ticketid 时 Updates 不报错0 rows affected返回成功。应检查 RowsAffected 或先用 First 确认存在。


⑭ [analysis_service.go:64,81] json.Marshal 和 io.ReadAll 错误被忽略

说明: jsonData, _ := json.Marshal(reqBody)body, _ := io.ReadAll(resp.Body) 都丢弃了 error。


⑮ [detail.vue:89-99] 处理人下拉硬编码 admin/user1/user2

说明: 应从后端获取用户列表,或至少与数据库用户数据一致。当前硬编码值 admin/user1/user2 与后端不对应。


⑯ [create.vue:66] form 提交 submitterid: 1 硬编码

说明: 后端已从 Auth 中间件获取真实 userid此字段多余且误导。应移除。


⑰ [store/user.ts:21] login 存 username 用的是 account 而非实际 username

说明: setUsername(account) 存的是登录账号,不是用户真实姓名。应使用 res.user.username

  async function login(account: string, password: string) {
    const res = await loginApi({ account, password })
    setToken(res.token)
-   setUsername(account)
+   setUsername(res.user.username)
  }

可选优化 (3)

⑱ [ticket_service.go:104] if priority >= 0 导致 priority=0 无法区分"未传"和"P0紧急"

说明: 与问题②同类UpdateTicket 的 priority 参数也应改为指针类型。


⑲ [ticket_service.go:58] ticketno 用 UUID 前 8 位有碰撞风险

说明: uuid.New().String()[:8] 理论上可能重复。可加时间戳或用更长前缀。


⑳ [analysis_service.go:30] AnalyzeTicket 参数过多

说明: 8 个参数的函数签名过长,建议传入结构体。


亮点

  • 后端分层清晰 (handler/service/model/dto)
  • 前端类型定义完整API 层封装规范
  • 操作日志设计完善,关键动作都有记录
  • CORS 中间件处理周全

📊 摘要

# 等级 文件:行 修改内容
🔴 detail.vue:7,17,22 前端字段名与后端不匹配,页面无法渲染
🔴 ticket_service.go:23 列表默认过滤 bug不传参只查 status=0
🔴 auth_service.go:13 session map 无锁,并发 panic
🔴 config.yaml:4-11 密码和 API Key 硬编码
🔴 detail.vue:238 状态更新调错 API
🔴 detail.vue:214 AI 确认调错 API
🔴 auth_handler.go:52 /auth/me 返回空 account
🔴 detail.vue:178 访问不存在的 ticket.assignee/aiAnalysis 字段
🔴 analysis_service.go:93 AI JSON 解析未处理 markdown 包裹
🔴 main.go 缺少 AutoMigrate
🟡 auth_service.go:15 MD5 密码哈希不安全
🟡 analysis_service.go:41 AI prompt 注入风险
🟡 ticket_service.go:111 更新不检查记录是否存在
🟡 analysis_service.go:64,81 错误被忽略
🟡 detail.vue:89 处理人下拉硬编码
🟡 create.vue:66 submitterid 硬编码
🟡 store/user.ts:21 username 存为 account
ticket_service.go:104 priority 零值歧义
ticket_service.go:58 ticketno 碰撞风险
analysis_service.go:30 函数参数过多

总计: 🔴10 🟡7 3

总体评价: 前端 detail.vue 与后端严重脱节,核心流程跑不通;后端有并发安全隐患和过滤逻辑 bug

质量评级: 需重构