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:
@ -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)
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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 趋势数据点
|
||||
|
||||
Reference in New Issue
Block a user