辣评关键 Bug 修复总结(二十七)

 

时间线说明

本文创建于 2026-02-06,后续持续补充了同一阶段内的关键修复案例。
文中各条 Git 提交 以实际提交日期为准,部分提交晚于本文初稿时间,属于后续追记内容。


Bug 修复总结概述

在辣评项目的开发过程中,我们遇到并解决了多个关键 Bug。本文总结了这些重要的 Bug 修复经验,包括问题现象、排查过程、解决方案和预防措施。

主要 Bug 类型

  • 数据库迁移问题
  • 前端空白页面问题
  • 跳转逻辑错误
  • SQL 歧义问题
  • 重复提交处理

Bug 1: 数据库表自动迁移问题

问题现象

Git 提交: e7c9c54 - fix: 修复数据库表自动迁移问题,添加 DebtRecord 模型(提交日期:2026-02-28)

症状:

  • 应用启动时报错:table debt_records not found
  • 补评功能无法正常使用
  • 数据库表缺失

问题排查

  1. 检查模型定义
    // 发现 DebtRecord 模型已定义但未添加到 AutoMigrate
    type DebtRecord struct {
        ID            uint
        UserID        uint
        CompetitionID uint
        DebtCount     int
        Status        string
        // ...
    }
    
  2. 检查数据库初始化代码
    // cmd/server/database/database.go
    func Initialize() error {
        // ...
        db.AutoMigrate(
            &models.User{},
            &models.Submission{},
            &models.Comment{},
            // DebtRecord 缺失!
        )
    }
    

解决方案

// cmd/server/database/database.go
func Initialize() error {
    // ...
    db.AutoMigrate(
        &models.User{},
        &models.Submission{},
        &models.Comment{},
        &models.Competition{},
        &models.QualificationStatistics{},
        &models.DebtRecord{},        // 添加缺失的模型
        &models.TaskRecord{},        // 同时补充其他缺失的模型
        &models.AdminOperationLog{},
        // ...
    )

    return nil
}

预防措施

  1. 创建模型检查清单 ```go // 在 models.go 文件末尾添加注释 /* AutoMigrate 清单:
    • User ✓
    • Submission ✓
    • Comment ✓
    • DebtRecord ✓
    • TaskRecord ✓ */ ```
  2. 添加单元测试
    func TestAllModelsRegistered(t *testing.T) {
        // 检查所有模型是否都已注册到 AutoMigrate
        expectedTables := []string{
            "users",
            "submissions",
            "comments",
            "debt_records",
            "task_records",
        }
    
        for _, table := range expectedTables {
            if !db.Migrator().HasTable(table) {
                t.Errorf("Table %s not found", table)
            }
        }
    }
    

Bug 2: 前端空白页面问题

问题现象

Git 提交: f35f9e2 - fix: 修复生产构建时 Element Plus 循环依赖导致的前端空白问题(提交日期:2026-02-28)

症状:

  • 生产环境前端页面完全空白
  • 浏览器控制台报错:Uncaught ReferenceError: Cannot access 'ElButton' before initialization
  • 开发环境正常

问题排查

  1. 检查浏览器控制台
    Uncaught ReferenceError: Cannot access 'ElButton' before initialization
    at element-plus.js:1234
    
  2. 分析构建产物
    # 检查构建后的文件
    ls -lh admin-vue/dist/assets/
    
    # 发现 element-plus 相关的 chunk 存在循环依赖
    
  3. 检查 Vite 配置
    // vite.config.js
    export default {
        build: {
            rollupOptions: {
                output: {
                    manualChunks: {
                        'element-plus': ['element-plus']  // 问题所在
                    }
                }
            }
        }
    }
    

解决方案

方案 1:调整分包策略

// vite.config.js
export default {
    build: {
        rollupOptions: {
            output: {
                manualChunks(id) {
                    // 不强制将 Element Plus 打包到单独的 chunk
                    if (id.includes('node_modules')) {
                        if (id.includes('element-plus')) {
                            // 让 Vite 自动处理 Element Plus 的分包
                            return 'vendor'
                        }
                        return 'vendor'
                    }
                }
            }
        }
    }
}

方案 2:按需导入

// main.js
// 改为按需导入,避免循环依赖
import { ElButton, ElInput, ElForm } from 'element-plus'

const app = createApp(App)
app.component('ElButton', ElButton)
app.component('ElInput', ElInput)
app.component('ElForm', ElForm)

