## 主要改进 ### 1. 修复AI解析Markdown渲染混乱 - 安装remark-gfm插件支持GitHub Flavored Markdown - 优化ReactMarkdown组件样式配置 - 支持表格、代码块、引用、列表等完整Markdown语法 - 改进代码块样式,区分行内代码和代码块 - 优化排版,提升可读性 ### 2. 用户界面优化 - 修改个人信息中"昵称"改为"姓名" - 调整答题设置提示位置到第一行 - 去除答题设置提示标题,使界面更简洁 ### 3. 新增用户功能 - 添加修改个人信息功能(姓名、身份类型) - 添加修改密码功能 - 优化用户信息显示和编辑体验 ### 4. 技术改进 - 修复Home.tsx中handleLogout函数声明顺序问题 - 添加更完善的Markdown样式定义 - 优化流式渲染体验 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
389 lines
8.8 KiB
Go
389 lines
8.8 KiB
Go
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,
|
||
UserType: user.UserType, // 返回用户类型
|
||
}
|
||
|
||
// 检查用户类型是否为空,如果为空,标识需要补充
|
||
needUserType := user.UserType == ""
|
||
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": true,
|
||
"message": "登录成功",
|
||
"data": models.LoginResponse{
|
||
Token: token,
|
||
User: userInfo,
|
||
},
|
||
"need_user_type": needUserType, // 添加标识,前端根据此标识显示补充弹窗
|
||
})
|
||
}
|
||
|
||
// 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,
|
||
UserType: req.UserType, // 保存用户类型
|
||
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,
|
||
UserType: newUser.UserType, // 返回用户类型
|
||
}
|
||
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": true,
|
||
"message": "注册成功",
|
||
"data": models.LoginResponse{
|
||
Token: token,
|
||
User: userInfo,
|
||
},
|
||
})
|
||
}
|
||
|
||
// UpdateUserTypeRequest 更新用户类型请求
|
||
type UpdateUserTypeRequest struct {
|
||
UserType string `json:"user_type" binding:"required,oneof=ordinary-person management-person"`
|
||
}
|
||
|
||
// UpdateUserType 更新用户类型
|
||
func UpdateUserType(c *gin.Context) {
|
||
var req UpdateUserTypeRequest
|
||
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{
|
||
"success": false,
|
||
"message": "请求参数错误,用户类型必须是 ordinary-person 或 management-person",
|
||
"error": err.Error(),
|
||
})
|
||
return
|
||
}
|
||
|
||
// 从上下文获取用户信息(由认证中间件设置)
|
||
username, exists := c.Get("username")
|
||
if !exists {
|
||
c.JSON(http.StatusUnauthorized, gin.H{
|
||
"success": false,
|
||
"message": "未授权访问",
|
||
})
|
||
return
|
||
}
|
||
|
||
db := database.GetDB()
|
||
var user models.User
|
||
if err := db.Where("username = ?", username).First(&user).Error; err != nil {
|
||
c.JSON(http.StatusNotFound, gin.H{
|
||
"success": false,
|
||
"message": "用户不存在",
|
||
})
|
||
return
|
||
}
|
||
|
||
// 更新用户类型
|
||
user.UserType = req.UserType
|
||
if err := db.Save(&user).Error; err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{
|
||
"success": false,
|
||
"message": "更新用户类型失败",
|
||
"error": err.Error(),
|
||
})
|
||
return
|
||
}
|
||
|
||
// 返回更新后的用户信息
|
||
userInfo := models.UserInfoResponse{
|
||
Username: user.Username,
|
||
Avatar: user.Avatar,
|
||
Nickname: user.Nickname,
|
||
UserType: user.UserType,
|
||
}
|
||
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": true,
|
||
"message": "用户类型更新成功",
|
||
"data": userInfo,
|
||
})
|
||
}
|
||
|
||
// UpdateProfileRequest 更新用户信息请求
|
||
type UpdateProfileRequest struct {
|
||
Nickname string `json:"nickname" binding:"required"`
|
||
UserType string `json:"user_type" binding:"required,oneof=ordinary-person management-person"`
|
||
}
|
||
|
||
// UpdateProfile 更新用户信息
|
||
func UpdateProfile(c *gin.Context) {
|
||
var req UpdateProfileRequest
|
||
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{
|
||
"success": false,
|
||
"message": "请求参数错误",
|
||
"error": err.Error(),
|
||
})
|
||
return
|
||
}
|
||
|
||
// 从上下文获取用户信息(由认证中间件设置)
|
||
username, exists := c.Get("username")
|
||
if !exists {
|
||
c.JSON(http.StatusUnauthorized, gin.H{
|
||
"success": false,
|
||
"message": "未授权访问",
|
||
})
|
||
return
|
||
}
|
||
|
||
db := database.GetDB()
|
||
var user models.User
|
||
if err := db.Where("username = ?", username).First(&user).Error; err != nil {
|
||
c.JSON(http.StatusNotFound, gin.H{
|
||
"success": false,
|
||
"message": "用户不存在",
|
||
})
|
||
return
|
||
}
|
||
|
||
// 更新用户信息
|
||
user.Nickname = req.Nickname
|
||
user.UserType = req.UserType
|
||
if err := db.Save(&user).Error; err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{
|
||
"success": false,
|
||
"message": "更新用户信息失败",
|
||
"error": err.Error(),
|
||
})
|
||
return
|
||
}
|
||
|
||
// 返回更新后的用户信息
|
||
userInfo := models.UserInfoResponse{
|
||
Username: user.Username,
|
||
Avatar: user.Avatar,
|
||
Nickname: user.Nickname,
|
||
UserType: user.UserType,
|
||
}
|
||
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": true,
|
||
"message": "用户信息更新成功",
|
||
"data": userInfo,
|
||
})
|
||
}
|
||
|
||
// ChangePasswordRequest 修改密码请求
|
||
type ChangePasswordRequest struct {
|
||
OldPassword string `json:"old_password" binding:"required"`
|
||
NewPassword string `json:"new_password" binding:"required,min=6"`
|
||
}
|
||
|
||
// ChangePassword 修改密码
|
||
func ChangePassword(c *gin.Context) {
|
||
var req ChangePasswordRequest
|
||
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{
|
||
"success": false,
|
||
"message": "请求参数错误,新密码长度至少为6位",
|
||
"error": err.Error(),
|
||
})
|
||
return
|
||
}
|
||
|
||
// 从上下文获取用户信息(由认证中间件设置)
|
||
username, exists := c.Get("username")
|
||
if !exists {
|
||
c.JSON(http.StatusUnauthorized, gin.H{
|
||
"success": false,
|
||
"message": "未授权访问",
|
||
})
|
||
return
|
||
}
|
||
|
||
db := database.GetDB()
|
||
var user models.User
|
||
if err := db.Where("username = ?", username).First(&user).Error; err != nil {
|
||
c.JSON(http.StatusNotFound, gin.H{
|
||
"success": false,
|
||
"message": "用户不存在",
|
||
})
|
||
return
|
||
}
|
||
|
||
// 验证旧密码
|
||
if !user.CheckPassword(req.OldPassword) {
|
||
c.JSON(http.StatusUnauthorized, gin.H{
|
||
"success": false,
|
||
"message": "当前密码错误",
|
||
})
|
||
return
|
||
}
|
||
|
||
// 更新密码
|
||
if err := user.HashPassword(req.NewPassword); err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{
|
||
"success": false,
|
||
"message": "密码加密失败",
|
||
"error": err.Error(),
|
||
})
|
||
return
|
||
}
|
||
|
||
// 清除旧的token,强制重新登录
|
||
user.Token = ""
|
||
|
||
if err := db.Save(&user).Error; err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{
|
||
"success": false,
|
||
"message": "密码更新失败",
|
||
"error": err.Error(),
|
||
})
|
||
return
|
||
}
|
||
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"success": true,
|
||
"message": "密码修改成功,请重新登录",
|
||
})
|
||
}
|