此提交完成了以下核心功能与优化: 1. 新增站点公告全流程支持:包含公告模型、CRUD接口、管理后台页面、公告详情页与首页展示 2. 实现评论楼层化与回复嵌套:重构评论系统为树状结构,支持回复评论与层级限制 3. 添加用户在线状态追踪:异步更新用户最后活跃时间并做SQL限频 4. 优化用户体验:移动端浮动操作条、统一板块标签样式、用户页签到积分展示、注册即登录优化 5. 其他细节优化:SEO站点地图新增公告页、评论分页与统计、登录/注册表单增强等
103 lines
2.5 KiB
Go
103 lines
2.5 KiB
Go
package middleware
|
||
|
||
import (
|
||
"net/http"
|
||
"strings"
|
||
|
||
"github.com/freefire/jiang13-bbs/service"
|
||
"github.com/gin-gonic/gin"
|
||
)
|
||
|
||
// AuthMiddleware 认证中间件
|
||
type AuthMiddleware struct {
|
||
auth *service.AuthService
|
||
}
|
||
|
||
func NewAuthMiddleware(auth *service.AuthService) *AuthMiddleware {
|
||
return &AuthMiddleware{auth: auth}
|
||
}
|
||
|
||
// OptionalAuth 可选登录:解析 token,失败不阻断
|
||
func (m *AuthMiddleware) OptionalAuth() gin.HandlerFunc {
|
||
return func(c *gin.Context) {
|
||
user, ok := m.parseToken(c)
|
||
if ok {
|
||
c.Set("user", user)
|
||
}
|
||
c.Next()
|
||
}
|
||
}
|
||
|
||
// RequireAuth 必须登录
|
||
func (m *AuthMiddleware) RequireAuth() gin.HandlerFunc {
|
||
return func(c *gin.Context) {
|
||
user, ok := m.parseToken(c)
|
||
if !ok {
|
||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "未登录"})
|
||
return
|
||
}
|
||
if user.Banned {
|
||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "账号已被封禁"})
|
||
return
|
||
}
|
||
c.Set("user", user)
|
||
c.Next()
|
||
}
|
||
}
|
||
|
||
// RequireAdmin 必须管理员
|
||
func (m *AuthMiddleware) RequireAdmin() gin.HandlerFunc {
|
||
return func(c *gin.Context) {
|
||
user, ok := m.parseToken(c)
|
||
if !ok {
|
||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "未登录"})
|
||
return
|
||
}
|
||
if user.Role != service.RoleAdmin {
|
||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "需要管理员权限"})
|
||
return
|
||
}
|
||
c.Set("user", user)
|
||
c.Next()
|
||
}
|
||
}
|
||
|
||
// parseToken 解析并校验 token:
|
||
// 1. 优先从 HttpOnly cookie 读取,回退 Authorization header
|
||
// 2. 校验 JWT 签名和过期
|
||
// 3. 查 DB 实时校验 token_version 和 banned 状态
|
||
func (m *AuthMiddleware) parseToken(c *gin.Context) (*service.UserClaims, bool) {
|
||
tokenStr, err := c.Cookie(service.CookieName)
|
||
if err != nil || tokenStr == "" {
|
||
auth := c.GetHeader("Authorization")
|
||
if auth == "" {
|
||
return nil, false
|
||
}
|
||
tokenStr = strings.TrimPrefix(auth, "Bearer ")
|
||
if tokenStr == auth {
|
||
return nil, false
|
||
}
|
||
}
|
||
claims, err := m.auth.ParseToken(tokenStr)
|
||
if err != nil {
|
||
return nil, false
|
||
}
|
||
// 实时校验:token_version 匹配 + 未封禁(防止旧 JWT 在封禁/改密码后仍有效)
|
||
if _, err := m.auth.ValidateClaims(claims); err != nil {
|
||
return nil, false
|
||
}
|
||
// 异步刷新在线心跳(SQL 每 60s 限频一次),不阻塞请求
|
||
go m.auth.TouchLastSeen(claims.ID)
|
||
return claims, true
|
||
}
|
||
|
||
// CurrentUser 从 context 获取当前用户
|
||
func CurrentUser(c *gin.Context) *service.UserClaims {
|
||
if v, ok := c.Get("user"); ok {
|
||
if u, ok := v.(*service.UserClaims); ok {
|
||
return u
|
||
}
|
||
}
|
||
return nil
|
||
}
|