726 lines
22 KiB
Go
726 lines
22 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"time"
|
|
|
|
"license-manager/internal/config"
|
|
"license-manager/internal/models"
|
|
"license-manager/internal/repository"
|
|
pkgcontext "license-manager/pkg/context"
|
|
"license-manager/pkg/i18n"
|
|
"license-manager/pkg/utils"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type CuUserService interface {
|
|
Register(ctx context.Context, req *models.CuUserRegisterRequest) (*models.CuUser, error)
|
|
Login(ctx context.Context, req *models.CuUserLoginRequest, ip string) (*models.CuUser, string, error)
|
|
GetProfile(ctx context.Context, userID string) (*models.CuUser, error)
|
|
UpdateProfile(ctx context.Context, userID string, req *models.CuUserProfileUpdateRequest) error
|
|
UpdatePhone(ctx context.Context, userID string, req *models.CuUserPhoneUpdateRequest) error
|
|
ChangePassword(ctx context.Context, userID, oldPassword, newPassword string) error
|
|
SendRegisterSms(ctx context.Context, req *models.CuUserSendRegisterSmsRequest) error
|
|
SendLoginSms(ctx context.Context, req *models.CuUserSendLoginSmsRequest) error
|
|
SendCurrentPhoneSms(ctx context.Context, userID string) error
|
|
SendNewPhoneSms(ctx context.Context, req *models.CuUserSendNewPhoneSmsRequest) error
|
|
VerifyRegisterSmsCode(ctx context.Context, phone, phoneCountryCode, code string) (bool, error)
|
|
VerifyPhoneCode(ctx context.Context, phone, phoneCountryCode, code, templateType string) (bool, error)
|
|
ForgotPassword(ctx context.Context, req *models.CuUserForgotPasswordRequest) error
|
|
ResetPassword(ctx context.Context, req *models.CuUserResetPasswordRequest) error
|
|
GetUsersByCustomer(ctx context.Context, customerID string, offset, limit int) ([]*models.CuUser, int64, error)
|
|
LockAccount(ctx context.Context, userID string, duration time.Duration) error
|
|
UnlockAccount(ctx context.Context, userID string) error
|
|
}
|
|
|
|
type cuUserService struct {
|
|
repo repository.CuUserRepository
|
|
customerRepo repository.CustomerRepository
|
|
smsService utils.SMSService
|
|
db *gorm.DB
|
|
}
|
|
|
|
func NewCuUserService(repo repository.CuUserRepository, customerRepo repository.CustomerRepository, smsService utils.SMSService, db *gorm.DB) CuUserService {
|
|
return &cuUserService{
|
|
repo: repo,
|
|
customerRepo: customerRepo,
|
|
smsService: smsService,
|
|
db: db,
|
|
}
|
|
}
|
|
|
|
func (s *cuUserService) Register(ctx context.Context, req *models.CuUserRegisterRequest) (*models.CuUser, error) {
|
|
lang := pkgcontext.GetLanguageFromContext(ctx)
|
|
|
|
// 业务逻辑:参数验证
|
|
if req == nil {
|
|
return nil, i18n.NewI18nError("900001", lang)
|
|
}
|
|
|
|
// 设置默认值
|
|
phoneCountryCode := req.PhoneCountryCode
|
|
if phoneCountryCode == "" {
|
|
phoneCountryCode = "+86" // 默认中国
|
|
}
|
|
|
|
// 检查手机号是否已存在
|
|
exists, err := s.repo.CheckPhoneExists(req.Phone, phoneCountryCode, "")
|
|
if err != nil {
|
|
return nil, i18n.NewI18nError("900004", lang, err.Error())
|
|
}
|
|
if exists {
|
|
return nil, i18n.NewI18nError("500002", lang) // 手机号已被注册
|
|
}
|
|
|
|
// 检查邮箱是否已存在(如果提供)
|
|
if req.Email != nil {
|
|
exists, err := s.repo.CheckEmailExists(*req.Email, "")
|
|
if err != nil {
|
|
return nil, i18n.NewI18nError("900004", lang, err.Error())
|
|
}
|
|
if exists {
|
|
return nil, i18n.NewI18nError("500002", lang) // 邮箱已被注册
|
|
}
|
|
}
|
|
|
|
// 处理客户ID:如果未提供,则自动创建客户
|
|
customerID := req.CustomerID
|
|
if customerID == "" {
|
|
// 创建新客户
|
|
customer, err := s.createDefaultCustomer(ctx, req.Phone, phoneCountryCode)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
customerID = customer.ID
|
|
}
|
|
|
|
// 生成密码哈希
|
|
var passwordHash, salt string
|
|
if req.Password != "" {
|
|
salt = utils.GenerateSalt()
|
|
passwordHash, err = utils.HashPasswordWithSalt(req.Password, salt)
|
|
if err != nil {
|
|
return nil, i18n.NewI18nError("900004", lang, err.Error())
|
|
}
|
|
}
|
|
|
|
// 创建用户
|
|
user := &models.CuUser{
|
|
CustomerID: customerID,
|
|
Phone: req.Phone,
|
|
PhoneCountryCode: phoneCountryCode,
|
|
Password: &passwordHash,
|
|
Salt: &salt,
|
|
UserRole: "member", // 默认普通成员
|
|
RealName: req.RealName,
|
|
Email: req.Email,
|
|
Status: "active",
|
|
PhoneVerified: true, // 注册时已验证
|
|
EmailVerified: false,
|
|
Language: "zh-CN",
|
|
Timezone: "Asia/Shanghai",
|
|
}
|
|
|
|
// 委托给Repository层进行数据创建
|
|
if err := s.repo.Create(user); err != nil {
|
|
return nil, i18n.NewI18nError("900004", lang, err.Error())
|
|
}
|
|
|
|
return user, nil
|
|
}
|
|
|
|
func (s *cuUserService) Login(ctx context.Context, req *models.CuUserLoginRequest, ip string) (*models.CuUser, string, error) {
|
|
lang := pkgcontext.GetLanguageFromContext(ctx)
|
|
|
|
// 业务逻辑:参数验证
|
|
if req == nil {
|
|
return nil, "", i18n.NewI18nError("900001", lang)
|
|
}
|
|
|
|
// 设置默认国家代码
|
|
phoneCountryCode := req.PhoneCountryCode
|
|
if phoneCountryCode == "" {
|
|
phoneCountryCode = "+86" // 默认中国
|
|
}
|
|
|
|
// 根据手机号查找用户
|
|
user, err := s.repo.GetByPhone(req.Phone, phoneCountryCode)
|
|
if err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, "", i18n.NewI18nError("500003", lang) // 手机号或密码错误
|
|
}
|
|
return nil, "", i18n.NewI18nError("900004", lang, err.Error())
|
|
}
|
|
|
|
// 检查账号状态
|
|
if user.Status != "active" {
|
|
return nil, "", i18n.NewI18nError("500003", lang) // 账号已被禁用
|
|
}
|
|
|
|
// 根据登录类型进行不同的验证
|
|
if req.LoginType == "sms" {
|
|
// 短信验证码登录
|
|
if req.SmsCode == "" {
|
|
return nil, "", i18n.NewI18nError("900001", lang) // 短信验证码不能为空
|
|
}
|
|
|
|
// 验证短信验证码
|
|
valid, err := s.smsService.VerifyCode(ctx, req.Phone, phoneCountryCode, req.SmsCode)
|
|
if err != nil {
|
|
switch err {
|
|
case utils.ErrSMSCodeNotExist:
|
|
return nil, "", i18n.NewI18nError("500015", lang) // 验证码不存在
|
|
case utils.ErrSMSCodeIncorrect:
|
|
return nil, "", i18n.NewI18nError("500014", lang) // 验证码错误或已过期
|
|
default:
|
|
return nil, "", i18n.NewI18nError("900004", lang, err.Error())
|
|
}
|
|
}
|
|
if !valid {
|
|
return nil, "", i18n.NewI18nError("500014", lang) // 验证码错误或已过期
|
|
}
|
|
} else {
|
|
// 密码登录(默认行为)
|
|
// 检查是否被锁定
|
|
if user.IsAccountLocked() {
|
|
return nil, "", i18n.NewI18nError("500003", lang) // 账号已被锁定
|
|
}
|
|
|
|
// 验证密码
|
|
if req.Password == "" {
|
|
return nil, "", i18n.NewI18nError("900001", lang) // 密码不能为空
|
|
}
|
|
|
|
if user.Password == nil {
|
|
return nil, "", i18n.NewI18nError("500003", lang) // 请先设置密码
|
|
}
|
|
|
|
if !utils.CheckPassword(req.Password, *user.Password, *user.Salt) {
|
|
// 增加登录失败次数
|
|
if err := s.repo.IncrementLoginAttempts(user.ID); err != nil {
|
|
// 记录错误但不影响登录流程
|
|
}
|
|
|
|
// 检查是否需要锁定账号
|
|
cfg := config.GetConfig()
|
|
if cfg != nil && user.LoginAttempts+1 >= cfg.Auth.Security.MaxLoginAttempts {
|
|
lockUntil := time.Now().Add(time.Duration(cfg.Auth.Security.LockoutDurationMinutes) * time.Minute)
|
|
if err := s.repo.LockAccount(user.ID, lockUntil); err != nil {
|
|
// 记录错误但不影响登录流程
|
|
}
|
|
return nil, "", i18n.NewI18nError("500003", lang) // 账号已被锁定
|
|
}
|
|
|
|
return nil, "", i18n.NewI18nError("500003", lang) // 手机号或密码错误
|
|
}
|
|
}
|
|
|
|
// 登录成功,重置登录失败次数并更新登录信息
|
|
// 注意:短信登录也需要重置失败次数,避免被锁定
|
|
if err := s.repo.ResetLoginAttempts(user.ID); err != nil {
|
|
// 记录错误但不影响登录流程
|
|
}
|
|
if err := s.repo.UpdateLoginInfo(user.ID, ip); err != nil {
|
|
// 记录错误但不影响登录流程
|
|
}
|
|
|
|
// 生成JWT Token
|
|
token, err := utils.GenerateCuToken(user.ID, user.CustomerID, user.UserRole, user.Phone)
|
|
if err != nil {
|
|
return nil, "", i18n.NewI18nError("500013", lang, err.Error())
|
|
}
|
|
|
|
return user, token, nil
|
|
}
|
|
|
|
func (s *cuUserService) GetProfile(ctx context.Context, userID string) (*models.CuUser, error) {
|
|
lang := pkgcontext.GetLanguageFromContext(ctx)
|
|
|
|
// 业务逻辑:参数验证
|
|
if userID == "" {
|
|
return nil, i18n.NewI18nError("900001", lang)
|
|
}
|
|
|
|
user, err := s.repo.GetByID(userID)
|
|
if err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, i18n.NewI18nError("500009", lang) // 用户不存在
|
|
}
|
|
return nil, i18n.NewI18nError("900004", lang, err.Error())
|
|
}
|
|
|
|
return user, nil
|
|
}
|
|
|
|
func (s *cuUserService) UpdateProfile(ctx context.Context, userID string, req *models.CuUserProfileUpdateRequest) error {
|
|
lang := pkgcontext.GetLanguageFromContext(ctx)
|
|
|
|
// 业务逻辑:参数验证
|
|
if userID == "" || req == nil {
|
|
return i18n.NewI18nError("900001", lang)
|
|
}
|
|
|
|
user, err := s.repo.GetByID(userID)
|
|
if err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return i18n.NewI18nError("500009", lang) // 用户不存在
|
|
}
|
|
return i18n.NewI18nError("900004", lang, err.Error())
|
|
}
|
|
|
|
// 检查邮箱是否已被其他用户使用
|
|
if req.Email != nil && *req.Email != "" {
|
|
exists, err := s.repo.CheckEmailExists(*req.Email, userID)
|
|
if err != nil {
|
|
return i18n.NewI18nError("900004", lang, err.Error())
|
|
}
|
|
if exists {
|
|
return i18n.NewI18nError("500006", lang) // 邮箱已被其他用户使用
|
|
}
|
|
user.Email = req.Email
|
|
user.EmailVerified = false // 修改邮箱后需要重新验证
|
|
}
|
|
|
|
// 更新其他字段
|
|
if req.RealName != nil {
|
|
user.RealName = req.RealName
|
|
}
|
|
if req.AvatarURL != nil {
|
|
user.AvatarURL = req.AvatarURL
|
|
}
|
|
if req.Language != nil {
|
|
user.Language = *req.Language
|
|
}
|
|
if req.Timezone != nil {
|
|
user.Timezone = *req.Timezone
|
|
}
|
|
if req.AdditionalInfo != nil {
|
|
user.AdditionalInfo = req.AdditionalInfo
|
|
}
|
|
if req.Remark != nil {
|
|
user.Remark = req.Remark
|
|
}
|
|
|
|
if err := s.repo.Update(user); err != nil {
|
|
return i18n.NewI18nError("900004", lang, err.Error())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *cuUserService) UpdatePhone(ctx context.Context, userID string, req *models.CuUserPhoneUpdateRequest) error {
|
|
lang := pkgcontext.GetLanguageFromContext(ctx)
|
|
|
|
// 业务逻辑:参数验证
|
|
if userID == "" || req == nil {
|
|
return i18n.NewI18nError("900001", lang)
|
|
}
|
|
|
|
// 检查新手机号是否已被使用
|
|
exists, err := s.repo.CheckPhoneExists(req.NewPhone, req.NewPhoneCountryCode, userID)
|
|
if err != nil {
|
|
return i18n.NewI18nError("900004", lang, err.Error())
|
|
}
|
|
if exists {
|
|
return i18n.NewI18nError("500007", lang) // 新手机号已被其他用户使用
|
|
}
|
|
|
|
// 验证当前手机号和新手机号的验证码
|
|
|
|
// 更新手机号
|
|
user, err := s.repo.GetByID(userID)
|
|
if err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return i18n.NewI18nError("500009", lang) // 用户不存在
|
|
}
|
|
return i18n.NewI18nError("900004", lang, err.Error())
|
|
}
|
|
|
|
user.Phone = req.NewPhone
|
|
user.PhoneCountryCode = req.NewPhoneCountryCode
|
|
user.PhoneVerified = true // 手机号已验证
|
|
|
|
if err := s.repo.Update(user); err != nil {
|
|
return i18n.NewI18nError("900004", lang, err.Error())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *cuUserService) ChangePassword(ctx context.Context, userID, oldPassword, newPassword string) error {
|
|
lang := pkgcontext.GetLanguageFromContext(ctx)
|
|
|
|
// 业务逻辑:参数验证
|
|
if userID == "" || oldPassword == "" || newPassword == "" {
|
|
return i18n.NewI18nError("900001", lang)
|
|
}
|
|
|
|
user, err := s.repo.GetByID(userID)
|
|
if err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return i18n.NewI18nError("500009", lang) // 用户不存在
|
|
}
|
|
return i18n.NewI18nError("900004", lang, err.Error())
|
|
}
|
|
|
|
// 验证旧密码
|
|
if user.Password == nil {
|
|
return i18n.NewI18nError("500008", lang) // 请先设置密码
|
|
}
|
|
|
|
if !utils.CheckPassword(oldPassword, *user.Password, *user.Salt) {
|
|
return i18n.NewI18nError("500008", lang) // 旧密码错误
|
|
}
|
|
|
|
// 生成新密码哈希
|
|
salt := utils.GenerateSalt()
|
|
passwordHash, err := utils.HashPasswordWithSalt(newPassword, salt)
|
|
if err != nil {
|
|
return i18n.NewI18nError("900004", lang, err.Error())
|
|
}
|
|
|
|
user.Password = &passwordHash
|
|
user.Salt = &salt
|
|
|
|
if err := s.repo.Update(user); err != nil {
|
|
return i18n.NewI18nError("900004", lang, err.Error())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// sendSmsCode 通用短信验证码发送逻辑
|
|
func (s *cuUserService) sendSmsCode(ctx context.Context, phone, phoneCountryCode, templateType string, checkUserExists bool) error {
|
|
lang := pkgcontext.GetLanguageFromContext(ctx)
|
|
|
|
// 设置默认国家代码
|
|
if phoneCountryCode == "" {
|
|
phoneCountryCode = "+86"
|
|
}
|
|
|
|
// 检查用户状态
|
|
exists, err := s.repo.CheckPhoneExists(phone, phoneCountryCode, "")
|
|
if err != nil {
|
|
return i18n.NewI18nError("900004", lang, err.Error())
|
|
}
|
|
|
|
if checkUserExists && !exists {
|
|
// 忘记密码:用户必须存在
|
|
return i18n.NewI18nError("500009", lang) // 用户不存在
|
|
} else if !checkUserExists && exists {
|
|
// 注册:用户不能存在
|
|
return i18n.NewI18nError("200002", lang) // 客户已存在
|
|
}
|
|
|
|
// 如果用户存在,检查账号状态(仅对忘记密码场景)
|
|
if checkUserExists && exists {
|
|
user, err := s.repo.GetByPhone(phone, phoneCountryCode)
|
|
if err != nil {
|
|
return i18n.NewI18nError("900004", lang, err.Error())
|
|
}
|
|
if user.Status != "active" {
|
|
return i18n.NewI18nError("500004", lang) // 账号已被禁用
|
|
}
|
|
}
|
|
|
|
// 发送验证码
|
|
var sendErr error
|
|
switch templateType {
|
|
case "register":
|
|
sendErr = s.smsService.SendRegisterCode(ctx, phone, phoneCountryCode)
|
|
case "reset_pwd":
|
|
sendErr = s.smsService.SendResetPwdCode(ctx, phone, phoneCountryCode)
|
|
case "login":
|
|
sendErr = s.smsService.SendLoginCode(ctx, phone, phoneCountryCode)
|
|
default:
|
|
return i18n.NewI18nError("900001", lang, "无效的模板类型")
|
|
}
|
|
|
|
if sendErr != nil {
|
|
switch sendErr {
|
|
case utils.ErrSMSRateLimited:
|
|
return i18n.NewI18nError("500016", lang) // 短信发送过于频繁,请稍后再试
|
|
default:
|
|
// 对于短信发送失败,结合本地化消息和具体错误信息,便于调试
|
|
return i18n.NewI18nError("500017", lang, sendErr.Error())
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *cuUserService) SendRegisterSms(ctx context.Context, req *models.CuUserSendRegisterSmsRequest) error {
|
|
lang := pkgcontext.GetLanguageFromContext(ctx)
|
|
|
|
// 参数验证
|
|
if req == nil {
|
|
return i18n.NewI18nError("900001", lang)
|
|
}
|
|
|
|
return s.sendSmsCode(ctx, req.Phone, req.PhoneCountryCode, "register", false)
|
|
}
|
|
|
|
// SendLoginSms 发送登录验证码
|
|
func (s *cuUserService) SendLoginSms(ctx context.Context, req *models.CuUserSendLoginSmsRequest) error {
|
|
return s.sendSmsCode(ctx, req.Phone, req.PhoneCountryCode, "login", true)
|
|
}
|
|
|
|
func (s *cuUserService) SendCurrentPhoneSms(ctx context.Context, userID string) error {
|
|
lang := pkgcontext.GetLanguageFromContext(ctx)
|
|
|
|
// 参数验证
|
|
if userID == "" {
|
|
return i18n.NewI18nError("900001", lang)
|
|
}
|
|
|
|
// 获取用户信息
|
|
user, err := s.repo.GetByID(userID)
|
|
if err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return i18n.NewI18nError("500009", lang) // 用户不存在
|
|
}
|
|
return i18n.NewI18nError("900004", lang, err.Error())
|
|
}
|
|
|
|
// 检查账号状态
|
|
if user.Status != "active" {
|
|
return i18n.NewI18nError("500004", lang) // 账号已被禁用
|
|
}
|
|
|
|
// 发送当前手机号验证码
|
|
err = s.smsService.SendCurrentPhoneCode(ctx, user.Phone, user.PhoneCountryCode)
|
|
if err != nil {
|
|
switch err {
|
|
case utils.ErrSMSRateLimited:
|
|
return i18n.NewI18nError("500016", lang) // 短信发送过于频繁,请稍后再试
|
|
default:
|
|
// 对于短信发送失败,结合本地化消息和具体错误信息,便于调试
|
|
return i18n.NewI18nError("500017", lang, err.Error())
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *cuUserService) SendNewPhoneSms(ctx context.Context, req *models.CuUserSendNewPhoneSmsRequest) error {
|
|
lang := pkgcontext.GetLanguageFromContext(ctx)
|
|
|
|
// 参数验证
|
|
if req == nil {
|
|
return i18n.NewI18nError("900001", lang)
|
|
}
|
|
|
|
// 设置默认国家代码
|
|
phoneCountryCode := req.NewPhoneCountryCode
|
|
if phoneCountryCode == "" {
|
|
phoneCountryCode = "+86"
|
|
}
|
|
|
|
// 检查新手机号是否已被注册
|
|
exists, err := s.repo.CheckPhoneExists(req.NewPhone, phoneCountryCode, "")
|
|
if err != nil {
|
|
return i18n.NewI18nError("900004", lang, err.Error())
|
|
}
|
|
if exists {
|
|
return i18n.NewI18nError("200002", lang) // 客户已存在
|
|
}
|
|
|
|
// 发送新手机号验证码
|
|
err = s.smsService.SendNewPhoneCode(ctx, req.NewPhone, phoneCountryCode)
|
|
if err != nil {
|
|
switch err {
|
|
case utils.ErrSMSRateLimited:
|
|
return i18n.NewI18nError("500016", lang) // 短信发送过于频繁,请稍后再试
|
|
default:
|
|
// 对于短信发送失败,结合本地化消息和具体错误信息,便于调试
|
|
return i18n.NewI18nError("500017", lang, err.Error())
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *cuUserService) VerifyRegisterSmsCode(ctx context.Context, phone, phoneCountryCode, code string) (bool, error) {
|
|
valid, err := s.smsService.VerifyCode(ctx, phone, phoneCountryCode, code)
|
|
if err != nil {
|
|
lang := pkgcontext.GetLanguageFromContext(ctx)
|
|
switch err {
|
|
case utils.ErrSMSCodeNotExist:
|
|
return false, i18n.NewI18nError("500015", lang) // 验证码不存在
|
|
case utils.ErrSMSCodeIncorrect:
|
|
return false, i18n.NewI18nError("500014", lang) // 验证码错误或已过期
|
|
default:
|
|
return false, i18n.NewI18nError("900004", lang, err.Error())
|
|
}
|
|
}
|
|
return valid, nil
|
|
}
|
|
|
|
func (s *cuUserService) VerifyPhoneCode(ctx context.Context, phone, phoneCountryCode, code, templateType string) (bool, error) {
|
|
valid, err := s.smsService.VerifyCode(ctx, phone, phoneCountryCode, code)
|
|
if err != nil {
|
|
lang := pkgcontext.GetLanguageFromContext(ctx)
|
|
switch err {
|
|
case utils.ErrSMSCodeNotExist:
|
|
return false, i18n.NewI18nError("500015", lang) // 验证码不存在
|
|
case utils.ErrSMSCodeIncorrect:
|
|
return false, i18n.NewI18nError("500014", lang) // 验证码错误或已过期
|
|
default:
|
|
return false, i18n.NewI18nError("900004", lang, err.Error())
|
|
}
|
|
}
|
|
return valid, nil
|
|
}
|
|
|
|
func (s *cuUserService) ForgotPassword(ctx context.Context, req *models.CuUserForgotPasswordRequest) error {
|
|
lang := pkgcontext.GetLanguageFromContext(ctx)
|
|
|
|
// 参数验证
|
|
if req == nil {
|
|
return i18n.NewI18nError("900001", lang)
|
|
}
|
|
|
|
return s.sendSmsCode(ctx, req.Phone, req.PhoneCountryCode, "reset_pwd", true)
|
|
}
|
|
|
|
func (s *cuUserService) ResetPassword(ctx context.Context, req *models.CuUserResetPasswordRequest) error {
|
|
lang := pkgcontext.GetLanguageFromContext(ctx)
|
|
|
|
// 业务逻辑:参数验证
|
|
if req == nil {
|
|
return i18n.NewI18nError("900001", lang)
|
|
}
|
|
|
|
// 设置默认国家代码
|
|
phoneCountryCode := req.PhoneCountryCode
|
|
if phoneCountryCode == "" {
|
|
phoneCountryCode = "+86" // 默认中国
|
|
}
|
|
|
|
// 查找用户
|
|
user, err := s.repo.GetByPhone(req.Phone, phoneCountryCode)
|
|
if err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return i18n.NewI18nError("500009", lang) // 用户不存在
|
|
}
|
|
return i18n.NewI18nError("900004", lang, err.Error())
|
|
}
|
|
|
|
// 检查账号状态
|
|
if user.Status != "active" {
|
|
return i18n.NewI18nError("500005", lang) // 账号已被禁用
|
|
}
|
|
|
|
// 验证短信验证码
|
|
valid, err := s.smsService.VerifyCode(ctx, req.Phone, phoneCountryCode, req.SmsCode)
|
|
if err != nil {
|
|
switch err {
|
|
case utils.ErrSMSCodeNotExist:
|
|
return i18n.NewI18nError("500015", lang) // 验证码不存在
|
|
case utils.ErrSMSCodeIncorrect:
|
|
return i18n.NewI18nError("500014", lang) // 验证码错误或已过期
|
|
default:
|
|
return i18n.NewI18nError("900004", lang, err.Error())
|
|
}
|
|
}
|
|
if !valid {
|
|
return i18n.NewI18nError("500014", lang) // 验证码错误或已过期
|
|
}
|
|
|
|
// 生成新密码哈希
|
|
salt := utils.GenerateSalt()
|
|
passwordHash, err := utils.HashPasswordWithSalt(req.NewPassword, salt)
|
|
if err != nil {
|
|
return i18n.NewI18nError("900004", lang, err.Error())
|
|
}
|
|
|
|
user.Password = &passwordHash
|
|
user.Salt = &salt
|
|
user.LockedUntil = nil // 解锁账号
|
|
user.LoginAttempts = 0 // 重置登录失败次数
|
|
|
|
if err := s.repo.Update(user); err != nil {
|
|
return i18n.NewI18nError("900004", lang, err.Error())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *cuUserService) GetUsersByCustomer(ctx context.Context, customerID string, offset, limit int) ([]*models.CuUser, int64, error) {
|
|
lang := pkgcontext.GetLanguageFromContext(ctx)
|
|
|
|
// 业务逻辑:参数验证
|
|
if customerID == "" {
|
|
return nil, 0, i18n.NewI18nError("900001", lang)
|
|
}
|
|
|
|
users, total, err := s.repo.GetByCustomerID(customerID, offset, limit)
|
|
if err != nil {
|
|
return nil, 0, i18n.NewI18nError("900004", lang, err.Error())
|
|
}
|
|
|
|
return users, total, nil
|
|
}
|
|
|
|
func (s *cuUserService) LockAccount(ctx context.Context, userID string, duration time.Duration) error {
|
|
lang := pkgcontext.GetLanguageFromContext(ctx)
|
|
|
|
// 业务逻辑:参数验证
|
|
if userID == "" {
|
|
return i18n.NewI18nError("900001", lang)
|
|
}
|
|
|
|
lockUntil := time.Now().Add(duration)
|
|
if err := s.repo.LockAccount(userID, lockUntil); err != nil {
|
|
return i18n.NewI18nError("900004", lang, err.Error())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *cuUserService) UnlockAccount(ctx context.Context, userID string) error {
|
|
lang := pkgcontext.GetLanguageFromContext(ctx)
|
|
|
|
// 业务逻辑:参数验证
|
|
if userID == "" {
|
|
return i18n.NewI18nError("900001", lang)
|
|
}
|
|
|
|
if err := s.repo.LockAccount(userID, nil); err != nil {
|
|
return i18n.NewI18nError("900004", lang, err.Error())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// createDefaultCustomer 创建默认客户(用于自动注册场景)
|
|
func (s *cuUserService) createDefaultCustomer(ctx context.Context, phone, phoneCountryCode string) (*models.Customer, error) {
|
|
lang := pkgcontext.GetLanguageFromContext(ctx)
|
|
|
|
// 使用手机号作为客户名称
|
|
customerName := phoneCountryCode + phone
|
|
companySize := "small"
|
|
contactPhone := phoneCountryCode + phone
|
|
|
|
// 创建默认客户
|
|
customer := &models.Customer{
|
|
CustomerName: customerName, // 客户名称默认为手机号
|
|
CustomerType: "individual", // 客户类型默认为个人客户
|
|
ContactPerson: contactPhone, // 联系人默认为手机号
|
|
CustomerLevel: "basic", // 等级默认为基础客户
|
|
Status: "active", // 状态为激活
|
|
CompanySize: &companySize, // 公司规模为小型企业
|
|
CreatedBy: "system", // 系统自动创建
|
|
Phone: &contactPhone, // 设置联系电话
|
|
}
|
|
|
|
// 委托给Repository层进行数据创建
|
|
if err := s.customerRepo.CreateCustomer(ctx, customer); err != nil {
|
|
return nil, i18n.NewI18nError("900004", lang, err.Error())
|
|
}
|
|
|
|
return customer, nil
|
|
}
|