// Package personal_customer 提供个人客户管理的业务逻辑服务 // 包含个人客户注册、登录、微信绑定、短信验证等功能 package personal_customer import ( "context" "fmt" "github.com/break/junhong_cmp_fiber/internal/model" "github.com/break/junhong_cmp_fiber/internal/service/verification" "github.com/break/junhong_cmp_fiber/internal/store/postgres" "github.com/break/junhong_cmp_fiber/pkg/auth" "go.uber.org/zap" "gorm.io/gorm" ) // Service 个人客户服务 type Service struct { store *postgres.PersonalCustomerStore phoneStore *postgres.PersonalCustomerPhoneStore verificationService *verification.Service jwtManager *auth.JWTManager logger *zap.Logger } // NewService 创建个人客户服务实例 func NewService( store *postgres.PersonalCustomerStore, phoneStore *postgres.PersonalCustomerPhoneStore, verificationService *verification.Service, jwtManager *auth.JWTManager, logger *zap.Logger, ) *Service { return &Service{ store: store, phoneStore: phoneStore, verificationService: verificationService, jwtManager: jwtManager, logger: logger, } } // SendVerificationCode 发送验证码 func (s *Service) SendVerificationCode(ctx context.Context, phone string) error { return s.verificationService.SendCode(ctx, phone) } // VerifyCode 验证验证码 func (s *Service) VerifyCode(ctx context.Context, phone string, code string) error { return s.verificationService.VerifyCode(ctx, phone, code) } // LoginByPhone 通过手机号登录 // 如果手机号不存在,自动创建新的个人客户 // 注意:此方法是临时实现,完整的登录流程应该是先微信授权,再绑定手机号 func (s *Service) LoginByPhone(ctx context.Context, phone string, code string) (string, *model.PersonalCustomer, error) { // 验证验证码 if err := s.verificationService.VerifyCode(ctx, phone, code); err != nil { s.logger.Warn("验证码验证失败", zap.String("phone", phone), zap.Error(err), ) return "", nil, fmt.Errorf("验证码验证失败: %w", err) } // 查找或创建个人客户 customer, err := s.store.GetByPhone(ctx, phone) if err != nil { if err == gorm.ErrRecordNotFound { // 客户不存在,创建新客户 // 注意:临时实现,使用空的微信信息(正式应该先微信授权) customer = &model.PersonalCustomer{ WxOpenID: "", // 临时为空,后续需绑定微信 WxUnionID: "", // 临时为空,后续需绑定微信 Status: 1, // 默认启用 } if err := s.store.Create(ctx, customer); err != nil { s.logger.Error("创建个人客户失败", zap.String("phone", phone), zap.Error(err), ) return "", nil, fmt.Errorf("创建个人客户失败: %w", err) } // 创建手机号绑定记录 // TODO: 这里需要通过 PersonalCustomerPhoneStore 来创建 // 暂时跳过,等待 PersonalCustomerPhoneStore 实现 s.logger.Info("创建新个人客户", zap.Uint("customer_id", customer.ID), zap.String("phone", phone), ) } else { s.logger.Error("查询个人客户失败", zap.String("phone", phone), zap.Error(err), ) return "", nil, fmt.Errorf("查询个人客户失败: %w", err) } } // 检查客户状态 if customer.Status == 0 { s.logger.Warn("个人客户已被禁用", zap.Uint("customer_id", customer.ID), zap.String("phone", phone), ) return "", nil, fmt.Errorf("账号已被禁用") } // 生成 Token(临时传递 phone,后续应该从 Token 中移除 phone 字段) token, err := s.jwtManager.GeneratePersonalCustomerToken(customer.ID, phone) if err != nil { s.logger.Error("生成 Token 失败", zap.Uint("customer_id", customer.ID), zap.String("phone", phone), zap.Error(err), ) return "", nil, fmt.Errorf("生成 Token 失败: %w", err) } s.logger.Info("个人客户登录成功", zap.Uint("customer_id", customer.ID), zap.String("phone", phone), ) return token, customer, nil } // BindWechat 绑定微信信息 func (s *Service) BindWechat(ctx context.Context, customerID uint, wxOpenID, wxUnionID string) error { // 获取客户 customer, err := s.store.GetByID(ctx, customerID) if err != nil { s.logger.Error("查询个人客户失败", zap.Uint("customer_id", customerID), zap.Error(err), ) return fmt.Errorf("查询个人客户失败: %w", err) } // 更新微信信息 customer.WxOpenID = wxOpenID customer.WxUnionID = wxUnionID if err := s.store.Update(ctx, customer); err != nil { s.logger.Error("更新微信信息失败", zap.Uint("customer_id", customerID), zap.Error(err), ) return fmt.Errorf("更新微信信息失败: %w", err) } s.logger.Info("绑定微信信息成功", zap.Uint("customer_id", customerID), zap.String("wx_open_id", wxOpenID), ) return nil } // UpdateProfile 更新个人资料 func (s *Service) UpdateProfile(ctx context.Context, customerID uint, nickname, avatarURL string) error { customer, err := s.store.GetByID(ctx, customerID) if err != nil { s.logger.Error("查询个人客户失败", zap.Uint("customer_id", customerID), zap.Error(err), ) return fmt.Errorf("查询个人客户失败: %w", err) } // 更新资料 if nickname != "" { customer.Nickname = nickname } if avatarURL != "" { customer.AvatarURL = avatarURL } if err := s.store.Update(ctx, customer); err != nil { s.logger.Error("更新个人资料失败", zap.Uint("customer_id", customerID), zap.Error(err), ) return fmt.Errorf("更新个人资料失败: %w", err) } s.logger.Info("更新个人资料成功", zap.Uint("customer_id", customerID), ) return nil } // GetProfile 获取个人资料 func (s *Service) GetProfile(ctx context.Context, customerID uint) (*model.PersonalCustomer, error) { customer, err := s.store.GetByID(ctx, customerID) if err != nil { s.logger.Error("查询个人客户失败", zap.Uint("customer_id", customerID), zap.Error(err), ) return nil, fmt.Errorf("查询个人客户失败: %w", err) } return customer, nil } // GetProfileWithPhone 获取个人资料(包含主手机号) func (s *Service) GetProfileWithPhone(ctx context.Context, customerID uint) (*model.PersonalCustomer, string, error) { // 获取客户信息 customer, err := s.store.GetByID(ctx, customerID) if err != nil { s.logger.Error("查询个人客户失败", zap.Uint("customer_id", customerID), zap.Error(err), ) return nil, "", fmt.Errorf("查询个人客户失败: %w", err) } // 获取主手机号 phone := "" primaryPhone, err := s.phoneStore.GetPrimaryPhone(ctx, customerID) if err != nil { if err != gorm.ErrRecordNotFound { s.logger.Error("查询主手机号失败", zap.Uint("customer_id", customerID), zap.Error(err), ) // 不返回错误,继续返回客户信息(手机号为空) } } else { phone = primaryPhone.Phone } return customer, phone, nil }