- 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 构造函数调用
227 lines
6.2 KiB
Go
227 lines
6.2 KiB
Go
package service
|
||
|
||
import (
|
||
"fmt"
|
||
|
||
"metazone.cc/metalab/internal/common"
|
||
"metazone.cc/metalab/internal/model"
|
||
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
// FavoriteStore FavoriteService 所需的最小仓储接口(ISP)
|
||
type FavoriteStore interface {
|
||
// Folder
|
||
CreateFolder(folder *model.Folder) error
|
||
FindFolderByID(id uint) (*model.Folder, error)
|
||
ListFoldersByUser(userID uint) ([]model.Folder, error)
|
||
UpdateFolder(folder *model.Folder) error
|
||
DeleteFolder(id uint) error
|
||
DeleteItemsByFolder(folderID uint) error
|
||
CountByNameAndUser(userID uint, name string, excludeID uint) (int64, error)
|
||
HasDefaultFolder(userID uint) (bool, error)
|
||
// FolderItem
|
||
CreateItem(item *model.FolderItem) error
|
||
DeleteItem(folderID, postID uint) error
|
||
FindItemByUserAndPost(userID, postID uint) (*model.FolderItem, error)
|
||
ListItemsByFolder(folderID uint, offset, limit int) ([]model.FolderItem, int64, error)
|
||
CountItemsByFolder(folderID uint) (int64, error)
|
||
// Post counter
|
||
IncrPostFavoritesCount(postID uint, delta int) error
|
||
// Trend
|
||
AggregateFavoritesByAuthor(userID uint, since string) ([]TrendPoint, error)
|
||
// Tx
|
||
WithTx(tx *gorm.DB) FavoriteStore
|
||
// Transaction 在事务内执行业务逻辑,自动管理 WithTx
|
||
Transaction(fn func(tx FavoriteStore) error) error
|
||
}
|
||
|
||
// FavoriteListResult 收藏夹列表 + 收藏内容
|
||
type FavoriteListResult struct {
|
||
Folders []model.Folder `json:"folders"`
|
||
}
|
||
|
||
// FolderItemsResult 收藏夹内容
|
||
type FolderItemsResult struct {
|
||
Items []model.FolderItem `json:"items"`
|
||
Total int64 `json:"total"`
|
||
Page int `json:"page"`
|
||
TotalPages int `json:"total_pages"`
|
||
}
|
||
|
||
// FavoriteStatus 某文章的收藏状态
|
||
type FavoriteStatus struct {
|
||
Favorited bool `json:"favorited"`
|
||
FolderID *uint `json:"folder_id,omitempty"`
|
||
FolderName *string `json:"folder_name,omitempty"`
|
||
}
|
||
|
||
// FavoriteService 收藏夹业务逻辑
|
||
type FavoriteService struct {
|
||
repo FavoriteStore
|
||
}
|
||
|
||
// NewFavoriteService 构造函数
|
||
func NewFavoriteService(repo FavoriteStore) *FavoriteService {
|
||
return &FavoriteService{repo: repo}
|
||
}
|
||
|
||
// ensureDefaultFolder 确保用户有默认收藏夹(不存在则创建)
|
||
func (s *FavoriteService) ensureDefaultFolder(userID uint) (*model.Folder, error) {
|
||
has, err := s.repo.HasDefaultFolder(userID)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("查询默认收藏夹: %w", err)
|
||
}
|
||
if has {
|
||
folders, err := s.repo.ListFoldersByUser(userID)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("查询收藏夹列表: %w", err)
|
||
}
|
||
for _, f := range folders {
|
||
if f.IsDefault {
|
||
return &f, nil
|
||
}
|
||
}
|
||
}
|
||
|
||
// 创建默认收藏夹
|
||
f := &model.Folder{
|
||
UserID: userID,
|
||
Name: "默认",
|
||
Description: "自动创建的默认收藏夹",
|
||
IsPublic: false,
|
||
IsDefault: true,
|
||
}
|
||
if err := s.repo.CreateFolder(f); err != nil {
|
||
return nil, fmt.Errorf("创建默认收藏夹: %w", err)
|
||
}
|
||
return f, nil
|
||
}
|
||
|
||
// ListFolders 获取用户的所有收藏夹
|
||
func (s *FavoriteService) ListFolders(userID uint) (*FavoriteListResult, error) {
|
||
folders, err := s.repo.ListFoldersByUser(userID)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("查询收藏夹: %w", err)
|
||
}
|
||
if folders == nil {
|
||
folders = []model.Folder{}
|
||
}
|
||
return &FavoriteListResult{Folders: folders}, nil
|
||
}
|
||
|
||
// CreateFolder 创建收藏夹
|
||
func (s *FavoriteService) CreateFolder(userID uint, name, description string, isPublic bool) (*model.Folder, error) {
|
||
if name == "" {
|
||
return nil, fmt.Errorf("收藏夹名称不能为空")
|
||
}
|
||
if len(name) > 50 {
|
||
return nil, fmt.Errorf("收藏夹名称不能超过 50 个字符")
|
||
}
|
||
|
||
// 检查名称是否重复
|
||
count, err := s.repo.CountByNameAndUser(userID, name, 0)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("检查名称: %w", err)
|
||
}
|
||
if count > 0 {
|
||
return nil, fmt.Errorf("收藏夹名称已存在")
|
||
}
|
||
|
||
f := &model.Folder{
|
||
UserID: userID,
|
||
Name: name,
|
||
Description: description,
|
||
IsPublic: isPublic,
|
||
}
|
||
if err := s.repo.CreateFolder(f); err != nil {
|
||
return nil, fmt.Errorf("创建收藏夹: %w", err)
|
||
}
|
||
return f, nil
|
||
}
|
||
|
||
// UpdateFolder 修改收藏夹(默认收藏夹不可改名/改描述)
|
||
func (s *FavoriteService) UpdateFolder(userID, folderID uint, name, description string, isPublic bool) error {
|
||
f, err := s.repo.FindFolderByID(folderID)
|
||
if err != nil {
|
||
return common.ErrPostNotFound // 复用错误哨兵
|
||
}
|
||
if f.UserID != userID {
|
||
return common.ErrPermissionDenied
|
||
}
|
||
|
||
if f.IsDefault {
|
||
// 默认收藏夹只允许修改公开性
|
||
f.IsPublic = isPublic
|
||
return s.repo.UpdateFolder(f)
|
||
}
|
||
|
||
if name == "" {
|
||
return fmt.Errorf("收藏夹名称不能为空")
|
||
}
|
||
|
||
// 检查名称重复
|
||
count, err := s.repo.CountByNameAndUser(userID, name, folderID)
|
||
if err != nil {
|
||
return fmt.Errorf("检查名称: %w", err)
|
||
}
|
||
if count > 0 {
|
||
return fmt.Errorf("收藏夹名称已存在")
|
||
}
|
||
|
||
f.Name = name
|
||
f.Description = description
|
||
f.IsPublic = isPublic
|
||
return s.repo.UpdateFolder(f)
|
||
}
|
||
|
||
// DeleteFolder 删除收藏夹(默认不可删)
|
||
func (s *FavoriteService) DeleteFolder(userID, folderID uint) error {
|
||
f, err := s.repo.FindFolderByID(folderID)
|
||
if err != nil {
|
||
return common.ErrPostNotFound
|
||
}
|
||
if f.UserID != userID {
|
||
return common.ErrPermissionDenied
|
||
}
|
||
if f.IsDefault {
|
||
return fmt.Errorf("默认收藏夹不可删除")
|
||
}
|
||
|
||
return s.repo.Transaction(func(txRepo FavoriteStore) error {
|
||
// 先清理 items 的 fans 计数
|
||
items, _, _ := txRepo.ListItemsByFolder(folderID, 0, 10000)
|
||
for _, item := range items {
|
||
if err := txRepo.IncrPostFavoritesCount(item.PostID, -1); err != nil {
|
||
return fmt.Errorf("更新文章收藏数: %w", err)
|
||
}
|
||
}
|
||
|
||
if err := txRepo.DeleteItemsByFolder(folderID); err != nil {
|
||
return fmt.Errorf("删除收藏内容: %w", err)
|
||
}
|
||
|
||
return txRepo.DeleteFolder(folderID)
|
||
})
|
||
}
|
||
|
||
// GetPostFavoriteStatus 查询文章收藏状态
|
||
func (s *FavoriteService) GetPostFavoriteStatus(userID, postID uint) (*FavoriteStatus, error) {
|
||
item, err := s.repo.FindItemByUserAndPost(userID, postID)
|
||
if err != nil {
|
||
return &FavoriteStatus{Favorited: false}, nil
|
||
}
|
||
|
||
f, err := s.repo.FindFolderByID(item.FolderID)
|
||
var folderName *string
|
||
if err == nil {
|
||
folderName = &f.Name
|
||
}
|
||
|
||
return &FavoriteStatus{
|
||
Favorited: true,
|
||
FolderID: &item.FolderID,
|
||
FolderName: folderName,
|
||
}, nil
|
||
}
|