# 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`(文件优先,环境变量永不生效)。