feat: 实现赞/踩系统(P2)
- 新增 PostLike/PostDislike 模型,复合主键 (user_id, post_id) - Post 表新增 LikesCount/DislikesCount 冗余计数器 - Repository 层:reaction_repo.go,含 WithTx 事务支持 - Service 层:reaction_service.go,赞踩互斥逻辑(6 态切换),事务包裹 - Controller 层:reaction_controller.go,3 个 API(ToggleLike/ToggleDislike/GetReaction) - 接口定义:controller/interfaces.go 新增 reactionUseCase,service/repository.go 新增 ReactionStore - 路由注册 + 依赖注入 + AutoMigrate - 前端:文章详情页赞踩按钮 + 状态查询 + 401 跳转登录 - 样式:posts.css 新增赞踩按钮样式
This commit is contained in:
155
internal/service/reaction_service.go
Normal file
155
internal/service/reaction_service.go
Normal file
@ -0,0 +1,155 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"metazone.cc/metalab/internal/common"
|
||||
"metazone.cc/metalab/internal/model"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ReactionType 反应类型
|
||||
type ReactionType string
|
||||
|
||||
const (
|
||||
ReactionNone ReactionType = "none"
|
||||
ReactionLiked ReactionType = "liked"
|
||||
ReactionDisliked ReactionType = "disliked"
|
||||
)
|
||||
|
||||
// ReactionService 赞/踩业务逻辑
|
||||
type ReactionService struct {
|
||||
db *gorm.DB
|
||||
repo ReactionStore
|
||||
}
|
||||
|
||||
// NewReactionService 构造函数
|
||||
func NewReactionService(db *gorm.DB, repo ReactionStore) *ReactionService {
|
||||
return &ReactionService{db: db, repo: repo}
|
||||
}
|
||||
|
||||
// ToggleLike 切换点赞(含踩互斥逻辑)
|
||||
// 返回:当前是否已点赞
|
||||
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)
|
||||
|
||||
// 查当前状态
|
||||
_, likeErr := txRepo.FindLike(userID, postID)
|
||||
hasLike := likeErr == nil
|
||||
|
||||
_, dislikeErr := txRepo.FindDislike(userID, postID)
|
||||
hasDislike := dislikeErr == nil
|
||||
|
||||
if hasLike {
|
||||
// 已赞 → 取消赞
|
||||
if err := txRepo.DeleteLike(userID, postID); err != nil {
|
||||
return fmt.Errorf("取消点赞失败: %w", err)
|
||||
}
|
||||
if err := txRepo.IncrPostLikes(postID, -1); err != nil {
|
||||
return fmt.Errorf("更新点赞计数失败: %w", err)
|
||||
}
|
||||
isLiked = false
|
||||
} else {
|
||||
// 无赞 → 加赞(若已有踩,先移除踩)
|
||||
if hasDislike {
|
||||
if err := txRepo.DeleteDislike(userID, postID); err != nil {
|
||||
return fmt.Errorf("移除踩记录失败: %w", err)
|
||||
}
|
||||
if err := txRepo.IncrPostDislikes(postID, -1); err != nil {
|
||||
return fmt.Errorf("更新踩计数失败: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := txRepo.CreateLike(&model.PostLike{UserID: userID, PostID: postID}); err != nil {
|
||||
return fmt.Errorf("创建点赞记录失败: %w", err)
|
||||
}
|
||||
if err := txRepo.IncrPostLikes(postID, 1); err != nil {
|
||||
return fmt.Errorf("更新点赞计数失败: %w", err)
|
||||
}
|
||||
isLiked = true
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return isLiked, err
|
||||
}
|
||||
|
||||
// ToggleDislike 切换踩(含赞互斥逻辑)
|
||||
// 返回:当前是否已踩
|
||||
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)
|
||||
|
||||
// 查当前状态
|
||||
_, likeErr := txRepo.FindLike(userID, postID)
|
||||
hasLike := likeErr == nil
|
||||
|
||||
_, dislikeErr := txRepo.FindDislike(userID, postID)
|
||||
hasDislike := dislikeErr == nil
|
||||
|
||||
if hasDislike {
|
||||
// 已踩 → 取消踩
|
||||
if err := txRepo.DeleteDislike(userID, postID); err != nil {
|
||||
return fmt.Errorf("取消踩失败: %w", err)
|
||||
}
|
||||
if err := txRepo.IncrPostDislikes(postID, -1); err != nil {
|
||||
return fmt.Errorf("更新踩计数失败: %w", err)
|
||||
}
|
||||
isDisliked = false
|
||||
} else {
|
||||
// 无踩 → 加踩(若已有赞,先移除赞)
|
||||
if hasLike {
|
||||
if err := txRepo.DeleteLike(userID, postID); err != nil {
|
||||
return fmt.Errorf("移除点赞记录失败: %w", err)
|
||||
}
|
||||
if err := txRepo.IncrPostLikes(postID, -1); err != nil {
|
||||
return fmt.Errorf("更新点赞计数失败: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := txRepo.CreateDislike(&model.PostDislike{UserID: userID, PostID: postID}); err != nil {
|
||||
return fmt.Errorf("创建踩记录失败: %w", err)
|
||||
}
|
||||
if err := txRepo.IncrPostDislikes(postID, 1); err != nil {
|
||||
return fmt.Errorf("更新踩计数失败: %w", err)
|
||||
}
|
||||
isDisliked = true
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return isDisliked, err
|
||||
}
|
||||
|
||||
// GetReaction 查询用户对文章的反应状态
|
||||
func (s *ReactionService) GetReaction(userID, postID uint) (ReactionType, error) {
|
||||
_, likeErr := s.repo.FindLike(userID, postID)
|
||||
if likeErr == nil {
|
||||
return ReactionLiked, nil
|
||||
}
|
||||
|
||||
_, dislikeErr := s.repo.FindDislike(userID, postID)
|
||||
if dislikeErr == nil {
|
||||
return ReactionDisliked, nil
|
||||
}
|
||||
|
||||
return ReactionNone, nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
return post.LikesCount, nil
|
||||
}
|
||||
Reference in New Issue
Block a user