Some checks failed
Build and Deploy (service.xpcool.com) / build-and-deploy (push) Failing after 35s
- 新增新房预售证表 house_presale 存储预售许可信息 - 为 house_community 表添加 avg_price 字段存储小区参考均价 - 增强服务器操作审计日志功能,新增管理员账号、IP归属地、错误信息、UA等字段 - 实现纯Go版IP归属地离线解析器,无外部依赖,支持二分查找 - 优化看房列表查询逻辑,修复Fields设置位置导致的SQL语法错误 - 集成招聘模块,添加独立数据库配置和Bark推送服务支持 - 重构日志查询接口,支持多维度筛选和综合分页列表展示 - 更新DAO实体结构同步数据库表结构调整
108 lines
3.8 KiB
Go
108 lines
3.8 KiB
Go
package middleware
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"github.com/gogf/gf/v2/net/ghttp"
|
||
"service.xpcool.com/internal/consts"
|
||
"service.xpcool.com/internal/library/jwt"
|
||
"service.xpcool.com/internal/library/response"
|
||
"strings"
|
||
"time"
|
||
)
|
||
|
||
type contextKey string
|
||
|
||
const (
|
||
UserIDKey contextKey = "userId"
|
||
AdminIDKey contextKey = "adminId"
|
||
PermissionKey contextKey = "permission"
|
||
)
|
||
|
||
func bearer(r *ghttp.Request) string {
|
||
// 统一从 Authorization: Bearer <token> 提取令牌。
|
||
return strings.TrimSpace(strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer"))
|
||
}
|
||
func auditParam(raw string) string {
|
||
// 操作日志需要参数用于审计,但绝不记录密码、令牌和验证码等敏感字段。
|
||
lower := strings.ToLower(raw)
|
||
if strings.Contains(lower, "password") || strings.Contains(lower, "token") || strings.Contains(lower, "code") {
|
||
return "[redacted]"
|
||
}
|
||
return raw
|
||
}
|
||
|
||
// extractErrorMsg 当响应状态码 >= 400 时,从响应体 JSON 中提取 msg 作为失败原因摘要。
|
||
// 非失败场景返回空字符串,避免无意义写入。
|
||
func extractErrorMsg(r *ghttp.Request) string {
|
||
if r.Response.Status < 400 {
|
||
return ""
|
||
}
|
||
body := r.Response.BufferString()
|
||
if len(body) == 0 {
|
||
return ""
|
||
}
|
||
var out struct {
|
||
Msg string `json:"msg"`
|
||
}
|
||
if err := json.Unmarshal([]byte(body), &out); err == nil && out.Msg != "" {
|
||
return out.Msg
|
||
}
|
||
return string(body)
|
||
}
|
||
func UserAuth(s *jwt.Service) ghttp.HandlerFunc {
|
||
// 用户端仅接受 scope=user 的 access token。
|
||
return func(r *ghttp.Request) {
|
||
c, err := s.Parse(bearer(r), "access", "user")
|
||
if err != nil {
|
||
response.JSON(r, consts.CodeUnauthorized, "login required", nil)
|
||
return
|
||
}
|
||
r.SetCtxVar(UserIDKey, c.Subject)
|
||
r.Middleware.Next()
|
||
}
|
||
}
|
||
func AdminAuthOnly(s *jwt.Service) ghttp.HandlerFunc {
|
||
// 仅校验 admin access token(不要求 X-Permission),用于登录后即可访问的
|
||
// 个人资料/权限码/菜单路由等接口,如 /auth/info、/auth/codes、/menu/routes。
|
||
return func(r *ghttp.Request) {
|
||
c, err := s.Parse(bearer(r), "access", "admin")
|
||
if err != nil {
|
||
response.JSON(r, consts.CodeUnauthorized, "admin login required", nil)
|
||
return
|
||
}
|
||
r.SetCtxVar(AdminIDKey, c.Subject)
|
||
r.Middleware.Next()
|
||
}
|
||
}
|
||
func AdminAuth(s *jwt.Service, permissionLookup func(context.Context, string, string) (string, error), permissionCheck func(context.Context, uint64, string) (bool, error), audit func(context.Context, uint64, string, string, string, string, string, int, int, string, string)) ghttp.HandlerFunc {
|
||
// 管理端接口鉴权:先解析 admin token,再按「请求方法+路径」反查所需权限码
|
||
// (admin_menu type=2 行的 path 映射),最后校验该管理员是否拥有该权限码。
|
||
// 未配置映射的接口一律拒绝,防止用任意已拥有权限码越权访问。
|
||
return func(r *ghttp.Request) {
|
||
start := time.Now()
|
||
c, err := s.Parse(bearer(r), "access", "admin")
|
||
if err != nil {
|
||
response.JSON(r, consts.CodeUnauthorized, "admin login required", nil)
|
||
return
|
||
}
|
||
permission, err := permissionLookup(r.Context(), r.Method, r.URL.Path)
|
||
if err != nil || permission == "" {
|
||
response.JSON(r, consts.CodeForbidden, "permission mapping not configured", nil)
|
||
return
|
||
}
|
||
ok, err := permissionCheck(r.Context(), c.Subject, permission)
|
||
if err != nil || !ok {
|
||
response.JSON(r, consts.CodeAdminPermissionDenied, "permission denied", nil)
|
||
return
|
||
}
|
||
defer func() {
|
||
// 失败时从响应体解析 msg 作为错误摘要;UA 直接取请求头。
|
||
audit(r.Context(), c.Subject, permission, r.Method, r.URL.Path, r.GetClientIp(), auditParam(r.GetBodyString()), int(time.Since(start).Milliseconds()), r.Response.Status, r.Header.Get("User-Agent"), extractErrorMsg(r))
|
||
}()
|
||
r.SetCtxVar(AdminIDKey, c.Subject)
|
||
r.SetCtxVar(PermissionKey, permission)
|
||
r.Middleware.Next()
|
||
}
|
||
}
|