预防措施

  1. 生产环境测试
    # 每次发布前在生产模式下测试
    npm run build
    npm run preview
    
  2. 添加构建检查
    // package.json
    {
        "scripts": {
            "build": "vite build",
            "build:check": "vite build && npm run preview"
        }
    }
    

Bug 3: 投稿编辑后跳转错误

问题现象

Git 提交: 8615142 - fix: 修复投稿编辑后跳转错误的问题(提交日期:2026-03-06)

症状:

  • 编辑投稿后跳转到错误的页面
  • 应该跳转到投稿详情页,实际跳转到列表页
  • 用户体验不佳

问题排查

<!-- 编辑投稿组件 -->
<script setup>
const handleSave = async () => {
    await updateSubmission(submissionId, form)

    // 问题:跳转路径错误
    router.push('/submissions')  // 跳转到列表页
}
</script>

解决方案

<script setup>
const handleSave = async () => {
    await updateSubmission(submissionId, form)

    // 修复:跳转到投稿详情页
    router.push(`/submissions/${submissionId}`)

    // 或者返回上一页
    // router.back()
}
</script>

预防措施

  1. 统一跳转逻辑
    // utils/navigation.js
    export const navigateAfterEdit = (router, id, type = 'detail') => {
        if (type === 'detail') {
            router.push(`/submissions/${id}`)
        } else if (type === 'list') {
            router.push('/submissions')
        } else if (type === 'back') {
            router.back()
        }
    }
    
  2. 添加用户选择
    <template>
        <el-button @click="handleSave('detail')">保存并查看</el-button>
        <el-button @click="handleSave('list')">保存并返回列表</el-button>
    </template>
    

Bug 4: 投稿管理用户名筛选 SQL 歧义错误

问题现象

Git 提交: 1b98cb0 - fix: 修复投稿管理用户名筛选SQL歧义错误(提交日期:2026-02-06)

症状:

  • 按用户名筛选投稿时报错
  • SQL 错误:ambiguous column name: username
  • 筛选功能无法使用

问题排查

// 原始代码
func GetSubmissions(filter *SubmissionFilter) ([]Submission, error) {
    query := db.Model(&Submission{}).
        Joins("LEFT JOIN users ON submissions.user_id = users.id")

    if filter.Username != "" {
        // 问题:username 字段在多个表中存在
        query = query.Where("username LIKE ?", "%"+filter.Username+"%")
    }

    var submissions []Submission
    return submissions, query.Find(&submissions).Error
}

SQL 错误:

SELECT * FROM submissions
LEFT JOIN users ON submissions.user_id = users.id
WHERE username LIKE '%test%'  -- 歧义:不知道是哪个表的 username

解决方案

func GetSubmissions(filter *SubmissionFilter) ([]Submission, error) {
    query := db.Model(&Submission{}).
        Joins("LEFT JOIN users ON submissions.user_id = users.id")

    if filter.Username != "" {
        // 修复:明确指定表名
        query = query.Where("users.username LIKE ?", "%"+filter.Username+"%")
    }

    var submissions []Submission
    return submissions, query.Find(&submissions).Error
}

预防措施

  1. 始终使用表名前缀
    // 好的做法
    query.Where("users.username = ?", username)
    query.Where("submissions.title LIKE ?", "%"+title+"%")
    
    // 避免
    query.Where("username = ?", username)  // 可能产生歧义
    
  2. 使用别名
    query := db.Table("submissions AS s").
        Joins("LEFT JOIN users AS u ON s.user_id = u.id").
        Where("u.username LIKE ?", "%"+filter.Username+"%")
    

Bug 5: 评论重复提交问题

问题现象

Git 提交: 6522a51 - fix:修复重复提交问题(提交日期:2025-06-24)

症状:

  • 用户快速点击提交按钮时,创建了多条相同的评论
  • 数据库中出现重复数据
  • 影响数据准确性

问题排查

<!-- 原始代码 -->
<template>
    <el-button @click="handleSubmit">提交评论</el-button>
</template>

<script setup>
const handleSubmit = async () => {
    // 问题:没有防止重复提交
    await createComment(form)
    ElMessage.success('提交成功')
}
</script>

解决方案

方案 1:前端防抖

<template>
    <el-button
        @click="handleSubmit"
        :loading="submitting"
        :disabled="submitting"
    >
        提交评论
    </el-button>
</template>

<script setup>
const submitting = ref(false)

const handleSubmit = async () => {
    if (submitting.value) return

    submitting.value = true
    try {
        await createComment(form)
        ElMessage.success('提交成功')
    } finally {
        submitting.value = false
    }
}
</script>

