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:
@ -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
|
||||||
|
|||||||
@ -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 创建收藏夹
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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)
|
||||||
|
|
||||||
// 用户空间
|
// 用户空间
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 趋势数据点
|
||||||
|
|||||||
Reference in New Issue
Block a user