4.0 KiB
4.0 KiB
4.4 反射元数据驱动机制
:::tip 概述
本项目最核心的机制:RouteSpec 表即路由定义。router.Bind 反射扫描控制器方法,按声明注册路由并写入进程内路由元数据表,权限校验、操作日志、公开白名单、OpenAPI 四个消费方读同一份数据。
:::
为什么这样设计
传统做法中路由、权限注解、日志注解分散在三处,漏配一处就出安全洞。本项目把它们收敛为一条声明:
"Page": {Method: "GET", Path: "/api/dept/page", Summary: "部门分页",
Permission: "sys:dept:page", Log: "部门管理-查询分页记录", LogType: "query"},
一条声明同时回答:路由怎么注册、谁可以访问、记不记日志、记什么类型。
Bind 执行流程
registerControllers 内 Bind(rg, new(handler.CXxx), map[string]RouteSpec{...})
│
▼
反射取控制器类型的方法集(含导出方法)
│
▼
按签名过滤:func (c *CXxx) M(ctx context.Context, req *dto.XxxReq) (*dto.XxxRes, error)
│ 签名不匹配 → 跳过(表现为 404,启动期即定)
▼
按方法名查 RouteSpec 表 → 逐条 rg.Handle(Method, Path, 包装函数)
│
▼
包装函数(请求期):
UnmarshalBody 逃生舱判断 → 绑定写入 query→body→path → binding 校验
→ 调 handler 方法 → response 信封写出
│
▼
meta.Register 写入路由元数据表(进程内 map,读写锁保护)
元数据表(RouteMeta)
// internal/router/meta/meta.go
type RouteMeta struct {
Method string // GET/POST/PUT/DELETE
Path string // 路由模板
Summary string // OpenAPI 摘要
Permission string // 权限节点;空=登录即可
Log string // 日志标题;空=不记录
LogType string // query/add/update/delete/import/export/reset
Public bool // 免登录
ReqType reflect.Type // 请求类型(文档 schema 反射用)
ResType reflect.Type // 响应类型
}
存储为进程内内存表(map["GET /api/dept/page"]RouteMeta + RWMutex),启动期写入、请求期只读,无持久化与跨进程同步——生命周期与进程一致。
四个消费方(同源)
| 消费方 | 读什么 | 行为 |
|---|---|---|
| authz 中间件 | Permission | 校验当前用户权限点集合是否命中;空则只要登录 |
| operation_log 中间件 | Log / LogType | 采集请求摘要异步落库;query 类受 logQuery 开关控制 |
| public 判断 | Public | IsPublicRequest 免登录放行(配合基础设施白名单) |
| OpenAPI 生成 | 全部 + ReqType/ResType | /api.json 与 /swagger 端点按元数据生成文档 |
任一消费方都不再自己维护清单——声明改一处,四处同步生效。
绑定细节
- 写入顺序 query → body → path,后写覆盖 → 最终优先级 path > body > query
- 校验 读 binding 标签,错误消息取 msg 标签,失败直接信封返回
- 逃生舱 结构体实现
UnmarshalBody(c *gin.Context) error则跳过默认解析(文件上传)
基础设施白名单(不经元数据)
internal/middleware/public.go 维护少量不经 Bind 注册的免登录路径:
/api/health 健康检查
/api.json, /swagger* 文档端点(api.docs 开关)
/metrics 指标端点(api.metrics 开关)
业务路由的公开判断一律走元数据表,两个来源在 IsPublicRequest 汇合。
对开发的影响
| 场景 | 做法 |
|---|---|
| 新增接口 | RouteSpec 表加一条 + handler 加同名方法,无注解、无散落注册 |
| 改权限码 | 只改声明,同步更新菜单节点 |
| 排查 403 | 看元数据表的 Permission 与角色授权 |
| 生成器 | 生成器自动追加 Bind 声明块——菜单/权限/路由一致性由工具保证 |