diff --git a/internal/repository/energy_repo.go b/internal/repository/energy_repo.go index 2d7ffac..55c0d7f 100644 --- a/internal/repository/energy_repo.go +++ b/internal/repository/energy_repo.go @@ -12,7 +12,8 @@ import ( // EnergyRepo 域能数据访问 type EnergyRepo struct { - db *gorm.DB + db *gorm.DB + fundStore service.FundStore // 用于 Transaction 时创建事务范围的 FundStore } // NewEnergyRepo 构造函数 @@ -20,11 +21,28 @@ func NewEnergyRepo(db *gorm.DB) *EnergyRepo { return &EnergyRepo{db: db} } +// SetFundStore 设置公户仓储引用(用于跨仓库事务) +func (r *EnergyRepo) SetFundStore(s service.FundStore) { + r.fundStore = s +} + // WithTx 基于给定事务连接创建新的 EnergyRepo(确保事务内操作原子性) func (r *EnergyRepo) WithTx(tx *gorm.DB) service.EnergyStore { return &EnergyRepo{db: tx} } +// Transaction 在事务内执行业务逻辑,自动管理 EnergyStore 和 FundStore 的事务范围 +func (r *EnergyRepo) Transaction(fn func(txEnergy service.EnergyStore, txFund service.FundStore) error) error { + return r.db.Transaction(func(tx *gorm.DB) error { + txEnergy := r.WithTx(tx) + var txFund service.FundStore + if r.fundStore != nil { + txFund = r.fundStore.WithTx(tx) + } + return fn(txEnergy, txFund) + }) +} + // FindUserByID 查找用户(获取能量余额等) func (r *EnergyRepo) FindUserByID(userID uint) (*model.User, error) { var user model.User diff --git a/internal/repository/favorite_repo.go b/internal/repository/favorite_repo.go index c3b879c..fb8c6b5 100644 --- a/internal/repository/favorite_repo.go +++ b/internal/repository/favorite_repo.go @@ -22,6 +22,13 @@ func (r *FavoriteRepo) WithTx(tx *gorm.DB) service.FavoriteStore { return &FavoriteRepo{db: tx} } +// Transaction 在事务内执行业务逻辑 +func (r *FavoriteRepo) Transaction(fn func(tx service.FavoriteStore) error) error { + return r.db.Transaction(func(tx *gorm.DB) error { + return fn(r.WithTx(tx)) + }) +} + // ======================== Folder CRUD ======================== // CreateFolder 创建收藏夹 diff --git a/internal/repository/follow_repo.go b/internal/repository/follow_repo.go index 9bc615b..d8fd04c 100644 --- a/internal/repository/follow_repo.go +++ b/internal/repository/follow_repo.go @@ -22,6 +22,13 @@ func (r *FollowRepo) WithTx(tx *gorm.DB) service.FollowStore { return &FollowRepo{db: tx} } +// Transaction 在事务内执行业务逻辑 +func (r *FollowRepo) Transaction(fn func(tx service.FollowStore) error) error { + return r.db.Transaction(func(tx *gorm.DB) error { + return fn(r.WithTx(tx)) + }) +} + // Create 创建关注 func (r *FollowRepo) Create(follow *model.UserFollow) error { return r.db.Create(follow).Error diff --git a/internal/repository/reaction_repo.go b/internal/repository/reaction_repo.go index 6510b98..372f61c 100644 --- a/internal/repository/reaction_repo.go +++ b/internal/repository/reaction_repo.go @@ -22,6 +22,22 @@ func (r *ReactionRepo) WithTx(tx *gorm.DB) service.ReactionStore { return &ReactionRepo{db: tx} } +// Transaction 在事务内执行业务逻辑 +func (r *ReactionRepo) Transaction(fn func(tx service.ReactionStore) error) error { + return r.db.Transaction(func(tx *gorm.DB) error { + return fn(r.WithTx(tx)) + }) +} + +// GetPostLikesCount 查询文章点赞数 +func (r *ReactionRepo) GetPostLikesCount(postID uint) (int, error) { + var post model.Post + if err := r.db.Select("likes_count").First(&post, postID).Error; err != nil { + return 0, err + } + return post.LikesCount, nil +} + // FindLike 查找点赞记录 func (r *ReactionRepo) FindLike(userID, postID uint) (*model.PostLike, error) { var like model.PostLike diff --git a/internal/router/deps_extra.go b/internal/router/deps_extra.go index ecf6caf..8a85c62 100644 --- a/internal/router/deps_extra.go +++ b/internal/router/deps_extra.go @@ -22,8 +22,9 @@ func buildDeps(db *gorm.DB, cfg *config.Config, siteSettings *config.SiteSetting // 域能系统 energyRepo := repository.NewEnergyRepo(db) fundRepo := repository.NewFundRepo(db) - energyService := service.NewEnergyService(db, energyRepo, nil, notificationService) - energyService.SetFundRepo(fundRepo) + energyRepo.SetFundStore(fundRepo) // 为跨仓库 Transaction 注入 FundStore + energyService := service.NewEnergyService(energyRepo, nil, notificationService) + energyService.SetFundRepo(fundRepo) // 只读查询 // 等级/签到/任务系统(链式注入域能服务) _, levelSvc, levelCtrl := buildDepsLevel(db, notificationService, authMdw) @@ -72,18 +73,18 @@ func buildDeps(db *gorm.DB, cfg *config.Config, siteSettings *config.SiteSetting // 赞/踩系统 reactionRepo := repository.NewReactionRepo(db) - reactionService := service.NewReactionService(db, reactionRepo) + reactionService := service.NewReactionService(reactionRepo) reactionCtrl := controller.NewReactionController(reactionService) // 关注系统 followRepo := repository.NewFollowRepo(db) - followService := service.NewFollowService(db, followRepo) + followService := service.NewFollowService(followRepo) followService.SetNotifier(notificationService) // 关注通知 followCtrl := controller.NewFollowController(followService) // 收藏夹系统(需在 SpaceController 之前初始化) favoriteRepo := repository.NewFavoriteRepo(db) - favoriteService := service.NewFavoriteService(db, favoriteRepo) + favoriteService := service.NewFavoriteService(favoriteRepo) favoriteCtrl := controller.NewFavoriteController(favoriteService) // 用户空间 diff --git a/internal/service/energy_admin.go b/internal/service/energy_admin.go index 684ba57..75cb51c 100644 --- a/internal/service/energy_admin.go +++ b/internal/service/energy_admin.go @@ -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) diff --git a/internal/service/energy_energize.go b/internal/service/energy_energize.go index 1b6c2c2..19ca1bd 100644 --- a/internal/service/energy_energize.go +++ b/internal/service/energy_energize.go @@ -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) } diff --git a/internal/service/energy_operations.go b/internal/service/energy_operations.go index e57b4a8..c2cac5a 100644 --- a/internal/service/energy_operations.go +++ b/internal/service/energy_operations.go @@ -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) } diff --git a/internal/service/energy_service.go b/internal/service/energy_service.go index e94afdf..29880db 100644 --- a/internal/service/energy_service.go +++ b/internal/service/energy_service.go @@ -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 } diff --git a/internal/service/favorite_items.go b/internal/service/favorite_items.go index a0382e0..bf6c4da 100644 --- a/internal/service/favorite_items.go +++ b/internal/service/favorite_items.go @@ -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) } diff --git a/internal/service/favorite_service.go b/internal/service/favorite_service.go index 5265e56..b468ea6 100644 --- a/internal/service/favorite_service.go +++ b/internal/service/favorite_service.go @@ -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 { diff --git a/internal/service/follow_service.go b/internal/service/follow_service.go index e029916..706dbf9 100644 --- a/internal/service/follow_service.go +++ b/internal/service/follow_service.go @@ -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) diff --git a/internal/service/reaction_service.go b/internal/service/reaction_service.go index ca5bffd..526c2e9 100644 --- a/internal/service/reaction_service.go +++ b/internal/service/reaction_service.go @@ -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 } diff --git a/internal/service/repository.go b/internal/service/repository.go index d0ab248..401518f 100644 --- a/internal/service/repository.go +++ b/internal/service/repository.go @@ -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 趋势数据点