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:
2026-03-13 09:38:18 +08:00
parent 1216423350
commit cf36b1350d
49 changed files with 2571 additions and 2218 deletions

View File

@@ -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)
})
}