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

@ -1,12 +1,6 @@
package service
import (
"fmt"
"time"
"metazone.cc/metalab/internal/common"
"metazone.cc/metalab/internal/model"
"gorm.io/gorm"
)
@ -59,480 +53,3 @@ const (
DailyEnergizeExpCap = 50 // 每日赋能经验上限
MaxEnergizePerPost = 20 // 单用户单文章最多赋能 2 域能×10
)
// Energize 赋能文章
// 返回:获得的经验值、错误
func (s *EnergyService) Energize(energizerID uint, postID uint, amount int) (int, error) {
// 验证赋能量
if amount != EnergyEnergize1 && amount != EnergyEnergize2 {
return 0, common.ErrInvalidEnergyAmount
}
var expGained int
var needExpUpdate bool
var expToday time.Time
var expTotal int
err := s.db.Transaction(func(tx *gorm.DB) error {
txRepo := s.repo.WithTx(tx)
var txFundRepo FundStore
if s.fundRepo != nil {
txFundRepo = s.fundRepo.WithTx(tx)
}
// 1. 检查帖子存在,获取作者
post, err := txRepo.FindPostByID(postID)
if err != nil {
if err == gorm.ErrRecordNotFound {
return common.ErrPostNotFound
}
return fmt.Errorf("查询帖子失败: %w", err)
}
// 2. 不能给自己赋能
if post.UserID == energizerID {
return common.ErrCannotEnergizeSelf
}
// 3. 检查赋能者域能余额owner 跳过)
energizer, err := txRepo.FindUserByID(energizerID)
if err != nil {
return fmt.Errorf("查询用户失败: %w", err)
}
if energizer.Role != model.RoleOwner && energizer.Energy < amount {
return common.ErrInsufficientEnergy
}
// 4. 检查单用户单文章赋能总量 ≤ MaxEnergizePerPost
total, err := txRepo.SumUserPostEnergy(energizerID, postID)
if err != nil {
return fmt.Errorf("查询赋能记录失败: %w", err)
}
if total+amount > MaxEnergizePerPost {
return common.ErrEnergizeLimitReached
}
// 5. 扣赋能者域能
if err := txRepo.AddEnergy(energizerID, -amount); err != nil {
return fmt.Errorf("扣减域能失败: %w", err)
}
// 6. 增加被赋能者(文章作者)域能(赋能量的十分之一)
receivedAmount := amount / 10 // 1 域能→0.12 域能→0.2
if err := txRepo.AddEnergy(post.UserID, receivedAmount); err != nil {
return fmt.Errorf("增加被赋能者域能失败: %w", err)
}
// 7. 更新 Post.total_energy_received
if err := txRepo.IncrPostEnergy(postID, amount); err != nil {
return fmt.Errorf("更新文章赋能计数失败: %w", err)
}
// 8. 插入赋能记录
eLog := &model.PostEnergizeLog{
UserID: energizerID,
PostID: postID,
Amount: amount,
}
if err := txRepo.CreateEnergizeLog(eLog); err != nil {
return fmt.Errorf("创建赋能记录失败: %w", err)
}
// 9. 写入域能流水 ×2
amountDisplay := float64(amount) / 10
receivedDisplay := float64(receivedAmount) / 10
energizerLog := &model.EnergyLog{
UserID: energizerID,
Amount: -amount,
Type: model.EnergyTypeEnergize,
RelatedType: "post",
RelatedID: postID,
Description: fmt.Sprintf("给文章 %d 赋能 %.1f 域能", postID, amountDisplay),
}
if err := txRepo.CreateEnergyLog(energizerLog); err != nil {
return fmt.Errorf("创建赋能者流水失败: %w", err)
}
energizedLog := &model.EnergyLog{
UserID: post.UserID,
Amount: receivedAmount,
Type: model.EnergyTypeEnergized,
RelatedType: "post",
RelatedID: postID,
Description: fmt.Sprintf("文章 %d 被赋能 +%.1f 域能", postID, receivedDisplay),
}
if err := txRepo.CreateEnergyLog(energizedLog); err != nil {
return fmt.Errorf("创建被赋能者流水失败: %w", err)
}
// 10. 赋能税收入公户(差价 = amount - receivedAmount
if txFundRepo != nil {
tax := amount - receivedAmount
if tax > 0 {
if err := txFundRepo.IncrBalance(tax); err != nil {
return fmt.Errorf("公户入账失败: %w", err)
}
taxDisplay := float64(tax) / 10
fundLog := &model.FundLog{
Amount: tax,
Type: model.FundTypeEnergizeTax,
RelatedType: "post",
RelatedID: postID,
Description: fmt.Sprintf("赋能文章 #%d 税收 %.1f 域能,赋能者 uid=%d", postID, taxDisplay, energizerID),
}
if err := txFundRepo.CreateLog(fundLog); err != nil {
return fmt.Errorf("创建公户流水失败: %w", err)
}
}
}
// 11. 计算经验值(溢出截断)—— 在事务内读取以保证一致性
loc, _ := time.LoadLocation("Asia/Shanghai")
now := time.Now().In(loc)
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, loc)
summary, err := txRepo.GetDailyEnergizeExp(energizerID, today)
currentDaily := 0
if err == nil && summary != nil {
currentDaily = summary.ExpEarned
}
expGain := amount // 1域能(DB10) → 10经验, 2域能(DB20) → 20经验
expRoom := DailyEnergizeExpCap - currentDaily
if expRoom < 0 {
expRoom = 0
}
if expGain > expRoom {
expGain = expRoom
}
expGained = expGain
if expGain > 0 {
needExpUpdate = true
expToday = today
expTotal = currentDaily + expGain
}
return nil
})
if err != nil {
return 0, err
}
// 12. 事务提交后,在事务外增加经验 + 更新每日汇总
if needExpUpdate {
if s.expSvc != nil {
if _, _, err := s.expSvc.AddExp(energizerID, expGained); err != nil {
return expGained, fmt.Errorf("赋能成功,但增加经验失败: %w", err)
}
}
newSummary := &model.DailyExpSummary{
UserID: energizerID,
Date: expToday,
ExpEarned: expTotal,
}
if err := s.repo.UpsertDailyExp(newSummary); err != nil {
return expGained, fmt.Errorf("赋能成功,但更新经验汇总失败: %w", err)
}
}
return expGained, nil
}
// CheckInEnergy 签到获得域能LV0 用户不调用此方法)
func (s *EnergyService) CheckInEnergy(userID uint) error {
if err := s.repo.AddEnergy(userID, EnergyCheckIn); err != nil {
return fmt.Errorf("增加签到域能失败: %w", err)
}
log := &model.EnergyLog{
UserID: userID,
Amount: EnergyCheckIn,
Type: model.EnergyTypeSignIn,
Description: "每日签到 +1.0 域能",
}
return s.repo.CreateEnergyLog(log)
}
// DeductOnRename 改名扣域能(首次改名免费,不调用此方法)
// 事务包裹:扣用户域能 + 入公户 + 写流水
func (s *EnergyService) DeductOnRename(userID uint) error {
return s.db.Transaction(func(tx *gorm.DB) error {
txRepo := s.repo.WithTx(tx)
// 检查域能余额(非 owner 需 ≥ 0
user, err := txRepo.FindUserByID(userID)
if err != nil {
return fmt.Errorf("查询用户失败: %w", err)
}
if user.Role != model.RoleOwner && user.Energy+EnergyRename < 0 {
return common.ErrInsufficientEnergy
}
if err := txRepo.AddEnergy(userID, EnergyRename); err != nil {
return fmt.Errorf("扣减改名域能失败: %w", err)
}
// 公户入账
if s.fundRepo != nil {
txFundRepo := s.fundRepo.WithTx(tx)
if err := txFundRepo.IncrBalance(-EnergyRename); err != nil { // EnergyRename = -60入公户 +60
return fmt.Errorf("公户入账失败: %w", err)
}
fundLog := &model.FundLog{
Amount: -EnergyRename,
Type: model.FundTypeRenameDeduction,
RelatedType: "user",
RelatedID: userID,
Description: fmt.Sprintf("用户 uid=%d 改名消耗 6.0 域能", userID),
}
if err := txFundRepo.CreateLog(fundLog); err != nil {
return fmt.Errorf("创建公户流水失败: %w", err)
}
}
log := &model.EnergyLog{
UserID: userID,
Amount: EnergyRename,
Type: model.EnergyTypeRename,
Description: "改名消耗 6.0 域能",
}
return txRepo.CreateEnergyLog(log)
})
}
// RefundOnRenameReject 改名审核被拒,从公户返还域能
func (s *EnergyService) RefundOnRenameReject(userID uint) error {
return s.db.Transaction(func(tx *gorm.DB) error {
txRepo := s.repo.WithTx(tx)
// 从公户支出(允许透支)
if s.fundRepo != nil {
txFundRepo := s.fundRepo.WithTx(tx)
if err := txFundRepo.IncrBalance(-EnergyRenameRefund); err != nil {
return fmt.Errorf("公户出账失败: %w", err)
}
fundLog := &model.FundLog{
Amount: -EnergyRenameRefund,
Type: model.FundTypeRenameRefund,
RelatedType: "user",
RelatedID: userID,
Description: fmt.Sprintf("用户 uid=%d 改名审核被拒,返还 6.0 域能", userID),
}
if err := txFundRepo.CreateLog(fundLog); err != nil {
return fmt.Errorf("创建公户流水失败: %w", err)
}
}
if err := txRepo.AddEnergy(userID, EnergyRenameRefund); err != nil {
return fmt.Errorf("返还改名域能失败: %w", err)
}
log := &model.EnergyLog{
UserID: userID,
Amount: EnergyRenameRefund,
Type: model.EnergyTypeRenameRefund,
Description: "改名审核被拒,返还 6.0 域能",
}
return txRepo.CreateEnergyLog(log)
})
}
// DeductOnDeletePost 删稿扣域能(扣文章作者的域能,无视负数)
// 事务包裹:扣用户域能 + 入公户 + 写流水
func (s *EnergyService) DeductOnDeletePost(authorUserID uint, postID uint) error {
return s.db.Transaction(func(tx *gorm.DB) error {
txRepo := s.repo.WithTx(tx)
if err := txRepo.AddEnergy(authorUserID, EnergyDeletePost); err != nil {
return fmt.Errorf("扣减删稿域能失败: %w", err)
}
// 公户入账
if s.fundRepo != nil {
txFundRepo := s.fundRepo.WithTx(tx)
if err := txFundRepo.IncrBalance(-EnergyDeletePost); err != nil { // EnergyDeletePost = -20入公户 +20
return fmt.Errorf("公户入账失败: %w", err)
}
fundLog := &model.FundLog{
Amount: -EnergyDeletePost,
Type: model.FundTypeDeletePost,
RelatedType: "post",
RelatedID: postID,
Description: fmt.Sprintf("用户 uid=%d 删除文章 #%d 消耗 2.0 域能", authorUserID, postID),
}
if err := txFundRepo.CreateLog(fundLog); err != nil {
return fmt.Errorf("创建公户流水失败: %w", err)
}
}
log := &model.EnergyLog{
UserID: authorUserID,
Amount: EnergyDeletePost,
Type: model.EnergyTypeDeletePost,
RelatedType: "post",
RelatedID: postID,
Description: "删除文章消耗 2.0 域能",
}
return txRepo.CreateEnergyLog(log)
})
}
// AdminAdjust 后台调整用户域能
// mode: "admin_transfer"(受公户余额约束)| "system_operation"(不受约束,不碰公户)
func (s *EnergyService) AdminAdjust(operatorUID uint, userIDs []uint, amount int, description string, mode string) error {
if mode == "" {
mode = model.FundTypeAdminTransfer
}
return s.db.Transaction(func(tx *gorm.DB) error {
txRepo := s.repo.WithTx(tx)
// admin_transfer公户出账需检查余额行锁防 TOCTOU 竞态)
isAdminTransfer := mode == model.FundTypeAdminTransfer
if isAdminTransfer && amount > 0 && s.fundRepo != nil {
txFundRepo := s.fundRepo.WithTx(tx)
totalCost := amount * len(userIDs)
balance, err := txFundRepo.LockAndGetBalance()
if err != nil {
return fmt.Errorf("查询公户余额失败: %w", err)
}
if balance < totalCost {
return common.ErrInsufficientFund
}
}
for _, uid := range userIDs {
if err := txRepo.AddEnergy(uid, amount); err != nil {
return fmt.Errorf("调整用户 %d 域能失败: %w", uid, err)
}
opUID := operatorUID
energyLog := &model.EnergyLog{
UserID: uid,
Amount: amount,
Type: model.EnergyTypeAdminAdjust,
Description: description,
OperatorUID: &opUID,
}
if err := txRepo.CreateEnergyLog(energyLog); err != nil {
return fmt.Errorf("创建调整流水失败: %w", err)
}
}
// admin_transfer公户扣款 + 公户流水system_operation 不碰公户)
if isAdminTransfer && s.fundRepo != nil {
txFundRepo := s.fundRepo.WithTx(tx)
totalCost := amount * len(userIDs)
if err := txFundRepo.IncrBalance(-totalCost); err != nil {
return fmt.Errorf("公户余额调整失败: %w", err)
}
totalDisplay := float64(totalCost) / 10
fundLog := &model.FundLog{
Amount: -totalCost,
Type: mode,
OperatorUID: &operatorUID,
Description: fmt.Sprintf("%s调整 %d 名用户域能 %.1f,说明:%s", getFundModeName(mode), len(userIDs), totalDisplay, description),
}
if len(userIDs) > 0 {
fundLog.RelatedType = "user"
fundLog.RelatedID = userIDs[0]
}
if err := txFundRepo.CreateLog(fundLog); err != nil {
return fmt.Errorf("创建公户流水失败: %w", err)
}
}
return nil
})
}
// getFundModeName 返回操作模式中文名
func getFundModeName(mode string) string {
switch mode {
case model.FundTypeAdminTransfer:
return "管理转出"
case model.FundTypeSystemOperation:
return "系统操作"
default:
return "后台调整"
}
}
// GetEnergyInfo 获取用户域能余额和每日赋能经验状态
func (s *EnergyService) GetEnergyInfo(userID uint) (*EnergyInfo, error) {
user, err := s.repo.FindUserByID(userID)
if err != nil {
return nil, err
}
loc, _ := time.LoadLocation("Asia/Shanghai")
now := time.Now().In(loc)
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, loc)
dailyExp := 0
summary, err := s.repo.GetDailyEnergizeExp(userID, today)
if err == nil && summary != nil {
dailyExp = summary.ExpEarned
}
return &EnergyInfo{
Energy: user.Energy,
DailyEnergizeExp: dailyExp,
DailyExpCap: DailyEnergizeExpCap,
DailyExpCapReached: dailyExp >= DailyEnergizeExpCap,
}, nil
}
// GetEnergyLogs 分页查询用户域能流水(近 N 天)
func (s *EnergyService) GetEnergyLogs(userID uint, days, page, pageSize int) ([]model.EnergyLog, int64, error) {
p := common.Pagination{Page: page, PageSize: pageSize}
p.DefaultPagination()
total, err := s.repo.CountEnergyLogsByUser(userID, days)
if err != nil {
return nil, 0, err
}
logs, err := s.repo.ListEnergyLogsByUser(userID, days, p.Offset(), p.PageSize)
if err != nil {
return nil, 0, err
}
if logs == nil {
logs = []model.EnergyLog{}
}
return logs, total, nil
}
// GetAdminEnergyLogs 后台查询全部域能流水
func (s *EnergyService) GetAdminEnergyLogs(energyType string, page, pageSize int) ([]model.EnergyLog, int64, error) {
p := common.Pagination{Page: page, PageSize: pageSize}
p.DefaultPagination()
return s.repo.ListAllEnergyLogs(energyType, p.Offset(), p.PageSize)
}
// GetFundBalance 获取公户余额
func (s *EnergyService) GetFundBalance() (int, error) {
if s.fundRepo == nil {
return 0, nil
}
return s.fundRepo.GetBalance()
}
// GetFundLogs 分页查询公户流水
func (s *EnergyService) GetFundLogs(logType string, page, pageSize int) ([]model.FundLog, int64, error) {
p := common.Pagination{Page: page, PageSize: pageSize}
p.DefaultPagination()
if s.fundRepo == nil {
return []model.FundLog{}, 0, nil
}
logs, total, err := s.fundRepo.ListLogs(logType, p.Offset(), p.PageSize)
if err != nil {
return nil, 0, err
}
if logs == nil {
logs = []model.FundLog{}
}
return logs, total, nil
}