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:
@ -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("`[^`]+`"),
|
||||
// 图片 
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user