批量优化

This commit is contained in:
hero920103 committed 2026-10-08 15:30:37 +08:00
1 parent 438145cae7
commit c925b198d1
50 files changed
+5716 -291

No files matched your search

+428
View File
@@ -0,0 +1,428 @@
package auth
import (
"encoding/json"
"io"
"net/url"
"strings"
"time"
"server/services"
"server/services/wechatmp"
beego "github.com/beego/beego/v2/server/web"
)
// AuthAccountController 统一认证中心自助账户:两步式注册 / 找回密码
//
// 注册两步:
// 1. 核验身份:手机号(短信验证码)或 账号(服务号验证码),通过即创建身份并签发 register_token;
// 2. 企业归属:绑定企业(join 申请) / 创建企业(资料+营业执照,平台审核) / 暂不加入。
type AuthAccountController struct {
beego.Controller
}
func (c *AuthAccountController) serveJSON(data map[string]interface{}) {
c.Data["json"] = data
_ = c.ServeJSON()
}
func (c *AuthAccountController) readJSON(v interface{}) bool {
body := c.Ctx.Input.RequestBody
if len(body) == 0 {
var err error
body, err = io.ReadAll(c.Ctx.Request.Body)
if err != nil || len(body) == 0 {
c.serveJSON(map[string]interface{}{"code": 400, "msg": "参数错误"})
return false
}
}
if err := json.Unmarshal(body, v); err != nil {
c.serveJSON(map[string]interface{}{"code": 400, "msg": "参数错误"})
return false
}
return true
}
// loginBackURL 组装回登录页地址,透传 client_id/redirect 与提示参数
func loginBackURL(clientID, redirect, notice, warn string) string {
params := url.Values{}
if clientID != "" {
params.Set("client_id", clientID)
}
if redirect != "" {
params.Set("redirect", redirect)
}
if notice != "" {
params.Set("notice", notice)
}
if warn != "" {
params.Set("warn", warn)
}
qs := params.Encode()
target := "/auth/login"
if qs != "" {
target += "?" + qs
}
return target
}
// RegisterPage 注册页
// GET /auth/register?client_id=xxx&redirect=<base64>
func (c *AuthAccountController) RegisterPage() {
c.Data["ClientID"] = strings.TrimSpace(c.GetString("client_id"))
c.Data["Redirect"] = c.GetString("redirect")
c.Data["Error"] = c.GetString("error")
c.TplName = "auth/register.tpl"
}
// ==================== 第一步:核验身份 ====================
// SendRegisterCode 发送手机号注册短信验证码
// POST /auth/register/send-code { phone }
func (c *AuthAccountController) SendRegisterCode() {
var req struct {
Phone string `json:"phone"`
}
if !c.readJSON(&req) {
return
}
if err := services.SendIdentityCode("register", req.Phone); err != nil {
c.serveJSON(map[string]interface{}{"code": 400, "msg": err.Error()})
return
}
c.serveJSON(map[string]interface{}{"code": 200, "msg": "验证码已发送,请查收短信"})
}
// WechatStart 账号注册:获取服务号带参二维码(扫码关注后服务号回复验证码)。
// 未认证公众号无法生成带参二维码时回退:qr_url 为空,notice 引导用户
// 在公众号内发送「注册」获取验证码。
// GET /auth/register/wechat/start
func (c *AuthAccountController) WechatStart() {
scene, qrURL, notice, expireAt, err := wechatmp.StartRegisterVerify(wechatmp.DefaultVerifyTTL)
if err != nil {
c.serveJSON(map[string]interface{}{"code": 400, "msg": err.Error()})
return
}
c.serveJSON(map[string]interface{}{
"code": 200,
"data": map[string]interface{}{
"scene": scene,
"qr_url": qrURL,
"notice": notice,
"expire_at": expireAt.Format(time.RFC3339),
},
})
}
// WechatStatus 账号注册:轮询扫码状态(展示「已扫码」)
// GET /auth/register/wechat/status?scene=xxx
func (c *AuthAccountController) WechatStatus() {
scene := strings.TrimSpace(c.GetString("scene"))
scanned, expireAt, err := wechatmp.GetRegisterVerifyStatus(scene)
if err != nil {
c.serveJSON(map[string]interface{}{"code": 400, "msg": err.Error()})
return
}
c.serveJSON(map[string]interface{}{
"code": 200,
"data": map[string]interface{}{
"scanned": scanned,
"expire_at": expireAt.Format(time.RFC3339),
},
})
}
// RegisterVerify 第一步核验:验证码通过即创建统一身份,返回 register_token
// POST /auth/register/verify
//
// mode=phone { mode, phone, code, password, confirm }
// mode=account { mode, account, scene, code, password, confirm }
func (c *AuthAccountController) RegisterVerify() {
var req struct {
Mode string `json:"mode"`
Phone string `json:"phone"`
Account string `json:"account"`
Scene string `json:"scene"`
Code string `json:"code"`
Password string `json:"password"`
Confirm string `json:"confirm"`
}
if !c.readJSON(&req) {
return
}
openID := ""
if req.Mode == "account" {
// 核销服务号验证码,得到扫码人 openid(注册完成后绑定微信登录):
// 有 scene 走二维码会话核销;无 scene(公众号发送「注册」)按验证码认领
var err error
if strings.TrimSpace(req.Scene) != "" {
openID, err = wechatmp.ConfirmRegisterVerify(req.Scene, req.Code)
} else {
openID, err = wechatmp.ConfirmRegisterVerifyByCode(req.Code)
}
if err != nil {
c.serveJSON(map[string]interface{}{"code": 400, "msg": err.Error()})
return
}
}
identity, token, err := services.VerifyAndCreateIdentity(services.RegisterVerifyInput{
Mode: req.Mode,
Phone: req.Phone,
Account: req.Account,
Code: req.Code,
Password: req.Password,
Confirm: req.Confirm,
OpenID: openID,
})
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{}{"register_token": token, "identity_id": identity.ID},
})
}
// ==================== 第二步:企业归属 ====================
// TenantSearch 搜索企业(绑定企业卡片)
// GET /auth/register/tenant/search?keyword=xxx
func (c *AuthAccountController) TenantSearch() {
keyword := strings.TrimSpace(c.GetString("keyword"))
if keyword == "" {
c.serveJSON(map[string]interface{}{"code": 400, "msg": "请输入企业名称或编码"})
return
}
list, err := services.SearchTenantsForRegister(keyword)
if err != nil {
c.serveJSON(map[string]interface{}{"code": 500, "msg": "搜索失败,请稍后重试"})
return
}
c.serveJSON(map[string]interface{}{"code": 200, "data": map[string]interface{}{"list": list}})
}
// ApplyJoin 绑定企业:提交加入申请,等待企业管理员审核
// POST /auth/register/apply-join { token, tid, client_id, redirect }
func (c *AuthAccountController) ApplyJoin() {
var req struct {
Token string `json:"token"`
Tid uint64 `json:"tid"`
ClientID string `json:"client_id"`
Redirect string `json:"redirect"`
}
if !c.readJSON(&req) {
return
}
identityID, openID, err := services.PeekRegisterToken(req.Token)
if err != nil {
c.serveJSON(map[string]interface{}{"code": 400, "msg": err.Error()})
return
}
apply, err := services.CreateJoinApply(identityID, req.Tid)
if err != nil {
c.serveJSON(map[string]interface{}{"code": 400, "msg": err.Error()})
return
}
services.ConsumeRegisterToken(req.Token)
services.BindRegisterWechat(identityID, openID)
c.serveJSON(map[string]interface{}{
"code": 200,
"msg": "申请已提交",
"redirect": loginBackURL(req.ClientID, req.Redirect, "registered_warn",
"注册成功!加入「"+apply.TenantName+"」的申请已提交,请等待企业管理员审核通过后登录"),
})
}
// ApplyCreate 创建企业:提交企业资料(营业执照),等待平台审核
// POST /auth/register/apply-create { token, tenant_name, credit_code, legal_person, license_file, contact_person, contact_phone, contact_email, address, client_id, redirect }
func (c *AuthAccountController) ApplyCreate() {
var req struct {
Token string `json:"token"`
TenantName string `json:"tenant_name"`
CreditCode string `json:"credit_code"`
LegalPerson string `json:"legal_person"`
LicenseFile string `json:"license_file"`
ContactPerson string `json:"contact_person"`
ContactPhone string `json:"contact_phone"`
ContactEmail string `json:"contact_email"`
Address string `json:"address"`
ClientID string `json:"client_id"`
Redirect string `json:"redirect"`
}
if !c.readJSON(&req) {
return
}
identityID, openID, err := services.PeekRegisterToken(req.Token)
if err != nil {
c.serveJSON(map[string]interface{}{"code": 400, "msg": err.Error()})
return
}
if _, err := services.CreateTenantApply(identityID, services.TenantCreateApplyInput{
TenantName: req.TenantName,
CreditCode: req.CreditCode,
LegalPerson: req.LegalPerson,
LicenseFile: req.LicenseFile,
ContactPerson: req.ContactPerson,
ContactPhone: req.ContactPhone,
ContactEmail: req.ContactEmail,
Address: req.Address,
}); err != nil {
c.serveJSON(map[string]interface{}{"code": 400, "msg": err.Error()})
return
}
services.ConsumeRegisterToken(req.Token)
services.BindRegisterWechat(identityID, openID)
c.serveJSON(map[string]interface{}{
"code": 200,
"msg": "申请已提交",
"redirect": loginBackURL(req.ClientID, req.Redirect, "registered_warn",
"注册成功!创建企业的申请已提交,请等待平台审核,审核结果将以短信通知"),
})
}
// FinishSkip 暂不加入企业,直接完成注册
// POST /auth/register/finish { token, client_id, redirect }
func (c *AuthAccountController) FinishSkip() {
var req struct {
Token string `json:"token"`
ClientID string `json:"client_id"`
Redirect string `json:"redirect"`
}
if !c.readJSON(&req) {
return
}
identityID, openID, err := services.PeekRegisterToken(req.Token)
if err != nil {
c.serveJSON(map[string]interface{}{"code": 400, "msg": err.Error()})
return
}
services.ConsumeRegisterToken(req.Token)
services.BindRegisterWechat(identityID, openID)
c.serveJSON(map[string]interface{}{
"code": 200,
"msg": "注册成功",
"redirect": loginBackURL(req.ClientID, req.Redirect, "registered", "注册成功!你当前未加入任何企业,可稍后在登录后申请加入"),
})
}
// UploadLicense 上传营业执照(注册页未登录,专用无鉴权端点)
// POST /auth/register/upload-license multipart 字段 file;仅 pdf/jpg/png,≤5MB
func (c *AuthAccountController) UploadLicense() {
file, header, err := c.GetFile("file")
if err != nil || file == nil {
c.serveJSON(map[string]interface{}{"code": 400, "msg": "请选择要上传的文件"})
return
}
defer file.Close()
// 类型与大小限制
name := strings.ToLower(header.Filename)
ok := strings.HasSuffix(name, ".pdf") || strings.HasSuffix(name, ".jpg") ||
strings.HasSuffix(name, ".jpeg") || strings.HasSuffix(name, ".png")
if !ok {
c.serveJSON(map[string]interface{}{"code": 400, "msg": "仅支持 PDF、JPG、PNG 格式"})
return
}
if header.Size > 5*1024*1024 {
c.serveJSON(map[string]interface{}{"code": 400, "msg": "文件大小不能超过 5MB"})
return
}
storage, err := services.GetStorageService()
if err != nil {
c.serveJSON(map[string]interface{}{"code": 500, "msg": "存储服务不可用,请稍后重试"})
return
}
staged, err := storage.Stage(file, header)
if err != nil {
c.serveJSON(map[string]interface{}{"code": 500, "msg": "文件读取失败,请重试"})
return
}
result, err := storage.Commit(staged, services.UploadContext{Source: services.SourcePlatform, Tid: 0, Tuid: 0})
if err != nil {
_ = storage.Discard(staged)
c.serveJSON(map[string]interface{}{"code": 500, "msg": "上传失败,请重试"})
return
}
// 文件登记(平台文件管理可见)
_, _ = services.CreateFileRecord(services.FileUploadParams{
Source: services.SourcePlatform,
Scope: services.ScopeTenant,
Tid: 0,
Uid: 0,
Name: header.Filename,
Ext: staged.Ext,
Cate: 0,
Size: uint64(staged.Size),
Src: result.URL,
ObjectKey: result.Key,
Storage: storage.Type(),
MD5: staged.MD5,
})
c.serveJSON(map[string]interface{}{
"code": 200,
"data": map[string]interface{}{"url": result.URL, "name": header.Filename},
})
}
// ==================== 找回密码 ====================
// ForgotPage 找回密码页
// GET /auth/forgot?client_id=xxx&redirect=<base64>
func (c *AuthAccountController) ForgotPage() {
c.Data["ClientID"] = strings.TrimSpace(c.GetString("client_id"))
c.Data["Redirect"] = c.GetString("redirect")
c.Data["Error"] = c.GetString("error")
c.TplName = "auth/forgot.tpl"
}
// SendResetCode 发送找回密码验证码
// POST /auth/forgot/send-code { phone }
func (c *AuthAccountController) SendResetCode() {
var req struct {
Phone string `json:"phone"`
}
if !c.readJSON(&req) {
return
}
if err := services.SendIdentityCode("reset", req.Phone); err != nil {
c.serveJSON(map[string]interface{}{"code": 400, "msg": err.Error()})
return
}
c.serveJSON(map[string]interface{}{"code": 200, "msg": "验证码已发送,请查收短信"})
}
// ResetSubmit 提交重置密码
// POST /auth/forgot/reset { phone, code, password, confirm, client_id, redirect }
func (c *AuthAccountController) ResetSubmit() {
var req struct {
Phone string `json:"phone"`
Code string `json:"code"`
Password string `json:"password"`
Confirm string `json:"confirm"`
ClientID string `json:"client_id"`
Redirect string `json:"redirect"`
}
if !c.readJSON(&req) {
return
}
if err := services.ResetIdentityPasswordByMobile(req.Phone, req.Code, req.Password, req.Confirm); err != nil {
c.serveJSON(map[string]interface{}{"code": 400, "msg": err.Error()})
return
}
c.serveJSON(map[string]interface{}{
"code": 200,
"msg": "密码已重置",
"redirect": loginBackURL(req.ClientID, req.Redirect, "reset", ""),
})
}
+3
View File
@@ -69,6 +69,9 @@ func (c *AuthLoginController) LoginPage() {
c.Data["Step"] = step
c.Data["Error"] = errMsg
c.Data["Tenants"] = tenants
// 自助账户流程提示:registered=注册成功 / registered_warn=注册成功但加入企业失败 / reset=密码已重置
c.Data["Notice"] = c.GetString("notice")
c.Data["WarnMsg"] = c.GetString("warn")
c.TplName = "auth/login.tpl"
}
+18 -7
View File
@@ -10,13 +10,13 @@ import (
"server/services/wechatmini"
"github.com/beego/beego/v2/client/orm"
beego "github.com/beego/beego/v2/server/web"
"github.com/beego/beego/v2/core/logs"
beego "github.com/beego/beego/v2/server/web"
)
// 小程序登录失败原因(前端按此分支给出不同引导)
const (
MiniReasonNotConfigured = "not_configured" // 服务端未配置小程序
MiniReasonNotConfigured = "not_configured" // 服务端未配置小程序
MiniReasonIdentityNotFound = "identity_not_found" // 微信未匹配到云泽账号
MiniReasonNoTenant = "no_tenant" // 账号未加入任何企业
MiniReasonWechatAPI = "wechat_api_error" // 微信接口报错
@@ -49,10 +49,19 @@ func (c *AuthWechatMiniController) fail(reason string, bizCode int, msg string)
c.serveJSON(map[string]interface{}{"code": bizCode, "msg": msg, "reason": reason})
}
// loadMiniConfig 按客户端传入的 appid 选择小程序配置(多应用支持),
// 未传 appid 时回退第一条启用配置
func loadMiniConfig(appID string) (*wechatmini.Config, error) {
if appID = strings.TrimSpace(appID); appID != "" {
return wechatmini.LoadEnabledAppByAppID(appID)
}
return wechatmini.LoadEnabledConfig()
}
// Config 小程序登录是否可用
// GET /auth/wechat/miniapp/config
// GET /auth/wechat/miniapp/config?appid=xxx(多应用时可指定,缺省取第一条启用配置)
func (c *AuthWechatMiniController) Config() {
cfg, err := wechatmini.LoadEnabledConfig()
cfg, err := loadMiniConfig(c.GetString("appid"))
if err != nil {
c.serveJSON(map[string]interface{}{
"code": 200,
@@ -72,6 +81,7 @@ func (c *AuthWechatMiniController) Login() {
Code string `json:"code"`
PhoneCode string `json:"phone_code"`
ClientID string `json:"client_id"`
AppID string `json:"appid"`
}
body := c.Ctx.Input.RequestBody
if len(body) == 0 {
@@ -92,7 +102,7 @@ func (c *AuthWechatMiniController) Login() {
return
}
cfg, err := wechatmini.LoadEnabledConfig()
cfg, err := loadMiniConfig(req.AppID)
if err != nil {
c.fail(MiniReasonNotConfigured, 400, "小程序登录未开通,请使用账号密码登录")
return
@@ -193,7 +203,8 @@ func (c *AuthWechatMiniController) Bind() {
}
var req struct {
Code string `json:"code"`
Code string `json:"code"`
AppID string `json:"appid"`
}
body := c.Ctx.Input.RequestBody
if len(body) == 0 {
@@ -209,7 +220,7 @@ func (c *AuthWechatMiniController) Bind() {
return
}
cfg, err := wechatmini.LoadEnabledConfig()
cfg, err := loadMiniConfig(req.AppID)
if err != nil {
c.fail(MiniReasonNotConfigured, 400, "小程序登录未开通")
return