时间线说明
本文创建于 2026-02-06,后续持续补充了同一阶段内的关键修复案例。
文中各条 Git 提交 以实际提交日期为准,部分提交晚于本文初稿时间,属于后续追记内容。
Bug 修复总结概述
在辣评项目的开发过程中,我们遇到并解决了多个关键 Bug。本文总结了这些重要的 Bug 修复经验,包括问题现象、排查过程、解决方案和预防措施。
主要 Bug 类型
- 数据库迁移问题
- 前端空白页面问题
- 跳转逻辑错误
- SQL 歧义问题
- 重复提交处理
Bug 1: 数据库表自动迁移问题
问题现象
Git 提交: e7c9c54 - fix: 修复数据库表自动迁移问题,添加 DebtRecord 模型(提交日期:2026-02-28)
症状:
- 应用启动时报错:
table debt_records not found - 补评功能无法正常使用
- 数据库表缺失
问题排查
- 检查模型定义
// 发现 DebtRecord 模型已定义但未添加到 AutoMigrate type DebtRecord struct { ID uint UserID uint CompetitionID uint DebtCount int Status string // ... } - 检查数据库初始化代码
// 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
}
预防措施
- 创建模型检查清单
```go
// 在 models.go 文件末尾添加注释
/*
AutoMigrate 清单:
- User ✓
- Submission ✓
- Comment ✓
- DebtRecord ✓
- TaskRecord ✓ */ ```
- 添加单元测试
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 - 开发环境正常
问题排查
- 检查浏览器控制台
Uncaught ReferenceError: Cannot access 'ElButton' before initialization at element-plus.js:1234 - 分析构建产物
# 检查构建后的文件 ls -lh admin-vue/dist/assets/ # 发现 element-plus 相关的 chunk 存在循环依赖 - 检查 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)
预防措施
- 生产环境测试
# 每次发布前在生产模式下测试 npm run build npm run preview - 添加构建检查
// 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>
预防措施
- 统一跳转逻辑
// 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() } } - 添加用户选择
<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
}
预防措施
- 始终使用表名前缀
// 好的做法 query.Where("users.username = ?", username) query.Where("submissions.title LIKE ?", "%"+title+"%") // 避免 query.Where("username = ?", username) // 可能产生歧义 - 使用别名
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
}
预防措施
- 统一提交按钮组件
<!-- 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> - 全局请求拦截
// 记录进行中的请求 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 修复经验总结
排查技巧
- 查看日志
- 后端日志:
journalctl -u laping -f - 前端控制台:浏览器开发者工具
- 数据库日志:GORM 日志模式
- 后端日志:
- 复现问题
- 记录复现步骤
- 确认环境差异
- 使用相同数据测试
- 二分查找
- Git bisect 定位引入 Bug 的提交
- 逐步注释代码缩小范围
- 添加调试信息
log.Printf("Debug: userID=%d, submissionID=%d", userID, submissionID)
预防措施
- 代码审查
- 提交前自我审查
- 团队成员互相审查
- 关注常见问题模式
- 自动化测试
- 单元测试覆盖核心逻辑
- 集成测试验证关键流程
- E2E 测试模拟用户操作
- 监控告警
- 错误日志监控
- 性能指标监控
- 异常行为告警
- 文档记录
- 记录已知问题
- 记录解决方案
- 分享经验教训
总结
通过系统地记录和分析这些关键 Bug,我们积累了宝贵的经验。这些经验不仅帮助我们快速解决问题,也指导我们在未来的开发中避免类似问题。
关键教训
- 数据库迁移 - 新增模型时务必添加到 AutoMigrate
- 前端构建 - 生产环境测试不可省略
- 跳转逻辑 - 明确用户期望的跳转目标
- SQL 查询 - 多表查询时明确指定表名
- 重复提交 - 前后端双重防护
最佳实践
- 完善的错误处理
- 充分的测试覆盖
- 详细的日志记录
- 及时的问题修复
- 持续的经验总结
这些 Bug 修复经验为辣评项目的稳定性和可靠性提供了重要保障。