fix: Service 层移除直接持有的 *gorm.DB,引入 Repository Transaction 方法

- ReactionStore/FollowStore/EnergyStore/FavoriteStore 接口新增 Transaction 方法
- EnergyStore.Transaction 支持跨仓库事务(EnergyStore + FundStore)
- ReactionRepo/FollowRepo/FavoriteRepo/EnergyRepo 实现 Transaction 方法
- ReactionRepo 新增 GetPostLikesCount 方法,消除 Service 直查 DB
- ReactionService/FollowService/FavoriteService/EnergyService 移除 db 字段
- EnergyRepo 通过 SetFundStore 注入 FundStore 用于跨仓库事务
- 更新 deps_extra.go 构造函数调用
This commit is contained in:
2026-06-02 16:46:44 +08:00
parent 1cb7a832f1
commit 3df7b2e672
14 changed files with 94 additions and 92 deletions

View File

@ -5,8 +5,6 @@ import (
"metazone.cc/metalab/internal/common"
"metazone.cc/metalab/internal/model"
"gorm.io/gorm"
)
// AdminAdjust 后台调整用户域能
@ -16,13 +14,10 @@ func (s *EnergyService) AdminAdjust(operatorUID uint, userIDs []uint, amount int
mode = model.FundTypeAdminTransfer
}
return s.db.Transaction(func(tx *gorm.DB) error {
txRepo := s.repo.WithTx(tx)
return s.repo.Transaction(func(txRepo EnergyStore, txFundRepo FundStore) error {
// admin_transfer公户出账需检查余额行锁防 TOCTOU 竞态)
isAdminTransfer := mode == model.FundTypeAdminTransfer
if isAdminTransfer && amount > 0 && s.fundRepo != nil {
txFundRepo := s.fundRepo.WithTx(tx)
if isAdminTransfer && amount > 0 && txFundRepo != nil {
totalCost := amount * len(userIDs)
balance, err := txFundRepo.LockAndGetBalance()
if err != nil {
@ -52,8 +47,7 @@ func (s *EnergyService) AdminAdjust(operatorUID uint, userIDs []uint, amount int
}
// admin_transfer公户扣款 + 公户流水system_operation 不碰公户)
if isAdminTransfer && s.fundRepo != nil {
txFundRepo := s.fundRepo.WithTx(tx)
if isAdminTransfer && txFundRepo != nil {
totalCost := amount * len(userIDs)
if err := txFundRepo.IncrBalance(-totalCost); err != nil {
return fmt.Errorf("公户余额调整失败: %w", err)

View File

@ -6,8 +6,6 @@ import (
"metazone.cc/metalab/internal/common"
"metazone.cc/metalab/internal/model"
"gorm.io/gorm"
)
// Energize 赋能文章
@ -23,19 +21,10 @@ func (s *EnergyService) Energize(energizerID uint, postID uint, amount int) (int
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)
}
err := s.repo.Transaction(func(txRepo EnergyStore, txFundRepo FundStore) error {
// 1. 检查帖子存在,获取作者
post, err := txRepo.FindPostByID(postID)
if err != nil {
if err == gorm.ErrRecordNotFound {
return common.ErrPostNotFound
}
return fmt.Errorf("查询帖子失败: %w", err)
}

View File

@ -5,8 +5,6 @@ import (
"metazone.cc/metalab/internal/common"
"metazone.cc/metalab/internal/model"
"gorm.io/gorm"
)
// CheckInEnergy 签到获得域能LV0 用户不调用此方法)
@ -27,9 +25,7 @@ func (s *EnergyService) CheckInEnergy(userID uint) error {
// DeductOnRename 改名扣域能(首次改名免费,不调用此方法)
// 事务包裹:扣用户域能 + 入公户 + 写流水
func (s *EnergyService) DeductOnRename(userID uint) error {
return s.db.Transaction(func(tx *gorm.DB) error {
txRepo := s.repo.WithTx(tx)
return s.repo.Transaction(func(txRepo EnergyStore, txFundRepo FundStore) error {
// 检查域能余额(非 owner 需 ≥ 0
user, err := txRepo.FindUserByID(userID)
if err != nil {
@ -44,8 +40,7 @@ func (s *EnergyService) DeductOnRename(userID uint) error {
}
// 公户入账
if s.fundRepo != nil {
txFundRepo := s.fundRepo.WithTx(tx)
if txFundRepo != nil {
if err := txFundRepo.IncrBalance(-EnergyRename); err != nil { // EnergyRename = -60入公户 +60
return fmt.Errorf("公户入账失败: %w", err)
}
@ -73,12 +68,9 @@ func (s *EnergyService) DeductOnRename(userID uint) error {
// RefundOnRenameReject 改名审核被拒,从公户返还域能
func (s *EnergyService) RefundOnRenameReject(userID uint) error {
return s.db.Transaction(func(tx *gorm.DB) error {
txRepo := s.repo.WithTx(tx)
return s.repo.Transaction(func(txRepo EnergyStore, txFundRepo FundStore) error {
// 从公户支出(允许透支)
if s.fundRepo != nil {
txFundRepo := s.fundRepo.WithTx(tx)
if txFundRepo != nil {
if err := txFundRepo.IncrBalance(-EnergyRenameRefund); err != nil {
return fmt.Errorf("公户出账失败: %w", err)
}
@ -111,16 +103,13 @@ func (s *EnergyService) RefundOnRenameReject(userID uint) error {
// DeductOnDeletePost 删稿扣域能(扣文章作者的域能,无视负数)
// 事务包裹:扣用户域能 + 入公户 + 写流水
func (s *EnergyService) DeductOnDeletePost(authorUserID uint, postID uint) error {
return s.db.Transaction(func(tx *gorm.DB) error {
txRepo := s.repo.WithTx(tx)
return s.repo.Transaction(func(txRepo EnergyStore, txFundRepo FundStore) error {
if err := txRepo.AddEnergy(authorUserID, EnergyDeletePost); err != nil {
return fmt.Errorf("扣减删稿域能失败: %w", err)
}
// 公户入账
if s.fundRepo != nil {
txFundRepo := s.fundRepo.WithTx(tx)
if txFundRepo != nil {
if err := txFundRepo.IncrBalance(-EnergyDeletePost); err != nil { // EnergyDeletePost = -20入公户 +20
return fmt.Errorf("公户入账失败: %w", err)
}

View File

@ -1,9 +1,5 @@
package service
import (
"gorm.io/gorm"
)
// expAdder EnergyService 对经验增加的最小依赖ISP
type expAdder interface {
AddExp(userID uint, delta int) (newExp int, levelUp bool, err error)
@ -11,16 +7,15 @@ type expAdder interface {
// EnergyService 域能业务逻辑
type EnergyService struct {
db *gorm.DB
repo EnergyStore
fundRepo FundStore
fundRepo FundStore // 只读查询用GetFundBalance/GetFundLogs
expSvc expAdder
notifSvc energyNotifier
}
// NewEnergyService 构造函数
func NewEnergyService(db *gorm.DB, repo EnergyStore, expSvc expAdder, notifSvc energyNotifier) *EnergyService {
return &EnergyService{db: db, repo: repo, expSvc: expSvc, notifSvc: notifSvc}
func NewEnergyService(repo EnergyStore, expSvc expAdder, notifSvc energyNotifier) *EnergyService {
return &EnergyService{repo: repo, expSvc: expSvc, notifSvc: notifSvc}
}
// SetExpAdder 链式注入经验服务(用于赋能获得经验)
@ -28,7 +23,7 @@ func (s *EnergyService) SetExpAdder(svc expAdder) {
s.expSvc = svc
}
// SetFundRepo 链式注入公户仓储
// SetFundRepo 链式注入公户仓储(只读查询)
func (s *EnergyService) SetFundRepo(repo FundStore) {
s.fundRepo = repo
}

View File

@ -5,8 +5,6 @@ import (
"metazone.cc/metalab/internal/common"
"metazone.cc/metalab/internal/model"
"gorm.io/gorm"
)
// AddToFolder 将文章加入收藏夹(会先移除该文章在其他收藏夹的收藏)
@ -20,8 +18,7 @@ func (s *FavoriteService) AddToFolder(userID, folderID, postID uint) error {
return common.ErrPermissionDenied
}
return s.db.Transaction(func(tx *gorm.DB) error {
txRepo := s.repo.WithTx(tx)
return s.repo.Transaction(func(txRepo FavoriteStore) error {
// 如果该文章已在其他收藏夹中,先移除
existing, err := txRepo.FindItemByUserAndPost(userID, postID)
@ -60,8 +57,7 @@ func (s *FavoriteService) RemoveFromFolder(userID, folderID, postID uint) error
return common.ErrPermissionDenied
}
return s.db.Transaction(func(tx *gorm.DB) error {
txRepo := s.repo.WithTx(tx)
return s.repo.Transaction(func(txRepo FavoriteStore) error {
if err := txRepo.DeleteItem(folderID, postID); err != nil {
return fmt.Errorf("移除收藏: %w", err)
}

View File

@ -32,6 +32,8 @@ type FavoriteStore interface {
AggregateFavoritesByAuthor(userID uint, since string) ([]TrendPoint, error)
// Tx
WithTx(tx *gorm.DB) FavoriteStore
// Transaction 在事务内执行业务逻辑,自动管理 WithTx
Transaction(fn func(tx FavoriteStore) error) error
}
// FavoriteListResult 收藏夹列表 + 收藏内容
@ -56,13 +58,12 @@ type FavoriteStatus struct {
// FavoriteService 收藏夹业务逻辑
type FavoriteService struct {
db *gorm.DB
repo FavoriteStore
}
// NewFavoriteService 构造函数
func NewFavoriteService(db *gorm.DB, repo FavoriteStore) *FavoriteService {
return &FavoriteService{db: db, repo: repo}
func NewFavoriteService(repo FavoriteStore) *FavoriteService {
return &FavoriteService{repo: repo}
}
// ensureDefaultFolder 确保用户有默认收藏夹(不存在则创建)
@ -187,9 +188,7 @@ func (s *FavoriteService) DeleteFolder(userID, folderID uint) error {
return fmt.Errorf("默认收藏夹不可删除")
}
return s.db.Transaction(func(tx *gorm.DB) error {
txRepo := s.repo.WithTx(tx)
return s.repo.Transaction(func(txRepo FavoriteStore) error {
// 先清理 items 的 fans 计数
items, _, _ := txRepo.ListItemsByFolder(folderID, 0, 10000)
for _, item := range items {

View File

@ -6,8 +6,6 @@ import (
"metazone.cc/metalab/internal/common"
"metazone.cc/metalab/internal/model"
"gorm.io/gorm"
)
// FollowStatus 关注关系状态
@ -33,14 +31,13 @@ type followNotifier interface {
// FollowService 关注业务逻辑
type FollowService struct {
db *gorm.DB
repo FollowStore
notifier followNotifier
}
// NewFollowService 构造函数
func NewFollowService(db *gorm.DB, repo FollowStore) *FollowService {
return &FollowService{db: db, repo: repo}
func NewFollowService(repo FollowStore) *FollowService {
return &FollowService{repo: repo}
}
// SetNotifier 注入通知服务
@ -55,9 +52,7 @@ func (s *FollowService) Toggle(followerID, followeeID uint) (bool, error) {
}
var isFollowing bool
err := s.db.Transaction(func(tx *gorm.DB) error {
txRepo := s.repo.WithTx(tx)
err := s.repo.Transaction(func(txRepo FollowStore) error {
exists, err := txRepo.Exists(followerID, followeeID)
if err != nil {
return fmt.Errorf("查询关注状态失败: %w", err)

View File

@ -4,10 +4,7 @@ import (
"fmt"
"time"
"metazone.cc/metalab/internal/common"
"metazone.cc/metalab/internal/model"
"gorm.io/gorm"
)
// ReactionType 反应类型
@ -21,13 +18,12 @@ const (
// ReactionService 赞/踩业务逻辑
type ReactionService struct {
db *gorm.DB
repo ReactionStore
}
// NewReactionService 构造函数
func NewReactionService(db *gorm.DB, repo ReactionStore) *ReactionService {
return &ReactionService{db: db, repo: repo}
func NewReactionService(repo ReactionStore) *ReactionService {
return &ReactionService{repo: repo}
}
// ToggleLike 切换点赞(含踩互斥逻辑)
@ -35,9 +31,7 @@ func NewReactionService(db *gorm.DB, repo ReactionStore) *ReactionService {
func (s *ReactionService) ToggleLike(userID, postID uint) (bool, error) {
var isLiked bool
err := s.db.Transaction(func(tx *gorm.DB) error {
txRepo := s.repo.WithTx(tx)
err := s.repo.Transaction(func(txRepo ReactionStore) error {
// 查当前状态
_, likeErr := txRepo.FindLike(userID, postID)
hasLike := likeErr == nil
@ -104,9 +98,7 @@ func (s *ReactionService) recordLikeForAggregation(postID uint, likerID uint) {
func (s *ReactionService) ToggleDislike(userID, postID uint) (bool, error) {
var isDisliked bool
err := s.db.Transaction(func(tx *gorm.DB) error {
txRepo := s.repo.WithTx(tx)
err := s.repo.Transaction(func(txRepo ReactionStore) error {
// 查当前状态
_, likeErr := txRepo.FindLike(userID, postID)
hasLike := likeErr == nil
@ -165,12 +157,9 @@ func (s *ReactionService) GetReaction(userID, postID uint) (ReactionType, error)
// GetPostLikesCount 查询文章点赞数(公开展示)
func (s *ReactionService) GetPostLikesCount(postID uint) (int, error) {
var post model.Post
if err := s.db.Select("likes_count").First(&post, postID).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return 0, common.ErrPostNotFound
}
return 0, fmt.Errorf("查询文章失败: %w", err)
count, err := s.repo.GetPostLikesCount(postID)
if err != nil {
return 0, fmt.Errorf("查询文章点赞数: %w", err)
}
return post.LikesCount, nil
return count, nil
}

View File

@ -113,6 +113,8 @@ type EnergyStore interface {
ListAllEnergyLogs(energyType string, offset, limit int) ([]model.EnergyLog, int64, error)
// WithTx 基于给定事务连接创建新的 EnergyStore确保事务内操作原子性
WithTx(tx *gorm.DB) EnergyStore
// Transaction 在事务内执行业务逻辑,自动管理 WithTx 和 FundStore 的事务范围
Transaction(fn func(txEnergy EnergyStore, txFund FundStore) error) error
}
// energyStore 向后兼容别名
@ -172,10 +174,13 @@ type ReactionStore interface {
IncrPostLikes(postID uint, delta int) error
IncrPostDislikes(postID uint, delta int) error
GetPostAuthorID(postID uint) (uint, error)
GetPostLikesCount(postID uint) (int, error)
UpsertDailyLikeSummary(authorUID uint, date string, likerUID string) error
GetUnnotifiedSummaries(userID uint) ([]model.DailyLikeSummary, error)
MarkSummaryNotified(id uint) error
WithTx(tx *gorm.DB) ReactionStore
// Transaction 在事务内执行业务逻辑,自动管理 WithTx
Transaction(fn func(tx ReactionStore) error) error
}
// FollowStore FollowService 所需的最小仓储接口ISP
@ -191,6 +196,8 @@ type FollowStore interface {
IncrFollowingCount(userID uint, delta int) error
GetFollowListPublic(userID uint) (bool, error)
WithTx(tx *gorm.DB) FollowStore
// Transaction 在事务内执行业务逻辑,自动管理 WithTx
Transaction(fn func(tx FollowStore) error) error
}
// TrendPoint 趋势数据点