261 lines
7.2 KiB
Go
261 lines
7.2 KiB
Go
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)
|
|
}
|