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:
@@ -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