This repository has been archived on 2026-06-21. You can view files and clone it, but cannot push or open issues or pull requests.
Files
MetaLab/internal/service/favorite_service.go
Victor_Jay 3df7b2e672 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 构造函数调用
2026-06-02 16:46:44 +08:00

227 lines
6.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}