feat: 管理端板块管理与旧版数据导入,补充部署运营文档

- 新增管理端板块管理页面与后端接口(admin_board)
- 新增旧版数据导入:legacyimport 服务、导入面板、importusers 命令行工具
- 聊天用户卡片、板块图标等 UI 组件与界面优化
- 补充 about/公告/1Panel 部署等文档
- gitignore 排除 dist/ 构建产物与 .agents/ 本地工具目录
This commit is contained in:
2026-09-24 03:37:44 +08:00
parent c1f6a6636b
commit 3e55b5d230
53 changed files with 5078 additions and 409 deletions

View File

@@ -56,7 +56,7 @@ func Setup(cfg *config.Config) (*gin.Engine, error) {
settingSvc := service.NewSettingService(model.DB)
ops := service.NewOperations(model.DB, cfg)
go ops.Run(context.Background())
postSvc := service.NewPostService(model.DB).WithSetting(settingSvc).WithDevMode(cfg.DevMode)
postSvc := service.NewPostService(model.DB).WithSetting(settingSvc).WithBoard(boardSvc).WithDevMode(cfg.DevMode)
commentSvc := service.NewCommentService(model.DB)
likeSvc := service.NewLikeService(model.DB)
notifSvc := service.NewNotificationService(model.DB)
@@ -70,6 +70,7 @@ func Setup(cfg *config.Config) (*gin.Engine, error) {
postFileSvc.WithOperations(ops)
pointsSvc := service.NewPointsService(model.DB)
adminUserSvc := service.NewAdminUserService(model.DB)
legacyImportSvc := service.NewLegacyImportService(model.DB, filepath.Join(cfg.DataDir, "uploads"))
moderationSvc := service.NewModerationService(model.DB, notifSvc)
chatSvc := service.NewChatService(model.DB, notifSvc)
telemetrySvc := service.NewTelemetryService(model.DB)
@@ -103,6 +104,7 @@ func Setup(cfg *config.Config) (*gin.Engine, error) {
Points: pointsSvc,
Setting: settingSvc,
AdminUser: adminUserSvc,
LegacyImport: legacyImportSvc,
Moderation: moderationSvc,
Chat: chatSvc,
Telemetry: telemetrySvc,
@@ -176,22 +178,22 @@ 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)
api.POST("/posts", middleware.RateLimitMiddleware(limiter, service.RatePost), h.CreatePost)
api.POST("/posts", middleware.StaffExempt(middleware.RateLimitMiddleware(limiter, service.RatePost)), h.CreatePost)
api.PUT("/posts/:id", h.UpdatePost)
api.POST("/posts/:id/unlock", middleware.RateLimitUserMiddleware(limiter, service.RateInteract), h.UnlockPostContent)
api.POST("/posts/:id/poll/vote", middleware.RateLimitUserMiddleware(limiter, service.RateInteract), h.VotePoll)
api.POST("/posts/:id/unlock", middleware.StaffExempt(middleware.RateLimitUserMiddleware(limiter, service.RateInteract)), h.UnlockPostContent)
api.POST("/posts/:id/poll/vote", middleware.StaffExempt(middleware.RateLimitUserMiddleware(limiter, service.RateInteract)), h.VotePoll)
api.POST("/posts/:id/poll/close", h.ClosePoll)
api.POST("/posts/:id/question/accept", middleware.RateLimitUserMiddleware(limiter, service.RateInteract), h.AcceptQuestion)
api.POST("/posts/:id/question/solve", middleware.RateLimitUserMiddleware(limiter, service.RateInteract), h.SolveQuestion)
api.POST("/posts/:id/question/reopen", middleware.RateLimitUserMiddleware(limiter, service.RateInteract), h.ReopenQuestion)
api.POST("/posts/:id/bounty/accept", middleware.RateLimitUserMiddleware(limiter, service.RateInteract), h.AcceptBounty)
api.POST("/posts/:id/question/accept", middleware.StaffExempt(middleware.RateLimitUserMiddleware(limiter, service.RateInteract)), h.AcceptQuestion)
api.POST("/posts/:id/question/solve", middleware.StaffExempt(middleware.RateLimitUserMiddleware(limiter, service.RateInteract)), h.SolveQuestion)
api.POST("/posts/:id/question/reopen", middleware.StaffExempt(middleware.RateLimitUserMiddleware(limiter, service.RateInteract)), h.ReopenQuestion)
api.POST("/posts/:id/bounty/accept", middleware.StaffExempt(middleware.RateLimitUserMiddleware(limiter, service.RateInteract)), h.AcceptBounty)
api.POST("/posts/:id/lottery/draw", h.DrawLottery)
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.PUT("/posts/:id/comments/:cid", middleware.RateLimitMiddleware(limiter, service.RateComment), h.UpdateComment)
api.POST("/posts/:id/comments", middleware.StaffExempt(middleware.RateLimitMiddleware(limiter, service.RateComment)), h.CreateComment)
api.PUT("/posts/:id/comments/:cid", middleware.StaffExempt(middleware.RateLimitMiddleware(limiter, service.RateComment)), h.UpdateComment)
api.DELETE("/posts/:id/comments/:cid", h.DeleteComment)
api.PUT("/posts/:id/comments/:cid/restore", h.RestoreComment)
api.DELETE("/posts/:id/comments/:cid/purge", h.PurgeComment)
@@ -202,13 +204,13 @@ func Setup(cfg *config.Config) (*gin.Engine, error) {
api.PUT("/notifications/read-all", h.MarkAllRead)
// 每日签到 / 积分
api.GET("/checkin", h.GetCheckin)
api.POST("/checkin", middleware.RateLimitMiddleware(limiter, service.RateComment), h.DoCheckin)
api.POST("/checkin", middleware.StaffExempt(middleware.RateLimitMiddleware(limiter, service.RateComment)), h.DoCheckin)
api.GET("/points", h.GetPointsBalance)
api.GET("/points/ledger", h.GetPointsLedger)
// 头像上传(裁剪后的 WebP)/ 帖子插图 / 媒体库 / 附件删除
api.POST("/upload/avatar", h.UploadAvatar)
api.POST("/upload/image", middleware.RateLimitMiddleware(limiter, service.RateUpload), h.UploadImage)
api.POST("/upload/file", middleware.RateLimitMiddleware(limiter, service.RateUpload), h.UploadPostFile)
api.POST("/upload/image", middleware.StaffExempt(middleware.RateLimitMiddleware(limiter, service.RateUpload)), h.UploadImage)
api.POST("/upload/file", middleware.StaffExempt(middleware.RateLimitMiddleware(limiter, service.RateUpload)), h.UploadPostFile)
api.PUT("/upload/file/:id/price", h.UpdatePostFilePrice)
api.DELETE("/upload/file/:id", h.DeletePostFile)
api.PUT("/avatar/use", h.UseAvatar)
@@ -216,7 +218,7 @@ func Setup(cfg *config.Config) (*gin.Engine, error) {
api.DELETE("/my/attachments/:id", h.DeleteAttachment)
// 时间线:从 Git commits 页导入(登录用户;适配器由超管配置)
api.POST("/timeline/from-git", middleware.RateLimitUserMiddleware(limiter, service.RateTimelineGit), h.TimelineFromGit)
api.POST("/timeline/from-git", middleware.StaffExempt(middleware.RateLimitUserMiddleware(limiter, service.RateTimelineGit)), h.TimelineFromGit)
// 群聊(二期):建群/成员/消息/未读;全站关闭消息时前台一律 403
chatAPI := api.Group("/chat", h.RequireMessagesOpen)
@@ -237,7 +239,7 @@ func Setup(cfg *config.Config) (*gin.Engine, error) {
chatAPI.PUT("/rooms/:id/members/:uid/mute", h.SetChatMemberMute)
chatAPI.PUT("/rooms/:id/members/:uid/role", h.SetChatMemberRole)
chatAPI.GET("/rooms/:id/messages", h.ListChatMessages)
chatAPI.POST("/rooms/:id/messages", middleware.RateLimitMiddleware(limiter, service.RateChat), h.SendChatMessage)
chatAPI.POST("/rooms/:id/messages", middleware.StaffExempt(middleware.RateLimitMiddleware(limiter, service.RateChat)), h.SendChatMessage)
chatAPI.DELETE("/rooms/:id/messages/:mid", h.RecallChatMessage)
chatAPI.PUT("/rooms/:id/read", h.MarkChatRead)
chatAPI.GET("/unread-summary", h.ChatUnreadSummary)
@@ -312,6 +314,8 @@ func Setup(cfg *config.Config) (*gin.Engine, error) {
opsAPI.GET("/:module/records", h.ModuleRecords)
staffAPI.GET("/diagnostics", authMW.RequirePerm(service.PermSettings), h.Diagnostics)
staffAPI.POST("/maintenance/actions", authMW.RequirePerm(service.PermSettings), h.MaintenanceAction)
// 数据导入(旧站 / WordPress / Typecho 等,source 白名单见 service.LegacyImportSource)
staffAPI.POST("/import/:source", authMW.RequirePerm(service.PermSettings), h.AdminImportLegacy)
staffAPI.PUT("/settings", authMW.RequirePerm(service.PermSettings), h.UpdateSettings)
staffAPI.POST("/upload/background", authMW.RequirePerm(service.PermSettings), middleware.RateLimitMiddleware(limiter, service.RateUpload), h.UploadBackground)
staffAPI.POST("/upload/background/from-media", authMW.RequirePerm(service.PermSettings), middleware.RateLimitMiddleware(limiter, service.RateUpload), h.UploadBackgroundFromMedia)
@@ -320,16 +324,31 @@ func Setup(cfg *config.Config) (*gin.Engine, error) {
// 用户与权限管理(超级管理员/站长):列表、角色授权、封禁、登录历史
usersAPI := staffAPI.Group("", authMW.RequirePerm(service.PermUsers))
usersAPI.GET("/users", h.AdminListUsers)
usersAPI.PUT("/users/:id/role", h.AdminUpdateUserRole)
usersAPI.PUT("/users/:id/messages", h.AdminSetUserMessages)
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)
{
usersAPI.GET("/users", h.AdminListUsers)
usersAPI.PUT("/users/:id/role", h.AdminUpdateUserRole)
usersAPI.PUT("/users/:id/messages", h.AdminSetUserMessages)
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)
}
// 板块管理(仅站长):增删改、排序、版主授权
boardsAPI := staffAPI.Group("", authMW.RequirePerm(service.PermBoards))
{
boardsAPI.GET("/boards", h.AdminListBoards)
boardsAPI.POST("/boards", h.AdminCreateBoard)
boardsAPI.PUT("/boards/:id", h.AdminUpdateBoard)
boardsAPI.DELETE("/boards/:id", h.AdminDeleteBoard)
boardsAPI.PUT("/boards/reorder", h.AdminReorderBoards)
boardsAPI.GET("/boards/:id/moderators", h.AdminListBoardModerators)
boardsAPI.POST("/boards/:id/moderators", h.AdminAddBoardModerator)
boardsAPI.DELETE("/boards/:id/moderators/:uid", h.AdminRemoveBoardModerator)
}
}
// 消息管理后台:登录即可(handler 内校验站点消息权或群管理员)