yanlongqi 2bcf6bdacc 实现完整的用户统计功能和认证系统
**统计功能**:
- 新增UserAnswerRecord模型记录用户答题历史
- 实现GetStatistics接口,统计题库总数、已答题数、正确率
- 在提交答案时自动记录答题历史
- 前端连接真实统计接口,显示实时数据

**认证系统优化**:
- 新增Auth中间件,实现基于Token的身份验证
- 登录和注册时自动生成并保存Token到数据库
- 所有需要登录的接口都通过Auth中间件保护
- 统一处理未授权请求,返回401状态码

**错题练习功能**:
- 新增GetRandomWrongQuestion接口,随机获取错题
- 支持错题练习模式(/question?mode=wrong)
- 优化错题本页面UI,移除已掌握功能
- 新增"开始错题练习"按钮,直接进入练习模式

**数据库迁移**:
- 新增user_answer_records表,记录用户答题历史
- User表新增token字段,存储用户登录凭证

**技术改进**:
- 统一错误处理,区分401未授权和404未找到
- 优化答题流程,记录历史和错题分离处理
- 移除异步记录错题,改为同步处理保证数据一致性

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-04 15:26:27 +08:00

58 lines
1.1 KiB
Go

package middleware
import (
"ankao/internal/database"
"ankao/internal/models"
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
// Auth 认证中间件
func Auth() gin.HandlerFunc {
return func(c *gin.Context) {
// 从请求头获取token
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
c.JSON(http.StatusUnauthorized, gin.H{
"success": false,
"message": "未登录",
})
c.Abort()
return
}
// 解析Bearer token
parts := strings.SplitN(authHeader, " ", 2)
if len(parts) != 2 || parts[0] != "Bearer" {
c.JSON(http.StatusUnauthorized, gin.H{
"success": false,
"message": "token格式错误",
})
c.Abort()
return
}
token := parts[1]
// 从数据库查找token对应的用户
db := database.GetDB()
var user models.User
if err := db.Where("token = ?", token).First(&user).Error; err != nil {
c.JSON(http.StatusUnauthorized, gin.H{
"success": false,
"message": "token无效或已过期",
})
c.Abort()
return
}
// 将用户ID设置到上下文
c.Set("user_id", user.ID)
c.Set("username", user.Username)
c.Next()
}
}