Files
tyapi-server/internal/domains/user/services/user_service.go

302 lines
8.0 KiB
Go
Raw Normal View History

package services
import (
"context"
"fmt"
2025-07-02 16:17:59 +08:00
"regexp"
"github.com/google/uuid"
"go.uber.org/zap"
"golang.org/x/crypto/bcrypt"
"tyapi-server/internal/domains/user/dto"
"tyapi-server/internal/domains/user/entities"
"tyapi-server/internal/domains/user/events"
"tyapi-server/internal/domains/user/repositories"
"tyapi-server/internal/shared/interfaces"
)
// UserService 用户服务实现
type UserService struct {
2025-07-02 16:17:59 +08:00
repo *repositories.UserRepository
smsCodeService *SMSCodeService
eventBus interfaces.EventBus
logger *zap.Logger
}
// NewUserService 创建用户服务
func NewUserService(
repo *repositories.UserRepository,
2025-07-02 16:17:59 +08:00
smsCodeService *SMSCodeService,
eventBus interfaces.EventBus,
logger *zap.Logger,
) *UserService {
return &UserService{
2025-07-02 16:17:59 +08:00
repo: repo,
smsCodeService: smsCodeService,
eventBus: eventBus,
logger: logger,
}
}
// Name 返回服务名称
func (s *UserService) Name() string {
return "user-service"
}
// Initialize 初始化服务
func (s *UserService) Initialize(ctx context.Context) error {
2025-07-02 16:17:59 +08:00
s.logger.Info("用户服务已初始化")
return nil
}
// HealthCheck 健康检查
func (s *UserService) HealthCheck(ctx context.Context) error {
2025-07-02 16:17:59 +08:00
// 简单的健康检查
return nil
}
// Shutdown 关闭服务
func (s *UserService) Shutdown(ctx context.Context) error {
2025-07-02 16:17:59 +08:00
s.logger.Info("用户服务已关闭")
return nil
}
2025-07-02 16:17:59 +08:00
// Register 用户注册
func (s *UserService) Register(ctx context.Context, registerReq *dto.RegisterRequest) (*entities.User, error) {
// 验证手机号格式
if !s.isValidPhone(registerReq.Phone) {
return nil, fmt.Errorf("手机号格式无效")
}
2025-07-02 16:17:59 +08:00
// 验证密码确认
if registerReq.Password != registerReq.ConfirmPassword {
return nil, fmt.Errorf("密码和确认密码不匹配")
}
// 验证短信验证码
if err := s.smsCodeService.VerifyCode(ctx, registerReq.Phone, registerReq.Code, entities.SMSSceneRegister); err != nil {
return nil, fmt.Errorf("验证码验证失败: %w", err)
}
2025-07-02 16:17:59 +08:00
// 检查手机号是否已存在
if err := s.checkPhoneDuplicate(ctx, registerReq.Phone); err != nil {
return nil, err
}
// 创建用户实体
2025-07-02 16:17:59 +08:00
user := registerReq.ToEntity()
user.ID = uuid.New().String()
2025-07-02 16:17:59 +08:00
// 哈希密码
hashedPassword, err := s.hashPassword(registerReq.Password)
if err != nil {
2025-07-02 16:17:59 +08:00
return nil, fmt.Errorf("密码加密失败: %w", err)
}
user.Password = hashedPassword
// 保存用户
if err := s.repo.Create(ctx, user); err != nil {
2025-07-02 16:17:59 +08:00
s.logger.Error("创建用户失败", zap.Error(err))
return nil, fmt.Errorf("创建用户失败: %w", err)
}
2025-07-02 16:17:59 +08:00
// 发布用户注册事件
event := events.NewUserRegisteredEvent(user, s.getCorrelationID(ctx))
if err := s.eventBus.Publish(ctx, event); err != nil {
2025-07-02 16:17:59 +08:00
s.logger.Warn("发布用户注册事件失败", zap.Error(err))
}
2025-07-02 16:17:59 +08:00
s.logger.Info("用户注册成功",
zap.String("user_id", user.ID),
2025-07-02 16:17:59 +08:00
zap.String("phone", user.Phone))
return user, nil
}
2025-07-02 16:17:59 +08:00
// LoginWithPassword 密码登录
func (s *UserService) LoginWithPassword(ctx context.Context, loginReq *dto.LoginWithPasswordRequest) (*entities.User, error) {
// 根据手机号查找用户
user, err := s.repo.FindByPhone(ctx, loginReq.Phone)
if err != nil {
2025-07-02 16:17:59 +08:00
return nil, fmt.Errorf("用户名或密码错误")
}
2025-07-02 16:17:59 +08:00
// 验证密码
if !s.checkPassword(loginReq.Password, user.Password) {
return nil, fmt.Errorf("用户名或密码错误")
}
2025-07-02 16:17:59 +08:00
// 发布用户登录事件
event := events.NewUserLoggedInEvent(
user.ID, user.Phone,
s.getClientIP(ctx), s.getUserAgent(ctx),
s.getCorrelationID(ctx))
if err := s.eventBus.Publish(ctx, event); err != nil {
s.logger.Warn("发布用户登录事件失败", zap.Error(err))
}
2025-07-02 16:17:59 +08:00
s.logger.Info("用户密码登录成功",
zap.String("user_id", user.ID),
2025-07-02 16:17:59 +08:00
zap.String("phone", user.Phone))
return user, nil
}
2025-07-02 16:17:59 +08:00
// LoginWithSMS 短信验证码登录
func (s *UserService) LoginWithSMS(ctx context.Context, loginReq *dto.LoginWithSMSRequest) (*entities.User, error) {
// 验证短信验证码
if err := s.smsCodeService.VerifyCode(ctx, loginReq.Phone, loginReq.Code, entities.SMSSceneLogin); err != nil {
return nil, fmt.Errorf("验证码验证失败: %w", err)
}
2025-07-02 16:17:59 +08:00
// 根据手机号查找用户
user, err := s.repo.FindByPhone(ctx, loginReq.Phone)
if err != nil {
2025-07-02 16:17:59 +08:00
return nil, fmt.Errorf("用户不存在")
}
2025-07-02 16:17:59 +08:00
// 发布用户登录事件
event := events.NewUserLoggedInEvent(
2025-07-02 16:17:59 +08:00
user.ID, user.Phone,
s.getClientIP(ctx), s.getUserAgent(ctx),
s.getCorrelationID(ctx))
if err := s.eventBus.Publish(ctx, event); err != nil {
2025-07-02 16:17:59 +08:00
s.logger.Warn("发布用户登录事件失败", zap.Error(err))
}
2025-07-02 16:17:59 +08:00
s.logger.Info("用户短信登录成功",
zap.String("user_id", user.ID),
2025-07-02 16:17:59 +08:00
zap.String("phone", user.Phone))
return user, nil
}
// ChangePassword 修改密码
func (s *UserService) ChangePassword(ctx context.Context, userID string, req *dto.ChangePasswordRequest) error {
2025-07-02 16:17:59 +08:00
// 验证新密码确认
if req.NewPassword != req.ConfirmNewPassword {
return fmt.Errorf("新密码和确认新密码不匹配")
}
// 获取用户信息
user, err := s.repo.GetByID(ctx, userID)
if err != nil {
2025-07-02 16:17:59 +08:00
return fmt.Errorf("用户不存在: %w", err)
}
2025-07-02 16:17:59 +08:00
// 验证短信验证码
if err := s.smsCodeService.VerifyCode(ctx, user.Phone, req.Code, entities.SMSSceneChangePassword); err != nil {
return fmt.Errorf("验证码验证失败: %w", err)
}
// 验证当前密码
if !s.checkPassword(req.OldPassword, user.Password) {
2025-07-02 16:17:59 +08:00
return fmt.Errorf("当前密码错误")
}
2025-07-02 16:17:59 +08:00
// 哈希新密码
hashedPassword, err := s.hashPassword(req.NewPassword)
if err != nil {
2025-07-02 16:17:59 +08:00
return fmt.Errorf("新密码加密失败: %w", err)
}
// 更新密码
user.Password = hashedPassword
if err := s.repo.Update(ctx, user); err != nil {
2025-07-02 16:17:59 +08:00
return fmt.Errorf("密码更新失败: %w", err)
}
// 发布密码修改事件
2025-07-02 16:17:59 +08:00
event := events.NewUserPasswordChangedEvent(user.ID, user.Phone, s.getCorrelationID(ctx))
if err := s.eventBus.Publish(ctx, event); err != nil {
2025-07-02 16:17:59 +08:00
s.logger.Warn("发布密码修改事件失败", zap.Error(err))
}
2025-07-02 16:17:59 +08:00
s.logger.Info("密码修改成功", zap.String("user_id", userID))
return nil
}
2025-07-02 16:17:59 +08:00
// GetByID 根据ID获取用户
func (s *UserService) GetByID(ctx context.Context, id string) (*entities.User, error) {
if id == "" {
return nil, fmt.Errorf("用户ID不能为空")
}
user, err := s.repo.GetByID(ctx, id)
if err != nil {
2025-07-02 16:17:59 +08:00
return nil, fmt.Errorf("用户不存在: %w", err)
}
2025-07-02 16:17:59 +08:00
return user, nil
}
2025-07-02 16:17:59 +08:00
// 工具方法
2025-07-02 16:17:59 +08:00
// checkPhoneDuplicate 检查手机号重复
func (s *UserService) checkPhoneDuplicate(ctx context.Context, phone string) error {
if _, err := s.repo.FindByPhone(ctx, phone); err == nil {
return fmt.Errorf("手机号已存在")
}
return nil
}
// hashPassword 加密密码
func (s *UserService) hashPassword(password string) (string, error) {
2025-07-02 16:17:59 +08:00
hashedBytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return "", err
}
2025-07-02 16:17:59 +08:00
return string(hashedBytes), nil
}
// checkPassword 验证密码
func (s *UserService) checkPassword(password, hash string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}
2025-07-02 16:17:59 +08:00
// isValidPhone 验证手机号格式
func (s *UserService) isValidPhone(phone string) bool {
// 简单的中国手机号验证11位数字以1开头
pattern := `^1[3-9]\d{9}$`
matched, _ := regexp.MatchString(pattern, phone)
return matched
}
2025-07-02 16:17:59 +08:00
// generateUserID 生成用户ID
func (s *UserService) generateUserID() string {
return uuid.New().String()
}
// getCorrelationID 获取关联ID
func (s *UserService) getCorrelationID(ctx context.Context) string {
if id := ctx.Value("correlation_id"); id != nil {
2025-07-02 16:17:59 +08:00
if strID, ok := id.(string); ok {
return strID
}
}
return uuid.New().String()
}
// getClientIP 获取客户端IP
func (s *UserService) getClientIP(ctx context.Context) string {
if ip := ctx.Value("client_ip"); ip != nil {
2025-07-02 16:17:59 +08:00
if strIP, ok := ip.(string); ok {
return strIP
}
}
2025-07-02 16:17:59 +08:00
return ""
}
// getUserAgent 获取用户代理
func (s *UserService) getUserAgent(ctx context.Context) string {
if ua := ctx.Value("user_agent"); ua != nil {
2025-07-02 16:17:59 +08:00
if strUA, ok := ua.(string); ok {
return strUA
}
}
2025-07-02 16:17:59 +08:00
return ""
}