Files
jiang13-bbs/backend/router/router.go
freefire 6da4309453 feat: 新增管理员用户管理功能与账号封禁体系
此提交完成了全站管理员后台核心功能开发:
1.  新增AdminUserService与管理接口,支持用户列表查询、角色变更与封禁解封
2.  重构认证链路,统一账号封禁错误码与处理逻辑
3.  新增全局强制下线机制,账号被封禁时自动弹出告知弹窗并清理登录态
4.  重构管理员后台路由与布局,统一权限校验逻辑
5.  优化通知轮询逻辑,页面切回前台时立即刷新未读计数
6.  合并原公告、外观管理页到统一后台面板
2026-09-15 02:52:26 +08:00

159 lines
5.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 router
import (
"path/filepath"
"time"
"github.com/freefire/jiang13-bbs/config"
"github.com/freefire/jiang13-bbs/handler"
"github.com/freefire/jiang13-bbs/middleware"
"github.com/freefire/jiang13-bbs/model"
"github.com/freefire/jiang13-bbs/service"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
)
// Setup 初始化路由
func Setup(cfg *config.Config) (*gin.Engine, error) {
if cfg.DevMode {
gin.SetMode(gin.DebugMode)
} else {
gin.SetMode(gin.ReleaseMode)
}
r := gin.New()
r.Use(gin.Recovery())
r.Use(gin.Logger())
// 全局安全响应头
r.Use(middleware.SecurityHeaders())
// CORS
r.Use(cors.New(cors.Config{
AllowOrigins: []string{"http://localhost:3000", "http://127.0.0.1:3000"},
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowHeaders: []string{"Origin", "Content-Type", "Authorization", "X-CSRF-Token"},
AllowCredentials: true,
MaxAge: 12 * time.Hour,
}))
// 服务
authSvc := service.NewAuthService(model.DB, cfg.JWTSecret)
boardSvc := service.NewBoardService(model.DB)
postSvc := service.NewPostService(model.DB)
commentSvc := service.NewCommentService(model.DB)
likeSvc := service.NewLikeService(model.DB)
notifSvc := service.NewNotificationService(model.DB)
overviewSvc := service.NewOverviewService(model.DB)
checkinSvc := service.NewCheckinService(model.DB)
announcementSvc := service.NewAnnouncementService(model.DB)
uploadSvc := service.NewUploadService(model.DB, filepath.Join(cfg.DataDir, "uploads"))
settingSvc := service.NewSettingService(model.DB)
adminUserSvc := service.NewAdminUserService(model.DB)
if err := uploadSvc.EnsureDir(); err != nil {
return nil, err
}
limiter := service.DefaultRateLimiter()
h := &handler.Handlers{
Cfg: cfg,
Auth: authSvc,
Board: boardSvc,
Post: postSvc,
Comment: commentSvc,
Like: likeSvc,
Notification: notifSvc,
OverviewSvc: overviewSvc,
Checkin: checkinSvc,
Announcement: announcementSvc,
Upload: uploadSvc,
Setting: settingSvc,
AdminUser: adminUserSvc,
}
authMW := middleware.NewAuthMiddleware(authSvc)
// 上传文件静态服务(data/uploads → /uploads)
r.Static("/uploads", filepath.Join(cfg.DataDir, "uploads"))
// 健康检查 & SEO
r.GET("/health", h.Health)
r.GET("/robots.txt", h.RobotsTxt)
r.GET("/sitemap.xml", h.SitemapXML)
// 公开 API(可选登录)
pubAPI := r.Group("/api", authMW.OptionalAuth())
{
pubAPI.GET("/me", h.Me)
pubAPI.GET("/boards", h.Boards)
pubAPI.GET("/overview", h.Overview)
pubAPI.GET("/posts", h.Posts)
pubAPI.GET("/posts/:id", h.PostDetail)
pubAPI.GET("/posts/:id/comments", h.PostComments)
pubAPI.GET("/users/:id", h.UserProfile)
pubAPI.GET("/users/:id/comments", h.UserComments)
pubAPI.GET("/announcements", h.AnnouncementsList)
pubAPI.GET("/announcements/:id", h.AnnouncementDetail)
pubAPI.GET("/settings", h.PublicSettings)
pubAPI.POST("/register", middleware.RateLimitMiddleware(limiter, service.RateRegister), h.Register)
pubAPI.POST("/login", middleware.RateLimitMiddleware(limiter, service.RateLogin), h.Login)
// refresh token 端点:access 过期后用 refresh 换新 token(需 CSRF 防护)
pubAPI.POST("/auth/refresh", middleware.CSRFMiddleware(), h.Refresh)
// 登出不依赖有效登录态:封禁/登录态失效后前端仍需凭它清除 cookie(CSRF 仍校验)
pubAPI.POST("/logout", middleware.CSRFMiddleware(), h.Logout)
}
// 需登录 API(先鉴权,再 CSRF 防护)
api := r.Group("/api", authMW.RequireAuth(), middleware.CSRFMiddleware())
{
api.POST("/change-password", h.ChangePassword)
api.PUT("/profile", h.UpdateProfile)
api.POST("/posts", middleware.RateLimitMiddleware(limiter, service.RatePost), h.CreatePost)
api.PUT("/posts/:id", h.UpdatePost)
api.DELETE("/posts/:id", h.DeletePost)
api.PUT("/posts/:id/pin", h.TogglePin)
api.PUT("/posts/:id/recommend", h.ToggleRecommend)
api.POST("/posts/:id/like", h.ToggleLike)
api.POST("/posts/:id/comments", middleware.RateLimitMiddleware(limiter, service.RateComment), h.CreateComment)
api.DELETE("/posts/:id/comments/:cid", h.DeleteComment)
// 通知
api.GET("/notifications", h.Notifications)
api.GET("/notifications/unread-count", h.UnreadCount)
api.PUT("/notifications/:id/read", h.MarkRead)
api.PUT("/notifications/read-all", h.MarkAllRead)
// 每日签到
api.GET("/checkin", h.GetCheckin)
api.POST("/checkin", middleware.RateLimitMiddleware(limiter, service.RateComment), h.DoCheckin)
// 头像上传(裁剪后的 WebP)/ 媒体库 / 附件删除
api.POST("/upload/avatar", h.UploadAvatar)
api.PUT("/avatar/use", h.UseAvatar)
api.GET("/my/media", h.MyMedia)
api.DELETE("/my/attachments/:id", h.DeleteAttachment)
}
// 管理员 API(同样需要 CSRF 防护)
adminAPI := r.Group("/api/admin", authMW.RequireAuth(), middleware.CSRFMiddleware(), authMW.RequireAdmin())
{
adminAPI.GET("/dashboard", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "admin dashboard"})
})
// 站点公告文章管理
adminAPI.GET("/announcements", h.AdminListAnnouncements)
adminAPI.POST("/announcements", h.AdminCreateAnnouncement)
adminAPI.PUT("/announcements/:id", h.AdminUpdateAnnouncement)
adminAPI.DELETE("/announcements/:id", h.AdminDeleteAnnouncement)
// 站点外观设置(主题色)
adminAPI.PUT("/settings", h.UpdateSettings)
// 用户管理:列表(搜索/筛选/汇总)、角色变更、封禁解封
adminAPI.GET("/users", h.AdminListUsers)
adminAPI.PUT("/users/:id/role", h.AdminUpdateUserRole)
adminAPI.PUT("/users/:id/ban", h.AdminSetUserBan)
}
r.NoRoute(func(c *gin.Context) {
c.JSON(404, gin.H{"error": "not found"})
})
return r, nil
}