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

@ -13,6 +13,7 @@ import (
// EnergyRepo 域能数据访问 // EnergyRepo 域能数据访问
type EnergyRepo struct { type EnergyRepo struct {
db *gorm.DB db *gorm.DB
fundStore service.FundStore // 用于 Transaction 时创建事务范围的 FundStore
} }
// NewEnergyRepo 构造函数 // NewEnergyRepo 构造函数
@ -20,11 +21,28 @@ func NewEnergyRepo(db *gorm.DB) *EnergyRepo {
return &EnergyRepo{db: db} return &EnergyRepo{db: db}
} }
// SetFundStore 设置公户仓储引用(用于跨仓库事务)
func (r *EnergyRepo) SetFundStore(s service.FundStore) {
r.fundStore = s
}
// WithTx 基于给定事务连接创建新的 EnergyRepo确保事务内操作原子性 // WithTx 基于给定事务连接创建新的 EnergyRepo确保事务内操作原子性
func (r *EnergyRepo) WithTx(tx *gorm.DB) service.EnergyStore { func (r *EnergyRepo) WithTx(tx *gorm.DB) service.EnergyStore {
return &EnergyRepo{db: tx} 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 查找用户(获取能量余额等) // FindUserByID 查找用户(获取能量余额等)
func (r *EnergyRepo) FindUserByID(userID uint) (*model.User, error) { func (r *EnergyRepo) FindUserByID(userID uint) (*model.User, error) {
var user model.User var user model.User

View File

@ -22,6 +22,13 @@ func (r *FavoriteRepo) WithTx(tx *gorm.DB) service.FavoriteStore {
return &FavoriteRepo{db: tx} 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 ======================== // ======================== Folder CRUD ========================
// CreateFolder 创建收藏夹 // CreateFolder 创建收藏夹

View File

@ -22,6 +22,13 @@ func (r *FollowRepo) WithTx(tx *gorm.DB) service.FollowStore {
return &FollowRepo{db: tx} 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 创建关注 // Create 创建关注
func (r *FollowRepo) Create(follow *model.UserFollow) error { func (r *FollowRepo) Create(follow *model.UserFollow) error {
return r.db.Create(follow).Error return r.db.Create(follow).Error

View File

@ -22,6 +22,22 @@ func (r *ReactionRepo) WithTx(tx *gorm.DB) service.ReactionStore {
return &ReactionRepo{db: tx} 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 查找点赞记录 // FindLike 查找点赞记录
func (r *ReactionRepo) FindLike(userID, postID uint) (*model.PostLike, error) { func (r *ReactionRepo) FindLike(userID, postID uint) (*model.PostLike, error) {
var like model.PostLike var like model.PostLike

View File

@ -22,8 +22,9 @@ func buildDeps(db *gorm.DB, cfg *config.Config, siteSettings *config.SiteSetting
// 域能系统 // 域能系统
energyRepo := repository.NewEnergyRepo(db) energyRepo := repository.NewEnergyRepo(db)
fundRepo := repository.NewFundRepo(db) fundRepo := repository.NewFundRepo(db)
energyService := service.NewEnergyService(db, energyRepo, nil, notificationService) energyRepo.SetFundStore(fundRepo) // 为跨仓库 Transaction 注入 FundStore
energyService.SetFundRepo(fundRepo) energyService := service.NewEnergyService(energyRepo, nil, notificationService)
energyService.SetFundRepo(fundRepo) // 只读查询
// 等级/签到/任务系统(链式注入域能服务) // 等级/签到/任务系统(链式注入域能服务)
_, levelSvc, levelCtrl := buildDepsLevel(db, notificationService, authMdw) _, 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) reactionRepo := repository.NewReactionRepo(db)
reactionService := service.NewReactionService(db, reactionRepo) reactionService := service.NewReactionService(reactionRepo)
reactionCtrl := controller.NewReactionController(reactionService) reactionCtrl := controller.NewReactionController(reactionService)
// 关注系统 // 关注系统
followRepo := repository.NewFollowRepo(db) followRepo := repository.NewFollowRepo(db)
followService := service.NewFollowService(db, followRepo) followService := service.NewFollowService(followRepo)
followService.SetNotifier(notificationService) // 关注通知 followService.SetNotifier(notificationService) // 关注通知
followCtrl := controller.NewFollowController(followService) followCtrl := controller.NewFollowController(followService)
// 收藏夹系统(需在 SpaceController 之前初始化) // 收藏夹系统(需在 SpaceController 之前初始化)
favoriteRepo := repository.NewFavoriteRepo(db) favoriteRepo := repository.NewFavoriteRepo(db)
favoriteService := service.NewFavoriteService(db, favoriteRepo) favoriteService := service.NewFavoriteService(favoriteRepo)
favoriteCtrl := controller.NewFavoriteController(favoriteService) favoriteCtrl := controller.NewFavoriteController(favoriteService)
// 用户空间 // 用户空间

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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