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

302 lines
8.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package services
import (
"context"
"fmt"
"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 {
repo *repositories.UserRepository
smsCodeService *SMSCodeService
eventBus interfaces.EventBus
logger *zap.Logger
}
// NewUserService 创建用户服务
func NewUserService(
repo *repositories.UserRepository,
smsCodeService *SMSCodeService,
eventBus interfaces.EventBus,
logger *zap.Logger,
) *UserService {
return &UserService{
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 {
s.logger.Info("用户服务已初始化")
return nil
}
// HealthCheck 健康检查
func (s *UserService) HealthCheck(ctx context.Context) error {
// 简单的健康检查
return nil
}
// Shutdown 关闭服务
func (s *UserService) Shutdown(ctx context.Context) error {
s.logger.Info("用户服务已关闭")
return nil
}
// Register 用户注册
func (s *UserService) Register(ctx context.Context, registerReq *dto.RegisterRequest) (*entities.User, error) {
// 验证手机号格式
if !s.isValidPhone(registerReq.Phone) {
return nil, fmt.Errorf("手机号格式无效")
}
// 验证密码确认
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)
}
// 检查手机号是否已存在
if err := s.checkPhoneDuplicate(ctx, registerReq.Phone); err != nil {
return nil, err
}
// 创建用户实体
user := registerReq.ToEntity()
user.ID = uuid.New().String()
// 哈希密码
hashedPassword, err := s.hashPassword(registerReq.Password)
if err != nil {
return nil, fmt.Errorf("密码加密失败: %w", err)
}
user.Password = hashedPassword
// 保存用户
if err := s.repo.Create(ctx, user); err != nil {
s.logger.Error("创建用户失败", zap.Error(err))
return nil, fmt.Errorf("创建用户失败: %w", err)
}
// 发布用户注册事件
event := events.NewUserRegisteredEvent(user, s.getCorrelationID(ctx))
if err := s.eventBus.Publish(ctx, event); err != nil {
s.logger.Warn("发布用户注册事件失败", zap.Error(err))
}
s.logger.Info("用户注册成功",
zap.String("user_id", user.ID),
zap.String("phone", user.Phone))
return user, nil
}
// 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 {
return nil, fmt.Errorf("用户名或密码错误")
}
// 验证密码
if !s.checkPassword(loginReq.Password, user.Password) {
return nil, fmt.Errorf("用户名或密码错误")
}
// 发布用户登录事件
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))
}
s.logger.Info("用户密码登录成功",
zap.String("user_id", user.ID),
zap.String("phone", user.Phone))
return user, nil
}
// 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)
}
// 根据手机号查找用户
user, err := s.repo.FindByPhone(ctx, loginReq.Phone)
if err != nil {
return nil, fmt.Errorf("用户不存在")
}
// 发布用户登录事件
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))
}
s.logger.Info("用户短信登录成功",
zap.String("user_id", user.ID),
zap.String("phone", user.Phone))
return user, nil
}
// ChangePassword 修改密码
func (s *UserService) ChangePassword(ctx context.Context, userID string, req *dto.ChangePasswordRequest) error {
// 验证新密码确认
if req.NewPassword != req.ConfirmNewPassword {
return fmt.Errorf("新密码和确认新密码不匹配")
}
// 获取用户信息
user, err := s.repo.GetByID(ctx, userID)
if err != nil {
return fmt.Errorf("用户不存在: %w", err)
}
// 验证短信验证码
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) {
return fmt.Errorf("当前密码错误")
}
// 哈希新密码
hashedPassword, err := s.hashPassword(req.NewPassword)
if err != nil {
return fmt.Errorf("新密码加密失败: %w", err)
}
// 更新密码
user.Password = hashedPassword
if err := s.repo.Update(ctx, user); err != nil {
return fmt.Errorf("密码更新失败: %w", err)
}
// 发布密码修改事件
event := events.NewUserPasswordChangedEvent(user.ID, user.Phone, s.getCorrelationID(ctx))
if err := s.eventBus.Publish(ctx, event); err != nil {
s.logger.Warn("发布密码修改事件失败", zap.Error(err))
}
s.logger.Info("密码修改成功", zap.String("user_id", userID))
return nil
}
// 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 {
return nil, fmt.Errorf("用户不存在: %w", err)
}
return user, nil
}
// 工具方法
// 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) {
hashedBytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return "", err
}
return string(hashedBytes), nil
}
// checkPassword 验证密码
func (s *UserService) checkPassword(password, hash string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}
// isValidPhone 验证手机号格式
func (s *UserService) isValidPhone(phone string) bool {
// 简单的中国手机号验证11位数字以1开头
pattern := `^1[3-9]\d{9}$`
matched, _ := regexp.MatchString(pattern, phone)
return matched
}
// 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 {
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 {
if strIP, ok := ip.(string); ok {
return strIP
}
}
return ""
}
// getUserAgent 获取用户代理
func (s *UserService) getUserAgent(ctx context.Context) string {
if ua := ctx.Value("user_agent"); ua != nil {
if strUA, ok := ua.(string); ok {
return strUA
}
}
return ""
}