基础架构
This commit is contained in:
304
internal/domains/user/services/enterprise_service.go
Normal file
304
internal/domains/user/services/enterprise_service.go
Normal file
@@ -0,0 +1,304 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"tyapi-server/internal/domains/user/entities"
|
||||
"tyapi-server/internal/domains/user/repositories"
|
||||
)
|
||||
|
||||
// EnterpriseService 企业信息领域服务
|
||||
type EnterpriseService struct {
|
||||
userRepo repositories.UserRepository
|
||||
enterpriseInfoRepo repositories.EnterpriseInfoRepository
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewEnterpriseService 创建企业信息领域服务
|
||||
func NewEnterpriseService(
|
||||
userRepo repositories.UserRepository,
|
||||
enterpriseInfoRepo repositories.EnterpriseInfoRepository,
|
||||
logger *zap.Logger,
|
||||
) *EnterpriseService {
|
||||
return &EnterpriseService{
|
||||
userRepo: userRepo,
|
||||
enterpriseInfoRepo: enterpriseInfoRepo,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateEnterpriseInfo 创建企业信息
|
||||
func (s *EnterpriseService) CreateEnterpriseInfo(ctx context.Context, userID, companyName, unifiedSocialCode, legalPersonName, legalPersonID string) (*entities.EnterpriseInfo, error) {
|
||||
// 检查用户是否存在
|
||||
_, err := s.userRepo.GetByID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("用户不存在: %w", err)
|
||||
}
|
||||
|
||||
// 检查用户是否已有企业信息
|
||||
existingInfo, err := s.enterpriseInfoRepo.GetByUserID(ctx, userID)
|
||||
if err == nil && existingInfo != nil {
|
||||
return nil, fmt.Errorf("用户已有企业信息")
|
||||
}
|
||||
|
||||
// 检查统一社会信用代码是否已存在
|
||||
exists, err := s.enterpriseInfoRepo.CheckUnifiedSocialCodeExists(ctx, unifiedSocialCode, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("检查企业信息失败: %w", err)
|
||||
}
|
||||
if exists {
|
||||
return nil, fmt.Errorf("统一社会信用代码已存在")
|
||||
}
|
||||
|
||||
// 创建企业信息
|
||||
enterpriseInfo := &entities.EnterpriseInfo{
|
||||
UserID: userID,
|
||||
CompanyName: companyName,
|
||||
UnifiedSocialCode: unifiedSocialCode,
|
||||
LegalPersonName: legalPersonName,
|
||||
LegalPersonID: legalPersonID,
|
||||
}
|
||||
|
||||
*enterpriseInfo, err = s.enterpriseInfoRepo.Create(ctx, *enterpriseInfo)
|
||||
if err != nil {
|
||||
s.logger.Error("创建企业信息失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("创建企业信息失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("企业信息创建成功",
|
||||
zap.String("user_id", userID),
|
||||
zap.String("enterprise_id", enterpriseInfo.ID),
|
||||
zap.String("company_name", companyName),
|
||||
)
|
||||
|
||||
return enterpriseInfo, nil
|
||||
}
|
||||
|
||||
// GetEnterpriseInfo 获取企业信息
|
||||
func (s *EnterpriseService) GetEnterpriseInfo(ctx context.Context, userID string) (*entities.EnterpriseInfo, error) {
|
||||
// 检查用户是否存在
|
||||
_, err := s.userRepo.GetByID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("用户不存在: %w", err)
|
||||
}
|
||||
|
||||
enterpriseInfo, err := s.enterpriseInfoRepo.GetByUserID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("企业信息不存在: %w", err)
|
||||
}
|
||||
|
||||
return enterpriseInfo, nil
|
||||
}
|
||||
|
||||
// UpdateEnterpriseInfo 更新企业信息(仅限未认证完成的情况)
|
||||
func (s *EnterpriseService) UpdateEnterpriseInfo(ctx context.Context, userID, companyName, unifiedSocialCode, legalPersonName, legalPersonID string) (*entities.EnterpriseInfo, error) {
|
||||
// 检查用户是否存在
|
||||
_, err := s.userRepo.GetByID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("用户不存在: %w", err)
|
||||
}
|
||||
|
||||
// 获取现有企业信息
|
||||
enterpriseInfo, err := s.enterpriseInfoRepo.GetByUserID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("企业信息不存在: %w", err)
|
||||
}
|
||||
|
||||
// 检查企业信息是否已认证完成(认证完成后不可修改)
|
||||
if enterpriseInfo.IsReadOnly() {
|
||||
return nil, fmt.Errorf("企业信息已认证完成,不可修改")
|
||||
}
|
||||
|
||||
// 检查统一社会信用代码是否已被其他用户使用
|
||||
if unifiedSocialCode != enterpriseInfo.UnifiedSocialCode {
|
||||
exists, err := s.enterpriseInfoRepo.CheckUnifiedSocialCodeExists(ctx, unifiedSocialCode, userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("检查企业信息失败: %w", err)
|
||||
}
|
||||
if exists {
|
||||
return nil, fmt.Errorf("统一社会信用代码已存在")
|
||||
}
|
||||
}
|
||||
|
||||
// 更新企业信息
|
||||
enterpriseInfo.CompanyName = companyName
|
||||
enterpriseInfo.UnifiedSocialCode = unifiedSocialCode
|
||||
enterpriseInfo.LegalPersonName = legalPersonName
|
||||
enterpriseInfo.LegalPersonID = legalPersonID
|
||||
|
||||
if err := s.enterpriseInfoRepo.Update(ctx, *enterpriseInfo); err != nil {
|
||||
s.logger.Error("更新企业信息失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("更新企业信息失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("企业信息更新成功",
|
||||
zap.String("user_id", userID),
|
||||
zap.String("enterprise_id", enterpriseInfo.ID),
|
||||
)
|
||||
|
||||
return enterpriseInfo, nil
|
||||
}
|
||||
|
||||
// UpdateOCRVerification 更新OCR验证状态
|
||||
func (s *EnterpriseService) UpdateOCRVerification(ctx context.Context, userID string, isVerified bool, rawData string, confidence float64) error {
|
||||
// 获取企业信息
|
||||
enterpriseInfo, err := s.enterpriseInfoRepo.GetByUserID(ctx, userID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("企业信息不存在: %w", err)
|
||||
}
|
||||
|
||||
// 更新OCR验证状态
|
||||
enterpriseInfo.UpdateOCRVerification(isVerified, rawData, confidence)
|
||||
|
||||
if err := s.enterpriseInfoRepo.Update(ctx, *enterpriseInfo); err != nil {
|
||||
s.logger.Error("更新OCR验证状态失败", zap.Error(err))
|
||||
return fmt.Errorf("更新OCR验证状态失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("OCR验证状态更新成功",
|
||||
zap.String("user_id", userID),
|
||||
zap.Bool("is_verified", isVerified),
|
||||
zap.Float64("confidence", confidence),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateFaceVerification 更新人脸识别验证状态
|
||||
func (s *EnterpriseService) UpdateFaceVerification(ctx context.Context, userID string, isVerified bool) error {
|
||||
// 获取企业信息
|
||||
enterpriseInfo, err := s.enterpriseInfoRepo.GetByUserID(ctx, userID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("企业信息不存在: %w", err)
|
||||
}
|
||||
|
||||
// 更新人脸识别验证状态
|
||||
enterpriseInfo.UpdateFaceVerification(isVerified)
|
||||
|
||||
if err := s.enterpriseInfoRepo.Update(ctx, *enterpriseInfo); err != nil {
|
||||
s.logger.Error("更新人脸识别验证状态失败", zap.Error(err))
|
||||
return fmt.Errorf("更新人脸识别验证状态失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("人脸识别验证状态更新成功",
|
||||
zap.String("user_id", userID),
|
||||
zap.Bool("is_verified", isVerified),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CompleteEnterpriseCertification 完成企业认证
|
||||
func (s *EnterpriseService) CompleteEnterpriseCertification(ctx context.Context, userID string) error {
|
||||
// 获取企业信息
|
||||
enterpriseInfo, err := s.enterpriseInfoRepo.GetByUserID(ctx, userID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("企业信息不存在: %w", err)
|
||||
}
|
||||
|
||||
// 检查是否已完成所有验证
|
||||
if !enterpriseInfo.IsOCRVerified || !enterpriseInfo.IsFaceVerified {
|
||||
return fmt.Errorf("企业信息验证未完成,无法完成认证")
|
||||
}
|
||||
|
||||
// 完成认证
|
||||
enterpriseInfo.CompleteCertification()
|
||||
|
||||
if err := s.enterpriseInfoRepo.Update(ctx, *enterpriseInfo); err != nil {
|
||||
s.logger.Error("完成企业认证失败", zap.Error(err))
|
||||
return fmt.Errorf("完成企业认证失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("企业认证完成",
|
||||
zap.String("user_id", userID),
|
||||
zap.String("enterprise_id", enterpriseInfo.ID),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckUnifiedSocialCodeExists 检查统一社会信用代码是否存在
|
||||
func (s *EnterpriseService) CheckUnifiedSocialCodeExists(ctx context.Context, unifiedSocialCode, excludeUserID string) (bool, error) {
|
||||
return s.enterpriseInfoRepo.CheckUnifiedSocialCodeExists(ctx, unifiedSocialCode, excludeUserID)
|
||||
}
|
||||
|
||||
// GetUserWithEnterpriseInfo 获取用户信息(包含企业信息)
|
||||
func (s *EnterpriseService) GetUserWithEnterpriseInfo(ctx context.Context, userID string) (*entities.User, error) {
|
||||
// 获取用户信息
|
||||
user, err := s.userRepo.GetByID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("用户不存在: %w", err)
|
||||
}
|
||||
|
||||
// 获取企业信息(如果存在)
|
||||
enterpriseInfo, err := s.enterpriseInfoRepo.GetByUserID(ctx, userID)
|
||||
if err != nil {
|
||||
// 企业信息不存在是正常的,不是错误
|
||||
s.logger.Debug("用户暂无企业信息", zap.String("user_id", userID))
|
||||
} else {
|
||||
user.EnterpriseInfo = enterpriseInfo
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// ValidateEnterpriseInfo 验证企业信息完整性
|
||||
func (s *EnterpriseService) ValidateEnterpriseInfo(ctx context.Context, userID string) error {
|
||||
enterpriseInfo, err := s.enterpriseInfoRepo.GetByUserID(ctx, userID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("企业信息不存在: %w", err)
|
||||
}
|
||||
|
||||
if err := enterpriseInfo.Validate(); err != nil {
|
||||
return fmt.Errorf("企业信息验证失败: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetEnterpriseInfoByUnifiedSocialCode 根据统一社会信用代码获取企业信息
|
||||
func (s *EnterpriseService) GetEnterpriseInfoByUnifiedSocialCode(ctx context.Context, unifiedSocialCode string) (*entities.EnterpriseInfo, error) {
|
||||
return s.enterpriseInfoRepo.GetByUnifiedSocialCode(ctx, unifiedSocialCode)
|
||||
}
|
||||
|
||||
// IsEnterpriseCertified 检查用户是否已完成企业认证
|
||||
func (s *EnterpriseService) IsEnterpriseCertified(ctx context.Context, userID string) (bool, error) {
|
||||
enterpriseInfo, err := s.enterpriseInfoRepo.GetByUserID(ctx, userID)
|
||||
if err != nil {
|
||||
// 没有企业信息,认为未认证
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return enterpriseInfo.IsFullyVerified(), nil
|
||||
}
|
||||
|
||||
// GetEnterpriseCertificationStatus 获取企业认证状态
|
||||
func (s *EnterpriseService) GetEnterpriseCertificationStatus(ctx context.Context, userID string) (map[string]interface{}, error) {
|
||||
enterpriseInfo, err := s.enterpriseInfoRepo.GetByUserID(ctx, userID)
|
||||
if err != nil {
|
||||
return map[string]interface{}{
|
||||
"has_enterprise_info": false,
|
||||
"is_certified": false,
|
||||
"message": "用户暂无企业信息",
|
||||
}, nil
|
||||
}
|
||||
|
||||
status := map[string]interface{}{
|
||||
"has_enterprise_info": true,
|
||||
"is_certified": enterpriseInfo.IsFullyVerified(),
|
||||
"is_readonly": enterpriseInfo.IsReadOnly(),
|
||||
"ocr_verified": enterpriseInfo.IsOCRVerified,
|
||||
"face_verified": enterpriseInfo.IsFaceVerified,
|
||||
"certified_at": enterpriseInfo.CertifiedAt,
|
||||
"company_name": enterpriseInfo.CompanyName,
|
||||
"unified_social_code": enterpriseInfo.UnifiedSocialCode,
|
||||
"legal_person_name": enterpriseInfo.LegalPersonName,
|
||||
"created_at": enterpriseInfo.CreatedAt,
|
||||
"updated_at": enterpriseInfo.UpdatedAt,
|
||||
}
|
||||
|
||||
return status, nil
|
||||
}
|
||||
@@ -5,31 +5,32 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"tyapi-server/internal/config"
|
||||
"tyapi-server/internal/domains/user/entities"
|
||||
"tyapi-server/internal/domains/user/repositories"
|
||||
"tyapi-server/internal/infrastructure/external/sms"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
"tyapi-server/internal/shared/sms"
|
||||
)
|
||||
|
||||
// SMSCodeService 短信验证码服务
|
||||
type SMSCodeService struct {
|
||||
repo *repositories.SMSCodeRepository
|
||||
smsClient sms.Service
|
||||
repo repositories.SMSCodeRepository
|
||||
smsClient *sms.AliSMSService
|
||||
cache interfaces.CacheService
|
||||
config config.SMSConfig
|
||||
appConfig config.AppConfig
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewSMSCodeService 创建短信验证码服务
|
||||
func NewSMSCodeService(
|
||||
repo *repositories.SMSCodeRepository,
|
||||
smsClient sms.Service,
|
||||
repo repositories.SMSCodeRepository,
|
||||
smsClient *sms.AliSMSService,
|
||||
cache interfaces.CacheService,
|
||||
config config.SMSConfig,
|
||||
appConfig config.AppConfig,
|
||||
logger *zap.Logger,
|
||||
) *SMSCodeService {
|
||||
return &SMSCodeService{
|
||||
@@ -37,31 +38,25 @@ func NewSMSCodeService(
|
||||
smsClient: smsClient,
|
||||
cache: cache,
|
||||
config: config,
|
||||
appConfig: appConfig,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// SendCode 发送验证码
|
||||
func (s *SMSCodeService) SendCode(ctx context.Context, phone string, scene entities.SMSScene, clientIP, userAgent string) error {
|
||||
// 1. 检查频率限制
|
||||
if err := s.checkRateLimit(ctx, phone); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 2. 生成验证码
|
||||
// 1. 生成验证码
|
||||
code := s.smsClient.GenerateCode(s.config.CodeLength)
|
||||
|
||||
// 3. 使用工厂方法创建SMS验证码记录
|
||||
// 2. 使用工厂方法创建SMS验证码记录
|
||||
smsCode, err := entities.NewSMSCode(phone, code, scene, s.config.ExpireTime, clientIP, userAgent)
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建验证码记录失败: %w", err)
|
||||
}
|
||||
|
||||
// 4. 设置ID
|
||||
smsCode.ID = uuid.New().String()
|
||||
|
||||
// 5. 保存验证码
|
||||
if err := s.repo.Create(ctx, smsCode); err != nil {
|
||||
// 4. 保存验证码
|
||||
*smsCode, err = s.repo.Create(ctx, *smsCode)
|
||||
if err != nil {
|
||||
s.logger.Error("保存短信验证码失败",
|
||||
zap.String("phone", smsCode.GetMaskedPhone()),
|
||||
zap.String("scene", smsCode.GetSceneName()),
|
||||
@@ -69,7 +64,7 @@ func (s *SMSCodeService) SendCode(ctx context.Context, phone string, scene entit
|
||||
return fmt.Errorf("保存验证码失败: %w", err)
|
||||
}
|
||||
|
||||
// 6. 发送短信
|
||||
// 5. 发送短信
|
||||
if err := s.smsClient.SendVerificationCode(ctx, phone, code); err != nil {
|
||||
// 记录发送失败但不删除验证码记录,让其自然过期
|
||||
s.logger.Error("发送短信验证码失败",
|
||||
@@ -79,8 +74,8 @@ func (s *SMSCodeService) SendCode(ctx context.Context, phone string, scene entit
|
||||
return fmt.Errorf("短信发送失败: %w", err)
|
||||
}
|
||||
|
||||
// 7. 更新发送记录缓存
|
||||
s.updateSendRecord(ctx, phone)
|
||||
// 6. 更新发送记录缓存
|
||||
s.updateSendRecord(ctx, phone, scene)
|
||||
|
||||
s.logger.Info("短信验证码发送成功",
|
||||
zap.String("phone", smsCode.GetMaskedPhone()),
|
||||
@@ -92,19 +87,33 @@ func (s *SMSCodeService) SendCode(ctx context.Context, phone string, scene entit
|
||||
|
||||
// VerifyCode 验证验证码
|
||||
func (s *SMSCodeService) VerifyCode(ctx context.Context, phone, code string, scene entities.SMSScene) error {
|
||||
// 开发模式下跳过验证码校验
|
||||
if s.appConfig.IsDevelopment() {
|
||||
s.logger.Info("开发模式:验证码校验已跳过",
|
||||
zap.String("phone", phone),
|
||||
zap.String("scene", string(scene)),
|
||||
zap.String("code", code))
|
||||
return nil
|
||||
}
|
||||
|
||||
// 1. 根据手机号和场景获取有效的验证码记录
|
||||
smsCode, err := s.repo.GetValidCode(ctx, phone, scene)
|
||||
smsCode, err := s.repo.GetValidByPhoneAndScene(ctx, phone, scene)
|
||||
if err != nil {
|
||||
return fmt.Errorf("验证码无效或已过期")
|
||||
}
|
||||
|
||||
// 2. 使用实体的验证方法
|
||||
// 2. 检查场景是否匹配
|
||||
if smsCode.Scene != scene {
|
||||
return fmt.Errorf("验证码错误或已过期")
|
||||
}
|
||||
|
||||
// 3. 使用实体的验证方法
|
||||
if err := smsCode.VerifyCode(code); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 3. 保存更新后的验证码状态
|
||||
if err := s.repo.Update(ctx, smsCode); err != nil {
|
||||
// 4. 保存更新后的验证码状态
|
||||
if err := s.repo.Update(ctx, *smsCode); err != nil {
|
||||
s.logger.Error("更新验证码状态失败",
|
||||
zap.String("code_id", smsCode.ID),
|
||||
zap.Error(err))
|
||||
@@ -120,10 +129,10 @@ func (s *SMSCodeService) VerifyCode(ctx context.Context, phone, code string, sce
|
||||
|
||||
// CanResendCode 检查是否可以重新发送验证码
|
||||
func (s *SMSCodeService) CanResendCode(ctx context.Context, phone string, scene entities.SMSScene) (bool, error) {
|
||||
// 1. 获取最近的验证码记录
|
||||
recentCode, err := s.repo.GetRecentCode(ctx, phone, scene)
|
||||
// 1. 获取最近的验证码记录(按场景)
|
||||
recentCode, err := s.repo.GetValidByPhoneAndScene(ctx, phone, scene)
|
||||
if err != nil {
|
||||
// 如果没有记录,可以发送
|
||||
// 如果没有该场景的记录,可以发送
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@@ -144,8 +153,8 @@ func (s *SMSCodeService) CanResendCode(ctx context.Context, phone string, scene
|
||||
|
||||
// GetCodeStatus 获取验证码状态信息
|
||||
func (s *SMSCodeService) GetCodeStatus(ctx context.Context, phone string, scene entities.SMSScene) (map[string]interface{}, error) {
|
||||
// 1. 获取最近的验证码记录
|
||||
recentCode, err := s.repo.GetRecentCode(ctx, phone, scene)
|
||||
// 1. 获取最近的验证码记录(按场景)
|
||||
recentCode, err := s.repo.GetValidByPhoneAndScene(ctx, phone, scene)
|
||||
if err != nil {
|
||||
return map[string]interface{}{
|
||||
"has_code": false,
|
||||
@@ -170,11 +179,11 @@ func (s *SMSCodeService) GetCodeStatus(ctx context.Context, phone string, scene
|
||||
}
|
||||
|
||||
// checkRateLimit 检查发送频率限制
|
||||
func (s *SMSCodeService) checkRateLimit(ctx context.Context, phone string) error {
|
||||
func (s *SMSCodeService) CheckRateLimit(ctx context.Context, phone string, scene entities.SMSScene) error {
|
||||
now := time.Now()
|
||||
|
||||
// 检查最小发送间隔
|
||||
lastSentKey := fmt.Sprintf("sms:last_sent:%s", phone)
|
||||
lastSentKey := fmt.Sprintf("sms:last_sent:%s:%s", scene, phone)
|
||||
var lastSent time.Time
|
||||
if err := s.cache.Get(ctx, lastSentKey, &lastSent); err == nil {
|
||||
if now.Sub(lastSent) < s.config.RateLimit.MinInterval {
|
||||
@@ -204,11 +213,11 @@ func (s *SMSCodeService) checkRateLimit(ctx context.Context, phone string) error
|
||||
}
|
||||
|
||||
// updateSendRecord 更新发送记录
|
||||
func (s *SMSCodeService) updateSendRecord(ctx context.Context, phone string) {
|
||||
func (s *SMSCodeService) updateSendRecord(ctx context.Context, phone string, scene entities.SMSScene) {
|
||||
now := time.Now()
|
||||
|
||||
// 更新最后发送时间
|
||||
lastSentKey := fmt.Sprintf("sms:last_sent:%s", phone)
|
||||
lastSentKey := fmt.Sprintf("sms:last_sent:%s:%s", scene, phone)
|
||||
s.cache.Set(ctx, lastSentKey, now, s.config.RateLimit.MinInterval)
|
||||
|
||||
// 更新每小时计数
|
||||
@@ -232,5 +241,5 @@ func (s *SMSCodeService) updateSendRecord(ctx context.Context, phone string) {
|
||||
|
||||
// CleanExpiredCodes 清理过期验证码
|
||||
func (s *SMSCodeService) CleanExpiredCodes(ctx context.Context) error {
|
||||
return s.repo.CleanupExpired(ctx)
|
||||
return s.repo.DeleteBatch(ctx, []string{})
|
||||
}
|
||||
|
||||
@@ -4,308 +4,126 @@ 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 用户服务实现
|
||||
// UserService 用户领域服务
|
||||
type UserService struct {
|
||||
repo *repositories.UserRepository
|
||||
smsCodeService *SMSCodeService
|
||||
eventBus interfaces.EventBus
|
||||
logger *zap.Logger
|
||||
userRepo repositories.UserRepository
|
||||
enterpriseService *EnterpriseService
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewUserService 创建用户服务
|
||||
// NewUserService 创建用户领域服务
|
||||
func NewUserService(
|
||||
repo *repositories.UserRepository,
|
||||
smsCodeService *SMSCodeService,
|
||||
eventBus interfaces.EventBus,
|
||||
userRepo repositories.UserRepository,
|
||||
enterpriseService *EnterpriseService,
|
||||
logger *zap.Logger,
|
||||
) *UserService {
|
||||
return &UserService{
|
||||
repo: repo,
|
||||
smsCodeService: smsCodeService,
|
||||
eventBus: eventBus,
|
||||
logger: logger,
|
||||
userRepo: userRepo,
|
||||
enterpriseService: enterpriseService,
|
||||
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) {
|
||||
// 1. 验证短信验证码
|
||||
if err := s.smsCodeService.VerifyCode(ctx, registerReq.Phone, registerReq.Code, entities.SMSSceneRegister); err != nil {
|
||||
return nil, fmt.Errorf("验证码验证失败: %w", err)
|
||||
}
|
||||
|
||||
// 2. 检查手机号是否已存在
|
||||
if err := s.checkPhoneDuplicate(ctx, registerReq.Phone); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 3. 使用工厂方法创建用户实体(业务规则验证在实体中完成)
|
||||
user, err := entities.NewUser(registerReq.Phone, registerReq.Password)
|
||||
// IsPhoneRegistered 检查手机号是否已注册
|
||||
func (s *UserService) IsPhoneRegistered(ctx context.Context, phone string) (bool, error) {
|
||||
_, err := s.userRepo.GetByPhone(ctx, phone)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("创建用户失败: %w", err)
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// 4. 设置用户ID
|
||||
user.ID = uuid.New().String()
|
||||
// GetUserWithEnterpriseInfo 获取用户信息(包含企业信息)
|
||||
func (s *UserService) GetUserWithEnterpriseInfo(ctx context.Context, userID string) (*entities.User, error) {
|
||||
// 通过企业服务获取用户信息(包含企业信息)
|
||||
return s.enterpriseService.GetUserWithEnterpriseInfo(ctx, userID)
|
||||
}
|
||||
|
||||
// 5. 保存用户
|
||||
if err := s.repo.Create(ctx, user); err != nil {
|
||||
s.logger.Error("创建用户失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("创建用户失败: %w", err)
|
||||
// GetUserByID 根据ID获取用户信息
|
||||
func (s *UserService) GetUserByID(ctx context.Context, userID string) (*entities.User, error) {
|
||||
user, err := s.userRepo.GetByID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("用户不存在: %w", err)
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// 6. 发布用户注册事件
|
||||
event := events.NewUserRegisteredEvent(user, s.getCorrelationID(ctx))
|
||||
if err := s.eventBus.Publish(ctx, event); err != nil {
|
||||
s.logger.Warn("发布用户注册事件失败", zap.Error(err))
|
||||
// GetUserByPhone 根据手机号获取用户信息
|
||||
func (s *UserService) GetUserByPhone(ctx context.Context, phone string) (*entities.User, error) {
|
||||
user, err := s.userRepo.GetByPhone(ctx, phone)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("用户不存在: %w", 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) {
|
||||
// 1. 根据手机号查找用户
|
||||
user, err := s.repo.FindByPhone(ctx, loginReq.Phone)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("用户名或密码错误")
|
||||
// UpdateUser 更新用户信息
|
||||
func (s *UserService) UpdateUser(ctx context.Context, user *entities.User) error {
|
||||
if err := s.userRepo.Update(ctx, *user); err != nil {
|
||||
s.logger.Error("更新用户信息失败", zap.Error(err))
|
||||
return fmt.Errorf("更新用户信息失败: %w", err)
|
||||
}
|
||||
|
||||
// 2. 检查用户是否可以登录(委托给实体)
|
||||
if !user.CanLogin() {
|
||||
return nil, fmt.Errorf("用户状态异常,无法登录")
|
||||
}
|
||||
|
||||
// 3. 验证密码(委托给实体)
|
||||
if !user.CheckPassword(loginReq.Password) {
|
||||
return nil, fmt.Errorf("用户名或密码错误")
|
||||
}
|
||||
|
||||
// 4. 发布用户登录事件
|
||||
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("用户密码登录成功",
|
||||
s.logger.Info("用户信息更新成功",
|
||||
zap.String("user_id", user.ID),
|
||||
zap.String("phone", user.Phone))
|
||||
zap.String("phone", user.Phone),
|
||||
)
|
||||
|
||||
return user, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoginWithSMS 短信验证码登录
|
||||
func (s *UserService) LoginWithSMS(ctx context.Context, loginReq *dto.LoginWithSMSRequest) (*entities.User, error) {
|
||||
// 1. 验证短信验证码
|
||||
if err := s.smsCodeService.VerifyCode(ctx, loginReq.Phone, loginReq.Code, entities.SMSSceneLogin); err != nil {
|
||||
return nil, fmt.Errorf("验证码验证失败: %w", err)
|
||||
}
|
||||
|
||||
// 2. 根据手机号查找用户
|
||||
user, err := s.repo.FindByPhone(ctx, loginReq.Phone)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("用户不存在")
|
||||
}
|
||||
|
||||
// 3. 检查用户是否可以登录(委托给实体)
|
||||
if !user.CanLogin() {
|
||||
return nil, fmt.Errorf("用户状态异常,无法登录")
|
||||
}
|
||||
|
||||
// 4. 发布用户登录事件
|
||||
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 {
|
||||
// 1. 获取用户信息
|
||||
user, err := s.repo.GetByID(ctx, userID)
|
||||
// ChangePassword 修改用户密码
|
||||
func (s *UserService) ChangePassword(ctx context.Context, userID, oldPassword, newPassword string) error {
|
||||
user, err := s.userRepo.GetByID(ctx, userID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("用户不存在: %w", err)
|
||||
}
|
||||
|
||||
// 2. 验证短信验证码
|
||||
if err := s.smsCodeService.VerifyCode(ctx, user.Phone, req.Code, entities.SMSSceneChangePassword); err != nil {
|
||||
return fmt.Errorf("验证码验证失败: %w", err)
|
||||
}
|
||||
|
||||
// 3. 执行业务逻辑(委托给实体)
|
||||
if err := user.ChangePassword(req.OldPassword, req.NewPassword, req.ConfirmNewPassword); err != nil {
|
||||
if err := user.ChangePassword(oldPassword, newPassword, newPassword); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 4. 保存用户
|
||||
if err := s.repo.Update(ctx, user); err != nil {
|
||||
return fmt.Errorf("密码更新失败: %w", err)
|
||||
if err := s.userRepo.Update(ctx, user); err != nil {
|
||||
s.logger.Error("密码修改失败", zap.Error(err))
|
||||
return fmt.Errorf("密码修改失败: %w", err)
|
||||
}
|
||||
|
||||
// 5. 发布密码修改事件
|
||||
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))
|
||||
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
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// DeactivateUser 停用用户
|
||||
func (s *UserService) DeactivateUser(ctx context.Context, userID string) error {
|
||||
// 1. 获取用户信息
|
||||
user, err := s.repo.GetByID(ctx, userID)
|
||||
// ValidateUser 验证用户信息
|
||||
func (s *UserService) ValidateUser(ctx context.Context, userID string) error {
|
||||
user, err := s.userRepo.GetByID(ctx, userID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("用户不存在: %w", err)
|
||||
}
|
||||
|
||||
// 2. 检查用户状态
|
||||
if user.IsDeleted() {
|
||||
return fmt.Errorf("用户已被停用")
|
||||
// 这里可以添加更多的用户验证逻辑
|
||||
if user.Phone == "" {
|
||||
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))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ================ 工具方法 ================
|
||||
|
||||
// checkPhoneDuplicate 检查手机号重复
|
||||
func (s *UserService) checkPhoneDuplicate(ctx context.Context, phone string) error {
|
||||
if _, err := s.repo.FindByPhone(ctx, phone); err == nil {
|
||||
return fmt.Errorf("手机号已存在")
|
||||
// GetUserStats 获取用户统计信息
|
||||
func (s *UserService) GetUserStats(ctx context.Context) (map[string]interface{}, error) {
|
||||
// 这里可以添加用户统计逻辑
|
||||
stats := map[string]interface{}{
|
||||
"total_users": 0, // 需要实现具体的统计逻辑
|
||||
"active_users": 0,
|
||||
"new_users_today": 0,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 ""
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user