diff --git a/controllers/backend_menu.go b/controllers/backend_menu.go index d60086c..1abd550 100644 --- a/controllers/backend_menu.go +++ b/controllers/backend_menu.go @@ -24,23 +24,72 @@ type menuPayload struct { Sort *int64 `json:"sort"` Status *int8 `json:"status"` IsVisible *int8 `json:"is_visible"` - IsPlatform *int8 `json:"is_platform"` + Views []int `json:"views"` Type *int8 `json:"type"` Permission *string `json:"permission"` } +func parseViews(raw *string) []int { + if raw == nil { + return nil + } + s := strings.TrimSpace(*raw) + if s == "" { + return nil + } + var arr []int + if err := json.Unmarshal([]byte(s), &arr); err != nil { + return nil + } + return arr +} + +func hasView(arr []int, v int) bool { + for _, n := range arr { + if n == v { + return true + } + } + return false +} + +func viewsJSON(views []int) string { + // 默认:平台端显示 + if len(views) == 0 { + views = []int{1} + } + b, _ := json.Marshal(views) + return string(b) +} + +func filterMenusByView(menus []models.SystemMenu, v int) []models.SystemMenu { + out := make([]models.SystemMenu, 0, len(menus)) + for _, m := range menus { + views := parseViews(m.Views) + // 兼容旧数据:views 为空时,平台端菜单默认可见(保持旧 is_platform=1 的常见默认体验) + // 租户端不做默认放行,避免把未迁移数据误暴露到租户端。 + if v == 1 && len(views) == 0 { + out = append(out, m) + continue + } + if hasView(views, v) { + out = append(out, m) + } + } + return out +} + // GetMenu 获取指定用户可见的菜单列表(简化版:当前先忽略用户权限,返回全部启用且平台端菜单) // 路由示例:GET /platform/menu/1 func (c *AdminMenuController) GetMenu() { // 从路由参数中解析用户 ID,占位保留,方便后续按用户权限过滤 _ = c.Ctx.Input.Param(":id") - // 查询所有启用且标记为平台端的菜单 + // 查询所有启用菜单,再按 views 过滤平台端可见 var menus []models.SystemMenu qs := models.Orm. QueryTable(new(models.SystemMenu)). - Filter("status", 1). - Filter("is_platform", 1) + Filter("status", 1) _, err := qs.All(&menus) if err != nil { c.Data["json"] = map[string]interface{}{ @@ -51,6 +100,7 @@ func (c *AdminMenuController) GetMenu() { _ = c.ServeJSON() return } + menus = filterMenusByView(menus, 1) // 将平铺的菜单列表构建为树形结构 menuTree := buildMenuTree(menus, 0) @@ -72,8 +122,7 @@ func (c *AdminMenuController) GetBackendMenu() { var menus []models.SystemMenu qs := models.Orm. QueryTable(new(models.SystemMenu)). - Filter("status", 1). - Filter("is_platform", 0) + Filter("status", 1) _, err := qs.All(&menus) if err != nil { c.Data["json"] = map[string]interface{}{ @@ -84,6 +133,7 @@ func (c *AdminMenuController) GetBackendMenu() { _ = c.ServeJSON() return } + menus = filterMenusByView(menus, 2) menuTree := buildMenuTree(menus, 0) c.Data["json"] = map[string]interface{}{ @@ -103,11 +153,6 @@ func (c *AdminMenuController) GetAllMenus() { qs := models.Orm.QueryTable(new(models.SystemMenu)) // 菜单管理默认返回全量菜单;仅在明确传 cid 时按分类筛选 // cid: 1平台角色 -> 平台菜单;2租户角色 -> 租户菜单 - if cid == 1 { - qs = qs.Filter("is_platform", 1) - } else if cid == 2 { - qs = qs.Filter("is_platform", 0) - } _, err := qs.All(&menus) if err != nil { c.Data["json"] = map[string]interface{}{ @@ -118,6 +163,11 @@ func (c *AdminMenuController) GetAllMenus() { _ = c.ServeJSON() return } + if cid == 1 { + menus = filterMenusByView(menus, 1) + } else if cid == 2 { + menus = filterMenusByView(menus, 2) + } tree := buildMenuTree(menus, 0) @@ -134,7 +184,6 @@ func (c *AdminMenuController) GetAllMenus() { func (c *AdminMenuController) GetAllBackendMenus() { var menus []models.SystemMenu _, err := models.Orm.QueryTable(new(models.SystemMenu)). - Filter("is_platform", 0). All(&menus) if err != nil { c.Data["json"] = map[string]interface{}{ @@ -145,6 +194,7 @@ func (c *AdminMenuController) GetAllBackendMenus() { _ = c.ServeJSON() return } + menus = filterMenusByView(menus, 2) tree := buildMenuTree(menus, 0) c.Data["json"] = map[string]interface{}{ "code": 200, @@ -165,7 +215,7 @@ type menuNode struct { Sort int64 `json:"sort"` Status int8 `json:"status"` IsVisible *int8 `json:"is_visible,omitempty"` - IsPlatform *int8 `json:"is_platform,omitempty"` + Views []int `json:"views,omitempty"` Type int8 `json:"type"` Permission string `json:"permission,omitempty"` Children []*menuNode `json:"children,omitempty"` @@ -183,7 +233,7 @@ func buildMenuTree(menus []models.SystemMenu, pid int64) []*menuNode { Sort: m.Sort, Status: m.Status, IsVisible: m.IsVisible, - IsPlatform: m.IsPlatform, + Views: parseViews(m.Views), Type: m.Type, } if m.Path != nil { @@ -257,7 +307,7 @@ func (c *AdminMenuController) CreateMenu() { Sort: valueInt64(payload.Sort, 0), Status: valueInt8(payload.Status, 1), IsVisible: ptrInt8(valueInt8(payload.IsVisible, 1)), - IsPlatform: ptrInt8(valueInt8(payload.IsPlatform, 1)), + Views: ptrString(viewsJSON(payload.Views)), Type: valueInt8(payload.Type, 1), } @@ -305,7 +355,7 @@ func (c *AdminMenuController) UpdateMenu() { "sort": valueInt64(payload.Sort, 0), "status": valueInt8(payload.Status, 1), "is_visible": valueInt8(payload.IsVisible, 1), - "is_platform": valueInt8(payload.IsPlatform, 1), + "views": viewsJSON(payload.Views), "type": valueInt8(payload.Type, 1), "permission": valueString(payload.Permission, ""), } diff --git a/controllers/platform_auth.go b/controllers/platform_auth.go index 1efd692..2cbce7a 100644 --- a/controllers/platform_auth.go +++ b/controllers/platform_auth.go @@ -5,6 +5,7 @@ import ( "io" "strings" + "server/models" "server/pkg/jwtutil" "server/services" @@ -14,12 +15,14 @@ import ( type platformLoginRequest struct { Account string `json:"account"` Password string `json:"password"` + Code string `json:"code"` } type backendLoginRequest struct { TenantName string `json:"tenant_name"` Account string `json:"account"` Password string `json:"password"` + Code string `json:"code"` } // PlatformAuthController 平台端认证控制器 @@ -59,6 +62,21 @@ func (c *PlatformAuthController) LoginPlatform() { _ = c.ServeJSON() return } + cfg, _ := models.GetPlatformLoginVerify() + if cfg.OpenVerifyEnabled == 1 { + if cfg.VerifyType == "sms" || cfg.VerifyType == "email" { + if strings.TrimSpace(req.Code) == "" { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "请输入验证码"} + _ = c.ServeJSON() + return + } + if err := services.VerifyPlatformLoginCode(req.Account, cfg.VerifyType, req.Code); err != nil { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": err.Error()} + _ = c.ServeJSON() + return + } + } + } // 控制器只做 HTTP 解析与响应编排,业务逻辑放 services 层 token, loginUser, err := services.PlatformAdminLogin(req.Account, req.Password) @@ -80,7 +98,6 @@ func (c *PlatformAuthController) LoginPlatform() { "id": loginUser.ID, "account": loginUser.Account, "name": loginUser.Name, - "tid": loginUser.Tid, "rid": loginUser.Rid, "avatar": loginUser.Avatar, "role_name": loginUser.RoleName, @@ -110,6 +127,21 @@ func (c *PlatformAuthController) LoginBackend() { _ = c.ServeJSON() return } + cfg, _ := models.GetPlatformLoginVerify() + if cfg.OpenVerifyEnabled == 1 { + if cfg.VerifyType == "sms" || cfg.VerifyType == "email" { + if strings.TrimSpace(req.Code) == "" { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "请输入验证码"} + _ = c.ServeJSON() + return + } + if err := services.VerifyBackendLoginCode(req.TenantName, req.Account, cfg.VerifyType, req.Code); err != nil { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": err.Error()} + _ = c.ServeJSON() + return + } + } + } token, loginUser, err := services.BackendLogin(req.TenantName, req.Account, req.Password) if err != nil { @@ -185,10 +217,45 @@ func (c *PlatformAuthController) GetCurrentUser() { // SendLoginCode 发送登录验证码(占位实现) func (c *PlatformAuthController) SendLoginCode() { - c.Data["json"] = map[string]interface{}{ - "code": 501, - "msg": "发送登录验证码暂未实现", + var req struct { + Account string `json:"account"` + TenantName string `json:"tenant_name"` + Channel string `json:"channel"` } + body, _ := io.ReadAll(c.Ctx.Request.Body) + if err := json.Unmarshal(body, &req); err != nil { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"} + _ = c.ServeJSON() + return + } + cfg, _ := models.GetPlatformLoginVerify() + if cfg.OpenVerifyEnabled != 1 { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "当前未开启验证"} + _ = c.ServeJSON() + return + } + channel := strings.TrimSpace(req.Channel) + if channel == "" { + channel = cfg.VerifyType + } + if channel != "sms" && channel != "email" { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "仅支持短信/邮箱验证码"} + _ = c.ServeJSON() + return + } + path := strings.ToLower(c.Ctx.Request.URL.Path) + var sendErr error + if strings.HasPrefix(path, "/backend/") { + sendErr = services.SendBackendLoginCode(req.TenantName, req.Account, channel) + } else { + sendErr = services.SendPlatformLoginCode(req.Account, channel) + } + if sendErr != nil { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": sendErr.Error()} + _ = c.ServeJSON() + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "验证码已发送"} _ = c.ServeJSON() } @@ -212,32 +279,60 @@ func (c *PlatformAuthController) Logout() { // GetGeetest3Infos 获取极验3.0配置(占位实现) func (c *PlatformAuthController) GetGeetest3Infos() { + cfg, _ := models.GetPlatformLoginVerify() + if cfg.Geetest3ID == nil || cfg.Geetest3Key == nil { + c.Data["json"] = map[string]interface{}{"code": 404, "msg": "未配置极验3参数"} + _ = c.ServeJSON() + return + } c.Data["json"] = map[string]interface{}{ - "code": 501, - "msg": "极验3.0暂未实现", + "code": 200, + "msg": "success", + "data": map[string]interface{}{ + "captcha_id": *cfg.Geetest3ID, + "captcha_key": *cfg.Geetest3Key, + }, } _ = c.ServeJSON() } // GetGeetest4Infos 获取极验4.0配置(占位实现) func (c *PlatformAuthController) GetGeetest4Infos() { + cfg, _ := models.GetPlatformLoginVerify() + if cfg.Geetest4ID == nil || cfg.Geetest4Key == nil { + c.Data["json"] = map[string]interface{}{"code": 404, "msg": "未配置极验4参数"} + _ = c.ServeJSON() + return + } c.Data["json"] = map[string]interface{}{ - "code": 501, - "msg": "极验4.0暂未实现", + "code": 200, + "msg": "success", + "data": map[string]interface{}{ + "captcha_id": *cfg.Geetest4ID, + "captcha_key": *cfg.Geetest4Key, + }, } _ = c.ServeJSON() } // GetOpenVerify 判断是否开启登录验证(占位实现) func (c *PlatformAuthController) GetOpenVerify() { + cfg, _ := models.GetPlatformLoginVerify() + openVerify := "0" + if cfg.OpenVerifyEnabled == 1 { + openVerify = "1" + } c.Data["json"] = map[string]interface{}{ "code": 200, "msg": "ok", - // data 为配置项数组,这里固定关闭验证:openVerify=0 "data": []map[string]string{ { "label": "openVerify", - "value": "0", + "value": openVerify, + }, + { + "label": "verifyType", + "value": cfg.VerifyType, }, }, } diff --git a/controllers/platform_login_verify.go b/controllers/platform_login_verify.go new file mode 100644 index 0000000..79ad429 --- /dev/null +++ b/controllers/platform_login_verify.go @@ -0,0 +1,124 @@ +package controllers + +import ( + "encoding/json" + "io" + "strings" + + "server/models" + + beego "github.com/beego/beego/v2/server/web" +) + +type PlatformLoginVerifyController struct { + beego.Controller +} + +type loginVerifyPayload struct { + OpenVerifyEnabled *int8 `json:"openVerify_enabled"` + VerifyType string `json:"use_geetest"` + Geetest3ID *string `json:"geetest3_id"` + Geetest3Key *string `json:"geetest3_key"` + Geetest4ID *string `json:"geetest4_id"` + Geetest4Key *string `json:"geetest4_key"` +} + +func normalizeVerifyType(v string) string { + switch strings.TrimSpace(v) { + case "sms", "geetest", "email", "captcha": + return strings.TrimSpace(v) + default: + return "captcha" + } +} + +// GetLoginVerifyInfos 获取登录验证配置 +// GET /platform/loginVerifyInfos +func (c *PlatformLoginVerifyController) GetLoginVerifyInfos() { + cfg, err := models.GetPlatformLoginVerify() + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "获取配置失败"} + _ = c.ServeJSON() + return + } + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{ + "openVerify_enabled": cfg.OpenVerifyEnabled, + "use_geetest": cfg.VerifyType, + "geetest3_id": cfg.Geetest3ID, + "geetest3_key": cfg.Geetest3Key, + "geetest4_id": cfg.Geetest4ID, + "geetest4_key": cfg.Geetest4Key, + }, + } + _ = c.ServeJSON() +} + +// SaveLoginVerifyInfos 保存登录验证配置 +// POST /platform/saveloginVerifyInfos +func (c *PlatformLoginVerifyController) SaveLoginVerifyInfos() { + var p loginVerifyPayload + raw, _ := io.ReadAll(c.Ctx.Request.Body) + if err := json.Unmarshal(raw, &p); err != nil { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"} + _ = c.ServeJSON() + return + } + + verifyType := normalizeVerifyType(p.VerifyType) + openVerifyEnabled := int8(1) + if p.OpenVerifyEnabled != nil { + openVerifyEnabled = *p.OpenVerifyEnabled + } + if verifyType == "geetest" { + if p.Geetest4ID == nil || strings.TrimSpace(*p.Geetest4ID) == "" { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "geetest4_id 不能为空"} + _ = c.ServeJSON() + return + } + if p.Geetest4Key == nil || strings.TrimSpace(*p.Geetest4Key) == "" { + c.Data["json"] = map[string]interface{}{"code": 400, "msg": "geetest4_key 不能为空"} + _ = c.ServeJSON() + return + } + } + + var existed models.PlatformLoginVerify + err := models.Orm.QueryTable(new(models.PlatformLoginVerify)).OrderBy("-id").One(&existed) + if err == nil { + update := map[string]interface{}{ + "open_verify_enabled": openVerifyEnabled, + "verify_type": verifyType, + "geetest3_id": p.Geetest3ID, + "geetest3_key": p.Geetest3Key, + "geetest4_id": p.Geetest4ID, + "geetest4_key": p.Geetest4Key, + } + _, err = models.Orm.QueryTable(new(models.PlatformLoginVerify)).Filter("id", existed.ID).Update(update) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "保存失败"} + _ = c.ServeJSON() + return + } + } else { + row := &models.PlatformLoginVerify{ + OpenVerifyEnabled: openVerifyEnabled, + VerifyType: verifyType, + Geetest3ID: p.Geetest3ID, + Geetest3Key: p.Geetest3Key, + Geetest4ID: p.Geetest4ID, + Geetest4Key: p.Geetest4Key, + } + if _, err := models.Orm.Insert(row); err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "保存失败"} + _ = c.ServeJSON() + return + } + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "保存成功"} + _ = c.ServeJSON() +} + diff --git a/controllers/platform_modules.go b/controllers/platform_modules.go index 3e8b534..ff63919 100644 --- a/controllers/platform_modules.go +++ b/controllers/platform_modules.go @@ -19,7 +19,7 @@ type PlatformModulesController struct { beego.Controller } -func (c *PlatformModulesController) platformClaims() (*jwtutil.Claims, error) { +func (c *PlatformModulesController) modulesClaims() (*jwtutil.Claims, error) { auth := c.Ctx.Request.Header.Get("Authorization") if auth == "" { return nil, fmt.Errorf("未登录") @@ -32,8 +32,19 @@ func (c *PlatformModulesController) platformClaims() (*jwtutil.Claims, error) { if err != nil { return nil, fmt.Errorf("无效的token") } - if claims.UserType != "platform" { - return nil, fmt.Errorf("无权访问") + // 语义更正: + // - /platform/* 只能 platform 访问 + // - /backend/* 只能 backend 访问 + // 兼容:历史 token 可能缺少 user_type(按 user 处理),此时都拒绝访问以避免越权。 + path := strings.ToLower(c.Ctx.Request.URL.Path) + if strings.HasPrefix(path, "/platform/") { + if claims.UserType != "platform" { + return nil, fmt.Errorf("无权访问") + } + } else if strings.HasPrefix(path, "/backend/") { + if claims.UserType != "backend" { + return nil, fmt.Errorf("无权访问") + } } return claims, nil } @@ -46,7 +57,7 @@ func (c *PlatformModulesController) jsonErr(httpStatus, bizCode int, msg string) // GetList GET /platform/modules/list func (c *PlatformModulesController) GetList() { - if _, err := c.platformClaims(); err != nil { + if _, err := c.modulesClaims(); err != nil { c.jsonErr(401, 401, err.Error()) return } @@ -73,7 +84,7 @@ func (c *PlatformModulesController) GetList() { // GetTenantList GET /platform/modules/getTenantList // 兼容旧接口命名:返回当前账号可见的模块。当前实现:返回 status=1 且 is_show=1 的全部模块。 func (c *PlatformModulesController) GetTenantList() { - if _, err := c.platformClaims(); err != nil { + if _, err := c.modulesClaims(); err != nil { c.jsonErr(401, 401, err.Error()) return } @@ -101,7 +112,7 @@ func (c *PlatformModulesController) GetTenantList() { // GetDetail GET /platform/modules/:id func (c *PlatformModulesController) GetDetail() { - if _, err := c.platformClaims(); err != nil { + if _, err := c.modulesClaims(); err != nil { c.jsonErr(401, 401, err.Error()) return } @@ -139,7 +150,7 @@ type modulePayload struct { // Add POST /platform/modules func (c *PlatformModulesController) Add() { - if _, err := c.platformClaims(); err != nil { + if _, err := c.modulesClaims(); err != nil { c.jsonErr(401, 401, err.Error()) return } @@ -194,7 +205,7 @@ func (c *PlatformModulesController) Add() { // Edit PUT /platform/modules/:id func (c *PlatformModulesController) Edit() { - if _, err := c.platformClaims(); err != nil { + if _, err := c.modulesClaims(); err != nil { c.jsonErr(401, 401, err.Error()) return } @@ -262,7 +273,7 @@ func (c *PlatformModulesController) Edit() { // Delete DELETE /platform/modules/:id(软删) func (c *PlatformModulesController) Delete() { - if _, err := c.platformClaims(); err != nil { + if _, err := c.modulesClaims(); err != nil { c.jsonErr(401, 401, err.Error()) return } @@ -291,7 +302,7 @@ func (c *PlatformModulesController) Delete() { // BatchDelete POST /platform/modules/batchDelete body:{ids:[]} func (c *PlatformModulesController) BatchDelete() { - if _, err := c.platformClaims(); err != nil { + if _, err := c.modulesClaims(); err != nil { c.jsonErr(401, 401, err.Error()) return } @@ -323,7 +334,7 @@ func (c *PlatformModulesController) BatchDelete() { // ChangeStatus POST /platform/modules/status body:{id,status} // 兼容前端:这里的 status 实际用于切换 is_show(显示开关)。 func (c *PlatformModulesController) ChangeStatus() { - if _, err := c.platformClaims(); err != nil { + if _, err := c.modulesClaims(); err != nil { c.jsonErr(401, 401, err.Error()) return } @@ -362,7 +373,7 @@ func (c *PlatformModulesController) ChangeStatus() { // GetSelectList GET /platform/modules/select/list func (c *PlatformModulesController) GetSelectList() { - if _, err := c.platformClaims(); err != nil { + if _, err := c.modulesClaims(); err != nil { c.jsonErr(401, 401, err.Error()) return } diff --git a/models/init.go b/models/init.go index 9c408ac..d4c51fb 100644 --- a/models/init.go +++ b/models/init.go @@ -47,6 +47,7 @@ func Init(_ string) { new(SystemDomainPool), new(SystemTenantDomain), new(SystemModules), + new(PlatformLoginVerify), ) // 创建全局 Ormer diff --git a/models/platform_login_verify.go b/models/platform_login_verify.go new file mode 100644 index 0000000..69ec5d2 --- /dev/null +++ b/models/platform_login_verify.go @@ -0,0 +1,34 @@ +package models + +import "time" + +// PlatformLoginVerify 平台登录验证配置(单行配置) +type PlatformLoginVerify struct { + ID uint64 `orm:"column(id);pk;auto" json:"id"` + OpenVerifyEnabled int8 `orm:"column(open_verify_enabled);default(1)" json:"openVerify_enabled"` // 0关闭 1开启 + VerifyType string `orm:"column(verify_type);size(20);default(captcha)" json:"verify_type"` // captcha/sms/geetest/email + Geetest3ID *string `orm:"column(geetest3_id);size(128);null" json:"geetest3_id"` + Geetest3Key *string `orm:"column(geetest3_key);size(255);null" json:"geetest3_key"` + Geetest4ID *string `orm:"column(geetest4_id);size(128);null" json:"geetest4_id"` + Geetest4Key *string `orm:"column(geetest4_key);size(255);null" json:"geetest4_key"` + CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"` + UpdateTime *time.Time `orm:"column(update_time);type(datetime);auto_now;null" json:"update_time"` +} + +func (m *PlatformLoginVerify) TableName() string { + return "yz_system_login_verify" +} + +func GetPlatformLoginVerify() (*PlatformLoginVerify, error) { + var cfg PlatformLoginVerify + err := Orm.QueryTable(new(PlatformLoginVerify)).OrderBy("-id").One(&cfg) + if err != nil { + // 默认配置:验证码 + return &PlatformLoginVerify{OpenVerifyEnabled: 1, VerifyType: "captcha"}, nil + } + if cfg.VerifyType == "" { + cfg.VerifyType = "captcha" + } + return &cfg, nil +} + diff --git a/models/system_menu.go b/models/system_menu.go index ba04500..757cc8e 100644 --- a/models/system_menu.go +++ b/models/system_menu.go @@ -13,7 +13,7 @@ type SystemMenu struct { Sort int64 `orm:"column(sort);default(0)" json:"sort"` // 排序号 Status int8 `orm:"column(status);default(0)" json:"status"` // 状态:1-启用,0-禁用 IsVisible *int8 `orm:"column(is_visible);null" json:"isVisible"` // 是否显示:1-显示 0-不显示 - IsPlatform *int8 `orm:"column(is_platform);null" json:"isPlatform"` // 是否平台:1-是 0-否 + Views *string `orm:"column(views);size(255);null" json:"views"` // 菜单显示端(JSON数组字符串):[1]=平台端 [2]=租户端 [1,2]=双端 Type int8 `orm:"column(type)" json:"type"` // 菜单类型:1-目录,2-页面,3-接口 Permission *string `orm:"column(permission);size(100);null" json:"permission"` // 权限标识(按钮类型时填写) Creater *string `orm:"column(creater);size(50);null" json:"creater"` // 创建者 diff --git a/routers/backend/backend.go b/routers/backend/backend.go index e9eccbf..c7e9c84 100644 --- a/routers/backend/backend.go +++ b/routers/backend/backend.go @@ -38,4 +38,13 @@ func RegisterAuthRoutes() { beego.Router("/backend/createmenu", &controllers.AdminMenuController{}, "post:CreateMenu") beego.Router("/backend/updatemenu/:id", &controllers.AdminMenuController{}, "put:UpdateMenu") beego.Router("/backend/deletemenu/:id", &controllers.AdminMenuController{}, "delete:DeleteMenu") + + // 模块管理(yz_system_modules)——语义更正:租户端走 /backend/modules/* + beego.Router("/backend/modules/list", &controllers.PlatformModulesController{}, "get:GetList") + beego.Router("/backend/modules/getTenantList", &controllers.PlatformModulesController{}, "get:GetTenantList") + beego.Router("/backend/modules/select/list", &controllers.PlatformModulesController{}, "get:GetSelectList") + beego.Router("/backend/modules/status", &controllers.PlatformModulesController{}, "post:ChangeStatus") + beego.Router("/backend/modules/batchDelete", &controllers.PlatformModulesController{}, "post:BatchDelete") + beego.Router("/backend/modules", &controllers.PlatformModulesController{}, "post:Add") + beego.Router("/backend/modules/:id", &controllers.PlatformModulesController{}, "get:GetDetail;put:Edit;delete:Delete") } diff --git a/routers/platform/platform.go b/routers/platform/platform.go index b1a6f26..3ab189a 100644 --- a/routers/platform/platform.go +++ b/routers/platform/platform.go @@ -19,6 +19,8 @@ func Register() { beego.Router("/platform/login/getGeetest3Infos", &controllers.PlatformAuthController{}, "get:GetGeetest3Infos") beego.Router("/platform/login/getGeetest4Infos", &controllers.PlatformAuthController{}, "get:GetGeetest4Infos") beego.Router("/platform/login/getOpenVerify", &controllers.PlatformAuthController{}, "get:GetOpenVerify") + beego.Router("/platform/loginVerifyInfos", &controllers.PlatformLoginVerifyController{}, "get:GetLoginVerifyInfos") + beego.Router("/platform/saveloginVerifyInfos", &controllers.PlatformLoginVerifyController{}, "post:SaveLoginVerifyInfos") // 找回密码相关 beego.Router("/platform/resetPassword", &controllers.PlatformAuthController{}, "post:ResetPassword") diff --git a/services/login_verify_code.go b/services/login_verify_code.go new file mode 100644 index 0000000..8a24c9e --- /dev/null +++ b/services/login_verify_code.go @@ -0,0 +1,130 @@ +package services + +import ( + "errors" + "fmt" + "math/rand" + "strings" + "sync" + "time" + + "server/models" +) + +type loginCodeItem struct { + Code string + Channel string + ExpiredAt time.Time +} + +var loginCodeStore sync.Map + +func codeKey(account, channel string) string { + return strings.ToLower(strings.TrimSpace(account)) + "|" + strings.TrimSpace(channel) +} + +func SendPlatformLoginCode(account, channel string) error { + account = strings.TrimSpace(account) + channel = strings.TrimSpace(channel) + if account == "" { + return errors.New("账号不能为空") + } + if channel != "sms" && channel != "email" { + return errors.New("仅支持短信或邮箱验证码") + } + + var u models.AdminUser + if err := models.Orm.QueryTable(new(models.AdminUser)).Filter("account", account).One(&u); err != nil { + return errors.New("用户不存在") + } + if u.Status == 0 { + return errors.New("账号已禁用") + } + if channel == "sms" && (u.Phone == nil || strings.TrimSpace(*u.Phone) == "") { + return errors.New("该账号未绑定手机号") + } + if channel == "email" && (u.Email == nil || strings.TrimSpace(*u.Email) == "") { + return errors.New("该账号未绑定邮箱") + } + + rand.Seed(time.Now().UnixNano()) + code := fmt.Sprintf("%06d", rand.Intn(1000000)) + loginCodeStore.Store(codeKey(account, channel), loginCodeItem{ + Code: code, + Channel: channel, + ExpiredAt: time.Now().Add(5 * time.Minute), + }) + // TODO: 接入短信/邮箱发送通道。当前阶段只做服务端验证码校验链路。 + return nil +} + +func VerifyPlatformLoginCode(account, channel, code string) error { + account = strings.TrimSpace(account) + channel = strings.TrimSpace(channel) + code = strings.TrimSpace(code) + if account == "" || code == "" { + return errors.New("验证码不能为空") + } + val, ok := loginCodeStore.Load(codeKey(account, channel)) + if !ok { + return errors.New("验证码不存在或已失效") + } + item, ok := val.(loginCodeItem) + if !ok { + return errors.New("验证码状态异常") + } + if time.Now().After(item.ExpiredAt) { + loginCodeStore.Delete(codeKey(account, channel)) + return errors.New("验证码已过期") + } + if item.Code != code { + return errors.New("验证码错误") + } + loginCodeStore.Delete(codeKey(account, channel)) + return nil +} + +func SendBackendLoginCode(tenantName, account, channel string) error { + tenantName = strings.TrimSpace(tenantName) + account = strings.TrimSpace(account) + channel = strings.TrimSpace(channel) + if tenantName == "" || account == "" { + return errors.New("租户名称和账号不能为空") + } + if channel != "sms" && channel != "email" { + return errors.New("仅支持短信或邮箱验证码") + } + var tenant models.Tenant + if err := models.Orm.QueryTable(new(models.Tenant)).Filter("tenant_name", tenantName).One(&tenant); err != nil { + return errors.New("租户不存在") + } + var user models.TenantUser + if err := models.Orm.QueryTable(new(models.TenantUser)). + Filter("tid", tenant.ID). + Filter("account", account). + One(&user); err != nil { + return errors.New("用户不存在") + } + if user.Status == 0 { + return errors.New("账号已禁用") + } + if channel == "sms" && (user.Phone == nil || strings.TrimSpace(*user.Phone) == "") { + return errors.New("该账号未绑定手机号") + } + if channel == "email" && (user.Email == nil || strings.TrimSpace(*user.Email) == "") { + return errors.New("该账号未绑定邮箱") + } + rand.Seed(time.Now().UnixNano()) + code := fmt.Sprintf("%06d", rand.Intn(1000000)) + loginCodeStore.Store(codeKey(tenantName+"#"+account, channel), loginCodeItem{ + Code: code, + Channel: channel, + ExpiredAt: time.Now().Add(5 * time.Minute), + }) + return nil +} + +func VerifyBackendLoginCode(tenantName, account, channel, code string) error { + return VerifyPlatformLoginCode(tenantName+"#"+account, channel, code) +} +