16 KiB
权限字段映射与 SQL 改造规范
适用范围:
leaudit-platform后端权限改造中的查询语句、资源详情、导出下载、统计聚合、管理列表
文档定位:为统一数据范围执行器的接入提供字段映射标准、SQL 拼接规范、资源回溯规范和反模式约束。
1. 文档目标
这份文档解决的是一个非常具体的问题:
统一数据范围执行器设计出来以后,后端各模块到底怎么把它安全、稳定地接到现有 SQL 上。
当前项目最大风险不是“不会写 scope”,而是:
- 同一个模块里不同查询使用了不同字段口径
- 列表、详情、下载、导出没有复用同一主资源边界
- 各 service 自己拼
1 = 0、region、created_by,可维护性很差 - 多表 join 时,究竟该按
u.area还是d.region,目前没有统一规范
所以这份文档的目标是:
- 定义统一字段映射标准
- 定义统一 alias 规范
- 定义统一 scope 子句拼接方式
- 定义详情、下载、导出等派生资源的“主资源回溯”规则
- 定义改造时哪些 SQL 能保留,哪些必须重构
2. 当前代码里已经暴露出的典型问题
结合现有实现,已经确认有以下模式:
documentServiceImpl/govdocServiceImpl- 通过
d.region+f.created_by控制范围
- 通过
usageStatsServiceImpl- 有时按
u.area - 有时按
d.region - 有时还要按
e.area_snapshot
- 有时按
contractTemplateServiceImpl- 既有
t.region - 又有
t.created_by - 还混入“省级模板可见”
- 既有
rbacAdminServiceImpl- 用户列表、组织树本质上按
u.area
- 用户列表、组织树本质上按
这说明项目不是“没有规则”,而是规则散了。
3. 核心原则
3.1 先确定主资源,再确定 scope 字段
SQL 改造第一步不是拼条件,而是先回答:
- 这个接口的主资源是谁?
例如:
- 文档列表:主资源是
document - 文档状态:主资源仍是
document - 公文报告下载:主资源不是
report_artifact,而是govdoc document - RAG 文档分段列表:主资源不是
segment,而是dataset - 交叉评查提案导出:主资源不是
proposal,而是“用户参与的任务文档”
只有先确定主资源,scope 才不会跑偏。
3.2 范围条件必须挂在主资源口径上
同一个接口里如果出现多表:
document ddocument_file fsso_users u
必须先规定:
- area 看哪个表
- self 看哪个表
不能在同一个模块里一会儿 u.area,一会儿 d.region,又没有解释。
3.3 列表、详情、删除、下载、导出必须继承同一套 scope
这条必须强制执行。
不允许出现:
- 列表按文档范围过滤
- 详情直接按
id - 下载直接按附件
id - 导出直接按 run
id
凡是派生资源,都必须回溯主资源。
4. 统一字段映射标准
4.1 标准字段分类
建议统一按 5 类字段管理:
area_fieldcreator_fieldowner_fielduser_fieldpublic_field
建议结构:
@dataclass
class ScopeFieldMapping:
area_field: str | None = None
creator_field: str | None = None
owner_field: str | None = None
user_field: str | None = None
public_field: str | None = None
4.2 字段语义定义
area_field
表示资源的地区归属字段。
例如:
d.regiont.regiondataset.areaapp.areau.area
creator_field
表示“创建该资源的用户”。
例如:
f.created_byt.created_byproposal.created_by
owner_field
表示“资源所有者”,和创建者不一定相同。
如果当前项目没有成熟 owner 模型,可以先不启用。
user_field
用于用户本身或用户快照类数据。
例如:
u.ide.user_id
public_field
表示是否公共可见。
例如:
dataset.is_publicapp.is_public
5. 各模块字段映射表
5.1 文档模块
主资源:leaudit_documents d
推荐映射:
| 类型 | 字段 |
|---|---|
area_field |
d.region |
creator_field |
f.created_by |
owner_field |
暂无 |
user_field |
f.created_by |
说明:
- 当前代码事实口径就是
d.region + f.created_by - 文档的“自己数据”不建议改成
d.created_by,因为现有实现明显依赖文件记录
5.2 公文模块
主资源:govdoc document / leaudit_documents d
推荐映射:
| 类型 | 字段 |
|---|---|
area_field |
d.region |
creator_field |
f.created_by |
user_field |
f.created_by |
说明:
- 公文 run、报告、原文都必须回溯到
d.region和f.created_by
5.3 使用统计模块
这块必须拆成两个口径。
用户口径统计
主资源:sso_users u 或 usage_login_events e
推荐映射:
| 类型 | 字段 |
|---|---|
area_field |
u.area 或 e.area_snapshot |
user_field |
u.id 或 e.user_id |
文档口径统计
主资源:文档上传/评查事件
推荐映射:
| 类型 | 字段 |
|---|---|
area_field |
d.region |
creator_field |
f.created_by |
user_field |
f.created_by |
说明:
areaScope=user和areaScope=document本质上是两套字段映射切换- 不能在一个 builder 里靠字符串替换硬凑
5.4 合同模板模块
主资源:contract_template t
推荐映射:
| 类型 | 字段 |
|---|---|
area_field |
t.region |
creator_field |
t.created_by |
public_field |
暂无;如后续有省级公共模板逻辑,应单独建模 |
说明:
- 当前模块有“省级模板 + 本地区模板”可见语义
- 这不是
is_public - 应在
ContractTemplatePolicy中定义成“系统公共范围”,不要混同布尔公开字段
5.5 RBAC 用户管理
主资源:sso_users u
推荐映射:
| 类型 | 字段 |
|---|---|
area_field |
u.area |
user_field |
u.id |
说明:
- 角色对象本身不按 area
- 但“查看哪个用户、给谁分配角色”按
u.area
5.6 RAG 模块
数据集
主资源:dataset
| 类型 | 字段 |
|---|---|
area_field |
dataset.area |
creator_field |
dataset.created_by |
public_field |
dataset.is_public |
聊天应用
主资源:app
| 类型 | 字段 |
|---|---|
area_field |
app.area |
public_field |
dataset.is_public 或显式 app.is_public |
说明:
- 如果 app 的公开性来自关联 dataset,就要在 policy 层明确写清,不要分散在查询里隐式推断
5.7 交叉评查
主资源:任务关系,而不是单表字段。
推荐映射:
| 类型 | 字段 |
|---|---|
user_field |
tm.user_id |
creator_field |
proposal.created_by |
说明:
- 交叉评查以
RELATION模型处理 - 这块不强制要求
area_field
6. Alias 统一规范
统一执行器要稳定接入,SQL alias 必须统一。
建议标准:
| 资源 | 推荐 alias |
|---|---|
| 文档主表 | d |
| 文档文件表 | f |
| 用户表 | u |
| 审查运行表 | r |
| 统计登录事件表 | e |
| 合同模板表 | t |
| RAG 数据集 | dataset |
| RAG 应用 | app |
| 交叉评查任务 | task |
| 交叉评查成员 | tm |
| 交叉评查提案 | proposal |
目的不是强迫重命名所有 SQL,而是:
- 新增或重构 SQL 时尽量统一
QueryScopeBuilder和ModulePolicy才能更容易复用
7. 参数命名规范
当前代码里已经有:
requested_regionscope_regionscope_user_idrequested_user_id
建议固化为统一标准:
| 参数 | 含义 |
|---|---|
requested_area |
请求方传入的地区过滤 |
scope_area |
当前 scope 决策后的有效地区 |
requested_user_id |
请求方显式筛选的用户 |
scope_user_id |
scope 决策后的当前用户 ID |
resource_id |
主资源 ID |
visible_areas |
PUBLIC_MIXED 或公共范围列表 |
不建议再混用:
query_areauser_arearegionscope_region
除非当前模块确实已经固定使用 region 作为数据库字段名,而不是参数名。
建议规则:
- 参数命名一律使用
area - 数据库字段保留
region/area原名
即:
COALESCE(d.region, '') = :scope_area
而不是:
COALESCE(d.region, '') = :scope_region
这样更利于统一执行器复用。
8. 标准 SQL 子句模板
8.1 ALL
无地区限制时:
1 = 1
带请求地区过滤时:
COALESCE({area_field}, '') = :requested_area
8.2 DEPT
COALESCE({area_field}, '') = :scope_area
并且在进入 SQL 前先做规则:
- 若
requested_area非空且不等于scope_area,直接拒绝或生成1 = 0
8.3 SELF
{creator_field} = :scope_user_id
若请求还传了 requested_user_id:
- 与
scope_user_id不相等时直接拒绝
8.4 PUBLIC_MIXED
(
COALESCE({area_field}, '') IN :visible_areas
OR {public_field} = TRUE
)
推荐 visible_areas 至少包含:
- 当前用户地区
省级''
具体由 RagPolicy 决定。
8.5 RELATION
不建议硬编码为通用模板。
应由 CrossReviewPolicy 输出,例如:
EXISTS (
SELECT 1
FROM leaudit_cross_review_task_member tm
WHERE tm.task_id = task.id
AND tm.user_id = :scope_user_id
AND tm.deleted_at IS NULL
)
9. 主资源回溯规范
这是本轮 SQL 改造里最重要的一条。
9.1 什么叫主资源回溯
当接口操作的不是主资源表,而是主资源衍生物时,必须回溯到主资源做 scope 判断。
9.2 必须回溯的场景
文档状态
虽然接口直接拿文档 ID 列表查状态,但本质仍是文档资源。
必须回溯:
documentfile
公文 run / report / original
不能只按:
run_idartifact_iddocument_id
直接查。
必须回溯到:
govdoc document doriginal file f
RAG dataset document / segment
不能只按:
document_idsegment_id
直接做可见性判断。
必须回溯到:
dataset
交叉评查 proposal export
不能只按 DocumentId 导出。
必须回溯到:
- 当前用户是否属于该任务关系链
10. 推荐接入方式
10.1 列表接口
推荐模式:
- 构造基础查询
- 获取
PermissionDecision - 由
QueryScopeBuilder生成scope_clause - 将
scope_clause.sql注入WHERE - 业务筛选条件作为附加条件继续追加
示意:
decision = await permissionScopeFacade.require(...)
scope_clause = queryScopeBuilder.build_by_mapping(...)
where_clauses = [
"d.deleted_at IS NULL",
scope_clause.sql,
]
params = {
**scope_clause.params,
}
10.2 详情接口
推荐模式:
不要先查,再判断。
而要直接:
SELECT ...
FROM ...
WHERE id = :resource_id
AND {scope_clause.sql}
这样天然避免“查到了但后面忘记拒绝”的风险。
10.3 删除/更新接口
推荐两种方式:
方式 A:先用带 scope 的 SELECT 锁定资源
SELECT id
FROM ...
WHERE id = :resource_id
AND {scope_clause.sql}
FOR UPDATE
方式 B:直接带 scope 执行 UPDATE/DELETE
UPDATE ...
SET ...
WHERE id = :resource_id
AND {scope_clause.sql}
建议优先用方式 A,更利于错误提示和审计。
10.4 下载/导出接口
推荐模式:
- 先回溯主资源并带 scope 查出合法记录
- 再根据合法记录生成下载地址或导出文件
不允许:
- 先按附件 ID 或 artifact ID 取到 OSS URL
- 再事后补判断
11. 统计聚合改造规范
统计类 SQL 最容易出错,因为不是简单查明细。
11.1 先收 scope,再聚合
推荐:
WITH scoped_docs AS (
SELECT d.id, d.region, f.created_by
FROM ...
WHERE ...
AND {scope_clause.sql}
)
SELECT ...
FROM scoped_docs
GROUP BY ...
不推荐:
先全量聚合,再在外层做地区过滤。
原因:
- 易错
- 性能差
SELF范围很容易被漏掉
11.2 用户口径和文档口径必须拆分
不要写一个函数同时隐式支持:
u.aread.regione.area_snapshot
建议明确:
build_user_area_scope_clause()build_document_area_scope_clause()build_login_snapshot_scope_clause()
或者在 UsageStatsPolicy 内按口径分发。
12. 反模式清单
以下写法在本轮改造中应视为禁用或逐步清理对象。
12.1 反模式:角色名直接拼 SQL 范围
bool_or(r.role_key IN ('super_admin', 'provincial_admin'))
问题:
- 角色名直接耦合能力
- 新角色无法扩展
12.2 反模式:详情先查后判
row = await session.execute(...)
if row and current_user["can_manage"]:
...
问题:
- 非法资源已经被读出来
- 派生接口最容易漏判
12.3 反模式:用 1 = 0 到处散落表达拒绝
1 = 0 可以作为最终 SQL 结果,但不应成为业务逻辑表达方式。
更推荐:
- 在决策层直接拒绝
- 或由 builder 明确返回
denied clause
12.4 反模式:同一模块 area 字段口径漂移
例如:
- 文档列表按
u.area - 文档详情按
d.region
这种写法必须统一。
12.5 反模式:下载 URL 先取后判
尤其是:
- 原文下载
- 报告下载
- 导出文件
必须先判 scope,再产出 URL。
13. 索引建议
统一执行器接入后,范围过滤会更集中,需要补齐索引。
建议优先确认以下索引:
文档/公文
documents(region)document_files(created_by)document_files(document_id, created_by)
用户
sso_users(area)sso_users(status, deleted_at, area)
合同模板
contract_templates(region)contract_templates(created_by)
RAG
rag_datasets(area, is_public)rag_apps(area)
交叉评查
task_member(task_id, user_id)proposal(document_id, created_by)
说明:
- 索引不是这轮文档的主体,但如果没有,会直接影响统一执行器上线后的查询性能
14. 推荐代码组织
建议新增或统一下面这类方法:
build_scope_clause_for_document(...)
build_scope_clause_for_user(...)
build_scope_clause_for_dataset(...)
resolve_primary_resource_scope(...)
更推荐的最终形态是:
decision = await permissionDecisionService.decide(ctx)
scope_clause = modulePolicy.build_clause(ctx, decision)
这样业务 service 只负责:
- 自身业务查询
- 接入 scope 子句
- 做结果组装
而不是自己解释权限模型。
15. 分模块改造建议
15.1 文档与公文
优先动作:
- 抽出统一
DocumentScopeMapping - 统一
d.region + f.created_by - 所有详情/状态/run/report/download 统一回溯主文档
15.2 使用统计
优先动作:
- 拆分用户口径和文档口径
- 不再通过字符串替换实现
area_snapshot - 将
SELF范围明确化
15.3 合同模板
优先动作:
- 把“省级模板可见”收敛为 policy
- 列表/搜索/详情共享同一范围规则
15.4 RBAC 用户管理
优先动作:
- 用户列表、组织树、角色分配统一按
u.area - 角色元数据接口不强加 area scope
15.5 RAG
优先动作:
PUBLIC_MIXED统一化- dataset 子资源全部回溯 dataset
16. 最终要求
后续所有权限改造相关 SQL,应满足以下要求:
- 能明确说清主资源是谁
- 能明确说清
area_field和creator_field是哪个 - 能明确说清详情/下载/导出是否回溯主资源
- 不再依赖角色名派生范围
- 新增接口时可以直接挂到统一执行器,而不是复制旧判断
如果做不到这 5 条,即使逻辑暂时跑通,也不算完成“统一权限架构”的 SQL 层落地。