refactor: introduce Wire dependency injection and interface-based architecture
- Add Google Wire for compile-time dependency injection - Replace concrete service types with interfaces across handlers - Remove global state (DB, cache) in favor of constructor injection - Split monolithic files into focused modules: - config: separate files for each config domain - dto: converters split by domain (user, post, message, group) - cache: separate metrics.go and redis_cache.go - Introduce unified apperrors package with string-based error codes - Add transaction support with context-aware repository methods - Upgrade golang.org/x/crypto from 0.17.0 to 0.26.0
This commit is contained in:
@@ -23,23 +23,63 @@ const (
|
||||
anonymousViewUserID = "_anon_view"
|
||||
)
|
||||
|
||||
// PostService 帖子服务
|
||||
type PostService struct {
|
||||
// PostService 帖子服务接口
|
||||
type PostService interface {
|
||||
// 帖子CRUD
|
||||
Create(ctx context.Context, userID, title, content string, images []string) (*model.Post, error)
|
||||
GetByID(ctx context.Context, id string) (*model.Post, error)
|
||||
Update(ctx context.Context, post *model.Post) error
|
||||
UpdateWithImages(ctx context.Context, post *model.Post, images *[]string) error
|
||||
Delete(ctx context.Context, id string) error
|
||||
|
||||
// 帖子列表
|
||||
List(ctx context.Context, page, pageSize int, userID string, includePending bool) ([]*model.Post, int64, error)
|
||||
GetLatestPosts(ctx context.Context, page, pageSize int, userID string) ([]*model.Post, int64, error)
|
||||
GetLatestPostsForOwner(ctx context.Context, page, pageSize int, userID string) ([]*model.Post, int64, error)
|
||||
GetUserPosts(ctx context.Context, userID string, page, pageSize int, includePending bool) ([]*model.Post, int64, error)
|
||||
GetFavorites(ctx context.Context, userID string, page, pageSize int) ([]*model.Post, int64, error)
|
||||
Search(ctx context.Context, keyword string, page, pageSize int) ([]*model.Post, int64, error)
|
||||
|
||||
// 关注和推荐
|
||||
GetFollowingPosts(ctx context.Context, userID string, page, pageSize int) ([]*model.Post, int64, error)
|
||||
GetHotPosts(ctx context.Context, page, pageSize int) ([]*model.Post, int64, error)
|
||||
GetRecommendedPosts(ctx context.Context, userID string, page, pageSize int) ([]*model.Post, int64, error)
|
||||
|
||||
// 交互功能
|
||||
Like(ctx context.Context, postID, userID string) error
|
||||
Unlike(ctx context.Context, postID, userID string) error
|
||||
IsLiked(ctx context.Context, postID, userID string) bool
|
||||
Favorite(ctx context.Context, postID, userID string) error
|
||||
Unfavorite(ctx context.Context, postID, userID string) error
|
||||
IsFavorited(ctx context.Context, postID, userID string) bool
|
||||
|
||||
// 交互状态查询
|
||||
GetPostInteractionStatus(ctx context.Context, postIDs []string, userID string) (map[string]bool, map[string]bool, error)
|
||||
GetPostInteractionStatusSingle(ctx context.Context, postID, userID string) (isLiked, isFavorited bool)
|
||||
|
||||
// 其他
|
||||
IncrementViews(ctx context.Context, postID, userID string) error
|
||||
}
|
||||
|
||||
// postServiceImpl 帖子服务实现
|
||||
type postServiceImpl struct {
|
||||
postRepo *repository.PostRepository
|
||||
systemMessageService SystemMessageService
|
||||
cache cache.Cache
|
||||
gorseClient gorse.Client
|
||||
postAIService *PostAIService
|
||||
txManager repository.TransactionManager // 事务管理器
|
||||
}
|
||||
|
||||
// NewPostService 创建帖子服务
|
||||
func NewPostService(postRepo *repository.PostRepository, systemMessageService SystemMessageService, gorseClient gorse.Client, postAIService *PostAIService) *PostService {
|
||||
return &PostService{
|
||||
func NewPostService(postRepo *repository.PostRepository, systemMessageService SystemMessageService, gorseClient gorse.Client, postAIService *PostAIService, cacheBackend cache.Cache, txManager repository.TransactionManager) PostService {
|
||||
return &postServiceImpl{
|
||||
postRepo: postRepo,
|
||||
systemMessageService: systemMessageService,
|
||||
cache: cache.GetCache(),
|
||||
cache: cacheBackend,
|
||||
gorseClient: gorseClient,
|
||||
postAIService: postAIService,
|
||||
txManager: txManager,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +90,7 @@ type PostListResult struct {
|
||||
}
|
||||
|
||||
// Create 创建帖子
|
||||
func (s *PostService) Create(ctx context.Context, userID, title, content string, images []string) (*model.Post, error) {
|
||||
func (s *postServiceImpl) Create(ctx context.Context, userID, title, content string, images []string) (*model.Post, error) {
|
||||
post := &model.Post{
|
||||
UserID: userID,
|
||||
Title: title,
|
||||
@@ -73,7 +113,7 @@ func (s *PostService) Create(ctx context.Context, userID, title, content string,
|
||||
return s.postRepo.GetByID(post.ID)
|
||||
}
|
||||
|
||||
func (s *PostService) reviewPostAsync(postID, userID, title, content string, images []string) {
|
||||
func (s *postServiceImpl) reviewPostAsync(postID, userID, title, content string, images []string) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Printf("[ERROR] Panic in post moderation async flow, fallback publish post=%s panic=%v", postID, r)
|
||||
@@ -139,7 +179,7 @@ func (s *PostService) reviewPostAsync(postID, userID, title, content string, ima
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PostService) updateModerationStatusWithRetry(postID string, status model.PostStatus, rejectReason string, reviewedBy string) error {
|
||||
func (s *postServiceImpl) updateModerationStatusWithRetry(postID string, status model.PostStatus, rejectReason string, reviewedBy string) error {
|
||||
const maxAttempts = 3
|
||||
const retryDelay = 200 * time.Millisecond
|
||||
|
||||
@@ -159,12 +199,12 @@ func (s *PostService) updateModerationStatusWithRetry(postID string, status mode
|
||||
return lastErr
|
||||
}
|
||||
|
||||
func (s *PostService) invalidatePostCaches(postID string) {
|
||||
func (s *postServiceImpl) invalidatePostCaches(postID string) {
|
||||
cache.InvalidatePostDetail(s.cache, postID)
|
||||
cache.InvalidatePostList(s.cache)
|
||||
}
|
||||
|
||||
func (s *PostService) notifyModerationRejected(userID, reason string) {
|
||||
func (s *postServiceImpl) notifyModerationRejected(userID, reason string) {
|
||||
if s.systemMessageService == nil || strings.TrimSpace(userID) == "" {
|
||||
return
|
||||
}
|
||||
@@ -187,17 +227,17 @@ func (s *PostService) notifyModerationRejected(userID, reason string) {
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取帖子
|
||||
func (s *PostService) GetByID(ctx context.Context, id string) (*model.Post, error) {
|
||||
func (s *postServiceImpl) GetByID(ctx context.Context, id string) (*model.Post, error) {
|
||||
return s.postRepo.GetByID(id)
|
||||
}
|
||||
|
||||
// Update 更新帖子
|
||||
func (s *PostService) Update(ctx context.Context, post *model.Post) error {
|
||||
func (s *postServiceImpl) Update(ctx context.Context, post *model.Post) error {
|
||||
return s.UpdateWithImages(ctx, post, nil)
|
||||
}
|
||||
|
||||
// UpdateWithImages 更新帖子并可选更新图片(images=nil 表示不更新图片)
|
||||
func (s *PostService) UpdateWithImages(ctx context.Context, post *model.Post, images *[]string) error {
|
||||
func (s *postServiceImpl) UpdateWithImages(ctx context.Context, post *model.Post, images *[]string) error {
|
||||
err := s.postRepo.UpdateWithImages(post, images)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -211,7 +251,7 @@ func (s *PostService) UpdateWithImages(ctx context.Context, post *model.Post, im
|
||||
}
|
||||
|
||||
// Delete 删除帖子
|
||||
func (s *PostService) Delete(ctx context.Context, id string) error {
|
||||
func (s *postServiceImpl) Delete(ctx context.Context, id string) error {
|
||||
err := s.postRepo.Delete(id)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -234,7 +274,7 @@ func (s *PostService) Delete(ctx context.Context, id string) error {
|
||||
}
|
||||
|
||||
// List 获取帖子列表(带缓存)
|
||||
func (s *PostService) List(ctx context.Context, page, pageSize int, userID string, includePending bool) ([]*model.Post, int64, error) {
|
||||
func (s *postServiceImpl) List(ctx context.Context, page, pageSize int, userID string, includePending bool) ([]*model.Post, int64, error) {
|
||||
cacheSettings := cache.GetSettings()
|
||||
postListTTL := cacheSettings.PostListTTL
|
||||
if postListTTL <= 0 {
|
||||
@@ -299,22 +339,22 @@ func (s *PostService) List(ctx context.Context, page, pageSize int, userID strin
|
||||
}
|
||||
|
||||
// GetLatestPosts 获取最新帖子(语义化别名)
|
||||
func (s *PostService) GetLatestPosts(ctx context.Context, page, pageSize int, userID string) ([]*model.Post, int64, error) {
|
||||
func (s *postServiceImpl) GetLatestPosts(ctx context.Context, page, pageSize int, userID string) ([]*model.Post, int64, error) {
|
||||
return s.List(ctx, page, pageSize, userID, false)
|
||||
}
|
||||
|
||||
// GetLatestPostsForOwner 获取作者视角帖子列表(包含待审核)
|
||||
func (s *PostService) GetLatestPostsForOwner(ctx context.Context, page, pageSize int, userID string) ([]*model.Post, int64, error) {
|
||||
func (s *postServiceImpl) GetLatestPostsForOwner(ctx context.Context, page, pageSize int, userID string) ([]*model.Post, int64, error) {
|
||||
return s.List(ctx, page, pageSize, userID, true)
|
||||
}
|
||||
|
||||
// GetUserPosts 获取用户帖子
|
||||
func (s *PostService) GetUserPosts(ctx context.Context, userID string, page, pageSize int, includePending bool) ([]*model.Post, int64, error) {
|
||||
func (s *postServiceImpl) GetUserPosts(ctx context.Context, userID string, page, pageSize int, includePending bool) ([]*model.Post, int64, error) {
|
||||
return s.postRepo.GetUserPosts(userID, page, pageSize, includePending)
|
||||
}
|
||||
|
||||
// Like 点赞
|
||||
func (s *PostService) Like(ctx context.Context, postID, userID string) error {
|
||||
func (s *postServiceImpl) Like(ctx context.Context, postID, userID string) error {
|
||||
// 获取帖子信息用于发送通知
|
||||
post, err := s.postRepo.GetByID(postID)
|
||||
if err != nil {
|
||||
@@ -352,7 +392,7 @@ func (s *PostService) Like(ctx context.Context, postID, userID string) error {
|
||||
}
|
||||
|
||||
// Unlike 取消点赞
|
||||
func (s *PostService) Unlike(ctx context.Context, postID, userID string) error {
|
||||
func (s *postServiceImpl) Unlike(ctx context.Context, postID, userID string) error {
|
||||
err := s.postRepo.Unlike(postID, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -374,12 +414,12 @@ func (s *PostService) Unlike(ctx context.Context, postID, userID string) error {
|
||||
}
|
||||
|
||||
// IsLiked 检查是否点赞
|
||||
func (s *PostService) IsLiked(ctx context.Context, postID, userID string) bool {
|
||||
func (s *postServiceImpl) IsLiked(ctx context.Context, postID, userID string) bool {
|
||||
return s.postRepo.IsLiked(postID, userID)
|
||||
}
|
||||
|
||||
// Favorite 收藏
|
||||
func (s *PostService) Favorite(ctx context.Context, postID, userID string) error {
|
||||
func (s *postServiceImpl) Favorite(ctx context.Context, postID, userID string) error {
|
||||
// 获取帖子信息用于发送通知
|
||||
post, err := s.postRepo.GetByID(postID)
|
||||
if err != nil {
|
||||
@@ -417,7 +457,7 @@ func (s *PostService) Favorite(ctx context.Context, postID, userID string) error
|
||||
}
|
||||
|
||||
// Unfavorite 取消收藏
|
||||
func (s *PostService) Unfavorite(ctx context.Context, postID, userID string) error {
|
||||
func (s *postServiceImpl) Unfavorite(ctx context.Context, postID, userID string) error {
|
||||
err := s.postRepo.Unfavorite(postID, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -439,12 +479,12 @@ func (s *PostService) Unfavorite(ctx context.Context, postID, userID string) err
|
||||
}
|
||||
|
||||
// IsFavorited 检查是否收藏
|
||||
func (s *PostService) IsFavorited(ctx context.Context, postID, userID string) bool {
|
||||
func (s *postServiceImpl) IsFavorited(ctx context.Context, postID, userID string) bool {
|
||||
return s.postRepo.IsFavorited(postID, userID)
|
||||
}
|
||||
|
||||
// GetPostInteractionStatus 批量获取帖子的交互状态(点赞、收藏)
|
||||
func (s *PostService) GetPostInteractionStatus(ctx context.Context, postIDs []string, userID string) (map[string]bool, map[string]bool, error) {
|
||||
func (s *postServiceImpl) GetPostInteractionStatus(ctx context.Context, postIDs []string, userID string) (map[string]bool, map[string]bool, error) {
|
||||
isLikedMap := make(map[string]bool)
|
||||
isFavoritedMap := make(map[string]bool)
|
||||
|
||||
@@ -461,7 +501,7 @@ func (s *PostService) GetPostInteractionStatus(ctx context.Context, postIDs []st
|
||||
}
|
||||
|
||||
// GetPostInteractionStatusSingle 获取单个帖子的交互状态
|
||||
func (s *PostService) GetPostInteractionStatusSingle(ctx context.Context, postID, userID string) (isLiked, isFavorited bool) {
|
||||
func (s *postServiceImpl) GetPostInteractionStatusSingle(ctx context.Context, postID, userID string) (isLiked, isFavorited bool) {
|
||||
if userID == "" {
|
||||
return false, false
|
||||
}
|
||||
@@ -469,7 +509,7 @@ func (s *PostService) GetPostInteractionStatusSingle(ctx context.Context, postID
|
||||
}
|
||||
|
||||
// IncrementViews 增加帖子观看量并同步到Gorse
|
||||
func (s *PostService) IncrementViews(ctx context.Context, postID, userID string) error {
|
||||
func (s *postServiceImpl) IncrementViews(ctx context.Context, postID, userID string) error {
|
||||
if err := s.postRepo.IncrementViews(postID); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -494,17 +534,17 @@ func (s *PostService) IncrementViews(ctx context.Context, postID, userID string)
|
||||
}
|
||||
|
||||
// GetFavorites 获取收藏列表
|
||||
func (s *PostService) GetFavorites(ctx context.Context, userID string, page, pageSize int) ([]*model.Post, int64, error) {
|
||||
func (s *postServiceImpl) GetFavorites(ctx context.Context, userID string, page, pageSize int) ([]*model.Post, int64, error) {
|
||||
return s.postRepo.GetFavorites(userID, page, pageSize)
|
||||
}
|
||||
|
||||
// Search 搜索帖子
|
||||
func (s *PostService) Search(ctx context.Context, keyword string, page, pageSize int) ([]*model.Post, int64, error) {
|
||||
func (s *postServiceImpl) Search(ctx context.Context, keyword string, page, pageSize int) ([]*model.Post, int64, error) {
|
||||
return s.postRepo.Search(keyword, page, pageSize)
|
||||
}
|
||||
|
||||
// GetFollowingPosts 获取关注用户的帖子(带缓存)
|
||||
func (s *PostService) GetFollowingPosts(ctx context.Context, userID string, page, pageSize int) ([]*model.Post, int64, error) {
|
||||
func (s *postServiceImpl) GetFollowingPosts(ctx context.Context, userID string, page, pageSize int) ([]*model.Post, int64, error) {
|
||||
cacheSettings := cache.GetSettings()
|
||||
postListTTL := cacheSettings.PostListTTL
|
||||
if postListTTL <= 0 {
|
||||
@@ -546,7 +586,7 @@ func (s *PostService) GetFollowingPosts(ctx context.Context, userID string, page
|
||||
}
|
||||
|
||||
// GetHotPosts 获取热门帖子(使用Gorse非个性化推荐)
|
||||
func (s *PostService) GetHotPosts(ctx context.Context, page, pageSize int) ([]*model.Post, int64, error) {
|
||||
func (s *postServiceImpl) GetHotPosts(ctx context.Context, page, pageSize int) ([]*model.Post, int64, error) {
|
||||
// 如果Gorse启用,使用自定义的非个性化推荐器
|
||||
if s.gorseClient.IsEnabled() {
|
||||
offset := (page - 1) * pageSize
|
||||
@@ -580,7 +620,7 @@ func (s *PostService) GetHotPosts(ctx context.Context, page, pageSize int) ([]*m
|
||||
}
|
||||
|
||||
// getHotPostsFromDB 从数据库获取热门帖子(降级路径)
|
||||
func (s *PostService) getHotPostsFromDB(ctx context.Context, page, pageSize int) ([]*model.Post, int64, error) {
|
||||
func (s *postServiceImpl) getHotPostsFromDB(ctx context.Context, page, pageSize int) ([]*model.Post, int64, error) {
|
||||
// 直接查询数据库,不再使用本地缓存(Gorse失败降级时使用)
|
||||
posts, total, err := s.postRepo.GetHotPosts(page, pageSize)
|
||||
if err != nil {
|
||||
@@ -590,7 +630,7 @@ func (s *PostService) getHotPostsFromDB(ctx context.Context, page, pageSize int)
|
||||
}
|
||||
|
||||
// GetRecommendedPosts 获取推荐帖子
|
||||
func (s *PostService) GetRecommendedPosts(ctx context.Context, userID string, page, pageSize int) ([]*model.Post, int64, error) {
|
||||
func (s *postServiceImpl) GetRecommendedPosts(ctx context.Context, userID string, page, pageSize int) ([]*model.Post, int64, error) {
|
||||
// 如果Gorse未启用或用户未登录,降级为热门帖子
|
||||
if !s.gorseClient.IsEnabled() || userID == "" {
|
||||
return s.GetHotPosts(ctx, page, pageSize)
|
||||
@@ -638,7 +678,7 @@ func (s *PostService) GetRecommendedPosts(ctx context.Context, userID string, pa
|
||||
}
|
||||
|
||||
// buildPostCategories 构建帖子的类别标签
|
||||
func (s *PostService) buildPostCategories(post *model.Post) []string {
|
||||
func (s *postServiceImpl) buildPostCategories(post *model.Post) []string {
|
||||
var categories []string
|
||||
|
||||
// 热度标签
|
||||
@@ -676,3 +716,17 @@ func (s *PostService) buildPostCategories(post *model.Post) []string {
|
||||
|
||||
return categories
|
||||
}
|
||||
|
||||
// ========== 事务管理器示例方法 ==========
|
||||
|
||||
// DeletePostWithTransaction 使用事务管理器删除帖子(示例)
|
||||
// 此方法展示如何在 Service 层使用事务管理器控制跨多个 Repository 的事务
|
||||
// 当需要在一个事务中执行多个 Repository 操作时,可以使用此模式
|
||||
func (s *postServiceImpl) DeletePostWithTransaction(ctx context.Context, postID string) error {
|
||||
// 使用事务管理器执行事务
|
||||
return s.txManager.RunInTransaction(ctx, func(ctx context.Context) error {
|
||||
// 在同一个事务中执行删除操作
|
||||
// Repository 方法会通过 context 获取事务 TX
|
||||
return s.postRepo.DeleteWithContext(ctx, postID)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user