feat: 友链/书库/移动端导航/排行榜等多模块功能

新增模块:
- 友链(friendlink)后端处理 + 前端管理页与友链板
- 书库(library):导入、章节、封面、阅读页、横竖版自适应 AdaptiveCoverSlot
- 顶栏导航(header_nav)配置与 MobileTabBar/MobileRailDrawers 移动端抽屉
- 排行榜 service 测试、站点页面测试、时间线发布(timeline_release)

其它改动:
- 后端 handlers/services 全量小幅调整
- 前端组件、库函数、URL/品牌/站点 URL 工具更新
- Lightbox 图片、MdEntries 条目卡、coverColor 派生色相等前端能力
This commit is contained in:
2026-09-30 16:30:07 +08:00
parent e487bc2e92
commit 4f3f3a265c
105 changed files with 12371 additions and 852 deletions

View File

@@ -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))