feat: 友链/书库/移动端导航/排行榜等多模块功能
新增模块: - 友链(friendlink)后端处理 + 前端管理页与友链板 - 书库(library):导入、章节、封面、阅读页、横竖版自适应 AdaptiveCoverSlot - 顶栏导航(header_nav)配置与 MobileTabBar/MobileRailDrawers 移动端抽屉 - 排行榜 service 测试、站点页面测试、时间线发布(timeline_release) 其它改动: - 后端 handlers/services 全量小幅调整 - 前端组件、库函数、URL/品牌/站点 URL 工具更新 - Lightbox 图片、MdEntries 条目卡、coverColor 派生色相等前端能力
This commit is contained in:
@@ -101,6 +101,7 @@ func Setup(cfg *config.Config) (*gin.Engine, error) {
|
||||
postFileSvc.WithOperations(ops)
|
||||
pointsSvc := service.NewPointsService(model.DB)
|
||||
leaderboardSvc := service.NewLeaderboardService(model.DB)
|
||||
librarySvc := service.NewLibraryService(model.DB, cfg.DataDir).WithSetting(settingSvc)
|
||||
adminUserSvc := service.NewAdminUserService(model.DB)
|
||||
legacyImportSvc := service.NewLegacyImportService(model.DB, filepath.Join(cfg.DataDir, "uploads"))
|
||||
moderationSvc := service.NewModerationService(model.DB, notifSvc)
|
||||
@@ -118,6 +119,9 @@ func Setup(cfg *config.Config) (*gin.Engine, error) {
|
||||
if err := postFileSvc.EnsureDir(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := librarySvc.EnsureDir(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
limiter := service.DefaultRateLimiter()
|
||||
|
||||
// 访问统计管道:攒批落库 + 保留期滚动清理(随进程生命周期)。
|
||||
@@ -155,8 +159,10 @@ func Setup(cfg *config.Config) (*gin.Engine, error) {
|
||||
HidePwd: service.NewHidePasswordCookie(cfg.JWTSecret, !cfg.DevMode),
|
||||
Ads: adSvc,
|
||||
Sidebar: service.NewSidebarService(model.DB),
|
||||
FriendLink: service.NewFriendLinkService(model.DB),
|
||||
Badge: badgeSvc,
|
||||
LeaderboardSvc: leaderboardSvc,
|
||||
LibrarySvc: librarySvc,
|
||||
}
|
||||
// 通知落库后统一推 WS 红点(点赞/评论/审核/@ 等共用)
|
||||
notifSvc.OnNotifyNew = func(userID uint) {
|
||||
@@ -180,6 +186,9 @@ func Setup(cfg *config.Config) (*gin.Engine, error) {
|
||||
// 上传文件静态服务(data/uploads → /uploads)
|
||||
r.Static("/uploads", filepath.Join(cfg.DataDir, "uploads"))
|
||||
|
||||
// 品牌图固定地址(友链等外部引用):按站点设置解析到当前图,换图不变链
|
||||
r.GET("/api/brand/:slot", h.BrandImage)
|
||||
|
||||
// 健康检查(sitemap / robots 由 Next.js Metadata Route 对外提供)
|
||||
r.GET("/health", h.Health)
|
||||
|
||||
@@ -194,6 +203,7 @@ func Setup(cfg *config.Config) (*gin.Engine, error) {
|
||||
pubAPI.GET("/boards/:id/sidebar", h.BoardSidebar)
|
||||
pubAPI.GET("/overview", h.Overview)
|
||||
pubAPI.GET("/sidebar", h.GetSidebar)
|
||||
pubAPI.GET("/friend-links", h.GetFriendLinks)
|
||||
pubAPI.GET("/posts", h.Posts)
|
||||
pubAPI.GET("/posts/:id", h.PostDetail)
|
||||
pubAPI.POST("/posts/:id/unlock-password", middleware.CSRFMiddleware(), middleware.RateLimitMiddleware(limiter, service.RateHidePassword), h.UnlockPostPassword)
|
||||
@@ -211,6 +221,10 @@ func Setup(cfg *config.Config) (*gin.Engine, error) {
|
||||
pubAPI.GET("/settings", h.PublicSettings)
|
||||
pubAPI.GET("/leaderboard", h.Leaderboard)
|
||||
pubAPI.GET("/leaderboard/me", h.LeaderboardMe)
|
||||
// 书库(公开浏览/下载;文件经专用端点消费,不进 /uploads 静态目录)
|
||||
pubAPI.GET("/library", h.LibraryList)
|
||||
pubAPI.GET("/library/:slug", h.LibraryDetail)
|
||||
pubAPI.GET("/library/files/:fid/download", h.LibraryFileDownload)
|
||||
pubAPI.GET("/site-state", h.SiteState)
|
||||
pubAPI.GET("/media/:object", h.PublicObject)
|
||||
pubAPI.POST("/auth/code", middleware.CSRFMiddleware(), h.SendEmailCode)
|
||||
@@ -234,6 +248,10 @@ func Setup(cfg *config.Config) (*gin.Engine, error) {
|
||||
api.GET("/me/login-devices", h.MyLoginDevices)
|
||||
api.DELETE("/me/login-devices/:id", h.RevokeLoginDevice)
|
||||
api.PUT("/profile", h.UpdateProfile)
|
||||
// 友情链接申请(登录用户;提交后进 pending 待审批)
|
||||
api.POST("/friend-links/apply", h.ApplyFriendLink)
|
||||
api.GET("/friend-links/mine", h.MyFriendLinks)
|
||||
api.PUT("/friend-links/:id", h.OwnerUpdateFriendLink)
|
||||
api.POST("/posts", middleware.StaffExempt(middleware.RateLimitMiddleware(limiter, service.RatePost)), h.CreatePost)
|
||||
api.PUT("/posts/:id", h.UpdatePost)
|
||||
api.GET("/posts/:id/history", h.PostEditHistory)
|
||||
@@ -281,6 +299,8 @@ func Setup(cfg *config.Config) (*gin.Engine, error) {
|
||||
|
||||
// 时间线:从 Git commits 页导入(登录用户;适配器由超管配置)
|
||||
api.POST("/timeline/from-git", middleware.StaffExempt(middleware.RateLimitUserMiddleware(limiter, service.RateTimelineGit)), h.TimelineFromGit)
|
||||
// 时间线:从 Git releases 页导入更新日志
|
||||
api.POST("/timeline/from-releases", middleware.StaffExempt(middleware.RateLimitUserMiddleware(limiter, service.RateTimelineGit)), h.TimelineFromReleases)
|
||||
|
||||
// 群聊(二期):建群/成员/消息/未读;全站关闭消息时前台一律 403
|
||||
chatAPI := api.Group("/chat", h.RequireMessagesOpen)
|
||||
@@ -367,6 +387,27 @@ func Setup(cfg *config.Config) (*gin.Engine, error) {
|
||||
announceAPI.POST("/pages", h.AdminCreateSitePage)
|
||||
announceAPI.PUT("/pages/:id", h.AdminUpdateSitePage)
|
||||
announceAPI.DELETE("/pages/:id", h.AdminDeleteSitePage)
|
||||
announceAPI.DELETE("/pages/:id/purge", h.AdminPurgeSitePage)
|
||||
|
||||
// 书库管理(管理员及以上,同公告/单页权限)
|
||||
libraryAPI := staffAPI.Group("/library", authMW.RequirePerm(service.PermAnnouncements))
|
||||
{
|
||||
libraryAPI.GET("/docs", h.AdminListLibraryDocs)
|
||||
libraryAPI.POST("/docs", h.AdminCreateLibraryDoc)
|
||||
libraryAPI.PUT("/docs/:id", h.AdminUpdateLibraryDoc)
|
||||
libraryAPI.DELETE("/docs/:id", h.AdminDeleteLibraryDoc)
|
||||
libraryAPI.DELETE("/docs/:id/purge", h.AdminPurgeLibraryDoc)
|
||||
libraryAPI.POST("/docs/:id/files", middleware.RateLimitMiddleware(limiter, service.RateUpload), h.AdminUploadLibraryFile)
|
||||
libraryAPI.DELETE("/files/:fid", h.AdminDeleteLibraryFile)
|
||||
// 章节(两级树:章 → 节)
|
||||
libraryAPI.POST("/docs/:id/sections", h.AdminCreateLibrarySection)
|
||||
libraryAPI.PUT("/sections/:sid", h.AdminUpdateLibrarySection)
|
||||
libraryAPI.DELETE("/sections/:sid", h.AdminDeleteLibrarySection)
|
||||
libraryAPI.POST("/sections/:sid/move", h.AdminMoveLibrarySection)
|
||||
libraryAPI.POST("/docs/:id/import-md", middleware.RateLimitMiddleware(limiter, service.RateUpload), h.AdminImportLibraryMarkdown)
|
||||
libraryAPI.POST("/docs/:id/import-md-batch", middleware.RateLimitMiddleware(limiter, service.RateUpload), h.AdminImportLibraryMarkdownBatch)
|
||||
libraryAPI.POST("/cover/from-media", middleware.RateLimitMiddleware(limiter, service.RateUpload), h.UploadLibraryCoverFromMedia)
|
||||
}
|
||||
|
||||
// 广告位管理(管理员及以上)
|
||||
adsAPI := staffAPI.Group("", authMW.RequirePerm(service.PermAnnouncements))
|
||||
@@ -400,6 +441,16 @@ func Setup(cfg *config.Config) (*gin.Engine, error) {
|
||||
staffAPI.PUT("/sidebar/widgets/:id", authMW.RequirePerm(service.PermSettings), h.AdminUpdateSidebarWidget)
|
||||
staffAPI.DELETE("/sidebar/widgets/:id", authMW.RequirePerm(service.PermSettings), h.AdminDeleteSidebarWidget)
|
||||
|
||||
// 友情链接管理(超级管理员/站长):CRUD + 审批
|
||||
friendLinkAPI := staffAPI.Group("/friend-links", authMW.RequirePerm(service.PermSettings))
|
||||
{
|
||||
friendLinkAPI.GET("", h.AdminListFriendLinks)
|
||||
friendLinkAPI.POST("", h.AdminCreateFriendLink)
|
||||
friendLinkAPI.PUT("/:id", h.AdminUpdateFriendLink)
|
||||
friendLinkAPI.POST("/:id/status", h.AdminSetFriendLinkStatus)
|
||||
friendLinkAPI.DELETE("/:id", h.AdminDeleteFriendLink)
|
||||
}
|
||||
|
||||
// 站点设置(超级管理员/站长)
|
||||
staffAPI.GET("/settings", authMW.RequirePerm(service.PermSettings), h.AdminGetSettings)
|
||||
opsAPI := staffAPI.Group("/settings/modules", authMW.RequirePerm(service.PermSettings))
|
||||
|
||||
Reference in New Issue
Block a user