package services import ( "errors" "fmt" "math/rand" "strings" "sync" "time" "server/models" "server/pkg/passwordutil" ) // resetCodeItem 存储找回密码的验证码 type resetCodeItem struct { Code string Channel string ExpiredAt time.Time } var resetCodeStore sync.Map // resetCodeKey 生成密码重置验证码的存储key func resetCodeKey(tenantName, account, phone, channel string) string { key := strings.ToLower(strings.TrimSpace(tenantName)) + "|" + strings.ToLower(strings.TrimSpace(account)) + "|" + strings.ToLower(strings.TrimSpace(phone)) + "|" + strings.TrimSpace(channel) return key } // VerifyTenantAccount 第一步:验证租户和账号是否存在 // 返回该账号关联的手机号和邮箱(用于第二步选择验证方式) func VerifyTenantAccount(tenantName, account string) (phone, email string, err error) { tenantName = strings.TrimSpace(tenantName) account = strings.TrimSpace(account) if tenantName == "" || account == "" { return "", "", errors.New("租户名称和账号不能为空") } // 验证租户是否存在 var tenant models.SystemTenant if err := models.Orm.QueryTable(new(models.SystemTenant)). Filter("tenant_name", tenantName). One(&tenant); err != nil { return "", "", errors.New("租户不存在") } if tenant.Status != 1 { return "", "", errors.New("租户已停用") } // 验证该租户下的账号是否存在 var tenantUser models.SystemTenantUser if err := models.Orm.QueryTable(new(models.SystemTenantUser)). Filter("tid", tenant.ID). Filter("account", account). One(&tenantUser); err != nil { return "", "", errors.New("账号不存在") } if tenantUser.Status == 0 { return "", "", errors.New("账号已禁用") } // 返回该账号的手机号和邮箱 phoneStr := "" if tenantUser.Phone != nil { phoneStr = strings.TrimSpace(*tenantUser.Phone) } emailStr := "" if tenantUser.Email != nil { emailStr = strings.TrimSpace(*tenantUser.Email) } if phoneStr == "" && emailStr == "" { return "", "", errors.New("账号未绑定手机号或邮箱,无法重置密码") } return phoneStr, emailStr, nil } // SendResetCode 第二步:验证手机号并发送验证码 func SendResetCode(tenantName, account, phone, channel string) error { tenantName = strings.TrimSpace(tenantName) account = strings.TrimSpace(account) phone = strings.TrimSpace(phone) channel = strings.TrimSpace(channel) if tenantName == "" || account == "" || phone == "" { return errors.New("租户名称、账号和手机号不能为空") } if channel != "sms" && channel != "email" { return errors.New("仅支持短信或邮箱验证码") } // 验证租户是否存在 var tenant models.SystemTenant if err := models.Orm.QueryTable(new(models.SystemTenant)). Filter("tenant_name", tenantName). One(&tenant); err != nil { return errors.New("租户不存在") } // 验证该租户下的账号和手机号是否匹配 var tenantUser models.SystemTenantUser if err := models.Orm.QueryTable(new(models.SystemTenantUser)). Filter("tid", tenant.ID). Filter("account", account). One(&tenantUser); err != nil { return errors.New("账号不存在") } if tenantUser.Status == 0 { return errors.New("账号已禁用") } // 根据验证渠道验证用户信息 if channel == "sms" { if tenantUser.Phone == nil || strings.TrimSpace(*tenantUser.Phone) != phone { return errors.New("手机号不匹配,请确认您输入的手机号正确") } } else if channel == "email" { if tenantUser.Email == nil || strings.TrimSpace(*tenantUser.Email) != phone { return errors.New("邮箱不匹配,请确认您输入的邮箱正确") } } // 生成验证码 rand.Seed(time.Now().UnixNano()) code := fmt.Sprintf("%06d", rand.Intn(1000000)) // 发送验证码 if channel == "sms" { content := "密码重置验证码:" + code if err := enqueueSMSTaskForPasswordReset(tenant.ID, phone, content, code); err != nil { return errors.New("短信发送失败,请重试") } } // TODO: 实现邮箱验证码发送逻辑 // 存储验证码(5分钟有效期) resetCodeStore.Store(resetCodeKey(tenantName, account, phone, channel), resetCodeItem{ Code: code, Channel: channel, ExpiredAt: time.Now().Add(5 * time.Minute), }) return nil } // VerifyResetCode 验证重置密码的验证码 func VerifyResetCode(tenantName, account, phone, channel, code string) error { tenantName = strings.TrimSpace(tenantName) account = strings.TrimSpace(account) phone = strings.TrimSpace(phone) channel = strings.TrimSpace(channel) code = strings.TrimSpace(code) if code == "" { return errors.New("验证码不能为空") } key := resetCodeKey(tenantName, account, phone, channel) val, ok := resetCodeStore.Load(key) if !ok { return errors.New("验证码不存在或已失效") } item, ok := val.(resetCodeItem) if !ok { return errors.New("验证码状态异常") } if time.Now().After(item.ExpiredAt) { resetCodeStore.Delete(key) return errors.New("验证码已过期") } if item.Code != code { return errors.New("验证码错误") } // 验证通过后删除验证码 resetCodeStore.Delete(key) return nil } // ResetPassword 第三步:重置密码 func ResetPassword(tenantName, account, phone, smsCode, newPassword, confirmPassword string) error { tenantName = strings.TrimSpace(tenantName) account = strings.TrimSpace(account) phone = strings.TrimSpace(phone) smsCode = strings.TrimSpace(smsCode) newPassword = strings.TrimSpace(newPassword) confirmPassword = strings.TrimSpace(confirmPassword) if tenantName == "" || account == "" || phone == "" { return errors.New("租户名称、账号和手机号不能为空") } if newPassword == "" { return errors.New("新密码不能为空") } if newPassword != confirmPassword { return errors.New("两次密码不一致") } if len(newPassword) < 6 { return errors.New("密码长度不能少于6个字符") } // 验证验证码(验证码验证后会被删除) if err := VerifyResetCode(tenantName, account, phone, "sms", smsCode); err != nil { return err } // 验证租户 var tenant models.SystemTenant if err := models.Orm.QueryTable(new(models.SystemTenant)). Filter("tenant_name", tenantName). One(&tenant); err != nil { return errors.New("租户不存在") } // 查询用户 var tenantUser models.SystemTenantUser if err := models.Orm.QueryTable(new(models.SystemTenantUser)). Filter("tid", tenant.ID). Filter("account", account). One(&tenantUser); err != nil { return errors.New("账号不存在") } // 验证手机号 if tenantUser.Phone == nil || strings.TrimSpace(*tenantUser.Phone) != phone { return errors.New("手机号不匹配") } // 哈希新密码 hashedPassword, err := passwordutil.Hash(newPassword) if err != nil { return errors.New("密码处理失败") } // 更新密码 _, err = models.Orm.QueryTable(new(models.SystemTenantUser)). Filter("id", tenantUser.ID). Update(map[string]interface{}{ "password": hashedPassword, }) if err != nil { return errors.New("密码更新失败") } return nil } // enqueueSMSTaskForPasswordReset 发送密码重置短信任务 func enqueueSMSTaskForPasswordReset(tid uint64, phone, content, code string) error { // 重用已有的短信发送逻辑 return enqueueSMSTaskForLogin(tid, phone, content, code) }