diff --git a/go/conf/app.conf b/go/conf/app.conf
index 29f9f07..c60a5df 100644
--- a/go/conf/app.conf
+++ b/go/conf/app.conf
@@ -18,7 +18,7 @@ maxmemory = 10485760
# MySQL - 远程连接配置
mysqluser = go-platform
mysqlpass = FSmJCSJ5wk8pjjDC
-mysqlurls = 212.64.112.158:3388
+mysqlurls = 10.31.100.3:3306
mysqldb = go-platform
# ORM配置
diff --git a/go/controllers/app_auth.go b/go/controllers/app_auth.go
new file mode 100644
index 0000000..1ca2923
--- /dev/null
+++ b/go/controllers/app_auth.go
@@ -0,0 +1,381 @@
+package controllers
+
+import (
+ "encoding/json"
+ "io"
+ "strings"
+
+ "server/models"
+ "server/pkg/jwtutil"
+ "server/services"
+
+ beego "github.com/beego/beego/v2/server/web"
+)
+
+// AppAuthController App移动端认证控制器
+type AppAuthController struct {
+ beego.Controller
+}
+
+func (c *AppAuthController) serveJSON(data map[string]interface{}) {
+ c.Data["json"] = data
+ _ = c.ServeJSON()
+}
+
+// LoginBackend App端登录(需要租户)
+func (c *AppAuthController) LoginBackend() {
+ var req struct {
+ TenantName string `json:"tenant_name"`
+ Account string `json:"account"`
+ Password string `json:"password"`
+ Code string `json:"code"`
+ }
+
+ body := c.Ctx.Input.RequestBody
+ if len(body) == 0 {
+ var err error
+ body, err = io.ReadAll(c.Ctx.Request.Body)
+ if err != nil {
+ c.serveJSON(map[string]interface{}{"code": 400, "msg": "参数错误"})
+ return
+ }
+ }
+ if len(body) == 0 {
+ c.serveJSON(map[string]interface{}{"code": 400, "msg": "参数错误"})
+ return
+ }
+ if err := json.Unmarshal(body, &req); err != nil {
+ c.serveJSON(map[string]interface{}{"code": 400, "msg": "参数错误"})
+ return
+ }
+
+ req.TenantName = strings.TrimSpace(req.TenantName)
+ req.Account = strings.TrimSpace(req.Account)
+ req.Password = strings.TrimSpace(req.Password)
+ if req.TenantName == "" || req.Account == "" || req.Password == "" {
+ c.serveJSON(map[string]interface{}{"code": 400, "msg": "租户名称、用户名或密码不能为空"})
+ return
+ }
+
+ token, loginUser, err := services.BackendLogin(req.TenantName, req.Account, req.Password)
+ if err != nil {
+ c.serveJSON(map[string]interface{}{"code": 401, "msg": err.Error()})
+ return
+ }
+
+ c.serveJSON(map[string]interface{}{
+ "code": 200,
+ "msg": "登录成功",
+ "data": map[string]interface{}{
+ "token": token,
+ "user": map[string]interface{}{
+ "id": loginUser.ID,
+ "account": loginUser.Account,
+ "name": loginUser.Name,
+ "tid": loginUser.Tid,
+ "rid": loginUser.Rid,
+ "avatar": loginUser.Avatar,
+ "role_name": loginUser.RoleName,
+ },
+ },
+ })
+}
+
+// GetCurrentUser App端当前登录用户信息,需 Bearer Token
+func (c *AppAuthController) GetCurrentUser() {
+ authHeader := c.Ctx.Request.Header.Get("Authorization")
+ if authHeader == "" {
+ c.serveJSON(map[string]interface{}{"code": 401, "msg": "未登录"})
+ return
+ }
+ authParts := strings.SplitN(authHeader, " ", 2)
+ if len(authParts) != 2 || authParts[0] != "Bearer" {
+ c.serveJSON(map[string]interface{}{"code": 401, "msg": "认证信息格式错误"})
+ return
+ }
+ claims, err := jwtutil.ParseToken(authParts[1])
+ if err != nil {
+ c.serveJSON(map[string]interface{}{"code": 401, "msg": "无效的token"})
+ return
+ }
+ if claims.UserType != "backend" && claims.UserType != "app" {
+ c.serveJSON(map[string]interface{}{"code": 403, "msg": "无权访问"})
+ return
+ }
+
+ var tenantUser models.SystemTenantUser
+ err = models.Orm.QueryTable(new(models.SystemTenantUser)).
+ Filter("uid", claims.UserID).
+ Filter("tid", claims.TenantId).
+ One(&tenantUser)
+ if err != nil {
+ c.serveJSON(map[string]interface{}{"code": 401, "msg": "用户不存在"})
+ return
+ }
+ if tenantUser.Status == 0 {
+ c.serveJSON(map[string]interface{}{"code": 401, "msg": "账号已禁用"})
+ return
+ }
+
+ account := ""
+ if tenantUser.Account != nil {
+ account = strings.TrimSpace(*tenantUser.Account)
+ }
+ name := ""
+ if tenantUser.Name != nil {
+ name = strings.TrimSpace(*tenantUser.Name)
+ }
+
+ c.serveJSON(map[string]interface{}{
+ "code": 200,
+ "msg": "success",
+ "data": map[string]interface{}{
+ "id": tenantUser.Uid,
+ "account": account,
+ "name": name,
+ "tid": tenantUser.Tid,
+ "rid": 0,
+ "avatar": "",
+ "role_name": "",
+ },
+ })
+}
+
+// SendLoginCode 发送App端登录验证码
+func (c *AppAuthController) SendLoginCode() {
+ var req struct {
+ Account string `json:"account"`
+ TenantName string `json:"tenant_name"`
+ Channel string `json:"channel"`
+ }
+ body := c.Ctx.Input.RequestBody
+ if len(body) == 0 {
+ var err error
+ body, err = io.ReadAll(c.Ctx.Request.Body)
+ if err != nil {
+ c.serveJSON(map[string]interface{}{"code": 400, "msg": "参数错误"})
+ return
+ }
+ }
+ if err := json.Unmarshal(body, &req); err != nil {
+ c.serveJSON(map[string]interface{}{"code": 400, "msg": "参数错误"})
+ return
+ }
+
+ cfg, _ := models.GetPlatformLoginVerify()
+ if cfg.OpenVerifyEnabled != 1 {
+ c.serveJSON(map[string]interface{}{"code": 400, "msg": "当前未开启验证"})
+ return
+ }
+ channel := strings.TrimSpace(req.Channel)
+ if channel == "" {
+ channel = cfg.VerifyType
+ }
+ if channel != "sms" && channel != "email" {
+ c.serveJSON(map[string]interface{}{"code": 400, "msg": "仅支持短信/邮箱验证码"})
+ return
+ }
+ if err := services.SendBackendLoginCode(req.TenantName, req.Account, channel); err != nil {
+ c.serveJSON(map[string]interface{}{"code": 400, "msg": err.Error()})
+ return
+ }
+ c.serveJSON(map[string]interface{}{"code": 200, "msg": "验证码已发送"})
+}
+
+// LoginBySms App端手机号验证码登录(占位实现)
+func (c *AppAuthController) LoginBySms() {
+ c.serveJSON(map[string]interface{}{
+ "code": 501,
+ "msg": "手机号验证码登录暂未实现",
+ })
+}
+
+// Logout App端退出登录(当前为无状态直接返回成功)
+func (c *AppAuthController) Logout() {
+ c.serveJSON(map[string]interface{}{
+ "code": 200,
+ "msg": "退出成功",
+ })
+}
+
+// GetGeetest3Infos 获取App端极验3.0配置
+func (c *AppAuthController) GetGeetest3Infos() {
+ cfg, _ := models.GetPlatformLoginVerify()
+ if cfg.Geetest3ID == nil || cfg.Geetest3Key == nil {
+ c.serveJSON(map[string]interface{}{"code": 404, "msg": "未配置极验3参数"})
+ return
+ }
+ c.serveJSON(map[string]interface{}{
+ "code": 200,
+ "msg": "success",
+ "data": map[string]interface{}{
+ "captcha_id": *cfg.Geetest3ID,
+ "captcha_key": *cfg.Geetest3Key,
+ },
+ })
+}
+
+// GetGeetest4Infos 获取App端极验4.0配置
+func (c *AppAuthController) GetGeetest4Infos() {
+ cfg, _ := models.GetPlatformLoginVerify()
+ if cfg.Geetest4ID == nil || cfg.Geetest4Key == nil {
+ c.serveJSON(map[string]interface{}{"code": 404, "msg": "未配置极验4参数"})
+ return
+ }
+ c.serveJSON(map[string]interface{}{
+ "code": 200,
+ "msg": "success",
+ "data": map[string]interface{}{
+ "captcha_id": *cfg.Geetest4ID,
+ "captcha_key": *cfg.Geetest4Key,
+ },
+ })
+}
+
+// GetOpenVerify 判断是否开启App端登录验证
+func (c *AppAuthController) GetOpenVerify() {
+ cfg, _ := models.GetPlatformLoginVerify()
+ openVerify := "0"
+ if cfg.OpenVerifyEnabled == 1 {
+ openVerify = "1"
+ }
+ c.serveJSON(map[string]interface{}{
+ "code": 200,
+ "msg": "ok",
+ "data": []map[string]string{
+ {
+ "label": "openVerify",
+ "value": openVerify,
+ },
+ {
+ "label": "verifyType",
+ "value": cfg.VerifyType,
+ },
+ },
+ })
+}
+
+// VerifyAccount 验证租户和账号是否存在(忘记密码第一步)
+func (c *AppAuthController) VerifyAccount() {
+ var req struct {
+ TenantName string `json:"tenant_name"`
+ Account string `json:"account"`
+ }
+ body := c.Ctx.Input.RequestBody
+ if len(body) == 0 {
+ var err error
+ body, err = io.ReadAll(c.Ctx.Request.Body)
+ if err != nil {
+ c.serveJSON(map[string]interface{}{"code": 400, "msg": "参数错误"})
+ return
+ }
+ }
+ if err := json.Unmarshal(body, &req); err != nil {
+ c.serveJSON(map[string]interface{}{"code": 400, "msg": "参数错误"})
+ return
+ }
+
+ phone, email, err := services.VerifyTenantAccount(req.TenantName, req.Account)
+ if err != nil {
+ c.serveJSON(map[string]interface{}{"code": 400, "msg": err.Error()})
+ return
+ }
+
+ c.serveJSON(map[string]interface{}{
+ "code": 200,
+ "msg": "验证成功",
+ "data": map[string]interface{}{
+ "phone": phone,
+ "email": email,
+ },
+ })
+}
+
+// SendResetCode 发送找回密码验证码(忘记密码第二步)
+func (c *AppAuthController) SendResetCode() {
+ var req struct {
+ TenantName string `json:"tenant_name"`
+ Account string `json:"account"`
+ Phone string `json:"phone"`
+ Channel string `json:"channel"`
+ }
+ body := c.Ctx.Input.RequestBody
+ if len(body) == 0 {
+ var err error
+ body, err = io.ReadAll(c.Ctx.Request.Body)
+ if err != nil {
+ c.serveJSON(map[string]interface{}{"code": 400, "msg": "参数错误"})
+ return
+ }
+ }
+ if err := json.Unmarshal(body, &req); err != nil {
+ c.serveJSON(map[string]interface{}{"code": 400, "msg": "参数错误"})
+ return
+ }
+
+ channel := strings.TrimSpace(req.Channel)
+ if channel == "" {
+ channel = "sms"
+ }
+
+ if err := services.SendResetCode(req.TenantName, req.Account, req.Phone, channel); err != nil {
+ c.serveJSON(map[string]interface{}{"code": 400, "msg": err.Error()})
+ return
+ }
+
+ c.serveJSON(map[string]interface{}{
+ "code": 200,
+ "msg": "验证码已发送",
+ })
+}
+
+// ResetPassword 重置密码(忘记密码第三步)
+func (c *AppAuthController) ResetPassword() {
+ var req struct {
+ TenantName string `json:"tenant_name"`
+ Account string `json:"account"`
+ Phone string `json:"phone"`
+ SmsCode string `json:"sms_code"`
+ NewPassword string `json:"new_password"`
+ ConfirmPassword string `json:"confirm_password"`
+ }
+ body := c.Ctx.Input.RequestBody
+ if len(body) == 0 {
+ var err error
+ body, err = io.ReadAll(c.Ctx.Request.Body)
+ if err != nil {
+ c.serveJSON(map[string]interface{}{"code": 400, "msg": "参数错误"})
+ return
+ }
+ }
+ if err := json.Unmarshal(body, &req); err != nil {
+ c.serveJSON(map[string]interface{}{"code": 400, "msg": "参数错误"})
+ return
+ }
+
+ if err := services.ResetPassword(req.TenantName, req.Account, req.Phone, req.SmsCode, req.NewPassword, req.ConfirmPassword); err != nil {
+ c.serveJSON(map[string]interface{}{"code": 400, "msg": err.Error()})
+ return
+ }
+
+ c.serveJSON(map[string]interface{}{
+ "code": 200,
+ "msg": "密码重置成功",
+ })
+}
+
+// Register App端注册(占位实现)
+func (c *AppAuthController) Register() {
+ c.serveJSON(map[string]interface{}{
+ "code": 501,
+ "msg": "注册暂未实现",
+ })
+}
+
+// SendRegisterCode 发送App端注册验证码(占位实现)
+func (c *AppAuthController) SendRegisterCode() {
+ c.serveJSON(map[string]interface{}{
+ "code": 501,
+ "msg": "发送注册验证码暂未实现",
+ })
+}
\ No newline at end of file
diff --git a/go/controllers/backend_auth.go b/go/controllers/backend_auth.go
index b09bf29..a33206d 100644
--- a/go/controllers/backend_auth.go
+++ b/go/controllers/backend_auth.go
@@ -67,7 +67,7 @@ func (c *BackendAuthController) LoginBackend() {
cfg, _ := models.GetPlatformLoginVerify()
if cfg.OpenVerifyEnabled == 1 {
- if cfg.VerifyType == "geetest4" {
+ if cfg.VerifyType == "geetest4" || cfg.VerifyType == "geetest" {
if req.LotNumber == "" || req.PassToken == "" || req.GenTime == "" || req.CaptchaOutput == "" {
c.serveJSON(map[string]interface{}{"code": 400, "msg": "请完成人机验证"})
return
@@ -319,4 +319,4 @@ func (c *BackendAuthController) SendResetCode() {
"code": 501,
"msg": "发送找回密码验证码暂未实现",
})
-}
+}
\ No newline at end of file
diff --git a/go/routers/app/app.go b/go/routers/app/app.go
index 208db7c..266098e 100644
--- a/go/routers/app/app.go
+++ b/go/routers/app/app.go
@@ -8,25 +8,26 @@ import (
// Register 注册移动端(app)路由。
func Register() {
- // 登录相关(复用 BackendAuthController)
- beego.Router("/app/login", &controllers.BackendAuthController{}, "post:LoginBackend")
- beego.Router("/app/sendLoginCode", &controllers.BackendAuthController{}, "post:SendLoginCode")
- beego.Router("/app/loginBySms", &controllers.BackendAuthController{}, "post:LoginBySms")
- beego.Router("/app/logout", &controllers.BackendAuthController{}, "post:Logout")
+ // 登录相关(使用 AppAuthController)
+ beego.Router("/app/login", &controllers.AppAuthController{}, "post:LoginBackend")
+ beego.Router("/app/sendLoginCode", &controllers.AppAuthController{}, "post:SendLoginCode")
+ beego.Router("/app/loginBySms", &controllers.AppAuthController{}, "post:LoginBySms")
+ beego.Router("/app/logout", &controllers.AppAuthController{}, "post:Logout")
// 当前用户信息
- beego.Router("/app/currentUser", &controllers.BackendAuthController{}, "get:GetCurrentUser")
+ beego.Router("/app/currentUser", &controllers.AppAuthController{}, "get:GetCurrentUser")
// 极验与登录验证配置
- beego.Router("/app/login/getGeetest3Infos", &controllers.BackendAuthController{}, "get:GetGeetest3Infos")
- beego.Router("/app/login/getGeetest4Infos", &controllers.BackendAuthController{}, "get:GetGeetest4Infos")
- beego.Router("/app/login/getOpenVerify", &controllers.BackendAuthController{}, "get:GetOpenVerify")
+ beego.Router("/app/login/getGeetest3Infos", &controllers.AppAuthController{}, "get:GetGeetest3Infos")
+ beego.Router("/app/login/getGeetest4Infos", &controllers.AppAuthController{}, "get:GetGeetest4Infos")
+ beego.Router("/app/login/getOpenVerify", &controllers.AppAuthController{}, "get:GetOpenVerify")
- // 找回密码
- beego.Router("/app/resetPassword", &controllers.BackendAuthController{}, "post:ResetPassword")
- beego.Router("/app/sendResetCode", &controllers.BackendAuthController{}, "post:SendResetCode")
+ // 找回密码(三步流程)
+ beego.Router("/app/verifyAccount", &controllers.AppAuthController{}, "post:VerifyAccount")
+ beego.Router("/app/sendResetCode", &controllers.AppAuthController{}, "post:SendResetCode")
+ beego.Router("/app/resetPassword", &controllers.AppAuthController{}, "post:ResetPassword")
// 注册
- beego.Router("/app/register", &controllers.BackendAuthController{}, "post:Register")
- beego.Router("/app/sendRegisterCode", &controllers.BackendAuthController{}, "post:SendRegisterCode")
+ beego.Router("/app/register", &controllers.AppAuthController{}, "post:Register")
+ beego.Router("/app/sendRegisterCode", &controllers.AppAuthController{}, "post:SendRegisterCode")
}
diff --git a/go/services/password_reset.go b/go/services/password_reset.go
new file mode 100644
index 0000000..bd9288d
--- /dev/null
+++ b/go/services/password_reset.go
@@ -0,0 +1,260 @@
+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)
+}
diff --git a/go/test.exe b/go/test.exe
deleted file mode 100644
index 167123e..0000000
Binary files a/go/test.exe and /dev/null differ
diff --git a/uniapp/App.vue b/uniapp/App.vue
index d3b8474..f692da2 100644
--- a/uniapp/App.vue
+++ b/uniapp/App.vue
@@ -1,7 +1,9 @@
+
+
diff --git a/uniapp/pages/login/geetest-webview.vue b/uniapp/pages/login/geetest-webview.vue
new file mode 100644
index 0000000..683809f
--- /dev/null
+++ b/uniapp/pages/login/geetest-webview.vue
@@ -0,0 +1,30 @@
+
+
+
+
+
diff --git a/uniapp/pages/login/login.vue b/uniapp/pages/login/login.vue
index cab5be1..9daca8a 100644
--- a/uniapp/pages/login/login.vue
+++ b/uniapp/pages/login/login.vue
@@ -1,16 +1,11 @@
-
-
账号登录
-
-
-
- +86
-
-
-
-
-
- {{ countdown > 0 ? `${countdown}s 后重发` : '获取验证码' }}
-
-
-
-
-
-
-
-
-
+
+
+
+
+ +86
+
+
+
+
+
+ {{ countdown > 0 ? `${countdown}s 后重发` : (sendingCode ? '发送中' : '获取验证码') }}
+
+
- 登 录
- 测试登录
+
+
+
+
+
+
+
+
+
+
+ {{ countdown > 0 ? `${countdown}s 后重发` : (sendingCode ? '发送中' : '获取验证码') }}
+
+
+ {{ verifyHint }}
+
+
+
+ ✓
+
+ 记住我
+
+
+
+
+ {{ submitting ? '登录中...' : '登 录' }}
+
+
+
+ 注册账号
+ 忘记密码
+
-
✓
@@ -104,37 +141,43 @@
《隐私政策》
-
-
-
- 其他方式
-
-
-
-
-
-
-
-
-
-
-
-
@@ -238,23 +435,6 @@ function socialLogin(type) {
margin-bottom: 64rpx;
}
-.logo {
- width: 96rpx;
- height: 96rpx;
- border-radius: $radius-lg;
- background: $color-primary-bg;
- display: flex;
- align-items: center;
- justify-content: center;
- margin-bottom: 32rpx;
-}
-
-.logo-char {
- font-size: 44rpx;
- font-weight: 600;
- color: $color-primary;
-}
-
.title {
display: block;
font-size: 48rpx;
@@ -296,9 +476,6 @@ function socialLogin(type) {
.form-card {
@include card;
padding: 32rpx 28rpx;
- display: flex;
- flex-direction: column;
- gap: 24rpx;
}
.form {
@@ -339,6 +516,47 @@ function socialLogin(type) {
}
}
+.verify-hint {
+ font-size: 22rpx;
+ color: $color-text-muted;
+ line-height: 1.5;
+ padding: 0 4rpx;
+}
+
+.remember-row {
+ display: flex;
+ align-items: center;
+ gap: 12rpx;
+ padding: 4rpx;
+}
+
+.remember-dot {
+ width: 32rpx;
+ height: 32rpx;
+ border-radius: 50%;
+ border: 1rpx solid $color-border;
+ background: $color-card;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+
+ &.on {
+ background: $color-primary;
+ border-color: $color-primary;
+ }
+}
+
+.remember-check {
+ font-size: 18rpx;
+ color: #fff;
+}
+
+.remember-text {
+ font-size: 26rpx;
+ color: $color-text-secondary;
+}
+
.btn-primary {
height: 96rpx;
background: $color-primary;
@@ -354,22 +572,21 @@ function socialLogin(type) {
&:active {
background: $color-primary-dark;
}
+
+ &.disabled {
+ opacity: 0.7;
+ }
}
-.btn-ghost {
- height: 96rpx;
- background: $color-card;
- border: 1rpx solid $color-border;
- border-radius: $radius-md;
+.link-row {
display: flex;
- align-items: center;
- justify-content: center;
- font-size: 28rpx;
- color: $color-text-secondary;
+ justify-content: space-between;
+ padding: 8rpx 4rpx 0;
+}
- &:active {
- background: $color-bg-page;
- }
+.link {
+ font-size: 26rpx;
+ color: $color-primary;
}
.agreement {
@@ -377,6 +594,7 @@ function socialLogin(type) {
align-items: flex-start;
gap: 12rpx;
margin-top: 32rpx;
+ padding-bottom: 60rpx;
}
.agree-dot {
@@ -411,58 +629,4 @@ function socialLogin(type) {
.agree-link {
color: $color-primary;
}
-
-.oauth {
- margin-top: 64rpx;
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 28rpx;
- padding-bottom: 60rpx;
-}
-
-.oauth-label {
- font-size: 24rpx;
- color: $color-text-muted;
- position: relative;
- padding: 0 32rpx;
-
- &::before,
- &::after {
- content: '';
- position: absolute;
- top: 50%;
- width: 80rpx;
- height: 1rpx;
- background: $color-border;
- }
-
- &::before {
- right: 100%;
- }
-
- &::after {
- left: 100%;
- }
-}
-
-.oauth-icons {
- display: flex;
- gap: 48rpx;
-}
-
-.oauth-btn {
- width: 88rpx;
- height: 88rpx;
- border-radius: 50%;
- background: $color-card;
- box-shadow: $shadow-card;
- display: flex;
- align-items: center;
- justify-content: center;
-
- &:active {
- background: $color-bg-page;
- }
-}
diff --git a/uniapp/pages/login/register.vue b/uniapp/pages/login/register.vue
new file mode 100644
index 0000000..b89a8bc
--- /dev/null
+++ b/uniapp/pages/login/register.vue
@@ -0,0 +1,341 @@
+
+
+
+
+
+ 返回
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ countdown > 0 ? `${countdown}s` : (codeLoading ? '发送中' : '获取验证码') }}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ loading ? '提交中...' : '注 册' }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/uniapp/pages/profile/profile.vue b/uniapp/pages/profile/profile.vue
index 5614945..e0e45f9 100644
--- a/uniapp/pages/profile/profile.vue
+++ b/uniapp/pages/profile/profile.vue
@@ -6,8 +6,8 @@
{{ avatarText }}
- {{ user?.nickname || '云泽用户' }}
- ID: {{ userId }}
+ {{ user?.nickname || user?.name || '云泽用户' }}
+ {{ user?.account ? `账号: ${user.account}` : `ID: ${userId}` }}
@@ -43,7 +43,7 @@
-
+
退出登录
@@ -56,20 +56,24 @@
+
+
+
+
+
+