2025-06-30 19:21:56 +08:00
|
|
|
package services
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
|
|
"github.com/google/uuid"
|
|
|
|
|
"go.uber.org/zap"
|
|
|
|
|
|
|
|
|
|
"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
|
2025-06-30 19:21:56 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewUserService 创建用户服务
|
|
|
|
|
func NewUserService(
|
|
|
|
|
repo *repositories.UserRepository,
|
2025-07-02 16:17:59 +08:00
|
|
|
smsCodeService *SMSCodeService,
|
2025-06-30 19:21:56 +08:00
|
|
|
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,
|
2025-06-30 19:21:56 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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("用户服务已初始化")
|
2025-06-30 19:21:56 +08:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// HealthCheck 健康检查
|
|
|
|
|
func (s *UserService) HealthCheck(ctx context.Context) error {
|
2025-07-02 16:17:59 +08:00
|
|
|
// 简单的健康检查
|
|
|
|
|
return nil
|
2025-06-30 19:21:56 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Shutdown 关闭服务
|
|
|
|
|
func (s *UserService) Shutdown(ctx context.Context) error {
|
2025-07-02 16:17:59 +08:00
|
|
|
s.logger.Info("用户服务已关闭")
|
2025-06-30 19:21:56 +08:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-02 16:17:59 +08:00
|
|
|
// Register 用户注册
|
|
|
|
|
func (s *UserService) Register(ctx context.Context, registerReq *dto.RegisterRequest) (*entities.User, error) {
|
2025-07-11 21:05:58 +08:00
|
|
|
// 1. 验证短信验证码
|
2025-07-02 16:17:59 +08:00
|
|
|
if err := s.smsCodeService.VerifyCode(ctx, registerReq.Phone, registerReq.Code, entities.SMSSceneRegister); err != nil {
|
|
|
|
|
return nil, fmt.Errorf("验证码验证失败: %w", err)
|
2025-06-30 19:21:56 +08:00
|
|
|
}
|
|
|
|
|
|
2025-07-11 21:05:58 +08:00
|
|
|
// 2. 检查手机号是否已存在
|
2025-07-02 16:17:59 +08:00
|
|
|
if err := s.checkPhoneDuplicate(ctx, registerReq.Phone); err != nil {
|
2025-06-30 19:21:56 +08:00
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-11 21:05:58 +08:00
|
|
|
// 3. 使用工厂方法创建用户实体(业务规则验证在实体中完成)
|
|
|
|
|
user, err := entities.NewUser(registerReq.Phone, registerReq.Password)
|
2025-06-30 19:21:56 +08:00
|
|
|
if err != nil {
|
2025-07-11 21:05:58 +08:00
|
|
|
return nil, fmt.Errorf("创建用户失败: %w", err)
|
2025-06-30 19:21:56 +08:00
|
|
|
}
|
|
|
|
|
|
2025-07-11 21:05:58 +08:00
|
|
|
// 4. 设置用户ID
|
|
|
|
|
user.ID = uuid.New().String()
|
|
|
|
|
|
|
|
|
|
// 5. 保存用户
|
2025-06-30 19:21:56 +08:00
|
|
|
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-06-30 19:21:56 +08:00
|
|
|
}
|
|
|
|
|
|
2025-07-11 21:05:58 +08:00
|
|
|
// 6. 发布用户注册事件
|
2025-07-02 16:17:59 +08:00
|
|
|
event := events.NewUserRegisteredEvent(user, s.getCorrelationID(ctx))
|
2025-06-30 19:21:56 +08:00
|
|
|
if err := s.eventBus.Publish(ctx, event); err != nil {
|
2025-07-02 16:17:59 +08:00
|
|
|
s.logger.Warn("发布用户注册事件失败", zap.Error(err))
|
2025-06-30 19:21:56 +08:00
|
|
|
}
|
|
|
|
|
|
2025-07-02 16:17:59 +08:00
|
|
|
s.logger.Info("用户注册成功",
|
2025-06-30 19:21:56 +08:00
|
|
|
zap.String("user_id", user.ID),
|
2025-07-02 16:17:59 +08:00
|
|
|
zap.String("phone", user.Phone))
|
2025-06-30 19:21:56 +08:00
|
|
|
|
|
|
|
|
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) {
|
2025-07-11 21:05:58 +08:00
|
|
|
// 1. 根据手机号查找用户
|
2025-07-02 16:17:59 +08:00
|
|
|
user, err := s.repo.FindByPhone(ctx, loginReq.Phone)
|
2025-06-30 19:21:56 +08:00
|
|
|
if err != nil {
|
2025-07-02 16:17:59 +08:00
|
|
|
return nil, fmt.Errorf("用户名或密码错误")
|
2025-06-30 19:21:56 +08:00
|
|
|
}
|
|
|
|
|
|
2025-07-11 21:05:58 +08:00
|
|
|
// 2. 检查用户是否可以登录(委托给实体)
|
|
|
|
|
if !user.CanLogin() {
|
|
|
|
|
return nil, fmt.Errorf("用户状态异常,无法登录")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3. 验证密码(委托给实体)
|
|
|
|
|
if !user.CheckPassword(loginReq.Password) {
|
2025-07-02 16:17:59 +08:00
|
|
|
return nil, fmt.Errorf("用户名或密码错误")
|
2025-06-30 19:21:56 +08:00
|
|
|
}
|
|
|
|
|
|
2025-07-11 21:05:58 +08:00
|
|
|
// 4. 发布用户登录事件
|
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-06-30 19:21:56 +08:00
|
|
|
}
|
|
|
|
|
|
2025-07-02 16:17:59 +08:00
|
|
|
s.logger.Info("用户密码登录成功",
|
2025-06-30 19:21:56 +08:00
|
|
|
zap.String("user_id", user.ID),
|
2025-07-02 16:17:59 +08:00
|
|
|
zap.String("phone", user.Phone))
|
2025-06-30 19:21:56 +08:00
|
|
|
|
|
|
|
|
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) {
|
2025-07-11 21:05:58 +08:00
|
|
|
// 1. 验证短信验证码
|
2025-07-02 16:17:59 +08:00
|
|
|
if err := s.smsCodeService.VerifyCode(ctx, loginReq.Phone, loginReq.Code, entities.SMSSceneLogin); err != nil {
|
|
|
|
|
return nil, fmt.Errorf("验证码验证失败: %w", err)
|
2025-06-30 19:21:56 +08:00
|
|
|
}
|
|
|
|
|
|
2025-07-11 21:05:58 +08:00
|
|
|
// 2. 根据手机号查找用户
|
2025-07-02 16:17:59 +08:00
|
|
|
user, err := s.repo.FindByPhone(ctx, loginReq.Phone)
|
2025-06-30 19:21:56 +08:00
|
|
|
if err != nil {
|
2025-07-02 16:17:59 +08:00
|
|
|
return nil, fmt.Errorf("用户不存在")
|
2025-06-30 19:21:56 +08:00
|
|
|
}
|
|
|
|
|
|
2025-07-11 21:05:58 +08:00
|
|
|
// 3. 检查用户是否可以登录(委托给实体)
|
|
|
|
|
if !user.CanLogin() {
|
|
|
|
|
return nil, fmt.Errorf("用户状态异常,无法登录")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 4. 发布用户登录事件
|
2025-06-30 19:21:56 +08:00
|
|
|
event := events.NewUserLoggedInEvent(
|
2025-07-02 16:17:59 +08:00
|
|
|
user.ID, user.Phone,
|
2025-06-30 19:21:56 +08:00
|
|
|
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-06-30 19:21:56 +08:00
|
|
|
}
|
|
|
|
|
|
2025-07-02 16:17:59 +08:00
|
|
|
s.logger.Info("用户短信登录成功",
|
2025-06-30 19:21:56 +08:00
|
|
|
zap.String("user_id", user.ID),
|
2025-07-02 16:17:59 +08:00
|
|
|
zap.String("phone", user.Phone))
|
2025-06-30 19:21:56 +08:00
|
|
|
|
|
|
|
|
return user, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ChangePassword 修改密码
|
|
|
|
|
func (s *UserService) ChangePassword(ctx context.Context, userID string, req *dto.ChangePasswordRequest) error {
|
2025-07-11 21:05:58 +08:00
|
|
|
// 1. 获取用户信息
|
2025-06-30 19:21:56 +08:00
|
|
|
user, err := s.repo.GetByID(ctx, userID)
|
|
|
|
|
if err != nil {
|
2025-07-02 16:17:59 +08:00
|
|
|
return fmt.Errorf("用户不存在: %w", err)
|
2025-06-30 19:21:56 +08:00
|
|
|
}
|
|
|
|
|
|
2025-07-11 21:05:58 +08:00
|
|
|
// 2. 验证短信验证码
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-11 21:05:58 +08:00
|
|
|
// 3. 执行业务逻辑(委托给实体)
|
|
|
|
|
if err := user.ChangePassword(req.OldPassword, req.NewPassword, req.ConfirmNewPassword); err != nil {
|
|
|
|
|
return err
|
2025-06-30 19:21:56 +08:00
|
|
|
}
|
|
|
|
|
|
2025-07-11 21:05:58 +08:00
|
|
|
// 4. 保存用户
|
2025-06-30 19:21:56 +08:00
|
|
|
if err := s.repo.Update(ctx, user); err != nil {
|
2025-07-02 16:17:59 +08:00
|
|
|
return fmt.Errorf("密码更新失败: %w", err)
|
2025-06-30 19:21:56 +08:00
|
|
|
}
|
|
|
|
|
|
2025-07-11 21:05:58 +08:00
|
|
|
// 5. 发布密码修改事件
|
2025-07-02 16:17:59 +08:00
|
|
|
event := events.NewUserPasswordChangedEvent(user.ID, user.Phone, s.getCorrelationID(ctx))
|
2025-06-30 19:21:56 +08:00
|
|
|
if err := s.eventBus.Publish(ctx, event); err != nil {
|
2025-07-02 16:17:59 +08:00
|
|
|
s.logger.Warn("发布密码修改事件失败", zap.Error(err))
|
2025-06-30 19:21:56 +08:00
|
|
|
}
|
|
|
|
|
|
2025-07-02 16:17:59 +08:00
|
|
|
s.logger.Info("密码修改成功", zap.String("user_id", userID))
|
|
|
|
|
|
2025-06-30 19:21:56 +08:00
|
|
|
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)
|
2025-06-30 19:21:56 +08:00
|
|
|
if err != nil {
|
2025-07-02 16:17:59 +08:00
|
|
|
return nil, fmt.Errorf("用户不存在: %w", err)
|
2025-06-30 19:21:56 +08:00
|
|
|
}
|
|
|
|
|
|
2025-07-02 16:17:59 +08:00
|
|
|
return user, nil
|
2025-06-30 19:21:56 +08:00
|
|
|
}
|
|
|
|
|
|
2025-07-11 21:05:58 +08:00
|
|
|
// UpdateUserProfile 更新用户信息
|
|
|
|
|
func (s *UserService) UpdateUserProfile(ctx context.Context, userID string, req *dto.UpdateProfileRequest) (*entities.User, error) {
|
|
|
|
|
// 1. 获取用户信息
|
|
|
|
|
user, err := s.repo.GetByID(ctx, userID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("用户不存在: %w", err)
|
|
|
|
|
}
|
2025-06-30 19:21:56 +08:00
|
|
|
|
2025-07-11 21:05:58 +08:00
|
|
|
// 2. 更新手机号(如果需要)
|
|
|
|
|
if req.Phone != "" && req.Phone != user.Phone {
|
|
|
|
|
// 检查新手机号是否已存在
|
|
|
|
|
if err := s.checkPhoneDuplicate(ctx, req.Phone); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 使用实体的方法设置手机号
|
|
|
|
|
if err := user.SetPhone(req.Phone); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2025-06-30 19:21:56 +08:00
|
|
|
}
|
2025-07-11 21:05:58 +08:00
|
|
|
|
|
|
|
|
// 3. 保存用户
|
|
|
|
|
if err := s.repo.Update(ctx, user); err != nil {
|
|
|
|
|
return nil, fmt.Errorf("更新用户信息失败: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s.logger.Info("用户信息更新成功", zap.String("user_id", userID))
|
|
|
|
|
|
|
|
|
|
return user, nil
|
2025-06-30 19:21:56 +08:00
|
|
|
}
|
|
|
|
|
|
2025-07-11 21:05:58 +08:00
|
|
|
// DeactivateUser 停用用户
|
|
|
|
|
func (s *UserService) DeactivateUser(ctx context.Context, userID string) error {
|
|
|
|
|
// 1. 获取用户信息
|
|
|
|
|
user, err := s.repo.GetByID(ctx, userID)
|
2025-06-30 19:21:56 +08:00
|
|
|
if err != nil {
|
2025-07-11 21:05:58 +08:00
|
|
|
return fmt.Errorf("用户不存在: %w", err)
|
2025-06-30 19:21:56 +08:00
|
|
|
}
|
|
|
|
|
|
2025-07-11 21:05:58 +08:00
|
|
|
// 2. 检查用户状态
|
|
|
|
|
if user.IsDeleted() {
|
|
|
|
|
return fmt.Errorf("用户已被停用")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3. 软删除用户(这里需要调用仓储的软删除方法)
|
|
|
|
|
if err := s.repo.SoftDelete(ctx, userID); err != nil {
|
|
|
|
|
return fmt.Errorf("停用用户失败: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s.logger.Info("用户停用成功", zap.String("user_id", userID))
|
2025-06-30 19:21:56 +08:00
|
|
|
|
2025-07-11 21:05:58 +08:00
|
|
|
return nil
|
2025-06-30 19:21:56 +08:00
|
|
|
}
|
|
|
|
|
|
2025-07-11 21:05:58 +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
|
2025-06-30 19:21:56 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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
|
2025-06-30 19:21:56 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
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-06-30 19:21:56 +08:00
|
|
|
}
|
|
|
|
|
}
|
2025-07-02 16:17:59 +08:00
|
|
|
return ""
|
2025-06-30 19:21:56 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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-06-30 19:21:56 +08:00
|
|
|
}
|
|
|
|
|
}
|
2025-07-02 16:17:59 +08:00
|
|
|
return ""
|
2025-06-30 19:21:56 +08:00
|
|
|
}
|