package handlers import ( "log/slog" "net/http" "time" "goblog/database" "goblog/models" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" ) var jwtSecret = []byte("your-secret-key-change-in-production") func JWTSecret() []byte { return jwtSecret } // AdminMenuItem 后台菜单项 type AdminMenuItem struct { Key string `json:"key"` // 页面标识,与页面 div 的 data-page 对应 Name string `json:"name"` // 菜单显示名称 } // AdminMenuGroup 后台菜单分组(Group 为空表示顶级菜单) type AdminMenuGroup struct { Group string `json:"group"` Items []AdminMenuItem `json:"items"` } // GetMenusByRole 根据角色返回后台菜单 func GetMenusByRole(role string) []AdminMenuGroup { if role == "admin" { return []AdminMenuGroup{ {Group: "", Items: []AdminMenuItem{ {Key: "dashboard", Name: "仪表盘"}, }}, {Group: "内容管理", Items: []AdminMenuItem{ {Key: "posts", Name: "文章管理"}, {Key: "categories", Name: "分类管理"}, {Key: "tags", Name: "标签管理"}, {Key: "pages", Name: "页面管理"}, {Key: "comments", Name: "评论管理"}, }}, {Group: "系统管理", Items: []AdminMenuItem{ {Key: "users", Name: "用户管理"}, {Key: "files", Name: "文件管理"}, {Key: "themes", Name: "主题管理"}, {Key: "settings", Name: "基础设置"}, }}, } } // 普通用户:仪表盘 + 文章管理 + 评论管理 return []AdminMenuGroup{ {Group: "", Items: []AdminMenuItem{ {Key: "dashboard", Name: "仪表盘"}, {Key: "posts", Name: "文章管理"}, {Key: "comments", Name: "评论管理"}, }}, } } // 登录请求 type LoginRequest struct { Username string `json:"username" binding:"required"` Password string `json:"password" binding:"required"` } // 注册请求 type RegisterRequest struct { Username string `json:"username" binding:"required,min=3,max=50"` Password string `json:"password" binding:"required,min=6"` Nickname string `json:"nickname"` Email string `json:"email" binding:"required,email"` } // JWT Claims type Claims struct { UserID uint `json:"user_id"` Username string `json:"username"` Role string `json:"role"` jwt.RegisteredClaims } // 登录 func Login(c *gin.Context) { var req LoginRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } var user models.User if err := database.DB.Where("username = ?", req.Username).First(&user).Error; err != nil { c.JSON(http.StatusUnauthorized, gin.H{"error": "用户名或密码错误"}) return } if user.Status == 0 || !user.IsActive { c.JSON(http.StatusForbidden, gin.H{"error": "账号已被禁用"}) return } // 使用 User 模型的 CheckPassword 方法 if !user.CheckPassword(req.Password) { c.JSON(http.StatusUnauthorized, gin.H{"error": "用户名或密码错误"}) return } // 记录登录信息 if err := user.RecordLogin(database.DB); err != nil { slog.Warn("记录登录信息失败", "user_id", user.ID, "error", err) } // 生成 JWT claims := Claims{ UserID: user.ID, Username: user.Username, Role: user.Role, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(7 * 24 * time.Hour)), IssuedAt: jwt.NewNumericDate(time.Now()), }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) tokenString, err := token.SignedString(jwtSecret) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "生成令牌失败"}) return } // 同时写入 Cookie,供服务端渲染后台页面时识别登录态 c.SetCookie("token", tokenString, 7*24*3600, "/", "", false, true) c.JSON(http.StatusOK, gin.H{ "token": tokenString, "user": gin.H{ "id": user.ID, "username": user.Username, "nickname": user.Nickname, "email": user.Email, "role": user.Role, "avatar": user.Avatar, "last_login_at": user.LastLoginAt, "login_count": user.LoginCount, }, // 菜单由登录接口根据角色返回 "menus": GetMenusByRole(user.Role), }) } // 退出登录(清除服务端 Cookie) func Logout(c *gin.Context) { c.SetCookie("token", "", -1, "/", "", false, true) c.JSON(http.StatusOK, gin.H{"message": "已退出登录"}) } // WebLogout 前台页面退出登录(清除 Cookie 后回首页) func WebLogout(c *gin.Context) { c.SetCookie("token", "", -1, "/", "", false, true) c.Redirect(http.StatusFound, "/") } // 获取当前用户信息 func GetCurrentUser(c *gin.Context) { userID, _ := c.Get("userID") var user models.User if err := database.DB.First(&user, userID).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"}) return } c.JSON(http.StatusOK, gin.H{"data": user}) } // 更新用户信息 func UpdateUser(c *gin.Context) { userID, _ := c.Get("userID") var user models.User if err := database.DB.First(&user, userID).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"}) return } var req struct { Nickname string `json:"nickname"` Email string `json:"email"` Avatar string `json:"avatar"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } updates := map[string]interface{}{} if req.Nickname != "" { updates["nickname"] = req.Nickname } if req.Email != "" { updates["email"] = req.Email } if req.Avatar != "" { updates["avatar"] = req.Avatar } if err := database.DB.Model(&user).Updates(updates).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "更新失败"}) return } c.JSON(http.StatusOK, gin.H{"data": user}) } // 修改密码 func ChangePassword(c *gin.Context) { userID, _ := c.Get("userID") var user models.User if err := database.DB.First(&user, userID).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"}) return } var req struct { OldPassword string `json:"old_password" binding:"required"` NewPassword string `json:"new_password" binding:"required,min=6"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // 使用 User 模型的 CheckPassword 方法 if !user.CheckPassword(req.OldPassword) { c.JSON(http.StatusBadRequest, gin.H{"error": "原密码错误"}) return } // 使用 User 模型的 SetPassword 方法 if err := user.SetPassword(req.NewPassword); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "密码加密失败"}) return } if err := database.DB.Save(&user).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "修改密码失败"}) return } c.JSON(http.StatusOK, gin.H{"message": "密码修改成功"}) }