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 }