方案 2:后端幂等性检查

// 使用幂等性 key
type CreateCommentRequest struct {
    UserID         uint   `json:"userId"`
    SubmissionID   uint   `json:"submissionId"`
    Content        string `json:"content"`
    Score          int    `json:"score"`
    IdempotencyKey string `json:"idempotencyKey"` // 幂等性 key
}

func CreateComment(req *CreateCommentRequest) error {
    // 检查幂等性 key
    var count int64
    db.Model(&Comment{}).
        Where("idempotency_key = ?", req.IdempotencyKey).
        Count(&count)

    if count > 0 {
        return errors.New("请求已处理")
    }

    // 创建评论
    comment := &Comment{
        UserID:         req.UserID,
        SubmissionID:   req.SubmissionID,
        Content:        req.Content,
        Score:          req.Score,
        IdempotencyKey: req.IdempotencyKey,
    }

    return db.Create(comment).Error
}

方案 3:数据库唯一索引

// 添加唯一索引防止重复
type Comment struct {
    ID           uint `gorm:"primaryKey"`
    UserID       uint `gorm:"uniqueIndex:idx_user_submission"`
    SubmissionID uint `gorm:"uniqueIndex:idx_user_submission"`
    Content      string
    Score        int
}

预防措施

  1. 统一提交按钮组件
    <!-- components/SubmitButton.vue -->
    <template>
        <el-button
            v-bind="$attrs"
            :loading="loading"
            :disabled="loading || disabled"
            @click="handleClick"
        >
            <slot></slot>
        </el-button>
    </template>
    
    <script setup>
    const props = defineProps({
        disabled: Boolean,
        onClick: Function
    })
    
    const loading = ref(false)
    
    const handleClick = async () => {
        if (loading.value) return
    
        loading.value = true
        try {
            await props.onClick?.()
        } finally {
            loading.value = false
        }
    }
    </script>
    
  2. 全局请求拦截
    // 记录进行中的请求
    const pendingRequests = new Map()
    
    axios.interceptors.request.use(config => {
        const key = `${config.method}:${config.url}`
    
        if (pendingRequests.has(key)) {
            return Promise.reject(new Error('重复请求'))
        }
    
        pendingRequests.set(key, true)
        return config
    })
    
    axios.interceptors.response.use(
        response => {
            const key = `${response.config.method}:${response.config.url}`
            pendingRequests.delete(key)
            return response
        },
        error => {
            const key = `${error.config.method}:${error.config.url}`
            pendingRequests.delete(key)
            return Promise.reject(error)
        }
    )
    

Bug 修复经验总结

排查技巧

  1. 查看日志
    • 后端日志:journalctl -u laping -f
    • 前端控制台:浏览器开发者工具
    • 数据库日志:GORM 日志模式
  2. 复现问题
    • 记录复现步骤
    • 确认环境差异
    • 使用相同数据测试
  3. 二分查找
    • Git bisect 定位引入 Bug 的提交
    • 逐步注释代码缩小范围
  4. 添加调试信息
    log.Printf("Debug: userID=%d, submissionID=%d", userID, submissionID)
    

预防措施

  1. 代码审查
    • 提交前自我审查
    • 团队成员互相审查
    • 关注常见问题模式
  2. 自动化测试
    • 单元测试覆盖核心逻辑
    • 集成测试验证关键流程
    • E2E 测试模拟用户操作
  3. 监控告警
    • 错误日志监控
    • 性能指标监控
    • 异常行为告警
  4. 文档记录
    • 记录已知问题
    • 记录解决方案
    • 分享经验教训

总结

通过系统地记录和分析这些关键 Bug,我们积累了宝贵的经验。这些经验不仅帮助我们快速解决问题,也指导我们在未来的开发中避免类似问题。

关键教训

  1. 数据库迁移 - 新增模型时务必添加到 AutoMigrate
  2. 前端构建 - 生产环境测试不可省略
  3. 跳转逻辑 - 明确用户期望的跳转目标
  4. SQL 查询 - 多表查询时明确指定表名
  5. 重复提交 - 前后端双重防护

最佳实践

  • 完善的错误处理
  • 充分的测试覆盖
  • 详细的日志记录
  • 及时的问题修复
  • 持续的经验总结

这些 Bug 修复经验为辣评项目的稳定性和可靠性提供了重要保障。

本文遵守 Attribution-NonCommercial 4.0 International 许可协议。 Attribution-NonCommercial 4.0 International