This repository has been archived on 2026-06-21. You can view files and clone it, but cannot push or open issues or pull requests.
Files
MetaLab/internal/service/audit_service.go
Victor_Jay 7d944cfa35 fix: 修复更名退款逻辑、审核列表显示及更名消耗确认提示
- 首次更名审核被拒不再退款,首次免费未扣费不产生退款
- 审核列表提交者列改用 UID,当前用户名列显示当前用户名
- 更名消耗提示改为确认弹窗,首次/非首次动态显示不同文案
2026-06-02 22:09:41 +08:00

228 lines
7.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package service
import (
"metazone.cc/metalab/internal/common"
"metazone.cc/metalab/internal/config"
"metazone.cc/metalab/internal/model"
)
// auditRepo AuditService 所需的最小仓储接口ISP
type auditRepo interface {
Create(audit *model.AuditSubmission) error
FindByID(id uint) (*model.AuditSubmission, error)
FindPendingByUserAndType(userID uint, auditType string) (*model.AuditSubmission, error)
Update(audit *model.AuditSubmission) error
ListAudits(auditType, status string, offset, limit int) ([]model.AuditSubmission, error)
CountAudits(auditType, status string) (int64, error)
FindPendingByUserID(userID uint) ([]model.AuditSubmission, error)
ExistsPendingByTypeAndValue(auditType, newValue string, excludeUserID uint) (bool, error)
}
// userProfileStore 审核服务所需的最小用户仓储接口ISP
type userProfileStore interface {
FindByID(id uint) (*model.User, error)
Update(user *model.User) error
ExistsByUsername(username string) (bool, error)
}
// siteSettingsChecker 审核服务所需的站点设置接口ISP仅读取审核开关
type siteSettingsChecker interface {
IsAuditEnabled() bool
IsAuditTypeEnabled(auditType string, defaultVal bool) bool
}
// auditNotifier AuditService 所需的通知接口ISP审核通过/拒绝时发送通知)
type auditNotifier interface {
NotifyAuditApproved(userID uint, auditType string, relatedID uint)
NotifyAuditRejected(userID uint, auditType, reason string, relatedID uint)
}
// auditTaskCompleter 审核通过时触发首次任务奖励的接口
type auditTaskCompleter interface {
CompleteTask(userID uint, taskType string) (newExp int, levelUp bool, err error)
HasCompletedTask(userID uint, taskType string) (bool, error)
}
// energyRenameRefunder 改名审核被拒时返还域能的接口ISP
type energyRenameRefunder interface {
RefundOnRenameReject(userID uint) error
}
// AuditService 审核业务逻辑
type AuditService struct {
auditRepo auditRepo
userRepo userProfileStore
settings siteSettingsChecker
defaultCfg config.AuditConfig
notifier auditNotifier
levelSvc auditTaskCompleter
energyRefundSvc energyRenameRefunder
}
// NewAuditService 构造函数
func NewAuditService(auditRepo auditRepo, userRepo userProfileStore, settings siteSettingsChecker, cfg config.AuditConfig, notifier auditNotifier, levelSvc auditTaskCompleter) *AuditService {
return &AuditService{
auditRepo: auditRepo,
userRepo: userRepo,
settings: settings,
defaultCfg: cfg,
notifier: notifier,
levelSvc: levelSvc,
}
}
// WithEnergyRefundService 链式注入域能退款服务
func (s *AuditService) WithEnergyRefundService(svc energyRenameRefunder) *AuditService {
s.energyRefundSvc = svc
return s
}
// ShouldAudit 判断指定用户是否需要走审核流程
// 规则admin 及以上角色免审
func (s *AuditService) ShouldAudit(userID uint) (bool, error) {
user, err := s.userRepo.FindByID(userID)
if err != nil {
return false, err
}
return !model.HasMinRole(user.Role, model.RoleAdmin), nil
}
// Submit 提交审核
// - 总开关未开 → 返回 ErrAuditDisabled
// - 类型开关未开 → 返回 ErrAuditTypeOff
// - 用户名类型 → 先检查是否已被占用
// - 若存在同类型 pending → 标记旧记录为 rejected理由"用户重新提交"),创建新 pending
func (s *AuditService) Submit(userID uint, auditType, newValue string) error {
// 检查总开关
if !s.settings.IsAuditEnabled() {
return common.ErrAuditDisabled
}
// 检查类型开关
if !s.settings.IsAuditTypeEnabled(auditType, s.typeDefault(auditType)) {
return common.ErrAuditTypeOff
}
// 用户名冲突检查(提交时即拦截,避免提交到后台后审核失败)
if auditType == model.AuditTypeUsername {
// 1) users 表中已被占用
exists, err := s.userRepo.ExistsByUsername(newValue)
if err != nil {
return err
}
if exists {
return common.ErrUsernameTaken
}
// 2) 审核表中已有其他用户提交了同名 pending
pendingExists, err := s.auditRepo.ExistsPendingByTypeAndValue(auditType, newValue, userID)
if err != nil {
return err
}
if pendingExists {
return common.ErrUsernameTaken
}
}
// 覆盖旧的同类型 pending
existing, err := s.auditRepo.FindPendingByUserAndType(userID, auditType)
if err == nil {
reason := "用户重新提交"
existing.Status = model.AuditStatusRejected
existing.RejectReason = &reason
_ = s.auditRepo.Update(existing)
}
submission := &model.AuditSubmission{
UserID: userID,
AuditType: auditType,
NewValue: newValue,
Status: model.AuditStatusPending,
}
return s.auditRepo.Create(submission)
}
// SubmitProfileChanges 批量提交资料变更(用户名/签名),每个变更字段独立提交
// 仅提交有变更的字段,无变更的跳过
func (s *AuditService) SubmitProfileChanges(userID uint, currentUser *model.User, newUsername, newBio string) error {
if newUsername != "" && newUsername != currentUser.Username {
if err := s.Submit(userID, model.AuditTypeUsername, newUsername); err != nil {
return err
}
}
if newBio != "" && newBio != currentUser.Bio {
if err := s.Submit(userID, model.AuditTypeBio, newBio); err != nil {
return err
}
}
return nil
}
// Approve 通过审核 → 更新 User 表对应字段
// 仅审核人可操作,不可审核自己的提交
func (s *AuditService) Approve(reviewerID uint, submissionID uint) error {
return s.review(reviewerID, submissionID, true, "")
}
// Reject 拒绝审核 → 记录拒绝理由
func (s *AuditService) Reject(reviewerID uint, submissionID uint, reason string) error {
return s.review(reviewerID, submissionID, false, reason)
}
// List 分页查询审核列表
func (s *AuditService) List(auditType, status string, page, pageSize int) (*AuditListResult, error) {
p := common.Pagination{Page: page, PageSize: pageSize}
p.DefaultPagination()
total, err := s.auditRepo.CountAudits(auditType, status)
if err != nil {
return nil, err
}
items, err := s.auditRepo.ListAudits(auditType, status, p.Offset(), p.PageSize)
if err != nil {
return nil, err
}
if items == nil {
items = []model.AuditSubmission{}
}
return &AuditListResult{
Items: items,
Total: total,
Page: p.Page,
}, nil
}
// GetPendingTypes 获取用户所有待审核类型(用于个人中心提示)
func (s *AuditService) GetPendingTypes(userID uint) ([]string, error) {
submissions, err := s.auditRepo.FindPendingByUserID(userID)
if err != nil {
return nil, err
}
types := make([]string, 0, len(submissions))
for _, sub := range submissions {
types = append(types, sub.AuditType)
}
return types, nil
}
// typeDefault 获取某审核类型在 config.yaml 中的默认开关值
func (s *AuditService) typeDefault(auditType string) bool {
switch auditType {
case model.AuditTypeUsername:
return s.defaultCfg.UsernameAudit
case model.AuditTypeAvatar:
return s.defaultCfg.AvatarAudit
case model.AuditTypeBio:
return s.defaultCfg.BioAudit
}
return true
}
// AuditListResult 审核列表查询结果
type AuditListResult struct {
Items []model.AuditSubmission `json:"items"`
Total int64 `json:"total"`
Page int `json:"page"`
}