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/realtime" "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) moderationSvc := service.NewModerationService(model.DB, notifSvc) chatSvc := service.NewChatService(model.DB, notifSvc) if err := uploadSvc.EnsureDir(); err != nil { return nil, err } limiter := service.DefaultRateLimiter() h := &handler.Handlers{ Cfg: cfg, Hub: realtime.NewHub(), Auth: authSvc, Board: boardSvc, Post: postSvc, Comment: commentSvc, Like: likeSvc, Notification: notifSvc, OverviewSvc: overviewSvc, Checkin: checkinSvc, Announcement: announcementSvc, Upload: uploadSvc, Setting: settingSvc, AdminUser: adminUserSvc, Moderation: moderationSvc, Chat: chatSvc, } // 通知落库后统一推 WS 红点(点赞/评论/审核/@ 等共用) notifSvc.OnNotifyNew = func(userID uint) { if h.Hub == nil { return } h.Hub.BroadcastUser(userID, realtime.Envelope{ Type: realtime.EventNotificationNew, }) } 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) // 实时通信总线(WebSocket,cookie 鉴权 + Origin 校验,处理器内部完成鉴权升级) r.GET("/api/ws", h.RealtimeWS) // 公开 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.GET("/chat/rooms", h.ListChatRooms) api.POST("/chat/rooms", h.CreateChatRoom) api.POST("/chat/direct", h.OpenDirectChat) api.GET("/chat/users", h.SearchChatUsers) api.GET("/chat/rooms/:id", h.GetChatRoom) api.PUT("/chat/rooms/:id", h.UpdateChatRoom) api.PUT("/chat/rooms/:id/pin", h.PinChatRoom) api.DELETE("/chat/rooms/:id", h.DissolveChatRoom) api.POST("/chat/rooms/:id/join", h.JoinChatRoom) api.POST("/chat/rooms/:id/leave", h.LeaveChatRoom) api.GET("/chat/rooms/:id/members", h.ListChatMembers) api.POST("/chat/rooms/:id/members", h.InviteChatMembers) api.DELETE("/chat/rooms/:id/members/:uid", h.KickChatMember) api.GET("/chat/rooms/:id/messages", h.ListChatMessages) api.POST("/chat/rooms/:id/messages", middleware.RateLimitMiddleware(limiter, service.RateChat), h.SendChatMessage) api.DELETE("/chat/rooms/:id/messages/:mid", h.RecallChatMessage) api.PUT("/chat/rooms/:id/read", h.MarkChatRead) api.GET("/chat/unread-summary", h.ChatUnreadSummary) } // 管理后台 API:RequireStaff 基础鉴权(板块管理员及以上,Actor 实时从 DB 现取), // 各功能点再由 RequirePerm 按角色放行;板块范围在 service 层按 Actor.BoardIDs 隔离 staffAPI := r.Group("/api/admin", authMW.RequireStaff(), middleware.CSRFMiddleware()) { staffAPI.GET("/dashboard", func(c *gin.Context) { c.JSON(200, gin.H{"message": "admin dashboard"}) }) // 内容审核队列(任意管理角色;板块管理员只见授权板块) staffAPI.GET("/moderation/pending-posts", h.AdminPendingPosts) staffAPI.GET("/moderation/pending-comments", h.AdminPendingComments) staffAPI.GET("/moderation/counts", h.AdminPendingCounts) staffAPI.PUT("/moderation/posts/:id/approve", h.AdminApprovePost) staffAPI.PUT("/moderation/posts/:id/reject", h.AdminRejectPost) staffAPI.PUT("/moderation/comments/:id/approve", h.AdminApproveComment) staffAPI.PUT("/moderation/comments/:id/reject", h.AdminRejectComment) // 站点公告文章管理(管理员及以上) announceAPI := staffAPI.Group("", authMW.RequirePerm(service.PermAnnouncements)) announceAPI.GET("/announcements", h.AdminListAnnouncements) announceAPI.POST("/announcements", h.AdminCreateAnnouncement) announceAPI.PUT("/announcements/:id", h.AdminUpdateAnnouncement) announceAPI.DELETE("/announcements/:id", h.AdminDeleteAnnouncement) // 站点外观设置(超级管理员/站长) staffAPI.PUT("/settings", authMW.RequirePerm(service.PermSettings), h.UpdateSettings) // 用户与权限管理(超级管理员/站长):列表、角色授权、封禁、登录历史 usersAPI := staffAPI.Group("", authMW.RequirePerm(service.PermUsers)) usersAPI.GET("/users", h.AdminListUsers) usersAPI.PUT("/users/:id/role", h.AdminUpdateUserRole) usersAPI.PUT("/users/:id/ban", h.AdminSetUserBan) usersAPI.GET("/users/:id/login-logs", h.AdminUserLoginLogs) // 站长内容档案(handler 内再校验 owner) usersAPI.GET("/users/:id", h.AdminGetUserAudit) usersAPI.GET("/users/:id/posts", h.AdminUserAuditPosts) usersAPI.GET("/users/:id/comments", h.AdminUserAuditComments) usersAPI.GET("/users/:id/messages", h.AdminUserAuditMessages) } r.NoRoute(func(c *gin.Context) { c.JSON(404, gin.H{"error": "not found"}) }) return r, nil }