refactor: 文件行数超标拆解(Service + Controller 9/16 完成)

- Service 层:energy_service → energize/operations/admin/query 四文件
- Service 层:post_service → helpers/audit + 核心 CRUD 保留
- Service 层:favorite_service → items + 核心文件夹操作保留
- Service 层:auth_service → password + 核心注册/登录保留
- Service 层:notification_service → notify + 核心列表/已读保留
- Service 层:audit_service → review + 核心列表/详情保留
- Controller 层:studio_controller → page/write/api/post_api/action 五文件
- Controller 层:post_controller → page/api/upload 三文件
- Controller 层:comment_controller → list/write/action/upload 四文件
- 清理未使用导入(notification_service.go 移除 fmt)
This commit is contained in:
2026-06-02 15:45:54 +08:00
parent 7cdb4d6698
commit ff9886f08b
31 changed files with 2409 additions and 2237 deletions

View File

@ -3,9 +3,6 @@ package service
import (
"errors"
"fmt"
"regexp"
"strings"
"unicode/utf8"
"metazone.cc/metalab/internal/common"
"metazone.cc/metalab/internal/config"
@ -14,65 +11,6 @@ import (
"gorm.io/gorm"
)
// mdPatterns Markdown 语法正则(用于生成纯文本摘要)
var mdPatterns = []*regexp.Regexp{
// 代码块 ```...```
regexp.MustCompile("(?s)```[^`]*```"),
// 行内代码 `...`
regexp.MustCompile("`[^`]+`"),
// 图片 ![alt](url)
regexp.MustCompile(`!\[.*?\]\(.*?\)`),
// 链接 [text](url)
regexp.MustCompile(`\[([^\]]*)\]\(.*?\)`),
// 标题标记 #
regexp.MustCompile(`^#{1,6}\s+`),
// 粗体/斜体/删除线标记
regexp.MustCompile(`\*{1,3}|_{1,3}|~~`),
// 引用 >
regexp.MustCompile(`^>\s?`),
// 列表标记
regexp.MustCompile(`^[\s]*[-*+]\s+`),
regexp.MustCompile(`^[\s]*\d+\.\s+`),
// 分割线
regexp.MustCompile(`^[-*_]{3,}\s*$`),
}
// generateExcerpt 从 Markdown 生成纯文本摘要(最多 300 字符)
// MD 作为纯文本存储无 XSS 风险,前端由 Vditor 渲染
func generateExcerpt(md string) string {
text := md
// 按行处理,保留行结构
lines := strings.Split(text, "\n")
var cleaned []string
for _, line := range lines {
cleanedLine := line
for _, re := range mdPatterns {
if re == mdPatterns[2] {
// 链接保留文字: [text](url) → text
cleanedLine = re.ReplaceAllString(cleanedLine, "$1")
} else {
cleanedLine = re.ReplaceAllString(cleanedLine, "")
}
}
cleanedLine = strings.TrimSpace(cleanedLine)
if cleanedLine != "" {
cleaned = append(cleaned, cleanedLine)
}
}
result := strings.Join(cleaned, " ")
// 按 rune 截断,不切中文字符
const maxChars = 300
if utf8.RuneCountInString(result) > maxChars {
runes := []rune(result)
result = string(runes[:maxChars]) + "..."
}
return result
}
// PostService 帖子业务逻辑
type PostService struct {
repo postStore
@ -202,189 +140,6 @@ func (s *PostService) GetOverview(userID uint) (*StudioOverview, error) {
}, nil
}
// Update 编辑帖子(权限在 controller 层检查)
func (s *PostService) Update(postID uint, title, body string) error {
post, err := s.findPost(postID)
if err != nil {
return err
}
if post.Status == model.PostStatusPending || post.IsLocked {
return common.ErrPostCannotEdit
}
// 已通过帖子:编辑内容保存为待审修订,不影响当前展示
if post.Status == model.PostStatusApproved {
post.PendingTitle = title
post.PendingBody = body
post.RejectReason = "" // 清空旧退回理由
return s.repo.Update(post)
}
// 草稿/已退回:直接修改原文(现有逻辑)
post.Title = title
post.Body = body
post.Excerpt = generateExcerpt(body)
if post.Status == model.PostStatusRejected {
post.Status = model.PostStatusDraft
post.RejectReason = ""
}
return s.repo.Update(post)
}
// Delete 软删除(权限在 controller 层检查)
func (s *PostService) Delete(postID uint) error {
return s.repo.SoftDelete(postID)
}
// SubmitForAudit 提交审核draft → pending
func (s *PostService) SubmitForAudit(postID uint) error {
post, err := s.findPost(postID)
if err != nil {
return err
}
if post.IsLocked {
return common.ErrPostCannotEdit
}
if post.Status != model.PostStatusDraft && post.Status != model.PostStatusRejected {
return common.ErrPostCannotSubmit
}
post.Status = model.PostStatusPending
return s.repo.Update(post)
}
// Approve 审核通过pending → approved或修订审核通过复制 Pending 到正式字段
func (s *PostService) Approve(postID uint) error {
post, err := s.findPost(postID)
if err != nil {
return err
}
wasRevision := post.PendingBody != ""
// 修订审核:将待审内容覆盖到正式字段
if wasRevision {
post.Title = post.PendingTitle
post.Body = post.PendingBody
post.Excerpt = generateExcerpt(post.PendingBody)
post.PendingTitle = ""
post.PendingBody = ""
post.Status = model.PostStatusApproved
post.RejectReason = ""
if err := s.repo.Update(post); err != nil {
return err
}
// 通知作者修订审核通过
if s.notifier != nil {
s.notifier.NotifyPostApproved(post.UserID, post.Title, post.ID)
}
return nil
}
// 首次审核通过
if post.Status != model.PostStatusPending {
return common.ErrPostCannotApprove
}
post.Status = model.PostStatusApproved
post.RejectReason = ""
if err := s.repo.Update(post); err != nil {
return err
}
// 通知作者审核通过
if s.notifier != nil {
s.notifier.NotifyPostApproved(post.UserID, post.Title, post.ID)
}
return nil
}
// Reject 退回pending/approved → rejected修订退回则清空 Pending 保持 approved
func (s *PostService) Reject(postID uint, reason string) error {
post, err := s.findPost(postID)
if err != nil {
return err
}
if post.Status != model.PostStatusPending && post.Status != model.PostStatusApproved {
return common.ErrPostCannotReject
}
// 修订退回:清空待审修订,保持已发布内容不变
if post.PendingBody != "" {
post.PendingTitle = ""
post.PendingBody = ""
post.RejectReason = reason
if err := s.repo.Update(post); err != nil {
return err
}
// 通知作者修订被退回
if s.notifier != nil {
s.notifier.NotifyPostRejected(post.UserID, post.Title, reason, post.ID)
}
return nil
}
// 普通退回(首次审核)
post.Status = model.PostStatusRejected
post.RejectReason = reason
if err := s.repo.Update(post); err != nil {
return err
}
// 通知作者审核被退回
if s.notifier != nil {
s.notifier.NotifyPostRejected(post.UserID, post.Title, reason, post.ID)
}
return nil
}
// Lock 锁定:设置 IsLocked 标志,禁止编辑,不改变帖子发布状态
func (s *PostService) Lock(postID uint, reason string) error {
post, err := s.findPost(postID)
if err != nil {
return err
}
// 待审核帖子不允许锁定(审核流程中)
if post.Status == model.PostStatusPending {
return common.ErrPostCannotLock
}
post.IsLocked = true
post.LockReason = reason
return s.repo.Update(post)
}
// Unlock 解锁:清除 IsLocked 标志,恢复可编辑
func (s *PostService) Unlock(postID uint) error {
post, err := s.findPost(postID)
if err != nil {
return err
}
if !post.IsLocked {
return common.ErrPostCannotUnlock
}
post.IsLocked = false
post.LockReason = ""
return s.repo.Update(post)
}
// Restore 恢复软删除
func (s *PostService) Restore(postID uint) error {
err := s.repo.Restore(postID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return common.ErrPostNotFound
}
return err
}
return nil
}
// ListPendingRevisions 查询有待审修订的帖子approved 且 PendingBody 不为空)
func (s *PostService) ListPendingRevisions(keyword string, page, pageSize int) ([]model.Post, int64, error) {
return s.listPageable(page, pageSize, func(offset, limit int) ([]model.Post, int64, error) {
return s.repo.FindPendingRevisions(keyword, offset, limit)
})
}
// IsPostAccessible 检查用户是否有权限访问/操作帖子(作者或 moderator+
func (s *PostService) IsPostAccessible(userID uint, role string, postUserID uint) bool {
return userID == postUserID || model.HasMinRole(role, model.RoleModerator)