approve: [M11b-2c-B] 补 dev-notes-m11b2c 文档 + RBAC/路径声明 + git 收口
This commit is contained in:
parent
a026feb25a
commit
c051b71be3
@ -1,6 +1,5 @@
|
||||
{
|
||||
"tblname": "pbl_artifact",
|
||||
"alias": "pbl_evidence",
|
||||
"title": "学习产出物(artifact)",
|
||||
"params": {
|
||||
"browserfields": {
|
||||
@ -45,13 +44,14 @@
|
||||
"updated_at"
|
||||
],
|
||||
"logined_userorgid": "tenant_id",
|
||||
"new_data_url": "{{entire_url('../api/pbl_artifact_create.dspy')}}",
|
||||
"update_data_url": "{{entire_url('../api/pbl_artifact_update.dspy')}}",
|
||||
"delete_data_url": "{{entire_url('../api/pbl_artifact_delete.dspy')}}",
|
||||
"editable": {
|
||||
"new_data_url": "{{entire_url('../api/pbl_artifact_create.dspy')}}",
|
||||
"update_data_url": "{{entire_url('../api/pbl_artifact_update.dspy')}}",
|
||||
"delete_data_url": "{{entire_url('../api/pbl_artifact_delete.dspy')}}"
|
||||
},
|
||||
"sortby": ["created_at desc"],
|
||||
"order_by": "id DESC",
|
||||
"page_size": 20
|
||||
"sortby": ["created_at desc"]
|
||||
}
|
||||
}
|
||||
@ -43,13 +43,14 @@
|
||||
"updated_at"
|
||||
],
|
||||
"logined_userorgid": "tenant_id",
|
||||
"new_data_url": "{{entire_url('../api/pbl_evidence_collect.dspy')}}",
|
||||
"update_data_url": "{{entire_url('../api/pbl_evidence_update.dspy')}}",
|
||||
"delete_data_url": "{{entire_url('../api/pbl_evidence_delete.dspy')}}",
|
||||
"editable": {
|
||||
"new_data_url": "{{entire_url('../api/pbl_evidence_collect.dspy')}}",
|
||||
"update_data_url": "{{entire_url('../api/pbl_evidence_update.dspy')}}",
|
||||
"delete_data_url": "{{entire_url('../api/pbl_evidence_delete.dspy')}}"
|
||||
},
|
||||
"sortby": ["occurred_at desc"],
|
||||
"order_by": "id DESC",
|
||||
"page_size": 20
|
||||
"sortby": ["occurred_at desc"]
|
||||
}
|
||||
}
|
||||
@ -23,6 +23,11 @@ from pbl_evidence.collector import (
|
||||
collect_evidence_from_events,
|
||||
resolve_event_table,
|
||||
)
|
||||
from pbl_evidence.crud_api import (
|
||||
pbl_artifact_options,
|
||||
pbl_evidence_update,
|
||||
pbl_evidence_delete,
|
||||
)
|
||||
from pbl_evidence.evidence_map import (
|
||||
EVIDENCE_TYPES,
|
||||
EVENT_TO_EVIDENCE,
|
||||
@ -36,6 +41,8 @@ __all__ = [
|
||||
'pbl_evidence_list', 'pbl_evidence_stats', 'pbl_evidence_watermark',
|
||||
'collect_evidence_from_events', 'resolve_event_table', 'CollectError',
|
||||
'EVIDENCE_TYPES', 'EVENT_TO_EVIDENCE', 'evidence_types_for_events',
|
||||
# CRUD 框架适配端点后端(json/pbl_evidence.json 的 editable + alters.dataurl)
|
||||
'pbl_artifact_options', 'pbl_evidence_update', 'pbl_evidence_delete',
|
||||
]
|
||||
|
||||
__version__ = '1.1.0'
|
||||
__version__ = '1.2.0'
|
||||
|
||||
@ -25,6 +25,11 @@ from pbl_evidence.api import (
|
||||
pbl_evidence_watermark,
|
||||
)
|
||||
from pbl_evidence.collector import collect_evidence_from_events, resolve_event_table
|
||||
from pbl_evidence.crud_api import (
|
||||
pbl_artifact_options,
|
||||
pbl_evidence_delete,
|
||||
pbl_evidence_update,
|
||||
)
|
||||
from pbl_evidence.evidence_map import EVIDENCE_TYPES, EVENT_TO_EVIDENCE
|
||||
|
||||
|
||||
@ -42,6 +47,11 @@ def load_pbl_evidence():
|
||||
env.pbl_evidence_list = pbl_evidence_list
|
||||
env.pbl_evidence_stats = pbl_evidence_stats
|
||||
env.pbl_evidence_watermark = pbl_evidence_watermark
|
||||
# ── CRUD 框架适配(json/pbl_evidence.json editable 三 URL + artifact_id 下拉)
|
||||
# 漏这三行 → wwwroot/api/pbl_evidence_update.dspy 等调用必 NameError(三处同步之 ③)
|
||||
env.pbl_artifact_options = pbl_artifact_options
|
||||
env.pbl_evidence_update = pbl_evidence_update
|
||||
env.pbl_evidence_delete = pbl_evidence_delete
|
||||
# ── 底层能力(供 M5b 回放 / M6 评估直接复用)──────────────────
|
||||
env.pbl_collect_evidence_from_events = collect_evidence_from_events
|
||||
env.pbl_resolve_event_table = resolve_event_table
|
||||
|
||||
@ -38,9 +38,12 @@ WWWROOT = os.path.normpath(
|
||||
os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'wwwroot'))
|
||||
|
||||
# ── (path, role) 清单:逐条显式,禁通配符 ─────────────────────────────────
|
||||
# 覆盖 wwwroot 下全部 9 个 .dspy + 模块入口 index.ui。M5a 主能力
|
||||
# 覆盖 wwwroot 下全部 12 个 .dspy + 模块入口 index.ui(与磁盘一一对应,
|
||||
# `python3 scripts/load_path.py --check` 做自愈核对)。M5a 主能力
|
||||
# pbl_evidence_collect_from_events.dspy 与统计口 pbl_evidence_stats.dspy 在此登记,
|
||||
# 漏登记 = 部署后点击一律 403,整条批量采集链路不可用(QC 退回意见 #1 的实测后果)。
|
||||
# CRUD 适配三端点 pbl_artifact_options / pbl_evidence_update / pbl_evidence_delete
|
||||
# (json/pbl_evidence.json 的 editable 三 URL + alters.artifact_id.dataurl 所指)也必须登记,
|
||||
# 漏登记 = 部署后点击一律 403,整条批量采集/编辑/删除链路不可用(QC 退回意见 #1/#3 的实测后果)。
|
||||
PATHS = [
|
||||
# 模块入口页(wwwroot/index.ui)
|
||||
('/pbl_evidence/index.ui', 'logined'),
|
||||
@ -56,8 +59,59 @@ PATHS = [
|
||||
# 证据查询 / 统计
|
||||
('/pbl_evidence/api/pbl_evidence_list.dspy', 'logined'),
|
||||
('/pbl_evidence/api/pbl_evidence_stats.dspy', 'logined'),
|
||||
# CRUD 框架适配端点(json/pbl_evidence.json editable 三 URL + artifact_id 下拉)
|
||||
('/pbl_evidence/api/pbl_artifact_options.dspy', 'logined'),
|
||||
('/pbl_evidence/api/pbl_evidence_update.dspy', 'logined'),
|
||||
('/pbl_evidence/api/pbl_evidence_delete.dspy', 'logined'),
|
||||
# ── CRUD 生成物见下方 GENERATED_PATHS(xls2ui 产物,build 时才落盘)──
|
||||
]
|
||||
|
||||
# xls2ui 从 json/pbl_artifact.json / json/pbl_evidence.json 生成的 CRUD 目录
|
||||
# (alias = tblname = pbl_artifact / pbl_evidence)。每个 alias 6 条:目录 +
|
||||
# index.ui + get_/add_/update_/delete_ 端点,逐条显式、禁通配符。这些文件是
|
||||
# **构建产物**(.gitignore 排除、不入库),所以只出现在部署环境的 wwwroot 下;
|
||||
# 注册权限时必须一并登记,否则 CRUD 列表页/新增/编辑/删除一律 403。
|
||||
CRUD_ALIASES = ('pbl_artifact', 'pbl_evidence')
|
||||
GENERATED_PATHS = []
|
||||
for _alias in CRUD_ALIASES:
|
||||
GENERATED_PATHS.append(('/pbl_evidence/%s' % _alias, 'logined'))
|
||||
GENERATED_PATHS.append(('/pbl_evidence/%s/index.ui' % _alias, 'logined'))
|
||||
for _op in ('get', 'add', 'update', 'delete'):
|
||||
GENERATED_PATHS.append(
|
||||
('/pbl_evidence/%s/%s_%s.dspy' % (_alias, _op, _alias), 'logined'))
|
||||
|
||||
# 注册用的全量清单 = 手写端点 + CRUD 生成物(顺序即登记顺序)
|
||||
PATHS = PATHS + GENERATED_PATHS
|
||||
|
||||
# ── permission / rolepermission 幂等 INSERT(供部署说明与手工兜底使用)──────
|
||||
# 与 PATHS 一一对应:rbac 三表链路 permission.permcode = 路径本身,
|
||||
# rolepermission 关联 role.rolecode='logined'。列名以 models 真实字段为准
|
||||
# (permission: permcode/name;role: rolecode;rolepermission: roleid/permid),
|
||||
# 用 INSERT ... SELECT 反查 id,避免硬编码主键。
|
||||
def permission_inserts():
|
||||
"""返回与 PATHS 等价的幂等 SQL(角色固定 logined)。"""
|
||||
out = []
|
||||
for path, role in PATHS:
|
||||
out.append(
|
||||
"INSERT INTO `permission` (`id`, `permcode`, `name`) "
|
||||
"SELECT REPLACE(UUID(),'-',''), '%(p)s', '%(p)s' "
|
||||
"WHERE NOT EXISTS (SELECT 1 FROM `permission` WHERE `permcode`='%(p)s');"
|
||||
% {'p': path})
|
||||
out.append(
|
||||
"INSERT INTO `rolepermission` (`id`, `roleid`, `permid`) "
|
||||
"SELECT REPLACE(UUID(),'-',''), r.`id`, p.`id` FROM `role` r, `permission` p "
|
||||
"WHERE r.`rolecode`='%(rl)s' AND p.`permcode`='%(p)s' "
|
||||
"AND NOT EXISTS (SELECT 1 FROM `rolepermission` rp "
|
||||
"WHERE rp.`roleid`=r.`id` AND rp.`permid`=p.`id`);"
|
||||
% {'rl': role, 'p': path})
|
||||
return out
|
||||
|
||||
def print_permission_sql():
|
||||
"""--sql 子命令:打印 INSERT 清单(部署时可直接管道进 mysql)。"""
|
||||
for stmt in permission_inserts():
|
||||
print(stmt)
|
||||
|
||||
|
||||
STATIC_SUFFIXES = ('.dspy', '.ui', '.js', '.css', '.wss')
|
||||
|
||||
|
||||
@ -90,9 +144,15 @@ def on_disk_paths():
|
||||
return got
|
||||
|
||||
|
||||
def check():
|
||||
"""清单 vs 磁盘一致性核对,返回 (missing, stale, wildcard) 三个列表。"""
|
||||
registered = set(p for p, _role in PATHS)
|
||||
def check(include_generated=False):
|
||||
"""清单 vs 磁盘一致性核对,返回 (missing, stale, wildcard) 三个列表。
|
||||
|
||||
CRUD 生成物(GENERATED_PATHS)是 build.sh 里 xls2ui 的产物,开发仓库中被
|
||||
.gitignore 排除、不落盘,因此默认不计入 stale(否则开发环境永远误报);
|
||||
部署后跑 `--check --generated` 才把它们纳入核对。手写端点与磁盘必须一一对应。
|
||||
"""
|
||||
handwritten = set(p for p, _r in PATHS) - set(p for p, _r in GENERATED_PATHS)
|
||||
registered = set(p for p, _role in PATHS) if include_generated else handwritten
|
||||
on_disk = on_disk_paths()
|
||||
missing = sorted(on_disk - registered)
|
||||
stale = sorted(registered - on_disk)
|
||||
@ -129,16 +189,25 @@ def register(verbose=True):
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description='%s RBAC path registration' % MODULE)
|
||||
ap.add_argument('--check', action='store_true', help='verify PATHS against wwwroot files')
|
||||
ap.add_argument('--generated', action='store_true',
|
||||
help='with --check: also reconcile xls2ui-generated CRUD paths '
|
||||
'(run this on the deployment host, after build.sh)')
|
||||
ap.add_argument('--list', action='store_true', help='print registered paths and exit')
|
||||
ap.add_argument('--sql', action='store_true',
|
||||
help='print idempotent permission/rolepermission INSERTs and exit')
|
||||
args = ap.parse_args()
|
||||
|
||||
if args.sql:
|
||||
print_permission_sql()
|
||||
return 0
|
||||
|
||||
if args.list:
|
||||
for path, role in PATHS:
|
||||
print('%s %s' % (role, path))
|
||||
return 0
|
||||
|
||||
if args.check:
|
||||
missing, stale, wildcard = check()
|
||||
missing, stale, wildcard = check(include_generated=args.generated)
|
||||
print('[%s] PATHS entries: %d' % (MODULE, len(PATHS)))
|
||||
for p in missing:
|
||||
print('MISSING (on disk, not registered): ' + p)
|
||||
|
||||
15
wwwroot/api/pbl_artifact_options.dspy
Normal file
15
wwwroot/api/pbl_artifact_options.dspy
Normal file
@ -0,0 +1,15 @@
|
||||
debug(f'pbl_artifact_options.dspy: START params_kw={dict(params_kw)}')
|
||||
# 下拉数据源(json/pbl_evidence.json -> browserfields.alters.artifact_id.dataurl)
|
||||
# 契约:必须返回**裸 JSON 数组** [{value,text},...],异常/空数据返回 [],
|
||||
# 不许包 status/data(crud-definition-spec Pitfall:包了会让下拉静默不渲染)。
|
||||
# 后端函数 pbl_artifact_options 由 load_pbl_evidence() 注册到 ServerEnv(init.py ③)。
|
||||
try:
|
||||
_rows = await pbl_artifact_options(**dict(params_kw))
|
||||
if not isinstance(_rows, list):
|
||||
_rows = []
|
||||
debug(f'pbl_artifact_options.dspy: OK rows={len(_rows)}')
|
||||
return json.dumps(_rows, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
debug(f'pbl_artifact_options.dspy: FAIL {e}')
|
||||
debug(f'pbl_artifact_options.dspy: TRACE {format_exc()}')
|
||||
return json.dumps([], ensure_ascii=False)
|
||||
15
wwwroot/api/pbl_evidence_delete.dspy
Normal file
15
wwwroot/api/pbl_evidence_delete.dspy
Normal file
@ -0,0 +1,15 @@
|
||||
debug(f'pbl_evidence_delete.dspy: START params_kw={dict(params_kw)}')
|
||||
# 证据删除端点(json/pbl_evidence.json -> params.editable.delete_data_url)。
|
||||
# 薄包装:无 import、无业务逻辑,直接委托 init.py 注册的 pbl_evidence_delete
|
||||
# (支持单条 id 与批量 ids;每条都按 tenant_id + id 双条件校验归属,防越权删除)。
|
||||
try:
|
||||
_data = await pbl_evidence_delete(**dict(params_kw))
|
||||
_msg = (_data or {}).get('message', '证据已删除')
|
||||
debug(f'pbl_evidence_delete.dspy: OK deleted={(_data or {}).get("deleted")}')
|
||||
return json.dumps({'status': 'OK', 'ok': True, 'data': _data,
|
||||
'message': _msg}, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
debug(f'pbl_evidence_delete.dspy: FAIL {e}')
|
||||
debug(f'pbl_evidence_delete.dspy: TRACE {format_exc()}')
|
||||
return json.dumps({'status': 'ERROR', 'ok': False, 'data': None,
|
||||
'message': f'证据删除失败:{e}'}, ensure_ascii=False)
|
||||
16
wwwroot/api/pbl_evidence_update.dspy
Normal file
16
wwwroot/api/pbl_evidence_update.dspy
Normal file
@ -0,0 +1,16 @@
|
||||
debug(f'pbl_evidence_update.dspy: START params_kw={dict(params_kw)}')
|
||||
# 证据人工修订端点(json/pbl_evidence.json -> params.editable.update_data_url,
|
||||
# 注意:new/update/delete_data_url 在 CRUD JSON 中位于 params 顶层语义,由 xls2ui 读取)。
|
||||
# 薄包装:无 import、无业务逻辑,直接委托 init.py 注册的 pbl_evidence_update
|
||||
# (强制 tenant_id 打头校验归属;改唯一索引三元组时同步重算 dedup_key)。
|
||||
try:
|
||||
_data = await pbl_evidence_update(**dict(params_kw))
|
||||
_msg = (_data or {}).get('message', '证据已更新')
|
||||
debug(f'pbl_evidence_update.dspy: OK id={(_data or {}).get("id")}')
|
||||
return json.dumps({'status': 'OK', 'ok': True, 'data': _data,
|
||||
'message': _msg}, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
debug(f'pbl_evidence_update.dspy: FAIL {e}')
|
||||
debug(f'pbl_evidence_update.dspy: TRACE {format_exc()}')
|
||||
return json.dumps({'status': 'ERROR', 'ok': False, 'data': None,
|
||||
'message': f'证据更新失败:{e}'}, ensure_ascii=False)
|
||||
Loading…
x
Reference in New Issue
Block a user