feat: 书库导入导出/图片变体/书籍搜索/小组件运行时等

新增:
- 书库导入导出(library_import/library_export)及测试
- 图片变体生成(image_variants)与响应式图片(responsiveImage)
- 书籍搜索(bookSearch)+ BookSearch/LibrarySearchGrid 组件
- 小组件运行时(widgetRuntime)与静态检查(widgetLint)
- 上传缓存中间件(upload_cache)与字体 CSS 提取脚本

其它:
- 后端 handlers/services 全量调整
- 前端页面、组件、库函数与配置更新
This commit is contained in:
2026-10-01 03:06:05 +08:00
parent 7fbd6ba699
commit ff2ab286fb
141 changed files with 7527 additions and 1407 deletions

View File

@@ -101,7 +101,9 @@ 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)
librarySvc := service.NewLibraryService(model.DB, cfg.DataDir).
WithSetting(settingSvc).
WithUploadsDir(filepath.Join(cfg.DataDir, "uploads"))
adminUserSvc := service.NewAdminUserService(model.DB)
legacyImportSvc := service.NewLegacyImportService(model.DB, filepath.Join(cfg.DataDir, "uploads"))
moderationSvc := service.NewModerationService(model.DB, notifSvc)
@@ -183,6 +185,9 @@ func Setup(cfg *config.Config) (*gin.Engine, error) {
// 挂在 OptionalAuth 之后可顺带拿到登录用户
r.Use(middleware.VisitCapture(visitSvc))
// 上传文件长缓存(必须先于 r.Static 注册;文件名随机不覆盖,可 immutable)
r.Use(middleware.UploadStaticCache())
// 上传文件静态服务(data/uploads → /uploads)
r.Static("/uploads", filepath.Join(cfg.DataDir, "uploads"))
@@ -209,7 +214,6 @@ func Setup(cfg *config.Config) (*gin.Engine, error) {
pubAPI.POST("/posts/:id/unlock-password", middleware.CSRFMiddleware(), middleware.RateLimitMiddleware(limiter, service.RateHidePassword), h.UnlockPostPassword)
pubAPI.GET("/posts/:id/attachments/:aid/download", h.DownloadPostAttachment)
pubAPI.GET("/posts/:id/comments", h.PostComments)
pubAPI.GET("/posts/:id/comments/:cid", h.CommentLocation)
pubAPI.GET("/users/:id", h.UserProfile)
pubAPI.GET("/users/:id/comments", h.UserComments)
pubAPI.GET("/users/:id/favorites", h.UserFavorites)
@@ -226,6 +230,8 @@ func Setup(cfg *config.Config) (*gin.Engine, error) {
pubAPI.GET("/library/:slug", h.LibraryDetail)
pubAPI.GET("/library/files/:fid/download", h.LibraryFileDownload)
pubAPI.GET("/site-state", h.SiteState)
// 自适应图片变体(?u=<源图URL>&w=<白名单宽度>,公开只读、immutable 缓存)
pubAPI.GET("/img", h.ImageVariant)
pubAPI.GET("/media/:object", h.PublicObject)
pubAPI.POST("/auth/code", middleware.CSRFMiddleware(), h.SendEmailCode)
pubAPI.POST("/auth/reset-password", middleware.CSRFMiddleware(), h.ResetPassword)
@@ -376,23 +382,34 @@ func Setup(cfg *config.Config) (*gin.Engine, error) {
// 群聊监管:移出后挂到 RequireAuth 组,允许「仅群管/消息 flag」用户访问
// (见下方 msgAPI)
// 站点公告文章管理(管理员及以上)
announceAPI := staffAPI.Group("", authMW.RequirePerm(service.PermAnnouncements))
// 渠道内容管理(公告/单页/书库/广告):RequireActor 允许任意登录账号进入,
// 由 RequirePerm 按渠道权限码放行——角色默认管理员及以上,
// 超管/站长亦可将单个渠道授予板块管理员或普通用户(账号级授权)
permAPI := r.Group("/api/admin", authMW.RequireActor(), middleware.CSRFMiddleware())
// 站点公告文章管理(PermAnnouncements)
announceAPI := permAPI.Group("", authMW.RequirePerm(service.PermAnnouncements))
announceAPI.GET("/announcements", h.AdminListAnnouncements)
announceAPI.POST("/announcements", h.AdminCreateAnnouncement)
announceAPI.PUT("/announcements/:id", h.AdminUpdateAnnouncement)
announceAPI.POST("/announcements/:id/pin", h.AdminToggleAnnouncementPin)
announceAPI.DELETE("/announcements/:id", h.AdminDeleteAnnouncement)
announceAPI.GET("/pages", h.AdminListSitePages)
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))
// 站点单页管理(PermPages)
pagesAPI := permAPI.Group("", authMW.RequirePerm(service.PermPages))
pagesAPI.GET("/pages", h.AdminListSitePages)
pagesAPI.POST("/pages", h.AdminCreateSitePage)
pagesAPI.PUT("/pages/:id", h.AdminUpdateSitePage)
pagesAPI.DELETE("/pages/:id", h.AdminDeleteSitePage)
pagesAPI.DELETE("/pages/:id/purge", h.AdminPurgeSitePage)
// 书库管理(PermLibrary)
libraryAPI := permAPI.Group("/library", authMW.RequirePerm(service.PermLibrary))
{
libraryAPI.GET("/docs", h.AdminListLibraryDocs)
libraryAPI.GET("/export", h.AdminExportAllLibrary)
libraryAPI.POST("/import", middleware.RateLimitMiddleware(limiter, service.RateUpload), h.AdminImportLibrary)
libraryAPI.GET("/docs/:id/export", h.AdminExportLibraryDoc)
libraryAPI.POST("/docs", h.AdminCreateLibraryDoc)
libraryAPI.PUT("/docs/:id", h.AdminUpdateLibraryDoc)
libraryAPI.DELETE("/docs/:id", h.AdminDeleteLibraryDoc)
@@ -409,8 +426,8 @@ func Setup(cfg *config.Config) (*gin.Engine, error) {
libraryAPI.POST("/cover/from-media", middleware.RateLimitMiddleware(limiter, service.RateUpload), h.UploadLibraryCoverFromMedia)
}
// 广告位管理(管理员及以上)
adsAPI := staffAPI.Group("", authMW.RequirePerm(service.PermAnnouncements))
// 广告位管理(PermAds)
adsAPI := permAPI.Group("", authMW.RequirePerm(service.PermAds))
adsAPI.GET("/ads/config", h.AdminGetAdsConfig)
adsAPI.PUT("/ads/config", h.AdminSaveAdsConfig)
adsAPI.GET("/ads", h.AdminListAds)
@@ -477,6 +494,8 @@ func Setup(cfg *config.Config) (*gin.Engine, error) {
usersAPI.GET("/users", h.AdminListUsers)
usersAPI.PUT("/users/:id/role", h.AdminUpdateUserRole)
usersAPI.PUT("/users/:id/messages", h.AdminSetUserMessages)
usersAPI.GET("/users/:id/perms", h.AdminGetUserPerms)
usersAPI.PUT("/users/:id/perms", h.AdminSetUserPerms)
usersAPI.PUT("/users/:id/ban", h.AdminSetUserBan)
usersAPI.GET("/users/:id/login-logs", h.AdminUserLoginLogs)
// 积分管理:手动调整(加分计累计/扣分仅扣余额)与积分流水分页