签入版本
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
# 4.4 反射元数据驱动机制
|
||||
|
||||
:::tip 概述
|
||||
本项目最核心的机制:**RouteSpec 表即路由定义**。`router.Bind` 反射扫描控制器方法,按声明注册路由并写入进程内路由元数据表,权限校验、操作日志、公开白名单、OpenAPI 四个消费方读同一份数据。
|
||||
:::
|
||||
|
||||
## 为什么这样设计
|
||||
|
||||
传统做法中路由、权限注解、日志注解分散在三处,漏配一处就出安全洞。本项目把它们收敛为一条声明:
|
||||
|
||||
```go
|
||||
"Page": {Method: "GET", Path: "/api/dept/page", Summary: "部门分页",
|
||||
Permission: "sys:dept:page", Log: "部门管理-查询分页记录", LogType: "query"},
|
||||
```
|
||||
|
||||
一条声明同时回答:路由怎么注册、谁可以访问、记不记日志、记什么类型。
|
||||
|
||||
## Bind 执行流程
|
||||
|
||||
```text
|
||||
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)
|
||||
|
||||
```go
|
||||
// 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 注册的免登录路径:
|
||||
|
||||
```text
|
||||
/api/health 健康检查
|
||||
/api.json, /swagger* 文档端点(api.docs 开关)
|
||||
/metrics 指标端点(api.metrics 开关)
|
||||
```
|
||||
|
||||
业务路由的公开判断一律走元数据表,两个来源在 `IsPublicRequest` 汇合。
|
||||
|
||||
## 对开发的影响
|
||||
|
||||
| 场景 | 做法 |
|
||||
|---|---|
|
||||
| 新增接口 | RouteSpec 表加一条 + handler 加同名方法,无注解、无散落注册 |
|
||||
| 改权限码 | 只改声明,同步更新菜单节点 |
|
||||
| 排查 403 | 看元数据表的 Permission 与角色授权 |
|
||||
| 生成器 | 生成器自动追加 Bind 声明块——菜单/权限/路由一致性由工具保证 |
|
||||
|
||||
## 下一步
|
||||
|
||||
→ [4.5 前端分层架构](/zh/architecture/frontend-layer.html)
|
||||
Reference in New Issue
Block a user