小程序一些功能更新

This commit is contained in:
李琦
2026-09-29 10:57:01 +08:00
parent 0cb70a7618
commit 3bb7fa8db3
96 changed files with 13890 additions and 213 deletions

View File

@@ -0,0 +1,207 @@
package logic
import (
"context"
"net/http"
"sort"
"strings"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"tool-api/internal/consts"
)
// ============================================================================
// 后台权限分级(T17)
//
// 模型:角色(超管/运营/只读)→ 权限点集合;中间件按「路由 → 权限点」放行。
// 权限点与角色的对应关系集中在本文件,避免散落到每个 handler。
// ============================================================================
// 权限点
const (
PermUserWrite = "user:write"
PermToolWrite = "tool:write"
PermModuleWrite = "module:write"
PermLevelWrite = "level:write"
PermFeedbackWrite = "feedback:write"
PermQuotaWrite = "quota:write"
PermEventWrite = "event:write"
PermAdminManage = "admin:manage"
PermAuditRead = "audit:read"
// 订单域:查看 / 退款(退款为可逆性资金动作,单独权限点便于收口)
PermOrderRead = "order:read"
PermOrderWrite = "order:write"
)
// rolePermissions 角色 → 权限点集合。
//
// 超管:全部
// 运营:内容读写(用户/工具/模块/等级/反馈/额度/年会/订单),不含管理员管理与审计
// 只读:无写权限,仅可查看(含订单查看)
var rolePermissions = map[string]map[string]bool{
consts.AdminRoleSuper: {
PermUserWrite: true, PermToolWrite: true, PermModuleWrite: true, PermLevelWrite: true,
PermFeedbackWrite: true, PermQuotaWrite: true, PermEventWrite: true,
PermAdminManage: true, PermAuditRead: true,
PermOrderRead: true, PermOrderWrite: true,
},
consts.AdminRoleOperator: {
PermUserWrite: true, PermToolWrite: true, PermModuleWrite: true, PermLevelWrite: true,
PermFeedbackWrite: true, PermQuotaWrite: true, PermEventWrite: true,
PermOrderRead: true, PermOrderWrite: true,
},
consts.AdminRoleReadonly: {
PermOrderRead: true,
},
}
// HasPermission 判断角色是否拥有权限点;perm 为空表示「仅需登录」。
// 未知角色(历史脏数据)→ 按最小权限处理(false),避免越权。
func HasPermission(role, perm string) bool {
if perm == "" {
return true
}
perms, ok := rolePermissions[role]
if !ok {
return false
}
return perms[perm]
}
// PermissionsOf 返回角色的权限点列表(前端据此隐藏入口),已排序。
func PermissionsOf(role string) []string {
perms := rolePermissions[role]
out := make([]string, 0, len(perms))
for p := range perms {
out = append(out, p)
}
sort.Strings(out)
return out
}
// roleNameOf 角色值 → 展示名
func roleNameOf(role string) string {
switch role {
case consts.AdminRoleSuper:
return "超级管理员"
case consts.AdminRoleOperator:
return "运营"
case consts.AdminRoleReadonly:
return "只读"
}
return role
}
// permissionForRoute 路由 → 所需权限点。空串 = 仅需登录(只读接口)。
// 采用「方法 + 路径前缀」映射;/user/quota-period 是额度操作,需先于 /user/ 命中。
func permissionForRoute(method, path string) string {
if method != http.MethodPost {
if strings.HasPrefix(path, "/audit/") {
return PermAuditRead
}
if strings.HasPrefix(path, "/admin/") {
return PermAdminManage
}
if strings.HasPrefix(path, "/order/") {
return PermOrderRead
}
return ""
}
switch {
case path == "/user/quota-period":
return PermQuotaWrite
case path == "/audit/record":
// 上报审计≠读取审计:任何登录的管理员都可记录自己的动作(如导出)
return ""
case strings.HasPrefix(path, "/user/"):
return PermUserWrite
case strings.HasPrefix(path, "/tools/"):
return PermToolWrite
case strings.HasPrefix(path, "/module/"):
return PermModuleWrite
case strings.HasPrefix(path, "/level/"):
return PermLevelWrite
case strings.HasPrefix(path, "/feedback/"):
return PermFeedbackWrite
case strings.HasPrefix(path, "/quota/"), strings.HasPrefix(path, "/plan/"):
return PermQuotaWrite
case strings.HasPrefix(path, "/promo/"), strings.HasPrefix(path, "/coupon/"):
// 优惠码 / 优惠券属商业化配置,归入「额度写」权限(运营+超管可写;只读不可写)
return PermQuotaWrite
case strings.HasPrefix(path, "/event/"):
return PermEventWrite
case strings.HasPrefix(path, "/order/"):
// 订单退款属可逆性资金动作,按「订单写」权限放行(运营+超管可写;只读不可写)
return PermOrderWrite
case strings.HasPrefix(path, "/admin/"):
return PermAdminManage
case strings.HasPrefix(path, "/audit/"):
return PermAuditRead
}
return ""
}
func writeForbidden(r *ghttp.Request, message string) {
r.Response.WriteJson(g.Map{"code": 403, "message": message, "result": nil})
r.Exit()
}
// AdminPerm 权限中间件(须挂在 AdminAuth 之后)。
// 从库中复核管理员最新状态与角色(使「停用/改角色」立即生效,无需重新登录),
// 按「路由 → 权限点」放行;管理员信息写入上下文,供审计复用。
func AdminPerm(r *ghttp.Request) {
adminId := r.GetCtxVar(consts.CtxAdminId).Int64()
record, err := g.Model(consts.TableAdminUsers).Where("id", adminId).One()
if err != nil {
// 鉴权已由 AdminAuth 完成;DB 抖动不应把所有人挡在门外 → 放行并告警
g.Log().Warningf(r.Context(), "[adminperm] 读取管理员失败,放行: %v", err)
r.Middleware.Next()
return
}
if record.IsEmpty() {
writeForbidden(r, "管理员不存在,请重新登录")
return
}
// status 列可能因迁移未执行而缺失:缺失视为启用(fail-open),避免全站被锁
if v, ok := record["status"]; ok && v.Int() != consts.AdminStatusEnabled {
writeForbidden(r, "账号已停用")
return
}
role := record["role_value"].String()
if !HasPermission(role, permissionForRoute(r.Method, r.URL.Path)) {
writeForbidden(r, "当前角色无此操作权限")
return
}
r.SetCtxVar(consts.CtxAdminRole, role)
r.SetCtxVar(consts.CtxAdminAccount, record["account"].String())
r.SetCtxVar(consts.CtxAdminName, record["nick_name"].String())
r.Middleware.Next()
}
// ===== 审计上下文取值 =====
func CtxAdminRole(ctx context.Context) string {
req := g.RequestFromCtx(ctx)
if req == nil {
return ""
}
return req.GetCtxVar(consts.CtxAdminRole).String()
}
func CtxAdminAccount(ctx context.Context) string {
req := g.RequestFromCtx(ctx)
if req == nil {
return ""
}
return req.GetCtxVar(consts.CtxAdminAccount).String()
}
func CtxAdminName(ctx context.Context) string {
req := g.RequestFromCtx(ctx)
if req == nil {
return ""
}
return req.GetCtxVar(consts.CtxAdminName).String()
}