117 lines
5.5 KiB
Markdown
117 lines
5.5 KiB
Markdown
# 4.7 横切机制
|
||
|
||
:::tip 概述
|
||
审计字段盖章、软删除改写、租户隔离三类横切逻辑不在任何业务代码中出现——它们统一在 gdb 模型钩子层(internal/model/hooks)执行。配合中间件链与 Bind 包装层,构成本项目的横切机制全景。
|
||
:::
|
||
|
||
## 横切机制全景
|
||
|
||
```text
|
||
请求横切(中间件链) 数据横切(模型钩子) 契约横切(Bind 包装层)
|
||
CORS Insert 盖章 权限校验(gmeta permission)
|
||
Response(panic/元信息) Update 盖章 参数解析(gvalid)
|
||
RateLimit Delete → 软删 信封写出
|
||
Demo del_flag=0 注入 OpenAPI 补注册
|
||
JWT(身份) tenant_id 注入
|
||
Tenant(状态)
|
||
OperationLog(采集)
|
||
```
|
||
|
||
## 全局钩子(internal/model/hooks/hooks.go)
|
||
|
||
钩子经 `dao.{X}.Ctx(ctx)` 构造模型时挂载(自定义模板 dao_index.tpl):
|
||
|
||
| 钩子 | 触发 | 行为 |
|
||
|------|------|------|
|
||
| Insert | 任意 Insert | 主键交数据库自增(不填 id);补 del_flag=0、create_time、create_user(ctx 用户名,回退真实姓名);租户级表自动携带 tenant_id |
|
||
| Update | 任意 Update | 自动盖章 update_time / update_user(仅 map 数据生效) |
|
||
| Delete | 任意 Delete | 物理删除一律改写为 `UPDATE del_flag=1` |
|
||
|
||
### 租户盖章规则(Insert)
|
||
|
||
```text
|
||
租户用户插入未显式指定 tenant_id → 填自身(防逃逸)
|
||
超管未显式指定 → 填 tenant.defaultId(默认 1)
|
||
显式指定具体值 → 从显式值
|
||
显式 0 → 建平台共享行
|
||
```
|
||
|
||
### 查询条件注入(hooks.ApplyCtx)
|
||
|
||
```text
|
||
所有表: WHERE del_flag = 0
|
||
租户级表且非平台上下文:WHERE tenant_id IN (0, 当前租户)
|
||
超管(ctx tenant_id=0):不过滤,全量可见
|
||
```
|
||
|
||
## 租户级表清单(tenantTables,9 张)
|
||
|
||
```text
|
||
goframe_user / role / dept / level / position / article / category / notice / link
|
||
```
|
||
|
||
**新增租户级表必须同步修改 hooks.go 的 tenantTables**(代码生成器不自动维护此清单)。漏加的后果是租户数据互相可见。
|
||
|
||
## 两层行级过滤的叠加关系
|
||
|
||
```text
|
||
第一层(硬规则,不可配置):租户隔离
|
||
dao.Ctx / hooks.ApplyCtx 注入 tenant_id IN(0,自身)
|
||
先于一切业务过滤,任何模块不可绕过
|
||
|
||
第二层(可配置):角色数据范围(data_scope 四档)
|
||
ModuleSpec.DataScope 挂点,在 applyFilters 链末环叠加:
|
||
Like → Eq → Keyword → Filter → DataScope
|
||
共享工厂 ApplyDataScope(deptColumn),模块一行声明即生效
|
||
```
|
||
|
||
详见 [5.8 多租户隔离](../core/tenant) 与 [5.9 数据权限](../core/data-scope)。
|
||
|
||
## 请求横切:中间件与包装层分工
|
||
|
||
```text
|
||
中间件链(跨路由共性) Bind 包装层(路由级个性)
|
||
认证状态(401) 权限码校验(403,读 gmeta)
|
||
租户状态(1005/1006) 参数解析与校验(gvalid)
|
||
限流(429)/演示(1015) 信封写出
|
||
日志采集(不短路) OpenAPI 元数据
|
||
```
|
||
|
||
权限校验不设独立中间件——因为它需要每路由不同的权限码,放在读路由元数据的包装层最自然(与日志注解同源,"注解即事实")。
|
||
|
||
## 日志横切(OperationLog)
|
||
|
||
```text
|
||
gmeta log 标签 → 路由元数据 → 中间件组装 entry(脱敏 param/result)
|
||
→ 有界通道 2048(满则丢弃告警,不阻塞业务)
|
||
→ 单 worker 攒批(100 条或 1s flush)
|
||
→ LogSink(operation_log 模块 init 注册)→ INSERT goframe_operation_log
|
||
```
|
||
|
||
脱敏规则:param 中 password/oldPassword/newPassword/salt/access_token/refresh_token → `***`;result 截断 8000 字节(UTF-8 边界回退)。
|
||
|
||
## 配置横切:环境变量覆盖(utility/config)
|
||
|
||
```text
|
||
启动入口 cmd.Run 最先执行 config.RegisterEnvOverride()
|
||
→ 包装默认文件适配器为 env-first 适配器(gcfg.Adapter 官方扩展点)
|
||
→ 全部 Get/MustGet 读取点零改动获得覆盖能力(含 gdb/gredis 经整节点读连接配置)
|
||
```
|
||
|
||
```text
|
||
优先级 GF_ 环境变量 > 配置文件(12-Factor;未设环境变量时行为不变)
|
||
键名 配置路径大写下划线 + GF_ 前缀(database.default.link → GF_DATABASE_DEFAULT_LINK)
|
||
空值 环境变量空串视为未设置,不清空文件有效值
|
||
范围 仅覆盖配置文件中已存在的键(新增键先在 config.yaml 声明)
|
||
```
|
||
|
||
典型场景:容器部署注入 `GF_DATABASE_DEFAULT_LINK` / `GF_FILE_UPLOADDIR` / `GF_JWT_SECRET`,镜像内 config.yaml 保持默认不动(同一镜像可部署多环境)。键名规则与 Docker 注入方式见 [2.5 配置文件说明](../quickstart/env-vars)。
|
||
|
||
## 横切开发纪律
|
||
|
||
+ 新增横切逻辑优先问:属于请求横切(中间件)/ 数据横切(钩子)/ 契约横切(Bind)/ 配置横切(utility/config)哪一层?不要散落在业务代码。
|
||
+ 修改钩子必须全表回归——它影响 25 张表的所有写入。
|
||
+ 钩子内禁止业务分支判断(如"某模块跳过盖章")——确有需要走逃生舱(显式值优先于自动盖章)。
|
||
+ 缓存类横切(字典/身份/租户摘要)必须有明确失效点或短 TTL 兜底(见 [4.8 缓存架构设计](./cache))。
|
||
+ 业务代码读配置一律用 `g.Cfg().Get/MustGet`,不要用 `GetWithEnv`(文件优先,环境变量永不生效)。
|