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:
@@ -72,6 +72,7 @@ func NewChatService(
|
||||
userRepo *repository.UserRepository,
|
||||
sensitive SensitiveService,
|
||||
sseHub *sse.Hub,
|
||||
cacheBackend cache.Cache,
|
||||
) ChatService {
|
||||
// 创建适配器
|
||||
convRepoAdapter := cache.NewConversationRepositoryAdapter(repo)
|
||||
@@ -79,7 +80,7 @@ func NewChatService(
|
||||
|
||||
// 创建会话缓存
|
||||
conversationCache := cache.NewConversationCache(
|
||||
cache.GetCache(),
|
||||
cacheBackend,
|
||||
convRepoAdapter,
|
||||
msgRepoAdapter,
|
||||
cache.DefaultConversationCacheSettings(),
|
||||
|
||||
@@ -24,12 +24,12 @@ type CommentService struct {
|
||||
}
|
||||
|
||||
// NewCommentService 创建评论服务
|
||||
func NewCommentService(commentRepo *repository.CommentRepository, postRepo *repository.PostRepository, systemMessageService SystemMessageService, gorseClient gorse.Client, postAIService *PostAIService) *CommentService {
|
||||
func NewCommentService(commentRepo *repository.CommentRepository, postRepo *repository.PostRepository, systemMessageService SystemMessageService, gorseClient gorse.Client, postAIService *PostAIService, cacheBackend cache.Cache) *CommentService {
|
||||
return &CommentService{
|
||||
commentRepo: commentRepo,
|
||||
postRepo: postRepo,
|
||||
systemMessageService: systemMessageService,
|
||||
cache: cache.GetCache(),
|
||||
cache: cacheBackend,
|
||||
gorseClient: gorseClient,
|
||||
postAIService: postAIService,
|
||||
}
|
||||
|
||||
@@ -43,9 +43,6 @@ type emailCodeServiceImpl struct {
|
||||
}
|
||||
|
||||
func NewEmailCodeService(emailService EmailService, cacheBackend cache.Cache) EmailCodeService {
|
||||
if cacheBackend == nil {
|
||||
cacheBackend = cache.GetCache()
|
||||
}
|
||||
return &emailCodeServiceImpl{
|
||||
emailService: emailService,
|
||||
cache: cacheBackend,
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"carrot_bbs/internal/cache"
|
||||
apperrors "carrot_bbs/internal/errors"
|
||||
"carrot_bbs/internal/model"
|
||||
"carrot_bbs/internal/pkg/sse"
|
||||
"carrot_bbs/internal/pkg/utils"
|
||||
@@ -23,25 +24,26 @@ const (
|
||||
GroupCacheJitter = 0.1
|
||||
)
|
||||
|
||||
// 群组服务错误定义
|
||||
// 群组服务错误定义 - 使用统一的 AppError
|
||||
var (
|
||||
ErrGroupNotFound = &ServiceError{Code: 404, Message: "群组不存在"}
|
||||
ErrNotGroupMember = &ServiceError{Code: 403, Message: "不是群成员"}
|
||||
ErrNotGroupAdmin = &ServiceError{Code: 403, Message: "不是群管理员"}
|
||||
ErrNotGroupOwner = &ServiceError{Code: 403, Message: "不是群主"}
|
||||
ErrGroupFull = &ServiceError{Code: 400, Message: "群已满"}
|
||||
ErrAlreadyMember = &ServiceError{Code: 400, Message: "已经是群成员"}
|
||||
ErrCannotRemoveOwner = &ServiceError{Code: 400, Message: "不能移除群主"}
|
||||
ErrCannotMuteOwner = &ServiceError{Code: 400, Message: "不能禁言群主"}
|
||||
ErrMuted = &ServiceError{Code: 403, Message: "你已被禁言"}
|
||||
ErrMuteAllEnabled = &ServiceError{Code: 403, Message: "全员禁言中"}
|
||||
ErrCannotJoin = &ServiceError{Code: 400, Message: "该群不允许加入"}
|
||||
ErrJoinRequestPending = &ServiceError{Code: 400, Message: "加群申请已提交"}
|
||||
ErrGroupRequestNotFound = &ServiceError{Code: 404, Message: "加群请求不存在"}
|
||||
ErrGroupRequestHandled = &ServiceError{Code: 400, Message: "该加群请求已处理"}
|
||||
ErrNotRequestTarget = &ServiceError{Code: 400, Message: "不是邀请目标用户"}
|
||||
ErrNoEligibleInvitee = &ServiceError{Code: 400, Message: "没有可邀请的用户"}
|
||||
ErrNotMutualFollow = &ServiceError{Code: 400, Message: "仅支持邀请互相关注用户"}
|
||||
ErrGroupNotFound = apperrors.ErrGroupNotFound
|
||||
ErrNotGroupMember = apperrors.ErrNotGroupMember
|
||||
ErrNotGroupAdmin = apperrors.ErrNotGroupAdmin
|
||||
ErrNotGroupOwner = apperrors.ErrNotGroupOwner
|
||||
ErrGroupFull = apperrors.ErrGroupFull
|
||||
ErrAlreadyMember = apperrors.ErrAlreadyGroupMember
|
||||
ErrCannotRemoveOwner = apperrors.ErrCannotRemoveOwner
|
||||
ErrCannotMuteOwner = apperrors.ErrCannotMuteOwner
|
||||
ErrMuted = apperrors.ErrMuted
|
||||
ErrMuteAllEnabled = apperrors.ErrMuteAllEnabled
|
||||
ErrCannotJoin = apperrors.ErrCannotJoin
|
||||
ErrJoinRequestPending = apperrors.ErrJoinRequestPending
|
||||
ErrGroupRequestNotFound = apperrors.ErrGroupRequestNotFound
|
||||
ErrGroupRequestHandled = apperrors.ErrGroupRequestHandled
|
||||
ErrNotRequestTarget = apperrors.ErrNotRequestTarget
|
||||
ErrNoEligibleInvitee = apperrors.ErrNoEligibleInvitee
|
||||
ErrNotMutualFollow = apperrors.ErrNotMutualFollow
|
||||
ErrInviteNotAccepted = apperrors.ErrInviteNotAccepted
|
||||
)
|
||||
|
||||
// GroupService 群组服务接口
|
||||
@@ -104,7 +106,7 @@ type groupService struct {
|
||||
}
|
||||
|
||||
// NewGroupService 创建群组服务
|
||||
func NewGroupService(db *gorm.DB, groupRepo repository.GroupRepository, userRepo *repository.UserRepository, messageRepo *repository.MessageRepository, sseHub *sse.Hub) GroupService {
|
||||
func NewGroupService(db *gorm.DB, groupRepo repository.GroupRepository, userRepo *repository.UserRepository, messageRepo *repository.MessageRepository, sseHub *sse.Hub, cacheBackend cache.Cache) GroupService {
|
||||
return &groupService{
|
||||
db: db,
|
||||
groupRepo: groupRepo,
|
||||
@@ -113,7 +115,7 @@ func NewGroupService(db *gorm.DB, groupRepo repository.GroupRepository, userRepo
|
||||
requestRepo: repository.NewGroupJoinRequestRepository(db),
|
||||
notifyRepo: repository.NewSystemNotificationRepository(db),
|
||||
sseHub: sseHub,
|
||||
cache: cache.GetCache(),
|
||||
cache: cacheBackend,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -937,7 +939,7 @@ func (s *groupService) SetGroupAddRequest(userID string, flag string, approve bo
|
||||
if req.RequestType == model.GroupJoinRequestTypeInvite {
|
||||
if req.ReviewerID == "" {
|
||||
// 被邀请人还未同意,管理员不能提前审批
|
||||
return &ServiceError{Code: 400, Message: "被邀请人尚未同意邀请,无法审批"}
|
||||
return ErrInviteNotAccepted
|
||||
}
|
||||
if req.ReviewerID != req.TargetUserID {
|
||||
// ReviewerID 不是被邀请人,说明已经被其他人处理过
|
||||
|
||||
@@ -37,14 +37,14 @@ type MessageService struct {
|
||||
}
|
||||
|
||||
// NewMessageService 创建消息服务
|
||||
func NewMessageService(db *gorm.DB, messageRepo *repository.MessageRepository) *MessageService {
|
||||
func NewMessageService(db *gorm.DB, messageRepo *repository.MessageRepository, cacheBackend cache.Cache) *MessageService {
|
||||
// 创建适配器
|
||||
convRepoAdapter := cache.NewConversationRepositoryAdapter(messageRepo)
|
||||
msgRepoAdapter := cache.NewMessageRepositoryAdapter(messageRepo)
|
||||
|
||||
// 创建会话缓存
|
||||
conversationCache := cache.NewConversationCache(
|
||||
cache.GetCache(),
|
||||
cacheBackend,
|
||||
convRepoAdapter,
|
||||
msgRepoAdapter,
|
||||
cache.DefaultConversationCacheSettings(),
|
||||
@@ -54,7 +54,7 @@ func NewMessageService(db *gorm.DB, messageRepo *repository.MessageRepository) *
|
||||
db: db,
|
||||
messageRepo: messageRepo,
|
||||
conversationCache: conversationCache,
|
||||
baseCache: cache.GetCache(),
|
||||
baseCache: cacheBackend,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
"carrot_bbs/internal/cache"
|
||||
apperrors "carrot_bbs/internal/errors"
|
||||
"carrot_bbs/internal/model"
|
||||
"carrot_bbs/internal/repository"
|
||||
)
|
||||
@@ -23,10 +24,10 @@ type NotificationService struct {
|
||||
}
|
||||
|
||||
// NewNotificationService 创建通知服务
|
||||
func NewNotificationService(notificationRepo *repository.NotificationRepository) *NotificationService {
|
||||
func NewNotificationService(notificationRepo *repository.NotificationRepository, cacheBackend cache.Cache) *NotificationService {
|
||||
return &NotificationService{
|
||||
notificationRepo: notificationRepo,
|
||||
cache: cache.GetCache(),
|
||||
cache: cacheBackend,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,4 +167,4 @@ func (s *NotificationService) ClearAllNotifications(ctx context.Context, userID
|
||||
}
|
||||
|
||||
// 错误定义
|
||||
var ErrUnauthorizedNotification = &ServiceError{Code: 403, Message: "unauthorized to delete this notification"}
|
||||
var ErrUnauthorizedNotification = apperrors.ErrUnauthorizedNotification
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"carrot_bbs/internal/dto"
|
||||
apperrors "carrot_bbs/internal/errors"
|
||||
"carrot_bbs/internal/model"
|
||||
"carrot_bbs/internal/repository"
|
||||
|
||||
@@ -15,10 +16,10 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidSchedulePayload = &ServiceError{Code: 400, Message: "invalid schedule payload"}
|
||||
ErrScheduleCourseNotFound = &ServiceError{Code: 404, Message: "schedule course not found"}
|
||||
ErrScheduleForbidden = &ServiceError{Code: 403, Message: "forbidden schedule operation"}
|
||||
ErrScheduleColorDuplicated = &ServiceError{Code: 400, Message: "course color already used"}
|
||||
ErrInvalidSchedulePayload = apperrors.ErrInvalidSchedulePayload
|
||||
ErrScheduleCourseNotFound = apperrors.ErrScheduleCourseNotFound
|
||||
ErrScheduleForbidden = apperrors.ErrScheduleForbidden
|
||||
ErrScheduleColorDuplicated = apperrors.ErrScheduleColorDuplicated
|
||||
)
|
||||
|
||||
var hexColorRegex = regexp.MustCompile(`^#[0-9A-F]{6}$`)
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"carrot_bbs/internal/model"
|
||||
"carrot_bbs/internal/repository"
|
||||
"errors"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
apperrors "carrot_bbs/internal/errors"
|
||||
"carrot_bbs/internal/model"
|
||||
"carrot_bbs/internal/repository"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrStickerAlreadyExists = &ServiceError{Code: 400, Message: "sticker already exists"}
|
||||
ErrInvalidStickerURL = &ServiceError{Code: 400, Message: "invalid sticker url"}
|
||||
ErrStickerAlreadyExists = apperrors.ErrStickerAlreadyExists
|
||||
ErrInvalidStickerURL = apperrors.ErrInvalidStickerURL
|
||||
)
|
||||
|
||||
// StickerService 自定义表情服务接口
|
||||
|
||||
@@ -42,13 +42,14 @@ func NewSystemMessageService(
|
||||
pushService PushService,
|
||||
userRepo *repository.UserRepository,
|
||||
postRepo *repository.PostRepository,
|
||||
cacheBackend cache.Cache,
|
||||
) SystemMessageService {
|
||||
return &systemMessageServiceImpl{
|
||||
notifyRepo: notifyRepo,
|
||||
pushService: pushService,
|
||||
userRepo: userRepo,
|
||||
postRepo: postRepo,
|
||||
cache: cache.GetCache(),
|
||||
cache: cacheBackend,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"carrot_bbs/internal/pkg/s3"
|
||||
|
||||
_ "golang.org/x/image/bmp"
|
||||
_ "golang.org/x/image/tiff"
|
||||
)
|
||||
@@ -23,11 +24,11 @@ import (
|
||||
// UploadService 上传服务
|
||||
type UploadService struct {
|
||||
s3Client *s3.Client
|
||||
userService *UserService
|
||||
userService UserService
|
||||
}
|
||||
|
||||
// NewUploadService 创建上传服务
|
||||
func NewUploadService(s3Client *s3.Client, userService *UserService) *UploadService {
|
||||
func NewUploadService(s3Client *s3.Client, userService UserService) *UploadService {
|
||||
return &UploadService{
|
||||
s3Client: s3Client,
|
||||
userService: userService,
|
||||
|
||||
@@ -6,13 +6,59 @@ import (
|
||||
"strings"
|
||||
|
||||
"carrot_bbs/internal/cache"
|
||||
apperrors "carrot_bbs/internal/errors"
|
||||
"carrot_bbs/internal/model"
|
||||
"carrot_bbs/internal/pkg/utils"
|
||||
"carrot_bbs/internal/repository"
|
||||
)
|
||||
|
||||
// UserService 用户服务
|
||||
type UserService struct {
|
||||
// UserService 用户服务接口
|
||||
type UserService interface {
|
||||
// 验证码相关
|
||||
SendRegisterCode(ctx context.Context, email string) error
|
||||
SendPasswordResetCode(ctx context.Context, email string) error
|
||||
SendCurrentUserEmailVerifyCode(ctx context.Context, userID, email string) error
|
||||
SendChangePasswordCode(ctx context.Context, userID string) error
|
||||
|
||||
// 邮箱验证
|
||||
VerifyCurrentUserEmail(ctx context.Context, userID, email, verificationCode string) error
|
||||
|
||||
// 用户认证
|
||||
Register(ctx context.Context, username, email, password, nickname, phone, verificationCode string) (*model.User, error)
|
||||
Login(ctx context.Context, account, password string) (*model.User, error)
|
||||
|
||||
// 用户查询
|
||||
GetUserByID(ctx context.Context, id string) (*model.User, error)
|
||||
GetUserPostCount(ctx context.Context, userID string) (int64, error)
|
||||
GetUserPostCountBatch(ctx context.Context, userIDs []string) (map[string]int64, error)
|
||||
GetUserByIDWithFollowingStatus(ctx context.Context, userID, currentUserID string) (*model.User, bool, error)
|
||||
GetUserByIDWithMutualFollowStatus(ctx context.Context, userID, currentUserID string) (*model.User, bool, bool, error)
|
||||
Search(ctx context.Context, keyword string, page, pageSize int) ([]*model.User, int64, error)
|
||||
CheckUsernameAvailable(ctx context.Context, username string) (bool, error)
|
||||
|
||||
// 用户更新
|
||||
UpdateUser(ctx context.Context, user *model.User) error
|
||||
ChangePassword(ctx context.Context, userID, oldPassword, newPassword, verificationCode string) error
|
||||
ResetPasswordByEmail(ctx context.Context, email, verificationCode, newPassword string) error
|
||||
|
||||
// 关注相关
|
||||
GetFollowers(ctx context.Context, userID string, page, pageSize int) ([]*model.User, int64, error)
|
||||
GetFollowing(ctx context.Context, userID string, page, pageSize int) ([]*model.User, int64, error)
|
||||
GetFollowingList(ctx context.Context, userID, page, pageSize string) ([]*model.User, error)
|
||||
GetFollowersList(ctx context.Context, userID, page, pageSize string) ([]*model.User, error)
|
||||
FollowUser(ctx context.Context, followerID, followeeID string) error
|
||||
UnfollowUser(ctx context.Context, followerID, followeeID string) error
|
||||
GetMutualFollowStatus(ctx context.Context, currentUserID string, targetUserIDs []string) (map[string][2]bool, error)
|
||||
|
||||
// 黑名单相关
|
||||
BlockUser(ctx context.Context, blockerID, blockedID string) error
|
||||
UnblockUser(ctx context.Context, blockerID, blockedID string) error
|
||||
GetBlockedUsers(ctx context.Context, blockerID string, page, pageSize int) ([]*model.User, int64, error)
|
||||
IsBlocked(ctx context.Context, blockerID, blockedID string) (bool, error)
|
||||
}
|
||||
|
||||
// userServiceImpl 用户服务实现
|
||||
type userServiceImpl struct {
|
||||
userRepo *repository.UserRepository
|
||||
systemMessageService SystemMessageService
|
||||
emailCodeService EmailCodeService
|
||||
@@ -24,8 +70,8 @@ func NewUserService(
|
||||
systemMessageService SystemMessageService,
|
||||
emailService EmailService,
|
||||
cacheBackend cache.Cache,
|
||||
) *UserService {
|
||||
return &UserService{
|
||||
) UserService {
|
||||
return &userServiceImpl{
|
||||
userRepo: userRepo,
|
||||
systemMessageService: systemMessageService,
|
||||
emailCodeService: NewEmailCodeService(emailService, cacheBackend),
|
||||
@@ -33,7 +79,7 @@ func NewUserService(
|
||||
}
|
||||
|
||||
// SendRegisterCode 发送注册验证码
|
||||
func (s *UserService) SendRegisterCode(ctx context.Context, email string) error {
|
||||
func (s *userServiceImpl) SendRegisterCode(ctx context.Context, email string) error {
|
||||
user, err := s.userRepo.GetByEmail(email)
|
||||
if err == nil && user != nil {
|
||||
return ErrEmailExists
|
||||
@@ -42,7 +88,7 @@ func (s *UserService) SendRegisterCode(ctx context.Context, email string) error
|
||||
}
|
||||
|
||||
// SendPasswordResetCode 发送找回密码验证码
|
||||
func (s *UserService) SendPasswordResetCode(ctx context.Context, email string) error {
|
||||
func (s *userServiceImpl) SendPasswordResetCode(ctx context.Context, email string) error {
|
||||
user, err := s.userRepo.GetByEmail(email)
|
||||
if err != nil || user == nil {
|
||||
return ErrUserNotFound
|
||||
@@ -51,7 +97,7 @@ func (s *UserService) SendPasswordResetCode(ctx context.Context, email string) e
|
||||
}
|
||||
|
||||
// SendCurrentUserEmailVerifyCode 发送当前用户邮箱验证验证码
|
||||
func (s *UserService) SendCurrentUserEmailVerifyCode(ctx context.Context, userID, email string) error {
|
||||
func (s *userServiceImpl) SendCurrentUserEmailVerifyCode(ctx context.Context, userID, email string) error {
|
||||
user, err := s.userRepo.GetByID(userID)
|
||||
if err != nil || user == nil {
|
||||
return ErrUserNotFound
|
||||
@@ -78,7 +124,7 @@ func (s *UserService) SendCurrentUserEmailVerifyCode(ctx context.Context, userID
|
||||
}
|
||||
|
||||
// VerifyCurrentUserEmail 验证当前用户邮箱
|
||||
func (s *UserService) VerifyCurrentUserEmail(ctx context.Context, userID, email, verificationCode string) error {
|
||||
func (s *userServiceImpl) VerifyCurrentUserEmail(ctx context.Context, userID, email, verificationCode string) error {
|
||||
user, err := s.userRepo.GetByID(userID)
|
||||
if err != nil || user == nil {
|
||||
return ErrUserNotFound
|
||||
@@ -107,7 +153,7 @@ func (s *UserService) VerifyCurrentUserEmail(ctx context.Context, userID, email,
|
||||
}
|
||||
|
||||
// SendChangePasswordCode 发送修改密码验证码
|
||||
func (s *UserService) SendChangePasswordCode(ctx context.Context, userID string) error {
|
||||
func (s *userServiceImpl) SendChangePasswordCode(ctx context.Context, userID string) error {
|
||||
user, err := s.userRepo.GetByID(userID)
|
||||
if err != nil || user == nil {
|
||||
return ErrUserNotFound
|
||||
@@ -119,7 +165,7 @@ func (s *UserService) SendChangePasswordCode(ctx context.Context, userID string)
|
||||
}
|
||||
|
||||
// Register 用户注册
|
||||
func (s *UserService) Register(ctx context.Context, username, email, password, nickname, phone, verificationCode string) (*model.User, error) {
|
||||
func (s *userServiceImpl) Register(ctx context.Context, username, email, password, nickname, phone, verificationCode string) (*model.User, error) {
|
||||
// 验证用户名
|
||||
if !utils.ValidateUsername(username) {
|
||||
return nil, ErrInvalidUsername
|
||||
@@ -199,7 +245,7 @@ func (s *UserService) Register(ctx context.Context, username, email, password, n
|
||||
}
|
||||
|
||||
// Login 用户登录
|
||||
func (s *UserService) Login(ctx context.Context, account, password string) (*model.User, error) {
|
||||
func (s *userServiceImpl) Login(ctx context.Context, account, password string) (*model.User, error) {
|
||||
account = strings.TrimSpace(account)
|
||||
var (
|
||||
user *model.User
|
||||
@@ -228,22 +274,22 @@ func (s *UserService) Login(ctx context.Context, account, password string) (*mod
|
||||
}
|
||||
|
||||
// GetUserByID 根据ID获取用户
|
||||
func (s *UserService) GetUserByID(ctx context.Context, id string) (*model.User, error) {
|
||||
func (s *userServiceImpl) GetUserByID(ctx context.Context, id string) (*model.User, error) {
|
||||
return s.userRepo.GetByID(id)
|
||||
}
|
||||
|
||||
// GetUserPostCount 获取用户帖子数(实时计算)
|
||||
func (s *UserService) GetUserPostCount(ctx context.Context, userID string) (int64, error) {
|
||||
func (s *userServiceImpl) GetUserPostCount(ctx context.Context, userID string) (int64, error) {
|
||||
return s.userRepo.GetPostsCount(userID)
|
||||
}
|
||||
|
||||
// GetUserPostCountBatch 批量获取用户帖子数(实时计算)
|
||||
func (s *UserService) GetUserPostCountBatch(ctx context.Context, userIDs []string) (map[string]int64, error) {
|
||||
func (s *userServiceImpl) GetUserPostCountBatch(ctx context.Context, userIDs []string) (map[string]int64, error) {
|
||||
return s.userRepo.GetPostsCountBatch(userIDs)
|
||||
}
|
||||
|
||||
// GetUserByIDWithFollowingStatus 根据ID获取用户(包含当前用户是否关注的状态)
|
||||
func (s *UserService) GetUserByIDWithFollowingStatus(ctx context.Context, userID, currentUserID string) (*model.User, bool, error) {
|
||||
func (s *userServiceImpl) GetUserByIDWithFollowingStatus(ctx context.Context, userID, currentUserID string) (*model.User, bool, error) {
|
||||
user, err := s.userRepo.GetByID(userID)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
@@ -263,7 +309,7 @@ func (s *UserService) GetUserByIDWithFollowingStatus(ctx context.Context, userID
|
||||
}
|
||||
|
||||
// GetUserByIDWithMutualFollowStatus 根据ID获取用户(包含双向关注状态)
|
||||
func (s *UserService) GetUserByIDWithMutualFollowStatus(ctx context.Context, userID, currentUserID string) (*model.User, bool, bool, error) {
|
||||
func (s *userServiceImpl) GetUserByIDWithMutualFollowStatus(ctx context.Context, userID, currentUserID string) (*model.User, bool, bool, error) {
|
||||
user, err := s.userRepo.GetByID(userID)
|
||||
if err != nil {
|
||||
return nil, false, false, err
|
||||
@@ -290,22 +336,22 @@ func (s *UserService) GetUserByIDWithMutualFollowStatus(ctx context.Context, use
|
||||
}
|
||||
|
||||
// UpdateUser 更新用户
|
||||
func (s *UserService) UpdateUser(ctx context.Context, user *model.User) error {
|
||||
func (s *userServiceImpl) UpdateUser(ctx context.Context, user *model.User) error {
|
||||
return s.userRepo.Update(user)
|
||||
}
|
||||
|
||||
// GetFollowers 获取粉丝
|
||||
func (s *UserService) GetFollowers(ctx context.Context, userID string, page, pageSize int) ([]*model.User, int64, error) {
|
||||
func (s *userServiceImpl) GetFollowers(ctx context.Context, userID string, page, pageSize int) ([]*model.User, int64, error) {
|
||||
return s.userRepo.GetFollowers(userID, page, pageSize)
|
||||
}
|
||||
|
||||
// GetFollowing 获取关注
|
||||
func (s *UserService) GetFollowing(ctx context.Context, userID string, page, pageSize int) ([]*model.User, int64, error) {
|
||||
func (s *userServiceImpl) GetFollowing(ctx context.Context, userID string, page, pageSize int) ([]*model.User, int64, error) {
|
||||
return s.userRepo.GetFollowing(userID, page, pageSize)
|
||||
}
|
||||
|
||||
// FollowUser 关注用户
|
||||
func (s *UserService) FollowUser(ctx context.Context, followerID, followeeID string) error {
|
||||
func (s *userServiceImpl) FollowUser(ctx context.Context, followerID, followeeID string) error {
|
||||
blocked, err := s.userRepo.IsBlockedEitherDirection(followerID, followeeID)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -357,7 +403,7 @@ func (s *UserService) FollowUser(ctx context.Context, followerID, followeeID str
|
||||
}
|
||||
|
||||
// UnfollowUser 取消关注用户
|
||||
func (s *UserService) UnfollowUser(ctx context.Context, followerID, followeeID string) error {
|
||||
func (s *userServiceImpl) UnfollowUser(ctx context.Context, followerID, followeeID string) error {
|
||||
// 检查是否已经关注
|
||||
isFollowing, err := s.userRepo.IsFollowing(followerID, followeeID)
|
||||
if err != nil {
|
||||
@@ -386,7 +432,7 @@ func (s *UserService) UnfollowUser(ctx context.Context, followerID, followeeID s
|
||||
}
|
||||
|
||||
// BlockUser 拉黑用户,并自动清理双向关注/粉丝关系
|
||||
func (s *UserService) BlockUser(ctx context.Context, blockerID, blockedID string) error {
|
||||
func (s *userServiceImpl) BlockUser(ctx context.Context, blockerID, blockedID string) error {
|
||||
if blockerID == blockedID {
|
||||
return ErrInvalidOperation
|
||||
}
|
||||
@@ -394,7 +440,7 @@ func (s *UserService) BlockUser(ctx context.Context, blockerID, blockedID string
|
||||
}
|
||||
|
||||
// UnblockUser 取消拉黑
|
||||
func (s *UserService) UnblockUser(ctx context.Context, blockerID, blockedID string) error {
|
||||
func (s *userServiceImpl) UnblockUser(ctx context.Context, blockerID, blockedID string) error {
|
||||
if blockerID == blockedID {
|
||||
return ErrInvalidOperation
|
||||
}
|
||||
@@ -402,17 +448,17 @@ func (s *UserService) UnblockUser(ctx context.Context, blockerID, blockedID stri
|
||||
}
|
||||
|
||||
// GetBlockedUsers 获取黑名单列表
|
||||
func (s *UserService) GetBlockedUsers(ctx context.Context, blockerID string, page, pageSize int) ([]*model.User, int64, error) {
|
||||
func (s *userServiceImpl) GetBlockedUsers(ctx context.Context, blockerID string, page, pageSize int) ([]*model.User, int64, error) {
|
||||
return s.userRepo.GetBlockedUsers(blockerID, page, pageSize)
|
||||
}
|
||||
|
||||
// IsBlocked 检查当前用户是否已拉黑目标用户
|
||||
func (s *UserService) IsBlocked(ctx context.Context, blockerID, blockedID string) (bool, error) {
|
||||
func (s *userServiceImpl) IsBlocked(ctx context.Context, blockerID, blockedID string) (bool, error) {
|
||||
return s.userRepo.IsBlocked(blockerID, blockedID)
|
||||
}
|
||||
|
||||
// GetFollowingList 获取关注列表(字符串参数版本)
|
||||
func (s *UserService) GetFollowingList(ctx context.Context, userID, page, pageSize string) ([]*model.User, error) {
|
||||
func (s *userServiceImpl) GetFollowingList(ctx context.Context, userID, page, pageSize string) ([]*model.User, error) {
|
||||
// 转换字符串参数为整数
|
||||
pageInt := 1
|
||||
pageSizeInt := 20
|
||||
@@ -434,7 +480,7 @@ func (s *UserService) GetFollowingList(ctx context.Context, userID, page, pageSi
|
||||
}
|
||||
|
||||
// GetFollowersList 获取粉丝列表(字符串参数版本)
|
||||
func (s *UserService) GetFollowersList(ctx context.Context, userID, page, pageSize string) ([]*model.User, error) {
|
||||
func (s *userServiceImpl) GetFollowersList(ctx context.Context, userID, page, pageSize string) ([]*model.User, error) {
|
||||
// 转换字符串参数为整数
|
||||
pageInt := 1
|
||||
pageSizeInt := 20
|
||||
@@ -456,12 +502,12 @@ func (s *UserService) GetFollowersList(ctx context.Context, userID, page, pageSi
|
||||
}
|
||||
|
||||
// GetMutualFollowStatus 批量获取双向关注状态
|
||||
func (s *UserService) GetMutualFollowStatus(ctx context.Context, currentUserID string, targetUserIDs []string) (map[string][2]bool, error) {
|
||||
func (s *userServiceImpl) GetMutualFollowStatus(ctx context.Context, currentUserID string, targetUserIDs []string) (map[string][2]bool, error) {
|
||||
return s.userRepo.GetMutualFollowStatus(currentUserID, targetUserIDs)
|
||||
}
|
||||
|
||||
// CheckUsernameAvailable 检查用户名是否可用
|
||||
func (s *UserService) CheckUsernameAvailable(ctx context.Context, username string) (bool, error) {
|
||||
func (s *userServiceImpl) CheckUsernameAvailable(ctx context.Context, username string) (bool, error) {
|
||||
user, err := s.userRepo.GetByUsername(username)
|
||||
if err != nil {
|
||||
return true, nil // 用户不存在,可用
|
||||
@@ -470,7 +516,7 @@ func (s *UserService) CheckUsernameAvailable(ctx context.Context, username strin
|
||||
}
|
||||
|
||||
// ChangePassword 修改密码
|
||||
func (s *UserService) ChangePassword(ctx context.Context, userID, oldPassword, newPassword, verificationCode string) error {
|
||||
func (s *userServiceImpl) ChangePassword(ctx context.Context, userID, oldPassword, newPassword, verificationCode string) error {
|
||||
// 获取用户
|
||||
user, err := s.userRepo.GetByID(userID)
|
||||
if err != nil {
|
||||
@@ -500,7 +546,7 @@ func (s *UserService) ChangePassword(ctx context.Context, userID, oldPassword, n
|
||||
}
|
||||
|
||||
// ResetPasswordByEmail 通过邮箱重置密码
|
||||
func (s *UserService) ResetPasswordByEmail(ctx context.Context, email, verificationCode, newPassword string) error {
|
||||
func (s *userServiceImpl) ResetPasswordByEmail(ctx context.Context, email, verificationCode, newPassword string) error {
|
||||
email = strings.TrimSpace(email)
|
||||
if !utils.ValidateEmail(email) {
|
||||
return ErrInvalidEmail
|
||||
@@ -526,40 +572,29 @@ func (s *UserService) ResetPasswordByEmail(ctx context.Context, email, verificat
|
||||
}
|
||||
|
||||
// Search 搜索用户
|
||||
func (s *UserService) Search(ctx context.Context, keyword string, page, pageSize int) ([]*model.User, int64, error) {
|
||||
func (s *userServiceImpl) Search(ctx context.Context, keyword string, page, pageSize int) ([]*model.User, int64, error) {
|
||||
return s.userRepo.Search(keyword, page, pageSize)
|
||||
}
|
||||
|
||||
// 错误定义
|
||||
// 错误定义 - 使用统一的 AppError
|
||||
var (
|
||||
ErrInvalidUsername = &ServiceError{Code: 400, Message: "invalid username"}
|
||||
ErrInvalidEmail = &ServiceError{Code: 400, Message: "invalid email"}
|
||||
ErrInvalidPhone = &ServiceError{Code: 400, Message: "invalid phone number"}
|
||||
ErrWeakPassword = &ServiceError{Code: 400, Message: "password too weak"}
|
||||
ErrUsernameExists = &ServiceError{Code: 400, Message: "username already exists"}
|
||||
ErrEmailExists = &ServiceError{Code: 400, Message: "email already exists"}
|
||||
ErrPhoneExists = &ServiceError{Code: 400, Message: "phone number already exists"}
|
||||
ErrUserNotFound = &ServiceError{Code: 404, Message: "user not found"}
|
||||
ErrUserBanned = &ServiceError{Code: 403, Message: "user is banned"}
|
||||
ErrUserBlocked = &ServiceError{Code: 403, Message: "blocked relationship exists"}
|
||||
ErrInvalidOperation = &ServiceError{Code: 400, Message: "invalid operation"}
|
||||
ErrEmailServiceUnavailable = &ServiceError{Code: 503, Message: "email service unavailable"}
|
||||
ErrVerificationCodeTooFrequent = &ServiceError{Code: 429, Message: "verification code sent too frequently"}
|
||||
ErrVerificationCodeInvalid = &ServiceError{Code: 400, Message: "invalid verification code"}
|
||||
ErrVerificationCodeExpired = &ServiceError{Code: 400, Message: "verification code expired"}
|
||||
ErrVerificationCodeUnavailable = &ServiceError{Code: 500, Message: "verification code storage unavailable"}
|
||||
ErrEmailAlreadyVerified = &ServiceError{Code: 400, Message: "email already verified"}
|
||||
ErrEmailNotBound = &ServiceError{Code: 400, Message: "email not bound"}
|
||||
ErrInvalidUsername = apperrors.ErrInvalidUsername
|
||||
ErrInvalidEmail = apperrors.ErrInvalidEmail
|
||||
ErrInvalidPhone = apperrors.ErrInvalidPhone
|
||||
ErrWeakPassword = apperrors.ErrWeakPassword
|
||||
ErrUsernameExists = apperrors.ErrUsernameExists
|
||||
ErrEmailExists = apperrors.ErrEmailExists
|
||||
ErrPhoneExists = apperrors.ErrPhoneExists
|
||||
ErrUserNotFound = apperrors.ErrUserNotFound
|
||||
ErrUserBanned = apperrors.ErrUserBanned
|
||||
ErrUserBlocked = apperrors.ErrUserBlocked
|
||||
ErrInvalidOperation = apperrors.ErrInvalidOperation
|
||||
ErrEmailServiceUnavailable = apperrors.ErrEmailServiceUnavailable
|
||||
ErrVerificationCodeTooFrequent = apperrors.ErrVerificationCodeTooFrequent
|
||||
ErrVerificationCodeInvalid = apperrors.ErrVerificationCodeInvalid
|
||||
ErrVerificationCodeExpired = apperrors.ErrVerificationCodeExpired
|
||||
ErrVerificationCodeUnavailable = apperrors.ErrVerificationCodeUnavailable
|
||||
ErrEmailAlreadyVerified = apperrors.ErrEmailAlreadyVerified
|
||||
ErrEmailNotBound = apperrors.ErrEmailNotBound
|
||||
ErrInvalidCredentials = apperrors.ErrInvalidCredentials
|
||||
)
|
||||
|
||||
// ServiceError 服务错误
|
||||
type ServiceError struct {
|
||||
Code int
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e *ServiceError) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
var ErrInvalidCredentials = &ServiceError{Code: 401, Message: "invalid username or password"}
|
||||
|
||||
Reference in New Issue
Block a user