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

176 lines
3.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package handlers
import (
"ankao/internal/database"
"ankao/internal/models"
"crypto/sha256"
"encoding/hex"
"fmt"
"net/http"
"time"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
// generateToken 生成简单的token实际项目应使用JWT
func generateToken(username string) string {
data := fmt.Sprintf("%s:%d", username, time.Now().Unix())
hash := sha256.Sum256([]byte(data))
return hex.EncodeToString(hash[:])
}
// Login 用户登录处理
func Login(c *gin.Context) {
var req models.LoginRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"success": false,
"message": "请求参数错误",
"error": err.Error(),
})
return
}
// 从数据库查找用户
var user models.User
db := database.GetDB()
result := db.Where("username = ?", req.Username).First(&user)
if result.Error != nil {
if result.Error == gorm.ErrRecordNotFound {
c.JSON(http.StatusUnauthorized, gin.H{
"success": false,
"message": "用户名或密码错误",
})
return
}
c.JSON(http.StatusInternalServerError, gin.H{
"success": false,
"message": "服务器错误",
"error": result.Error.Error(),
})
return
}
// 验证密码
if !user.CheckPassword(req.Password) {
c.JSON(http.StatusUnauthorized, gin.H{
"success": false,
"message": "用户名或密码错误",
})
return
}
// 生成token
token := generateToken(req.Username)
// 保存token到数据库
user.Token = token
if err := db.Save(&user).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"success": false,
"message": "token保存失败",
})
return
}
// 返回用户信息(不包含密码)
userInfo := models.UserInfoResponse{
Username: user.Username,
Avatar: user.Avatar,
Nickname: user.Nickname,
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "登录成功",
"data": models.LoginResponse{
Token: token,
User: userInfo,
},
})
}
// Register 用户注册处理
func Register(c *gin.Context) {
var req models.RegisterRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"success": false,
"message": "请求参数错误",
"error": err.Error(),
})
return
}
db := database.GetDB()
// 检查用户名是否已存在
var existingUser models.User
result := db.Where("username = ?", req.Username).First(&existingUser)
if result.Error == nil {
c.JSON(http.StatusBadRequest, gin.H{
"success": false,
"message": "用户名已存在",
})
return
}
// 创建新用户
newUser := models.User{
Username: req.Username,
Nickname: req.Nickname,
Avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=" + req.Username, // 使用用户名生成默认头像
}
// 如果没有提供昵称,使用用户名
if newUser.Nickname == "" {
newUser.Nickname = req.Username
}
// 加密密码
if err := newUser.HashPassword(req.Password); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"success": false,
"message": "密码加密失败",
"error": err.Error(),
})
return
}
// 生成token
token := generateToken(req.Username)
// 设置token
newUser.Token = token
// 保存到数据库
if err := db.Create(&newUser).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"success": false,
"message": "注册失败",
"error": err.Error(),
})
return
}
// 返回用户信息
userInfo := models.UserInfoResponse{
Username: newUser.Username,
Avatar: newUser.Avatar,
Nickname: newUser.Nickname,
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "注册成功",
"data": models.LoginResponse{
Token: token,
User: userInfo,
},
})
}