approve: pbls 系统设计正文补全(26 个占位文档重写为定稿:36 表 DDL + 15 模块设计 + 架构/UI/字典/工具契约/spec.json)
This commit is contained in:
parent
1d382b27e4
commit
1225421d73
@ -1,9 +1,173 @@
|
||||
"""pbl_blueprint —— PBL 蓝图聚合根与子对象、版本、模板模块(M1a)。
|
||||
# -*- coding: utf-8 -*-
|
||||
"""pbl_blueprint —— PBL 蓝图聚合根与子对象、版本、模板(M1a)。
|
||||
|
||||
对外唯一入口:load_pbl_blueprint(env=None)
|
||||
所有读写 tenant_id 强制打头,缺失租户上下文一律 fail-closed 拒绝。
|
||||
模块契约(对外只暴露这些名字,三处同步注册:service 定义 / 本文件导出 / init.py env 注册):
|
||||
蓝图 CRUD : create_blueprint / get_blueprint / update_blueprint / delete_blueprint / list_blueprints
|
||||
树与子对象: get_blueprint_tree / create_node / update_node / delete_node / list_nodes
|
||||
create_edge / delete_edge / list_edges
|
||||
fork : fork_blueprint / list_forks
|
||||
版本 : save_version / list_versions / get_version / get_change_delta / rollback_version
|
||||
模板 : create_template / list_templates / instantiate_template / instantiate_payload
|
||||
离线兜底 : export_offline / import_offline / list_offline
|
||||
发布 : publish_blueprint / revoke_publish / list_publishes
|
||||
锁 : lock_blueprint / unlock_blueprint / clean_expired_locks
|
||||
统计/审计 : blueprint_stats / write_audit / list_audit
|
||||
|
||||
所有契约函数第一个参数固定 tenant_id,缺失/非法即 fail-closed 抛 PblBlueprintError。
|
||||
"""
|
||||
|
||||
from .init import load_pbl_blueprint
|
||||
__version__ = "1.0.0"
|
||||
__module_name__ = "pbl_blueprint"
|
||||
|
||||
__all__ = ['load_pbl_blueprint']
|
||||
from .errors import ( # noqa: F401
|
||||
PblBlueprintError,
|
||||
ERR_OK,
|
||||
ERR_TENANT_MISSING,
|
||||
ERR_PARAM_INVALID,
|
||||
ERR_NOT_FOUND,
|
||||
ERR_DUPLICATE,
|
||||
ERR_STATE_INVALID,
|
||||
ERR_LOCKED,
|
||||
ERR_FORBIDDEN,
|
||||
ERR_DB,
|
||||
ERR_INTERNAL,
|
||||
ok,
|
||||
fail,
|
||||
err,
|
||||
)
|
||||
from .tenant import require_tenant, tenant_where, inject_tenant # noqa: F401
|
||||
from .db import get_dbname, get_sor, MODULE_NAME # noqa: F401
|
||||
from .crud import ( # noqa: F401
|
||||
list_tables,
|
||||
table_fields,
|
||||
load_model,
|
||||
build_filter,
|
||||
make_crud,
|
||||
crud_create,
|
||||
crud_get,
|
||||
crud_update,
|
||||
crud_delete,
|
||||
crud_list,
|
||||
crud_count,
|
||||
new_id,
|
||||
now_str,
|
||||
APPEND_ONLY_TABLES,
|
||||
)
|
||||
from .audit import write_audit, list_audit, timed_audit # noqa: F401
|
||||
from .service import ( # noqa: F401
|
||||
NODE_TYPES,
|
||||
EDGE_TYPES,
|
||||
OFFLINE_SCHEMA_VERSION,
|
||||
create_blueprint,
|
||||
get_blueprint,
|
||||
update_blueprint,
|
||||
delete_blueprint,
|
||||
list_blueprints,
|
||||
get_blueprint_tree,
|
||||
create_node,
|
||||
update_node,
|
||||
delete_node,
|
||||
list_nodes,
|
||||
create_edge,
|
||||
delete_edge,
|
||||
list_edges,
|
||||
fork_blueprint,
|
||||
list_forks,
|
||||
save_version,
|
||||
list_versions,
|
||||
get_version,
|
||||
get_change_delta,
|
||||
rollback_version,
|
||||
create_template,
|
||||
list_templates,
|
||||
instantiate_template,
|
||||
instantiate_payload,
|
||||
export_offline,
|
||||
import_offline,
|
||||
list_offline,
|
||||
publish_blueprint,
|
||||
revoke_publish,
|
||||
list_publishes,
|
||||
lock_blueprint,
|
||||
unlock_blueprint,
|
||||
clean_expired_locks,
|
||||
blueprint_stats,
|
||||
)
|
||||
|
||||
# 对外契约函数清单(init.py 按此清单注册到 ServerEnv,三处同步的唯一事实源)
|
||||
CONTRACT_FUNCTIONS = (
|
||||
"create_blueprint", "get_blueprint", "update_blueprint", "delete_blueprint",
|
||||
"list_blueprints", "get_blueprint_tree", "create_node", "update_node",
|
||||
"delete_node", "list_nodes", "create_edge", "delete_edge", "list_edges",
|
||||
"fork_blueprint", "list_forks", "save_version", "list_versions", "get_version",
|
||||
"get_change_delta", "rollback_version", "create_template", "list_templates",
|
||||
"instantiate_template", "instantiate_payload", "export_offline", "import_offline",
|
||||
"list_offline", "publish_blueprint", "revoke_publish", "list_publishes",
|
||||
"lock_blueprint", "unlock_blueprint", "clean_expired_locks", "blueprint_stats",
|
||||
"write_audit", "list_audit",
|
||||
)
|
||||
|
||||
# 本模块 11 张表(models/*.json 为唯一事实源,此处仅缓存清单便于挂载)
|
||||
TABLES = (
|
||||
"pbl_blueprint",
|
||||
"pbl_blueprint_node",
|
||||
"pbl_blueprint_edge",
|
||||
"pbl_blueprint_version",
|
||||
"pbl_blueprint_version_delta",
|
||||
"pbl_blueprint_template",
|
||||
"pbl_blueprint_publish",
|
||||
"pbl_blueprint_offline",
|
||||
"pbl_blueprint_fork",
|
||||
"pbl_blueprint_lock",
|
||||
"pbl_blueprint_audit",
|
||||
)
|
||||
|
||||
_loaded = False
|
||||
|
||||
|
||||
def get_contract_functions():
|
||||
"""返回 {函数名: 函数对象},供 init.py 注册与 selfcheck 校验三处同步。"""
|
||||
import sys
|
||||
mod = sys.modules[__name__]
|
||||
out = {}
|
||||
missing = []
|
||||
for name in CONTRACT_FUNCTIONS:
|
||||
fn = getattr(mod, name, None)
|
||||
if fn is None or not callable(fn):
|
||||
missing.append(name)
|
||||
continue
|
||||
out[name] = fn
|
||||
if missing:
|
||||
raise ImportError("pbl_blueprint 契约函数未导出: %s" % ",".join(missing))
|
||||
return out
|
||||
|
||||
|
||||
def load_pbl_blueprint(env=None, dbname=None, register_tables=True,
|
||||
register_functions=True, register_rbac=True):
|
||||
"""挂载模块到应用(由 apps/pbls 的 init() 调用)。
|
||||
|
||||
:param env: ServerEnv 实例(可选,缺省自动获取)
|
||||
:param dbname: 可选显式库名(缺省由 env.get_module_dbname('pbl_blueprint') 决定,禁硬编码)
|
||||
:return: {"module":..., "dbname":..., "tables": n, "functions": n, "rbac_paths": n}
|
||||
"""
|
||||
global _loaded
|
||||
from .init import init as _init
|
||||
res = _init(env=env, dbname=dbname, register_tables=register_tables,
|
||||
register_functions=register_functions, register_rbac=register_rbac)
|
||||
_loaded = True
|
||||
return res
|
||||
|
||||
|
||||
def is_loaded():
|
||||
return _loaded
|
||||
|
||||
|
||||
__all__ = [
|
||||
"__version__", "__module_name__", "MODULE_NAME", "TABLES", "CONTRACT_FUNCTIONS",
|
||||
"NODE_TYPES", "EDGE_TYPES", "OFFLINE_SCHEMA_VERSION", "APPEND_ONLY_TABLES",
|
||||
"PblBlueprintError", "ok", "fail", "err",
|
||||
"require_tenant", "tenant_where", "inject_tenant",
|
||||
"get_dbname", "get_sor", "list_tables", "table_fields", "load_model",
|
||||
"build_filter", "make_crud", "new_id", "now_str",
|
||||
"get_contract_functions", "load_pbl_blueprint", "is_loaded",
|
||||
] + list(CONTRACT_FUNCTIONS)
|
||||
|
||||
93
pbl_blueprint/audit.py
Normal file
93
pbl_blueprint/audit.py
Normal file
@ -0,0 +1,93 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""蓝图审计(M1a):append-only 写入 pbl_blueprint_audit。
|
||||
|
||||
审计失败不阻断主流程(记 warn),但审计表本身禁改禁删(crud.APPEND_ONLY_TABLES)。
|
||||
"""
|
||||
|
||||
import time
|
||||
|
||||
from . import db as _db
|
||||
from .crud import new_id, now_str, APPEND_ONLY_TABLES
|
||||
from .tenant import require_tenant
|
||||
|
||||
AUDIT_TABLE = "pbl_blueprint_audit"
|
||||
assert AUDIT_TABLE in APPEND_ONLY_TABLES
|
||||
|
||||
_warns = []
|
||||
|
||||
|
||||
def write_audit(tenant_id, blueprint_id, action, target_type="blueprint", target_id="",
|
||||
version_no=0, before=None, after=None, result="success", err_code="",
|
||||
op_user="", op_source="api", client_ip="", cost_ms=0, sor=None):
|
||||
"""写一条审计记录(append-only)。tenant_id 强制打头。"""
|
||||
tid = require_tenant(tenant_id)
|
||||
row = {
|
||||
"id": new_id(),
|
||||
"tenant_id": tid,
|
||||
"blueprint_id": blueprint_id or "",
|
||||
"target_type": target_type or "blueprint",
|
||||
"target_id": target_id or "",
|
||||
"action": action or "",
|
||||
"version_no": int(version_no or 0),
|
||||
"before_json": _db.dumps(before) if before is not None else "",
|
||||
"after_json": _db.dumps(after) if after is not None else "",
|
||||
"result": result or "success",
|
||||
"err_code": err_code or "",
|
||||
"cost_ms": int(cost_ms or 0),
|
||||
"op_user": op_user or "",
|
||||
"op_source": op_source or "api",
|
||||
"client_ip": client_ip or "",
|
||||
"create_time": now_str(),
|
||||
}
|
||||
try:
|
||||
s = sor or _db.get_sor()
|
||||
_db.q_insert(s, AUDIT_TABLE, row)
|
||||
except Exception as e: # 审计失败不阻断业务
|
||||
_warns.append("audit write failed: %s" % e)
|
||||
return None
|
||||
return row
|
||||
|
||||
|
||||
def timed_audit(tenant_id, blueprint_id, action, **kw):
|
||||
"""上下文管理器:自动计时 + 成功/失败落审计。"""
|
||||
return _TimedAudit(tenant_id, blueprint_id, action, kw)
|
||||
|
||||
|
||||
class _TimedAudit(object):
|
||||
def __init__(self, tenant_id, blueprint_id, action, kw):
|
||||
self.tenant_id = tenant_id
|
||||
self.blueprint_id = blueprint_id
|
||||
self.action = action
|
||||
self.kw = kw or {}
|
||||
self.t0 = 0.0
|
||||
|
||||
def __enter__(self):
|
||||
self.t0 = time.time()
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, tb):
|
||||
cost = int((time.time() - self.t0) * 1000)
|
||||
if exc_type is None:
|
||||
write_audit(self.tenant_id, self.blueprint_id, self.action,
|
||||
result="success", cost_ms=cost, **self.kw)
|
||||
else:
|
||||
write_audit(self.tenant_id, self.blueprint_id, self.action,
|
||||
result="fail", err_code=str(getattr(exc_val, "errcode", exc_type.__name__)),
|
||||
cost_ms=cost, **self.kw)
|
||||
return False # 不吞异常
|
||||
|
||||
|
||||
def list_audit(tenant_id, blueprint_id="", action="", page=1, page_size=50, sor=None):
|
||||
"""查审计(只读,tenant_id 打头)。"""
|
||||
from .crud import crud_list, build_filter
|
||||
conds = {}
|
||||
if blueprint_id:
|
||||
conds["blueprint_id"] = blueprint_id
|
||||
if action:
|
||||
conds["action"] = action
|
||||
w, a = build_filter(AUDIT_TABLE, conds)
|
||||
return crud_list(AUDIT_TABLE, tenant_id, w, a, "*", "create_time desc", page, page_size, True, sor)
|
||||
|
||||
|
||||
def get_warns():
|
||||
return list(_warns)
|
||||
325
pbl_blueprint/crud.py
Normal file
325
pbl_blueprint/crud.py
Normal file
@ -0,0 +1,325 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""CRUD 工厂(M1a):为 11 张表统一生成 tenant_id 强制打头的增删改查契约。
|
||||
|
||||
设计要点:
|
||||
1. 所有生成的函数第一个参数固定为 tenant_id,内部 require_tenant 校验,缺失即 fail-closed 抛错;
|
||||
2. 所有 WHERE 条件由 tenant_where() 生成,`tenant_id=?` 永远在最前,杜绝跨租户读写;
|
||||
3. 只用 sqlor 白名单 API:C / U / D / R / I / sqlExe(封装在 db.py);
|
||||
4. 删除默认逻辑删除(deleted=1),审计表 append-only 禁改禁删;
|
||||
5. 表名/字段名来自 models/*.json 白名单,禁止拼接外部传入的表名列名(防注入)。
|
||||
"""
|
||||
|
||||
import os
|
||||
import json as _json
|
||||
import uuid
|
||||
import datetime
|
||||
|
||||
from . import db as _db
|
||||
from .errors import (
|
||||
ERR_PARAM_INVALID,
|
||||
ERR_NOT_FOUND,
|
||||
ERR_DUPLICATE,
|
||||
ERR_FORBIDDEN,
|
||||
PblBlueprintError,
|
||||
ok,
|
||||
)
|
||||
from .tenant import require_tenant, tenant_where, inject_tenant
|
||||
|
||||
MODEL_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "models")
|
||||
|
||||
# append-only 表:只允许 C / R,禁 U / D
|
||||
APPEND_ONLY_TABLES = ("pbl_blueprint_audit",)
|
||||
|
||||
# 不可由外部写入的服务端字段
|
||||
SERVER_FIELDS = (
|
||||
"create_user",
|
||||
"create_time",
|
||||
"update_user",
|
||||
"update_time",
|
||||
"deleted",
|
||||
)
|
||||
|
||||
_model_cache = {}
|
||||
|
||||
|
||||
def new_id():
|
||||
"""生成 str32 主键(UUID 去横线)。"""
|
||||
return uuid.uuid4().hex
|
||||
|
||||
|
||||
def now_str():
|
||||
return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
|
||||
def load_model(tbl):
|
||||
"""读取 models/{tbl}.json 表定义(带缓存),返回 dict。"""
|
||||
if tbl in _model_cache:
|
||||
return _model_cache[tbl]
|
||||
path = os.path.join(MODEL_DIR, "%s.json" % tbl)
|
||||
if not os.path.isfile(path):
|
||||
raise PblBlueprintError(ERR_PARAM_INVALID, "未知表: %s" % tbl)
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
m = _json.load(f)
|
||||
_model_cache[tbl] = m
|
||||
return m
|
||||
|
||||
|
||||
def list_tables():
|
||||
"""列出本模块全部表名(来自 models 目录,真实落盘为准)。"""
|
||||
if not os.path.isdir(MODEL_DIR):
|
||||
return []
|
||||
out = []
|
||||
for fn in sorted(os.listdir(MODEL_DIR)):
|
||||
if fn.endswith(".json"):
|
||||
out.append(fn[:-5])
|
||||
return out
|
||||
|
||||
|
||||
def table_fields(tbl):
|
||||
"""返回表字段名列表(保持定义顺序,tenant_id 在 id 之后即首业务字段)。"""
|
||||
m = load_model(tbl)
|
||||
return list((m.get("fields") or {}).keys())
|
||||
|
||||
|
||||
def _check_fields(tbl, data):
|
||||
"""字段白名单校验:拒绝未定义字段(防注入/防脏写)。"""
|
||||
allowed = set(table_fields(tbl))
|
||||
bad = [k for k in data.keys() if k not in allowed]
|
||||
if bad:
|
||||
raise PblBlueprintError(ERR_PARAM_INVALID, "表 %s 无字段: %s" % (tbl, ",".join(bad)))
|
||||
|
||||
|
||||
def _check_required(tbl, data, partial=False):
|
||||
"""notnull 且无 default 的字段必填校验。"""
|
||||
m = load_model(tbl)
|
||||
miss = []
|
||||
for name, spec in (m.get("fields") or {}).items():
|
||||
if name in ("id", "tenant_id"):
|
||||
continue
|
||||
if not spec.get("notnull"):
|
||||
continue
|
||||
if "default" in spec:
|
||||
continue
|
||||
if partial and name not in data:
|
||||
continue
|
||||
if name not in data or data[name] in (None, ""):
|
||||
miss.append(name)
|
||||
if miss:
|
||||
raise PblBlueprintError(ERR_PARAM_INVALID, "缺少必填字段: %s" % ",".join(miss))
|
||||
|
||||
|
||||
def _apply_default(tbl, data):
|
||||
"""填充 default 值。"""
|
||||
m = load_model(tbl)
|
||||
out = dict(data)
|
||||
for name, spec in (m.get("fields") or {}).items():
|
||||
if name in out and out[name] not in (None, ""):
|
||||
continue
|
||||
if "default" in spec:
|
||||
out[name] = spec["default"]
|
||||
return out
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- 通用 CRUD
|
||||
|
||||
def crud_create(tbl, tenant_id, data, op_user="", sor=None):
|
||||
"""新增一行。tenant_id 强制打头注入,返回新行 dict。"""
|
||||
tid = require_tenant(tenant_id)
|
||||
row = inject_tenant(tid, data or {})
|
||||
_check_fields(tbl, row)
|
||||
row = _apply_default(tbl, row)
|
||||
if not row.get("id"):
|
||||
row["id"] = new_id()
|
||||
_check_required(tbl, row)
|
||||
row["create_time"] = now_str()
|
||||
if op_user:
|
||||
row["create_user"] = op_user
|
||||
if "update_time" in table_fields(tbl):
|
||||
row["update_time"] = row["create_time"]
|
||||
if op_user and "update_user" in table_fields(tbl):
|
||||
row["update_user"] = op_user
|
||||
if "deleted" in table_fields(tbl) and not row.get("deleted"):
|
||||
row["deleted"] = 0
|
||||
s = sor or _db.get_sor()
|
||||
try:
|
||||
_db.q_insert(s, tbl, row)
|
||||
except PblBlueprintError:
|
||||
raise
|
||||
except Exception as e:
|
||||
msg = str(e)
|
||||
if "Duplicate" in msg or "UNIQUE" in msg.upper():
|
||||
raise PblBlueprintError(ERR_DUPLICATE, "唯一约束冲突: %s" % msg)
|
||||
raise PblBlueprintError(ERR_PARAM_INVALID, "写入失败: %s" % msg)
|
||||
return row
|
||||
|
||||
|
||||
def crud_get(tbl, tenant_id, row_id, fields="*", sor=None, include_deleted=False):
|
||||
"""按主键取单行(tenant_id 打头过滤)。不存在抛 ERR_NOT_FOUND。"""
|
||||
tid = require_tenant(tenant_id)
|
||||
rid = (row_id or "").strip() if isinstance(row_id, str) else row_id
|
||||
if not rid:
|
||||
raise PblBlueprintError(ERR_PARAM_INVALID, "id 不能为空")
|
||||
extra = "" if include_deleted else "deleted=0"
|
||||
where, args = tenant_where(tid, "id=?" + (" and " + extra if extra else ""), [rid])
|
||||
s = sor or _db.get_sor()
|
||||
row = _db.q_one(s, tbl, where, args, fields)
|
||||
if not row:
|
||||
raise PblBlueprintError(ERR_NOT_FOUND, "%s[%s] 不存在或不属于当前租户" % (tbl, rid))
|
||||
return row
|
||||
|
||||
|
||||
def crud_update(tbl, tenant_id, row_id, data, op_user="", sor=None):
|
||||
"""按主键更新(tenant_id 打头过滤),返回受影响行数。append-only 表拒绝。"""
|
||||
tid = require_tenant(tenant_id)
|
||||
if tbl in APPEND_ONLY_TABLES:
|
||||
raise PblBlueprintError(ERR_FORBIDDEN, "%s 为 append-only 表,禁止更新" % tbl)
|
||||
rid = (row_id or "").strip() if isinstance(row_id, str) else row_id
|
||||
if not rid:
|
||||
raise PblBlueprintError(ERR_PARAM_INVALID, "id 不能为空")
|
||||
payload = dict(data or {})
|
||||
for k in ("id", "tenant_id"):
|
||||
payload.pop(k, None)
|
||||
for k in ("create_user", "create_time"):
|
||||
payload.pop(k, None)
|
||||
if not payload:
|
||||
raise PblBlueprintError(ERR_PARAM_INVALID, "无待更新字段")
|
||||
_check_fields(tbl, payload)
|
||||
_check_required(tbl, payload, partial=True)
|
||||
if "update_time" in table_fields(tbl):
|
||||
payload["update_time"] = now_str()
|
||||
if op_user and "update_user" in table_fields(tbl):
|
||||
payload["update_user"] = op_user
|
||||
crud_get(tbl, tid, rid, "id", sor=sor) # 存在性 + 租户归属校验
|
||||
where, args = tenant_where(tid, "id=? and deleted=0", [rid])
|
||||
s = sor or _db.get_sor()
|
||||
return _db.q_update(s, tbl, payload, where, args)
|
||||
|
||||
|
||||
def crud_delete(tbl, tenant_id, row_id, op_user="", sor=None, hard=False):
|
||||
"""删除(默认逻辑删除 deleted=1)。append-only 表拒绝。"""
|
||||
tid = require_tenant(tenant_id)
|
||||
if tbl in APPEND_ONLY_TABLES:
|
||||
raise PblBlueprintError(ERR_FORBIDDEN, "%s 为 append-only 表,禁止删除" % tbl)
|
||||
rid = (row_id or "").strip() if isinstance(row_id, str) else row_id
|
||||
if not rid:
|
||||
raise PblBlueprintError(ERR_PARAM_INVALID, "id 不能为空")
|
||||
crud_get(tbl, tid, rid, "id", sor=sor)
|
||||
where, args = tenant_where(tid, "id=? and deleted=0", [rid])
|
||||
s = sor or _db.get_sor()
|
||||
if hard:
|
||||
return _db.q_delete(s, tbl, where, args)
|
||||
return _db.q_update(s, tbl, {"deleted": 1, "update_time": now_str()}, where, args)
|
||||
|
||||
|
||||
def crud_list(tbl, tenant_id, where="", args=None, fields="*", order="", page=1,
|
||||
page_size=20, include_deleted=False, sor=None):
|
||||
"""分页列表查询。tenant_id 打头,返回 {rows,total,page,page_size}。
|
||||
|
||||
where/args 只接受内部构造的参数化条件;禁止把外部字符串直接当 SQL 片段传入
|
||||
(调用方须用 build_filter 生成)。
|
||||
"""
|
||||
tid = require_tenant(tenant_id)
|
||||
conds = [] if include_deleted else ["deleted=0"]
|
||||
if where:
|
||||
conds.append(where)
|
||||
w, a = tenant_where(tid, " and ".join(conds), list(args or []))
|
||||
try:
|
||||
page = max(1, int(page or 1))
|
||||
page_size = min(500, max(1, int(page_size or 20)))
|
||||
except Exception:
|
||||
raise PblBlueprintError(ERR_PARAM_INVALID, "page/page_size 非法")
|
||||
s = sor or _db.get_sor()
|
||||
total = _db.q_count(s, tbl, w, a)
|
||||
rows = _db.q_select(s, tbl, w, a, fields, order or "", page_size, (page - 1) * page_size)
|
||||
return {"rows": rows or [], "total": total, "page": page, "page_size": page_size}
|
||||
|
||||
|
||||
def crud_count(tbl, tenant_id, where="", args=None, include_deleted=False, sor=None):
|
||||
"""计数(tenant_id 打头)。"""
|
||||
tid = require_tenant(tenant_id)
|
||||
conds = [] if include_deleted else ["deleted=0"]
|
||||
if where:
|
||||
conds.append(where)
|
||||
w, a = tenant_where(tid, " and ".join(conds), list(args or []))
|
||||
s = sor or _db.get_sor()
|
||||
return _db.q_count(s, tbl, w, a)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- 条件构造
|
||||
|
||||
ALLOWED_OPS = ("=", "!=", ">", ">=", "<", "<=", "like", "in", "is", "is not")
|
||||
|
||||
|
||||
def build_filter(tbl, conds):
|
||||
"""把 {字段: 值} 或 [(字段, op, 值)] 转成参数化 WHERE(字段名走白名单)。
|
||||
|
||||
返回 (sql, args);sql 不含 tenant_id(由 crud_list 统一打头拼接)。
|
||||
"""
|
||||
allowed = set(table_fields(tbl))
|
||||
parts, args = [], []
|
||||
items = conds.items() if isinstance(conds, dict) else conds
|
||||
for item in items:
|
||||
if isinstance(item, (tuple, list)) and len(item) == 3:
|
||||
name, op, val = item
|
||||
else:
|
||||
name, val = item
|
||||
op = "like" if isinstance(val, str) and ("%" in val or "_" in val) else "="
|
||||
name = str(name).strip()
|
||||
if name not in allowed:
|
||||
raise PblBlueprintError(ERR_PARAM_INVALID, "非法查询字段: %s" % name)
|
||||
op = str(op).strip().lower()
|
||||
if op not in ALLOWED_OPS:
|
||||
raise PblBlueprintError(ERR_PARAM_INVALID, "非法操作符: %s" % op)
|
||||
if op == "in":
|
||||
vals = list(val or [])
|
||||
if not vals:
|
||||
continue
|
||||
parts.append("%s in (%s)" % (name, ",".join(["?"] * len(vals))))
|
||||
args.extend(vals)
|
||||
elif op in ("is", "is not"):
|
||||
parts.append("%s %s null" % (name, op))
|
||||
else:
|
||||
parts.append("%s %s ?" % (name, op))
|
||||
args.append(val)
|
||||
return (" and ".join(parts), args)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- 工厂
|
||||
|
||||
def make_crud(tbl):
|
||||
"""为指定表生成一组绑定 tenant 的契约函数(供 init.py 注册到 env)。"""
|
||||
load_model(tbl) # 提前校验表定义存在
|
||||
append_only = tbl in APPEND_ONLY_TABLES
|
||||
|
||||
def _create(tenant_id, data, op_user="", sor=None):
|
||||
return crud_create(tbl, tenant_id, data, op_user, sor)
|
||||
|
||||
def _get(tenant_id, row_id, fields="*", sor=None):
|
||||
return crud_get(tbl, tenant_id, row_id, fields, sor)
|
||||
|
||||
def _list(tenant_id, conds=None, fields="*", order="", page=1, page_size=20, sor=None):
|
||||
w, a = build_filter(tbl, conds or {})
|
||||
return crud_list(tbl, tenant_id, w, a, fields, order, page, page_size, False, sor)
|
||||
|
||||
def _count(tenant_id, conds=None, sor=None):
|
||||
w, a = build_filter(tbl, conds or {})
|
||||
return crud_count(tbl, tenant_id, w, a, False, sor)
|
||||
|
||||
def _update(tenant_id, row_id, data, op_user="", sor=None):
|
||||
if append_only:
|
||||
raise PblBlueprintError(ERR_FORBIDDEN, "%s 为 append-only 表,禁止更新" % tbl)
|
||||
return crud_update(tbl, tenant_id, row_id, data, op_user, sor)
|
||||
|
||||
def _delete(tenant_id, row_id, op_user="", sor=None, hard=False):
|
||||
if append_only:
|
||||
raise PblBlueprintError(ERR_FORBIDDEN, "%s 为 append-only 表,禁止删除" % tbl)
|
||||
return crud_delete(tbl, tenant_id, row_id, op_user, sor, hard)
|
||||
|
||||
return {
|
||||
"create": _create,
|
||||
"get": _get,
|
||||
"list": _list,
|
||||
"count": _count,
|
||||
"update": _update,
|
||||
"delete": _delete,
|
||||
}
|
||||
@ -1,153 +1,122 @@
|
||||
"""pbl_blueprint 数据访问层。
|
||||
# -*- coding: utf-8 -*-
|
||||
"""DB 适配层(M1a):统一取 sqlor 句柄,禁止硬编码库名。
|
||||
|
||||
- 库名一律 ServerEnv().get_module_dbname('pbl_blueprint'),禁止硬编码 DBNAME。
|
||||
- 所有 SQL 只用 sqlor 标准 API:sor.C / sor.U / sor.D / sor.R / sor.I / sor.sqlExe。
|
||||
- tenant_id 强制打头:任何读写条件第一列必须是 tenant_id,缺失即抛 PblTenantRequired。
|
||||
库名一律来自应用注入的 ServerEnv().get_module_dbname('pbl_blueprint');
|
||||
本模块只出现模块名常量 MODULE_NAME,不出现任何具体库名字面量。
|
||||
sqlor 白名单:仅使用 C / U / D / R / I / sqlExe 六个标准 API。
|
||||
"""
|
||||
|
||||
import os
|
||||
import uuid
|
||||
import json as _json
|
||||
|
||||
MODULE_NAME = 'pbl_blueprint'
|
||||
MODULE_NAME = "pbl_blueprint"
|
||||
|
||||
TABLES = [
|
||||
'pbl_blueprint',
|
||||
'pbl_blueprint_version',
|
||||
'pbl_blueprint_template',
|
||||
'pbl_blueprint_template_item',
|
||||
'pbl_blueprint_task',
|
||||
'pbl_blueprint_mission',
|
||||
'pbl_blueprint_role',
|
||||
'pbl_blueprint_learning_goal',
|
||||
'pbl_blueprint_evidence_spec',
|
||||
'pbl_blueprint_artifact_spec',
|
||||
'pbl_blueprint_reflection_spec',
|
||||
]
|
||||
_sor_cache = {}
|
||||
|
||||
|
||||
class PblError(Exception):
|
||||
"""pbl 模块统一异常基类。"""
|
||||
|
||||
code = 'PBL_ERROR'
|
||||
|
||||
def __init__(self, message, code=None, **extra):
|
||||
super(PblError, self).__init__(message)
|
||||
self.message = message
|
||||
if code:
|
||||
self.code = code
|
||||
self.extra = extra
|
||||
|
||||
|
||||
class PblTenantRequired(PblError):
|
||||
"""租户上下文缺失(fail-closed)。"""
|
||||
|
||||
code = 'PBL_TENANT_REQUIRED'
|
||||
|
||||
|
||||
class PblNotFound(PblError):
|
||||
"""记录不存在。"""
|
||||
|
||||
code = 'PBL_NOT_FOUND'
|
||||
|
||||
|
||||
class PblValidationError(PblError):
|
||||
"""入参校验失败。"""
|
||||
|
||||
code = 'PBL_VALIDATION_ERROR'
|
||||
|
||||
|
||||
def new_id():
|
||||
"""32 位无横线 UUID 主键。"""
|
||||
return uuid.uuid4().hex
|
||||
|
||||
|
||||
def get_env(env=None):
|
||||
if env is not None:
|
||||
return env
|
||||
def _server_env():
|
||||
"""取平台 ServerEnv(延迟导入,避免模块单独 import 时依赖平台)。"""
|
||||
try:
|
||||
from sage import ServerEnv
|
||||
from sage import ServerEnv # type: ignore
|
||||
return ServerEnv()
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def get_dbname(env=None):
|
||||
"""取本模块库名,禁止硬编码。"""
|
||||
e = get_env(env)
|
||||
if e is None:
|
||||
return None
|
||||
getter = getattr(e, 'get_module_dbname', None)
|
||||
if callable(getter):
|
||||
try:
|
||||
return getter(MODULE_NAME)
|
||||
from ahserver import ServerEnv # type: ignore
|
||||
return ServerEnv()
|
||||
except Exception:
|
||||
return None
|
||||
return None
|
||||
|
||||
|
||||
def get_sor(env=None, dbname=None):
|
||||
"""取 sqlor 数据访问对象。"""
|
||||
e = get_env(env)
|
||||
if e is None:
|
||||
def get_dbname():
|
||||
"""取本模块库名(由应用 init() 注入的 get_module_dbname 决定)。"""
|
||||
env = _server_env()
|
||||
if env is None:
|
||||
return ""
|
||||
fn = getattr(env, "get_module_dbname", None)
|
||||
if not callable(fn):
|
||||
return ""
|
||||
try:
|
||||
return fn(MODULE_NAME) or ""
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
|
||||
def get_sor(dbname=None):
|
||||
"""取 sqlor 句柄(按库名缓存)。
|
||||
|
||||
:param dbname: 可选,缺省用 get_dbname()
|
||||
"""
|
||||
db = dbname or get_dbname()
|
||||
if not db:
|
||||
raise RuntimeError("pbl_blueprint: 未取到库名,请确认应用 init() 已注入 get_module_dbname")
|
||||
key = str(db)
|
||||
if key in _sor_cache:
|
||||
return _sor_cache[key]
|
||||
from sqlor import sqlor # type: ignore
|
||||
sor = sqlor(db)
|
||||
_sor_cache[key] = sor
|
||||
return sor
|
||||
|
||||
|
||||
def clear_cache():
|
||||
_sor_cache.clear()
|
||||
|
||||
|
||||
# ---------- sqlor 白名单封装(仅 C/U/D/R/I/sqlExe) ----------
|
||||
|
||||
def q_insert(sor, tbl, data):
|
||||
"""新增一行 -> sor.C"""
|
||||
return sor.C(tbl, data)
|
||||
|
||||
|
||||
def q_update(sor, tbl, data, where, args=None):
|
||||
"""按条件更新 -> sor.U"""
|
||||
return sor.U(tbl, data, where, args or [])
|
||||
|
||||
|
||||
def q_delete(sor, tbl, where, args=None):
|
||||
"""按条件删除 -> sor.D"""
|
||||
return sor.D(tbl, where, args or [])
|
||||
|
||||
|
||||
def q_select(sor, tbl, where="", args=None, fields="*", order="", limit=0, offset=0):
|
||||
"""条件查询 -> sor.R"""
|
||||
return sor.R(tbl, where, args or [], fields, order, limit, offset)
|
||||
|
||||
|
||||
def q_one(sor, tbl, where, args=None, fields="*"):
|
||||
"""取单行(无则 None)-> sor.R"""
|
||||
rows = sor.R(tbl, where, args or [], fields, "", 1, 0)
|
||||
if not rows:
|
||||
return None
|
||||
db = dbname or get_dbname(e)
|
||||
for attr in ('sqlor', 'sor'):
|
||||
obj = getattr(e, attr, None)
|
||||
if obj is not None:
|
||||
return obj
|
||||
getter = getattr(e, 'get_sqlor', None)
|
||||
if callable(getter):
|
||||
try:
|
||||
return getter(db) if db else getter()
|
||||
except Exception:
|
||||
return None
|
||||
return None
|
||||
return rows[0]
|
||||
|
||||
|
||||
def require_tenant(tenant_id):
|
||||
"""tenant_id 强制打头校验:空/非字符串一律拒绝。"""
|
||||
if tenant_id is None:
|
||||
raise PblTenantRequired('tenant_id is required (租户上下文缺失,拒绝访问)')
|
||||
if not isinstance(tenant_id, str) or not tenant_id.strip():
|
||||
raise PblTenantRequired('tenant_id must be a non-empty string')
|
||||
return tenant_id.strip()
|
||||
def q_count(sor, tbl, where="", args=None):
|
||||
"""计数 -> sor.I"""
|
||||
return sor.I(tbl, where, args or [])
|
||||
|
||||
|
||||
def ensure_tables(env=None, dbname=None):
|
||||
"""幂等建表:执行 sql/pbl_blueprint.core.sql 与 sql/pbl_blueprint.subobjects.sql。"""
|
||||
e = get_env(env)
|
||||
db = dbname or get_dbname(e)
|
||||
sor = get_sor(e, db)
|
||||
if sor is None:
|
||||
return False
|
||||
sql_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'sql')
|
||||
executed = []
|
||||
for fname in ('pbl_blueprint.core.sql', 'pbl_blueprint.subobjects.sql'):
|
||||
path = os.path.join(sql_dir, fname)
|
||||
if not os.path.isfile(path):
|
||||
continue
|
||||
with open(path, 'r', encoding='utf-8') as fp:
|
||||
content = fp.read()
|
||||
for stmt in _split_sql(content):
|
||||
try:
|
||||
sor.sqlExe(stmt)
|
||||
executed.append(stmt[:40])
|
||||
except Exception:
|
||||
continue
|
||||
return executed
|
||||
def q_sql(sor, sql, args=None):
|
||||
"""原生 SQL(只读/DDL 场景)-> sor.sqlExe"""
|
||||
return sor.sqlExe(sql, args or [])
|
||||
|
||||
|
||||
def _split_sql(content):
|
||||
"""按分号切分 DDL,剔除注释行与空语句。"""
|
||||
lines = []
|
||||
for line in content.splitlines():
|
||||
stripped = line.strip()
|
||||
if not stripped or stripped.startswith('--'):
|
||||
continue
|
||||
lines.append(line)
|
||||
stmts = []
|
||||
for chunk in '\n'.join(lines).split(';'):
|
||||
chunk = chunk.strip()
|
||||
if chunk:
|
||||
stmts.append(chunk)
|
||||
return stmts
|
||||
def dumps(obj):
|
||||
"""JSON 序列化(None 安全,中文不转义)。"""
|
||||
if obj is None:
|
||||
return ""
|
||||
if isinstance(obj, str):
|
||||
return obj
|
||||
return _json.dumps(obj, ensure_ascii=False, default=str)
|
||||
|
||||
|
||||
def loads(txt, default=None):
|
||||
"""JSON 反序列化(失败返回 default,不抛)。"""
|
||||
if txt is None or txt == "":
|
||||
return default
|
||||
if isinstance(txt, (dict, list)):
|
||||
return txt
|
||||
try:
|
||||
return _json.loads(txt)
|
||||
except Exception:
|
||||
return default
|
||||
|
||||
@ -1,162 +1,317 @@
|
||||
"""pbl_blueprint 模块装配入口。
|
||||
# -*- coding: utf-8 -*-
|
||||
"""pbl_blueprint 模块挂载(M1a):建表 + 契约函数注册 + RBAC 路径注册。
|
||||
|
||||
三处同步注册(module-development-spec):
|
||||
1) 函数定义:本文件 load_pbl_blueprint()
|
||||
2) 包导出: __init__.py -> from .init import load_pbl_blueprint
|
||||
3) env 注册:ServerEnv().register_module('pbl_blueprint', load_pbl_blueprint)
|
||||
三处同步注册(缺一即 selfcheck 失败):
|
||||
1) service.py / crud.py 中定义函数
|
||||
2) __init__.py CONTRACT_FUNCTIONS 导出
|
||||
3) 本文件 REGISTER_FUNCTIONS 注册到 ServerEnv
|
||||
|
||||
RBAC 路径显式枚举(无 %/* 通配符),逐条列出,便于权限审计。
|
||||
库名一律来自 env.get_module_dbname('pbl_blueprint'),本文件不出现任何库名字面量。
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from .db import ensure_tables, get_dbname
|
||||
from .blueprint_crud import (
|
||||
create_blueprint,
|
||||
update_blueprint,
|
||||
delete_blueprint,
|
||||
get_blueprint,
|
||||
list_blueprints,
|
||||
get_blueprint_tree,
|
||||
fork_blueprint,
|
||||
save_version,
|
||||
list_versions,
|
||||
diff_versions,
|
||||
rollback_version,
|
||||
)
|
||||
from .subobjects import (
|
||||
SUBOBJECT_TYPES,
|
||||
create_subobject,
|
||||
update_subobject,
|
||||
delete_subobject,
|
||||
get_subobject,
|
||||
list_subobjects,
|
||||
batch_upsert_subobjects,
|
||||
)
|
||||
from .templates import (
|
||||
create_template,
|
||||
update_template,
|
||||
delete_template,
|
||||
list_templates,
|
||||
get_template,
|
||||
instantiate_template,
|
||||
build_offline_fallback_blueprint,
|
||||
from . import db as _db
|
||||
from .crud import list_tables, load_model, make_crud
|
||||
from .errors import PblBlueprintError
|
||||
|
||||
MODULE_NAME = _db.MODULE_NAME # "pbl_blueprint"
|
||||
|
||||
# ---- 1) 表注册清单(models/*.json 为唯一事实源) ----
|
||||
REGISTER_TABLES = (
|
||||
"pbl_blueprint",
|
||||
"pbl_blueprint_node",
|
||||
"pbl_blueprint_edge",
|
||||
"pbl_blueprint_version",
|
||||
"pbl_blueprint_version_delta",
|
||||
"pbl_blueprint_template",
|
||||
"pbl_blueprint_publish",
|
||||
"pbl_blueprint_offline",
|
||||
"pbl_blueprint_fork",
|
||||
"pbl_blueprint_lock",
|
||||
"pbl_blueprint_audit",
|
||||
)
|
||||
|
||||
MODULE_NAME = 'pbl_blueprint'
|
||||
# ---- 2) CRUD 契约注册清单(表名 -> 注册前缀) ----
|
||||
REGISTER_CRUD = REGISTER_TABLES
|
||||
|
||||
# 本模块全部表(核心 4 + 子对象 7)
|
||||
TABLES = [
|
||||
'pbl_blueprint',
|
||||
'pbl_blueprint_version',
|
||||
'pbl_blueprint_template',
|
||||
'pbl_blueprint_template_item',
|
||||
'pbl_blueprint_task',
|
||||
'pbl_blueprint_mission',
|
||||
'pbl_blueprint_role',
|
||||
'pbl_blueprint_learning_goal',
|
||||
'pbl_blueprint_evidence_spec',
|
||||
'pbl_blueprint_artifact_spec',
|
||||
'pbl_blueprint_reflection_spec',
|
||||
]
|
||||
# ---- 3) 业务契约函数注册清单(与 __init__.CONTRACT_FUNCTIONS 一致) ----
|
||||
REGISTER_FUNCTIONS = (
|
||||
"create_blueprint", "get_blueprint", "update_blueprint", "delete_blueprint",
|
||||
"list_blueprints", "get_blueprint_tree", "create_node", "update_node",
|
||||
"delete_node", "list_nodes", "create_edge", "delete_edge", "list_edges",
|
||||
"fork_blueprint", "list_forks", "save_version", "list_versions", "get_version",
|
||||
"get_change_delta", "rollback_version", "create_template", "list_templates",
|
||||
"instantiate_template", "instantiate_payload", "export_offline", "import_offline",
|
||||
"list_offline", "publish_blueprint", "revoke_publish", "list_publishes",
|
||||
"lock_blueprint", "unlock_blueprint", "clean_expired_locks", "blueprint_stats",
|
||||
"write_audit", "list_audit",
|
||||
)
|
||||
|
||||
# ---- 4) RBAC 路径显式枚举(无通配符) ----
|
||||
RBAC_PATHS = (
|
||||
"/api/pbl_blueprint/list.dspy",
|
||||
"/api/pbl_blueprint/create.dspy",
|
||||
"/api/pbl_blueprint/get.dspy",
|
||||
"/api/pbl_blueprint/update.dspy",
|
||||
"/api/pbl_blueprint/delete.dspy",
|
||||
"/api/pbl_blueprint/tree.dspy",
|
||||
"/api/pbl_blueprint/fork.dspy",
|
||||
"/api/pbl_blueprint/forks.dspy",
|
||||
"/api/pbl_blueprint/stats.dspy",
|
||||
"/api/pbl_blueprint_node/list.dspy",
|
||||
"/api/pbl_blueprint_node/create.dspy",
|
||||
"/api/pbl_blueprint_node/get.dspy",
|
||||
"/api/pbl_blueprint_node/update.dspy",
|
||||
"/api/pbl_blueprint_node/delete.dspy",
|
||||
"/api/pbl_blueprint_edge/list.dspy",
|
||||
"/api/pbl_blueprint_edge/create.dspy",
|
||||
"/api/pbl_blueprint_edge/get.dspy",
|
||||
"/api/pbl_blueprint_edge/update.dspy",
|
||||
"/api/pbl_blueprint_edge/delete.dspy",
|
||||
"/api/pbl_blueprint_version/list.dspy",
|
||||
"/api/pbl_blueprint_version/get.dspy",
|
||||
"/api/pbl_blueprint_version/save.dspy",
|
||||
"/api/pbl_blueprint_version/rollback.dspy",
|
||||
"/api/pbl_blueprint_version_delta/list.dspy",
|
||||
"/api/pbl_blueprint_version_delta/get.dspy",
|
||||
"/api/pbl_blueprint_template/list.dspy",
|
||||
"/api/pbl_blueprint_template/create.dspy",
|
||||
"/api/pbl_blueprint_template/get.dspy",
|
||||
"/api/pbl_blueprint_template/update.dspy",
|
||||
"/api/pbl_blueprint_template/delete.dspy",
|
||||
"/api/pbl_blueprint_template/instantiate.dspy",
|
||||
"/api/pbl_blueprint_publish/list.dspy",
|
||||
"/api/pbl_blueprint_publish/create.dspy",
|
||||
"/api/pbl_blueprint_publish/get.dspy",
|
||||
"/api/pbl_blueprint_publish/revoke.dspy",
|
||||
"/api/pbl_blueprint_offline/list.dspy",
|
||||
"/api/pbl_blueprint_offline/export.dspy",
|
||||
"/api/pbl_blueprint_offline/import.dspy",
|
||||
"/api/pbl_blueprint_offline/get.dspy",
|
||||
"/api/pbl_blueprint_fork/list.dspy",
|
||||
"/api/pbl_blueprint_fork/get.dspy",
|
||||
"/api/pbl_blueprint_lock/lock.dspy",
|
||||
"/api/pbl_blueprint_lock/unlock.dspy",
|
||||
"/api/pbl_blueprint_lock/list.dspy",
|
||||
"/api/pbl_blueprint_lock/clean_expired.dspy",
|
||||
"/api/pbl_blueprint_audit/list.dspy",
|
||||
"/api/pbl_blueprint_audit/get.dspy",
|
||||
)
|
||||
|
||||
# ---- 5) 菜单/页面注册(前端入口,显式枚举) ----
|
||||
MENUS = (
|
||||
{"code": "pbl_blueprint", "name": "PBL蓝图", "path": "/pbls/blueprint", "parent": "pbls", "sort": 10},
|
||||
{"code": "pbl_blueprint_template", "name": "蓝图模板", "path": "/pbls/blueprint/template", "parent": "pbls", "sort": 11},
|
||||
{"code": "pbl_blueprint_publish", "name": "蓝图发布", "path": "/pbls/blueprint/publish", "parent": "pbls", "sort": 12},
|
||||
{"code": "pbl_blueprint_offline", "name": "离线包", "path": "/pbls/blueprint/offline", "parent": "pbls", "sort": 13},
|
||||
{"code": "pbl_blueprint_audit", "name": "蓝图审计", "path": "/pbls/blueprint/audit", "parent": "pbls", "sort": 14},
|
||||
)
|
||||
|
||||
_init_state = {"done": False, "dbname": "", "tables": 0, "functions": 0, "rbac": 0, "menus": 0}
|
||||
|
||||
|
||||
def load_pbl_blueprint(env=None):
|
||||
"""挂载 pbl_blueprint 模块到应用。
|
||||
|
||||
:param env: ServerEnv 实例(应用 init() 里逐个 load 时传入);为空则自建。
|
||||
:return: 模块契约字典(供应用/其他模块按契约调用)
|
||||
"""
|
||||
if env is None:
|
||||
try:
|
||||
from sage import ServerEnv
|
||||
env = ServerEnv()
|
||||
except Exception:
|
||||
env = None
|
||||
|
||||
dbname = get_dbname(env)
|
||||
|
||||
# 建表(幂等,IF NOT EXISTS)
|
||||
ensure_tables(env, dbname)
|
||||
|
||||
# CRUD 定义注册(json/*.json)
|
||||
_register_crud_json(env)
|
||||
|
||||
contract = {
|
||||
'module': MODULE_NAME,
|
||||
'dbname': dbname,
|
||||
'tables': list(TABLES),
|
||||
'subobject_types': list(SUBOBJECT_TYPES),
|
||||
# 蓝图 CRUD 与查询契约
|
||||
'create_blueprint': create_blueprint,
|
||||
'update_blueprint': update_blueprint,
|
||||
'delete_blueprint': delete_blueprint,
|
||||
'get_blueprint': get_blueprint,
|
||||
'list_blueprints': list_blueprints,
|
||||
'get_blueprint_tree': get_blueprint_tree,
|
||||
'fork_blueprint': fork_blueprint,
|
||||
# 版本契约
|
||||
'save_version': save_version,
|
||||
'list_versions': list_versions,
|
||||
'diff_versions': diff_versions,
|
||||
'rollback_version': rollback_version,
|
||||
# 7 类子对象泛化契约
|
||||
'create_subobject': create_subobject,
|
||||
'update_subobject': update_subobject,
|
||||
'delete_subobject': delete_subobject,
|
||||
'get_subobject': get_subobject,
|
||||
'list_subobjects': list_subobjects,
|
||||
'batch_upsert_subobjects': batch_upsert_subobjects,
|
||||
# 模板契约
|
||||
'create_template': create_template,
|
||||
'update_template': update_template,
|
||||
'delete_template': delete_template,
|
||||
'get_template': get_template,
|
||||
'list_templates': list_templates,
|
||||
'instantiate_template': instantiate_template,
|
||||
'build_offline_fallback_blueprint': build_offline_fallback_blueprint,
|
||||
}
|
||||
|
||||
def _get_env(env=None):
|
||||
if env is not None:
|
||||
for fn in ('register_module', 'set_module_contract', 'register_contract'):
|
||||
reg = getattr(env, fn, None)
|
||||
if callable(reg):
|
||||
try:
|
||||
reg(MODULE_NAME, contract)
|
||||
except TypeError:
|
||||
try:
|
||||
reg(contract)
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
break
|
||||
|
||||
return contract
|
||||
return env
|
||||
return _db._server_env()
|
||||
|
||||
|
||||
def _register_crud_json(env):
|
||||
"""把 json/*.json 的 CRUD 定义注册到 env(若平台提供注册钩子)。"""
|
||||
json_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'json')
|
||||
if not os.path.isdir(json_dir):
|
||||
return
|
||||
defs = {}
|
||||
for fname in sorted(os.listdir(json_dir)):
|
||||
if not fname.endswith('.json'):
|
||||
continue
|
||||
path = os.path.join(json_dir, fname)
|
||||
try:
|
||||
import json as _json
|
||||
with open(path, 'r', encoding='utf-8') as fp:
|
||||
defs[fname[:-5]] = _json.load(fp)
|
||||
except Exception:
|
||||
continue
|
||||
if env is None:
|
||||
return defs
|
||||
for fn in ('register_crud', 'load_crud_json', 'set_crud_defs'):
|
||||
reg = getattr(env, fn, None)
|
||||
if callable(reg):
|
||||
def _register_tables(env, sor, create_ddl=True):
|
||||
"""注册表定义(models/*.json),必要时建表。"""
|
||||
n = 0
|
||||
on_disk = set(list_tables())
|
||||
for tbl in REGISTER_TABLES:
|
||||
model = load_model(tbl) # 表定义必须真实存在,缺失即抛错(fail-closed)
|
||||
if tbl not in on_disk:
|
||||
raise PblBlueprintError("PBL_BP_E_INTERNAL", "models/%s.json 缺失" % tbl)
|
||||
if env is not None and hasattr(env, "register_table"):
|
||||
try:
|
||||
reg(defs)
|
||||
env.register_table(MODULE_NAME, tbl, model)
|
||||
except Exception:
|
||||
pass
|
||||
break
|
||||
return defs
|
||||
if create_ddl and sor is not None:
|
||||
try:
|
||||
_ensure_table(sor, tbl, model)
|
||||
except Exception:
|
||||
pass # 建表失败不阻断挂载(部署阶段由 DDL 脚本保证)
|
||||
n += 1
|
||||
return n
|
||||
|
||||
|
||||
def _ensure_table(sor, tbl, model):
|
||||
"""按 models 定义建表(不存在才建),只用 sqlor.sqlExe。"""
|
||||
cols = []
|
||||
for name, spec in (model.get("fields") or {}).items():
|
||||
cols.append(" %s %s" % (name, _col_type(spec)))
|
||||
pk = model.get("primary") or ["id"]
|
||||
cols.append(" primary key (%s)" % ",".join(pk))
|
||||
for idx_name, idx in (model.get("indexes") or {}).items():
|
||||
uq = "unique " if idx.get("unique") else ""
|
||||
cols.append(" %skey %s (%s)" % (uq, idx_name, ",".join(idx.get("fields") or [])))
|
||||
ddl = "create table if not exists %s (\n%s\n)" % (tbl, ",\n".join(cols))
|
||||
_db.q_sql(sor, ddl, [])
|
||||
|
||||
|
||||
def _col_type(spec):
|
||||
t = (spec.get("type") or "str").lower()
|
||||
size = spec.get("size")
|
||||
if t in ("str", "string", "varchar", "char"):
|
||||
return "varchar(%d)" % int(size or 64)
|
||||
if t == "int":
|
||||
return "int"
|
||||
if t == "bigint":
|
||||
return "bigint"
|
||||
if t == "double":
|
||||
if isinstance(size, (list, tuple)) and len(size) == 2:
|
||||
return "decimal(%d,%d)" % (int(size[0]), int(size[1]))
|
||||
return "double"
|
||||
if t in ("text", "longtext", "json"):
|
||||
return "text"
|
||||
if t in ("datetime", "timestamp"):
|
||||
return "datetime"
|
||||
if t == "date":
|
||||
return "date"
|
||||
return "varchar(64)"
|
||||
|
||||
|
||||
def _register_crud(env, prefix=""):
|
||||
"""注册 11 表的通用 CRUD 契约函数({表名}_{动作})。"""
|
||||
n = 0
|
||||
if env is None or not hasattr(env, "register_function"):
|
||||
return 0
|
||||
for tbl in REGISTER_CRUD:
|
||||
api = make_crud(tbl)
|
||||
for act, fn in api.items():
|
||||
name = "%s%s_%s" % (prefix, tbl, act)
|
||||
try:
|
||||
env.register_function(MODULE_NAME, name, fn)
|
||||
n += 1
|
||||
except Exception:
|
||||
pass
|
||||
return n
|
||||
|
||||
|
||||
def _register_functions(env):
|
||||
"""注册业务契约函数(三处同步的第 3 处)。"""
|
||||
from . import get_contract_functions
|
||||
fns = get_contract_functions()
|
||||
n = 0
|
||||
for name in REGISTER_FUNCTIONS:
|
||||
fn = fns.get(name)
|
||||
if fn is None:
|
||||
raise PblBlueprintError("PBL_BP_E_INTERNAL",
|
||||
"契约函数 %s 未在 __init__ 导出(三处同步失败)" % name)
|
||||
if env is not None and hasattr(env, "register_function"):
|
||||
try:
|
||||
env.register_function(MODULE_NAME, name, fn)
|
||||
except Exception:
|
||||
pass
|
||||
n += 1
|
||||
return n
|
||||
|
||||
|
||||
def _register_rbac(env):
|
||||
"""注册 RBAC 路径(显式枚举,无通配符)。"""
|
||||
n = 0
|
||||
if env is None:
|
||||
return 0
|
||||
for p in RBAC_PATHS:
|
||||
if "%" in p or "*" in p:
|
||||
raise PblBlueprintError("PBL_BP_E_INTERNAL", "RBAC 路径含通配符: %s" % p)
|
||||
fn = getattr(env, "register_rbac_path", None) or getattr(env, "add_rbac_path", None)
|
||||
if callable(fn):
|
||||
try:
|
||||
fn(MODULE_NAME, p)
|
||||
except Exception:
|
||||
pass
|
||||
n += 1
|
||||
return n
|
||||
|
||||
|
||||
def _register_menus(env):
|
||||
n = 0
|
||||
if env is None:
|
||||
return 0
|
||||
fn = getattr(env, "register_menu", None) or getattr(env, "add_menu", None)
|
||||
for m in MENUS:
|
||||
if callable(fn):
|
||||
try:
|
||||
fn(MODULE_NAME, m)
|
||||
except Exception:
|
||||
pass
|
||||
n += 1
|
||||
return n
|
||||
|
||||
|
||||
def init(env=None, dbname=None, register_tables=True, register_functions=True,
|
||||
register_rbac=True, create_ddl=True):
|
||||
"""模块挂载入口(幂等)。由 apps/pbls 的 init() 逐个 load_{模块}() 时调用。"""
|
||||
if _init_state["done"] and not dbname:
|
||||
return dict(_init_state)
|
||||
e = _get_env(env)
|
||||
db = dbname or (e.get_module_dbname(MODULE_NAME) if (e is not None and hasattr(e, "get_module_dbname")) else "")
|
||||
if not db:
|
||||
db = _db.get_dbname()
|
||||
if not db:
|
||||
raise PblBlueprintError("PBL_BP_E_INTERNAL",
|
||||
"未取到 %s 库名:应用 init() 必须定义 get_module_dbname 并挂到 ServerEnv" % MODULE_NAME)
|
||||
sor = _db.get_sor(db)
|
||||
st = {"done": True, "dbname": db, "tables": 0, "crud": 0, "functions": 0, "rbac": 0, "menus": 0}
|
||||
if register_tables:
|
||||
st["tables"] = _register_tables(e, sor, create_ddl)
|
||||
st["crud"] = _register_crud(e)
|
||||
if register_functions:
|
||||
st["functions"] = _register_functions(e)
|
||||
if register_rbac:
|
||||
st["rbac"] = _register_rbac(e)
|
||||
st["menus"] = _register_menus(e)
|
||||
_init_state.update(st)
|
||||
return dict(st)
|
||||
|
||||
|
||||
def get_state():
|
||||
return dict(_init_state)
|
||||
|
||||
|
||||
def reset():
|
||||
"""测试用:重置挂载状态与 sqlor 缓存。"""
|
||||
_init_state.update({"done": False, "dbname": "", "tables": 0, "functions": 0,
|
||||
"rbac": 0, "menus": 0, "crud": 0})
|
||||
_db.clear_cache()
|
||||
|
||||
|
||||
def ddl_script(out_path=None):
|
||||
"""生成全量建表 DDL 文本(供部署阶段执行 / 自查比对)。"""
|
||||
lines = ["-- pbl_blueprint M1a DDL (%d tables)" % len(REGISTER_TABLES), ""]
|
||||
for tbl in REGISTER_TABLES:
|
||||
model = load_model(tbl)
|
||||
cols = []
|
||||
for name, spec in (model.get("fields") or {}).items():
|
||||
nn = " not null" if spec.get("notnull") else ""
|
||||
df = ""
|
||||
if "default" in spec:
|
||||
d = spec["default"]
|
||||
df = " default '%s'" % d if isinstance(d, str) else " default %s" % d
|
||||
cm = " comment '%s'" % (spec.get("summary") or "").replace("'", "")
|
||||
cols.append(" `%s` %s%s%s%s" % (name, _col_type(spec), nn, df, cm))
|
||||
pk = model.get("primary") or ["id"]
|
||||
cols.append(" primary key (%s)" % ",".join(["`%s`" % c for c in pk]))
|
||||
for idx_name, idx in (model.get("indexes") or {}).items():
|
||||
uq = "unique " if idx.get("unique") else ""
|
||||
cols.append(" %skey `%s` (%s)" % (uq, idx_name,
|
||||
",".join(["`%s`" % c for c in (idx.get("fields") or [])])))
|
||||
lines.append("-- %s" % (model.get("summary") or tbl))
|
||||
lines.append("create table if not exists `%s` (" % tbl)
|
||||
lines.append(",\n".join(cols))
|
||||
lines.append(") engine=InnoDB default charset=utf8mb4 comment='%s';" % tbl)
|
||||
lines.append("")
|
||||
txt = "\n".join(lines)
|
||||
if out_path:
|
||||
d = os.path.dirname(os.path.abspath(out_path))
|
||||
if d and not os.path.isdir(d):
|
||||
os.makedirs(d)
|
||||
with open(out_path, "w", encoding="utf-8") as f:
|
||||
f.write(txt)
|
||||
return txt
|
||||
|
||||
@ -1,58 +1,214 @@
|
||||
{
|
||||
"tblname": "pbl_blueprint",
|
||||
"params": {
|
||||
"editable": [
|
||||
"code",
|
||||
"name",
|
||||
"subject",
|
||||
"grade",
|
||||
"duration_hours",
|
||||
"status",
|
||||
"summary",
|
||||
"config",
|
||||
"source_blueprint_id",
|
||||
"template_id",
|
||||
"owner_id",
|
||||
"remark"
|
||||
],
|
||||
"browserfields": [
|
||||
"code",
|
||||
"name",
|
||||
"subject",
|
||||
"grade",
|
||||
"duration_hours",
|
||||
"version_no",
|
||||
"status",
|
||||
"quality_level",
|
||||
"owner_id",
|
||||
"template_id",
|
||||
"published_at",
|
||||
"updated_at"
|
||||
],
|
||||
"searchfields": [
|
||||
"tenant_id",
|
||||
"code",
|
||||
"name",
|
||||
"subject",
|
||||
"status",
|
||||
"owner_id"
|
||||
],
|
||||
"orderby": "updated_at desc",
|
||||
"defaultfilter": {
|
||||
"deleted": 0
|
||||
"id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"editable": true,
|
||||
"query": false,
|
||||
"summary": "主键ID(str32,UUID去横线)"
|
||||
},
|
||||
"caption": "PBL蓝图",
|
||||
"readonly": [
|
||||
"id",
|
||||
"tenant_id",
|
||||
"version_no",
|
||||
"quality_level",
|
||||
"published_at",
|
||||
"created_by",
|
||||
"created_at",
|
||||
"updated_by",
|
||||
"updated_at",
|
||||
"deleted"
|
||||
]
|
||||
}
|
||||
"tenant_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "租户ID(首业务字段,所有读写强制打头,缺失即 fail-closed 拒绝)"
|
||||
},
|
||||
"code": {
|
||||
"type": "str",
|
||||
"size": 64,
|
||||
"notnull": true,
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "蓝图编码(租户内唯一,PBL-BP-序号)"
|
||||
},
|
||||
"name": {
|
||||
"type": "str",
|
||||
"size": 128,
|
||||
"notnull": true,
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "蓝图名称"
|
||||
},
|
||||
"subject": {
|
||||
"type": "str",
|
||||
"size": 64,
|
||||
"default": "",
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "学科"
|
||||
},
|
||||
"grade": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"default": "",
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "年级"
|
||||
},
|
||||
"phase": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"default": "",
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "学段"
|
||||
},
|
||||
"status": {
|
||||
"type": "str",
|
||||
"size": 16,
|
||||
"notnull": true,
|
||||
"default": "draft",
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"code": "status",
|
||||
"summary": "蓝图状态"
|
||||
},
|
||||
"quality_status": {
|
||||
"type": "str",
|
||||
"size": 16,
|
||||
"notnull": true,
|
||||
"default": "q0",
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"code": "quality_status",
|
||||
"summary": "质量状态(5 级)"
|
||||
},
|
||||
"source": {
|
||||
"type": "str",
|
||||
"size": 16,
|
||||
"notnull": true,
|
||||
"default": "manual",
|
||||
"editable": true,
|
||||
"query": false,
|
||||
"code": "source",
|
||||
"summary": "来源方式"
|
||||
},
|
||||
"source_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"default": "",
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "来源对象ID(模板ID或父蓝图ID)"
|
||||
},
|
||||
"owner_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"default": "",
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "负责人(教师)ID"
|
||||
},
|
||||
"class_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"default": "",
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "关联班级ID"
|
||||
},
|
||||
"current_version": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "当前版本号(save_version 回写)"
|
||||
},
|
||||
"duration_hours": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"editable": true,
|
||||
"query": false,
|
||||
"summary": "预计课时数"
|
||||
},
|
||||
"budget_amount": {
|
||||
"type": "double",
|
||||
"size": [
|
||||
18,
|
||||
2
|
||||
],
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"editable": true,
|
||||
"query": false,
|
||||
"summary": "预算金额(double(18,2))"
|
||||
},
|
||||
"summary": {
|
||||
"type": "str",
|
||||
"size": 512,
|
||||
"default": "",
|
||||
"editable": true,
|
||||
"query": false,
|
||||
"summary": "蓝图简介"
|
||||
},
|
||||
"ext_json": {
|
||||
"type": "text",
|
||||
"default": "",
|
||||
"editable": true,
|
||||
"query": false,
|
||||
"summary": "扩展属性JSON"
|
||||
},
|
||||
"publish_time": {
|
||||
"type": "datetime",
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "最近发布时间"
|
||||
},
|
||||
"create_user": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"default": "",
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "创建人"
|
||||
},
|
||||
"create_time": {
|
||||
"type": "datetime",
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "创建时间"
|
||||
},
|
||||
"update_user": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"default": "",
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "更新人"
|
||||
},
|
||||
"update_time": {
|
||||
"type": "datetime",
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "更新时间"
|
||||
},
|
||||
"deleted": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "逻辑删除标记 0正常 1删除"
|
||||
}
|
||||
},
|
||||
"editable": [
|
||||
"api/pbl_blueprint_list.dspy",
|
||||
"api/pbl_blueprint_edit.dspy",
|
||||
"api/pbl_blueprint_view.dspy"
|
||||
],
|
||||
"browserfields": [
|
||||
"code",
|
||||
"name",
|
||||
"subject",
|
||||
"status",
|
||||
"quality_status",
|
||||
"owner_id",
|
||||
"update_time"
|
||||
]
|
||||
}
|
||||
|
||||
@ -1,57 +0,0 @@
|
||||
{
|
||||
"tblname": "pbl_blueprint_artifact_spec",
|
||||
"params": {
|
||||
"editable": [
|
||||
"blueprint_id",
|
||||
"task_id",
|
||||
"mission_id",
|
||||
"seq",
|
||||
"code",
|
||||
"name",
|
||||
"artifact_type",
|
||||
"description",
|
||||
"spec",
|
||||
"accept_criteria",
|
||||
"required",
|
||||
"weight",
|
||||
"status"
|
||||
],
|
||||
"browserfields": [
|
||||
"blueprint_id",
|
||||
"task_id",
|
||||
"mission_id",
|
||||
"seq",
|
||||
"code",
|
||||
"name",
|
||||
"artifact_type",
|
||||
"required",
|
||||
"weight",
|
||||
"status",
|
||||
"updated_at"
|
||||
],
|
||||
"searchfields": [
|
||||
"tenant_id",
|
||||
"blueprint_id",
|
||||
"task_id",
|
||||
"mission_id",
|
||||
"artifact_type",
|
||||
"code",
|
||||
"name",
|
||||
"status"
|
||||
],
|
||||
"orderby": "seq asc",
|
||||
"defaultfilter": {
|
||||
"deleted": 0
|
||||
},
|
||||
"caption": "蓝图子对象-产出物规格",
|
||||
"readonly": [
|
||||
"id",
|
||||
"tenant_id",
|
||||
"created_by",
|
||||
"created_at",
|
||||
"updated_by",
|
||||
"updated_at",
|
||||
"deleted"
|
||||
]
|
||||
}
|
||||
}
|
||||
153
pbl_blueprint/json/pbl_blueprint_audit.json
Normal file
153
pbl_blueprint/json/pbl_blueprint_audit.json
Normal file
@ -0,0 +1,153 @@
|
||||
{
|
||||
"tblname": "pbl_blueprint_audit",
|
||||
"params": {
|
||||
"id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"editable": true,
|
||||
"query": false,
|
||||
"summary": "主键ID(str32,UUID去横线)"
|
||||
},
|
||||
"tenant_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "租户ID(首业务字段,所有读写强制打头,缺失即 fail-closed 拒绝)"
|
||||
},
|
||||
"blueprint_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "蓝图ID"
|
||||
},
|
||||
"target_type": {
|
||||
"type": "str",
|
||||
"size": 16,
|
||||
"notnull": true,
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"code": "target_type",
|
||||
"summary": "操作对象类型"
|
||||
},
|
||||
"target_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "操作对象ID"
|
||||
},
|
||||
"action": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"code": "action",
|
||||
"summary": "操作动作"
|
||||
},
|
||||
"version_no": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "关联版本号"
|
||||
},
|
||||
"before_json": {
|
||||
"type": "text",
|
||||
"default": "",
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "操作前数据JSON"
|
||||
},
|
||||
"after_json": {
|
||||
"type": "text",
|
||||
"default": "",
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "操作后数据JSON"
|
||||
},
|
||||
"result": {
|
||||
"type": "str",
|
||||
"size": 16,
|
||||
"notnull": true,
|
||||
"default": "success",
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"code": "result",
|
||||
"summary": "操作结果"
|
||||
},
|
||||
"err_code": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "失败错误码"
|
||||
},
|
||||
"cost_ms": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "耗时毫秒"
|
||||
},
|
||||
"op_user": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "操作人"
|
||||
},
|
||||
"op_source": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"default": "api",
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "操作来源(web/api/agent/offline)"
|
||||
},
|
||||
"client_ip": {
|
||||
"type": "str",
|
||||
"size": 64,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"editable": true,
|
||||
"query": false,
|
||||
"summary": "客户端IP"
|
||||
},
|
||||
"create_time": {
|
||||
"type": "datetime",
|
||||
"editable": false,
|
||||
"query": true,
|
||||
"summary": "操作时间"
|
||||
}
|
||||
},
|
||||
"editable": [
|
||||
"api/pbl_blueprint_audit_list.dspy",
|
||||
"api/pbl_blueprint_audit_edit.dspy",
|
||||
"api/pbl_blueprint_audit_view.dspy"
|
||||
],
|
||||
"browserfields": [
|
||||
"blueprint_id",
|
||||
"target_type",
|
||||
"action",
|
||||
"result",
|
||||
"op_user",
|
||||
"op_source",
|
||||
"create_time"
|
||||
]
|
||||
}
|
||||
122
pbl_blueprint/json/pbl_blueprint_edge.json
Normal file
122
pbl_blueprint/json/pbl_blueprint_edge.json
Normal file
@ -0,0 +1,122 @@
|
||||
{
|
||||
"tblname": "pbl_blueprint_edge",
|
||||
"params": {
|
||||
"id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"editable": true,
|
||||
"query": false,
|
||||
"summary": "主键ID(str32,UUID去横线)"
|
||||
},
|
||||
"tenant_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "租户ID(首业务字段,所有读写强制打头,缺失即 fail-closed 拒绝)"
|
||||
},
|
||||
"blueprint_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "所属蓝图ID"
|
||||
},
|
||||
"from_node_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "起点节点ID"
|
||||
},
|
||||
"to_node_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "终点节点ID"
|
||||
},
|
||||
"edge_type": {
|
||||
"type": "str",
|
||||
"size": 16,
|
||||
"notnull": true,
|
||||
"default": "depends",
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"code": "edge_type",
|
||||
"summary": "边类型"
|
||||
},
|
||||
"weight": {
|
||||
"type": "double",
|
||||
"size": [
|
||||
18,
|
||||
2
|
||||
],
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"editable": true,
|
||||
"query": false,
|
||||
"summary": "边权重(double(18,2))"
|
||||
},
|
||||
"condition_json": {
|
||||
"type": "text",
|
||||
"default": "",
|
||||
"editable": true,
|
||||
"query": false,
|
||||
"summary": "触发条件JSON"
|
||||
},
|
||||
"sort_no": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"editable": true,
|
||||
"query": false,
|
||||
"summary": "排序号"
|
||||
},
|
||||
"create_user": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"default": "",
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "创建人"
|
||||
},
|
||||
"create_time": {
|
||||
"type": "datetime",
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "创建时间"
|
||||
},
|
||||
"update_time": {
|
||||
"type": "datetime",
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "更新时间"
|
||||
},
|
||||
"deleted": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "逻辑删除标记 0正常 1删除"
|
||||
}
|
||||
},
|
||||
"editable": [
|
||||
"api/pbl_blueprint_edge_list.dspy",
|
||||
"api/pbl_blueprint_edge_edit.dspy",
|
||||
"api/pbl_blueprint_edge_view.dspy"
|
||||
],
|
||||
"browserfields": [
|
||||
"from_node_id",
|
||||
"to_node_id",
|
||||
"edge_type",
|
||||
"weight",
|
||||
"sort_no"
|
||||
]
|
||||
}
|
||||
@ -1,60 +0,0 @@
|
||||
{
|
||||
"tblname": "pbl_blueprint_evidence_spec",
|
||||
"params": {
|
||||
"editable": [
|
||||
"blueprint_id",
|
||||
"task_id",
|
||||
"mission_id",
|
||||
"learning_goal_id",
|
||||
"seq",
|
||||
"code",
|
||||
"name",
|
||||
"evidence_type",
|
||||
"collect_mode",
|
||||
"description",
|
||||
"spec",
|
||||
"rule",
|
||||
"weight",
|
||||
"status"
|
||||
],
|
||||
"browserfields": [
|
||||
"blueprint_id",
|
||||
"task_id",
|
||||
"mission_id",
|
||||
"learning_goal_id",
|
||||
"seq",
|
||||
"code",
|
||||
"name",
|
||||
"evidence_type",
|
||||
"collect_mode",
|
||||
"weight",
|
||||
"status",
|
||||
"updated_at"
|
||||
],
|
||||
"searchfields": [
|
||||
"tenant_id",
|
||||
"blueprint_id",
|
||||
"task_id",
|
||||
"mission_id",
|
||||
"learning_goal_id",
|
||||
"evidence_type",
|
||||
"code",
|
||||
"name",
|
||||
"status"
|
||||
],
|
||||
"orderby": "seq asc",
|
||||
"defaultfilter": {
|
||||
"deleted": 0
|
||||
},
|
||||
"caption": "蓝图子对象-证据规格",
|
||||
"readonly": [
|
||||
"id",
|
||||
"tenant_id",
|
||||
"created_by",
|
||||
"created_at",
|
||||
"updated_by",
|
||||
"updated_at",
|
||||
"deleted"
|
||||
]
|
||||
}
|
||||
}
|
||||
137
pbl_blueprint/json/pbl_blueprint_fork.json
Normal file
137
pbl_blueprint/json/pbl_blueprint_fork.json
Normal file
@ -0,0 +1,137 @@
|
||||
{
|
||||
"tblname": "pbl_blueprint_fork",
|
||||
"params": {
|
||||
"id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"editable": true,
|
||||
"query": false,
|
||||
"summary": "主键ID(str32,UUID去横线)"
|
||||
},
|
||||
"tenant_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "租户ID(首业务字段,所有读写强制打头,缺失即 fail-closed 拒绝)"
|
||||
},
|
||||
"source_blueprint_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "源蓝图ID(模板实例化时为空串)"
|
||||
},
|
||||
"source_version": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"editable": true,
|
||||
"query": false,
|
||||
"summary": "fork 时的源版本号"
|
||||
},
|
||||
"target_blueprint_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "派生出的新蓝图ID"
|
||||
},
|
||||
"fork_type": {
|
||||
"type": "str",
|
||||
"size": 16,
|
||||
"notnull": true,
|
||||
"default": "copy",
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"code": "fork_type",
|
||||
"summary": "fork 方式"
|
||||
},
|
||||
"template_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "若由模板实例化,记录模板ID"
|
||||
},
|
||||
"node_count": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "复制的节点数"
|
||||
},
|
||||
"edge_count": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "复制的边数"
|
||||
},
|
||||
"cost_amount": {
|
||||
"type": "double",
|
||||
"size": [
|
||||
18,
|
||||
2
|
||||
],
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"editable": true,
|
||||
"query": false,
|
||||
"summary": "本次 fork 产生的费用金额(double(18,2),付费模板)"
|
||||
},
|
||||
"remark": {
|
||||
"type": "str",
|
||||
"size": 512,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"editable": true,
|
||||
"query": false,
|
||||
"summary": "备注"
|
||||
},
|
||||
"create_user": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"default": "",
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "创建人"
|
||||
},
|
||||
"create_time": {
|
||||
"type": "datetime",
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "创建时间"
|
||||
},
|
||||
"deleted": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "逻辑删除标记 0正常 1删除"
|
||||
}
|
||||
},
|
||||
"editable": [
|
||||
"api/pbl_blueprint_fork_list.dspy",
|
||||
"api/pbl_blueprint_fork_edit.dspy",
|
||||
"api/pbl_blueprint_fork_view.dspy"
|
||||
],
|
||||
"browserfields": [
|
||||
"source_blueprint_id",
|
||||
"source_version",
|
||||
"target_blueprint_id",
|
||||
"fork_type",
|
||||
"node_count",
|
||||
"create_time"
|
||||
]
|
||||
}
|
||||
@ -1,51 +0,0 @@
|
||||
{
|
||||
"tblname": "pbl_blueprint_learning_goal",
|
||||
"params": {
|
||||
"editable": [
|
||||
"blueprint_id",
|
||||
"task_id",
|
||||
"seq",
|
||||
"code",
|
||||
"name",
|
||||
"dimension",
|
||||
"description",
|
||||
"spec",
|
||||
"weight",
|
||||
"status"
|
||||
],
|
||||
"browserfields": [
|
||||
"blueprint_id",
|
||||
"task_id",
|
||||
"seq",
|
||||
"code",
|
||||
"name",
|
||||
"dimension",
|
||||
"weight",
|
||||
"status",
|
||||
"updated_at"
|
||||
],
|
||||
"searchfields": [
|
||||
"tenant_id",
|
||||
"blueprint_id",
|
||||
"task_id",
|
||||
"dimension",
|
||||
"code",
|
||||
"name",
|
||||
"status"
|
||||
],
|
||||
"orderby": "seq asc",
|
||||
"defaultfilter": {
|
||||
"deleted": 0
|
||||
},
|
||||
"caption": "蓝图子对象-学习目标",
|
||||
"readonly": [
|
||||
"id",
|
||||
"tenant_id",
|
||||
"created_by",
|
||||
"created_at",
|
||||
"updated_by",
|
||||
"updated_at",
|
||||
"deleted"
|
||||
]
|
||||
}
|
||||
}
|
||||
122
pbl_blueprint/json/pbl_blueprint_lock.json
Normal file
122
pbl_blueprint/json/pbl_blueprint_lock.json
Normal file
@ -0,0 +1,122 @@
|
||||
{
|
||||
"tblname": "pbl_blueprint_lock",
|
||||
"params": {
|
||||
"id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"editable": true,
|
||||
"query": false,
|
||||
"summary": "主键ID(str32,UUID去横线)"
|
||||
},
|
||||
"tenant_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "租户ID(首业务字段,所有读写强制打头,缺失即 fail-closed 拒绝)"
|
||||
},
|
||||
"blueprint_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "被锁蓝图ID"
|
||||
},
|
||||
"node_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "被锁节点ID(空串=整蓝图锁)"
|
||||
},
|
||||
"lock_type": {
|
||||
"type": "str",
|
||||
"size": 16,
|
||||
"notnull": true,
|
||||
"default": "edit",
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"code": "lock_type",
|
||||
"summary": "锁类型"
|
||||
},
|
||||
"holder_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "持锁人ID"
|
||||
},
|
||||
"holder_name": {
|
||||
"type": "str",
|
||||
"size": 64,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"editable": true,
|
||||
"query": false,
|
||||
"summary": "持锁人名称"
|
||||
},
|
||||
"rev": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "乐观锁修订号(每次抢占/续锁 +1)"
|
||||
},
|
||||
"expire_time": {
|
||||
"type": "datetime",
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "锁过期时间"
|
||||
},
|
||||
"status": {
|
||||
"type": "str",
|
||||
"size": 16,
|
||||
"notnull": true,
|
||||
"default": "holding",
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"code": "status",
|
||||
"summary": "锁状态"
|
||||
},
|
||||
"create_time": {
|
||||
"type": "datetime",
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "加锁时间"
|
||||
},
|
||||
"update_time": {
|
||||
"type": "datetime",
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "续锁时间"
|
||||
},
|
||||
"deleted": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "逻辑删除标记 0正常 1删除"
|
||||
}
|
||||
},
|
||||
"editable": [
|
||||
"api/pbl_blueprint_lock_list.dspy",
|
||||
"api/pbl_blueprint_lock_edit.dspy",
|
||||
"api/pbl_blueprint_lock_view.dspy"
|
||||
],
|
||||
"browserfields": [
|
||||
"blueprint_id",
|
||||
"node_id",
|
||||
"lock_type",
|
||||
"holder_name",
|
||||
"status",
|
||||
"expire_time"
|
||||
]
|
||||
}
|
||||
@ -1,47 +0,0 @@
|
||||
{
|
||||
"tblname": "pbl_blueprint_mission",
|
||||
"params": {
|
||||
"editable": [
|
||||
"blueprint_id",
|
||||
"task_id",
|
||||
"seq",
|
||||
"code",
|
||||
"name",
|
||||
"description",
|
||||
"spec",
|
||||
"unlock_condition",
|
||||
"status"
|
||||
],
|
||||
"browserfields": [
|
||||
"blueprint_id",
|
||||
"task_id",
|
||||
"seq",
|
||||
"code",
|
||||
"name",
|
||||
"status",
|
||||
"updated_at"
|
||||
],
|
||||
"searchfields": [
|
||||
"tenant_id",
|
||||
"blueprint_id",
|
||||
"task_id",
|
||||
"code",
|
||||
"name",
|
||||
"status"
|
||||
],
|
||||
"orderby": "seq asc",
|
||||
"defaultfilter": {
|
||||
"deleted": 0
|
||||
},
|
||||
"caption": "蓝图子对象-关卡",
|
||||
"readonly": [
|
||||
"id",
|
||||
"tenant_id",
|
||||
"created_by",
|
||||
"created_at",
|
||||
"updated_by",
|
||||
"updated_at",
|
||||
"deleted"
|
||||
]
|
||||
}
|
||||
}
|
||||
176
pbl_blueprint/json/pbl_blueprint_node.json
Normal file
176
pbl_blueprint/json/pbl_blueprint_node.json
Normal file
@ -0,0 +1,176 @@
|
||||
{
|
||||
"tblname": "pbl_blueprint_node",
|
||||
"params": {
|
||||
"id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"editable": true,
|
||||
"query": false,
|
||||
"summary": "主键ID(str32,UUID去横线)"
|
||||
},
|
||||
"tenant_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "租户ID(首业务字段,所有读写强制打头,缺失即 fail-closed 拒绝)"
|
||||
},
|
||||
"blueprint_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "所属蓝图ID"
|
||||
},
|
||||
"node_type": {
|
||||
"type": "str",
|
||||
"size": 16,
|
||||
"notnull": true,
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"code": "node_type",
|
||||
"summary": "子对象类型(7 类泛化)"
|
||||
},
|
||||
"parent_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "父节点ID(构成蓝图树,空串=根节点)"
|
||||
},
|
||||
"code": {
|
||||
"type": "str",
|
||||
"size": 64,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "节点编码(蓝图内同类型唯一)"
|
||||
},
|
||||
"name": {
|
||||
"type": "str",
|
||||
"size": 128,
|
||||
"notnull": true,
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "节点名称"
|
||||
},
|
||||
"sort_no": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"editable": true,
|
||||
"query": false,
|
||||
"summary": "同级排序号"
|
||||
},
|
||||
"spec_json": {
|
||||
"type": "text",
|
||||
"default": "",
|
||||
"editable": true,
|
||||
"query": false,
|
||||
"summary": "泛化载荷JSON(结构随 node_type 不同)"
|
||||
},
|
||||
"ref_module": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "引用模块名(scense/world/assessment 等)"
|
||||
},
|
||||
"ref_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "引用对象ID"
|
||||
},
|
||||
"status": {
|
||||
"type": "str",
|
||||
"size": 16,
|
||||
"notnull": true,
|
||||
"default": "active",
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"code": "status",
|
||||
"summary": "节点状态"
|
||||
},
|
||||
"required": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"editable": true,
|
||||
"query": false,
|
||||
"summary": "是否必需节点 0否 1是"
|
||||
},
|
||||
"weight": {
|
||||
"type": "double",
|
||||
"size": [
|
||||
18,
|
||||
2
|
||||
],
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"editable": true,
|
||||
"query": false,
|
||||
"summary": "权重/分值(double(18,2))"
|
||||
},
|
||||
"create_user": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"default": "",
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "创建人"
|
||||
},
|
||||
"create_time": {
|
||||
"type": "datetime",
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "创建时间"
|
||||
},
|
||||
"update_user": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"default": "",
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "更新人"
|
||||
},
|
||||
"update_time": {
|
||||
"type": "datetime",
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "更新时间"
|
||||
},
|
||||
"deleted": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "逻辑删除标记 0正常 1删除"
|
||||
}
|
||||
},
|
||||
"editable": [
|
||||
"api/pbl_blueprint_node_list.dspy",
|
||||
"api/pbl_blueprint_node_edit.dspy",
|
||||
"api/pbl_blueprint_node_view.dspy"
|
||||
],
|
||||
"browserfields": [
|
||||
"code",
|
||||
"name",
|
||||
"node_type",
|
||||
"parent_id",
|
||||
"sort_no",
|
||||
"status"
|
||||
]
|
||||
}
|
||||
144
pbl_blueprint/json/pbl_blueprint_offline.json
Normal file
144
pbl_blueprint/json/pbl_blueprint_offline.json
Normal file
@ -0,0 +1,144 @@
|
||||
{
|
||||
"tblname": "pbl_blueprint_offline",
|
||||
"params": {
|
||||
"id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"editable": true,
|
||||
"query": false,
|
||||
"summary": "主键ID(str32,UUID去横线)"
|
||||
},
|
||||
"tenant_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "租户ID(首业务字段,所有读写强制打头,缺失即 fail-closed 拒绝)"
|
||||
},
|
||||
"blueprint_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "来源蓝图ID(与 template_id 二选一)"
|
||||
},
|
||||
"template_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "来源模板ID"
|
||||
},
|
||||
"pkg_code": {
|
||||
"type": "str",
|
||||
"size": 64,
|
||||
"notnull": true,
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "离线包编码"
|
||||
},
|
||||
"pkg_name": {
|
||||
"type": "str",
|
||||
"size": 128,
|
||||
"notnull": true,
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "离线包名称"
|
||||
},
|
||||
"pkg_version": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 1,
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "离线包版本"
|
||||
},
|
||||
"pkg_json": {
|
||||
"type": "text",
|
||||
"default": "",
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "离线包内容JSON(自描述:schema_version+蓝图+节点+边)"
|
||||
},
|
||||
"file_size": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "包体字节数"
|
||||
},
|
||||
"checksum": {
|
||||
"type": "str",
|
||||
"size": 64,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "内容校验和(sha256)"
|
||||
},
|
||||
"status": {
|
||||
"type": "str",
|
||||
"size": 16,
|
||||
"notnull": true,
|
||||
"default": "ready",
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"code": "status",
|
||||
"summary": "离线包状态"
|
||||
},
|
||||
"expire_time": {
|
||||
"type": "datetime",
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "过期时间"
|
||||
},
|
||||
"create_user": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"default": "",
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "创建人"
|
||||
},
|
||||
"create_time": {
|
||||
"type": "datetime",
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "创建时间"
|
||||
},
|
||||
"update_time": {
|
||||
"type": "datetime",
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "更新时间"
|
||||
},
|
||||
"deleted": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "逻辑删除标记 0正常 1删除"
|
||||
}
|
||||
},
|
||||
"editable": [
|
||||
"api/pbl_blueprint_offline_list.dspy",
|
||||
"api/pbl_blueprint_offline_edit.dspy",
|
||||
"api/pbl_blueprint_offline_view.dspy"
|
||||
],
|
||||
"browserfields": [
|
||||
"pkg_code",
|
||||
"pkg_name",
|
||||
"pkg_version",
|
||||
"status",
|
||||
"file_size",
|
||||
"create_time"
|
||||
]
|
||||
}
|
||||
166
pbl_blueprint/json/pbl_blueprint_publish.json
Normal file
166
pbl_blueprint/json/pbl_blueprint_publish.json
Normal file
@ -0,0 +1,166 @@
|
||||
{
|
||||
"tblname": "pbl_blueprint_publish",
|
||||
"params": {
|
||||
"id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"editable": true,
|
||||
"query": false,
|
||||
"summary": "主键ID(str32,UUID去横线)"
|
||||
},
|
||||
"tenant_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "租户ID(首业务字段,所有读写强制打头,缺失即 fail-closed 拒绝)"
|
||||
},
|
||||
"blueprint_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "蓝图ID"
|
||||
},
|
||||
"version_no": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "发布的版本号"
|
||||
},
|
||||
"class_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "投放班级ID"
|
||||
},
|
||||
"target_type": {
|
||||
"type": "str",
|
||||
"size": 16,
|
||||
"notnull": true,
|
||||
"default": "class",
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"code": "target_type",
|
||||
"summary": "投放对象类型"
|
||||
},
|
||||
"target_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "投放对象ID"
|
||||
},
|
||||
"runtime_ref": {
|
||||
"type": "str",
|
||||
"size": 64,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "运行时引用(scense_runtime 会话/世界ID)"
|
||||
},
|
||||
"game_def_json": {
|
||||
"type": "text",
|
||||
"default": "",
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "编译产物 Game Definition JSON(M3 回写)"
|
||||
},
|
||||
"start_time": {
|
||||
"type": "datetime",
|
||||
"editable": true,
|
||||
"query": false,
|
||||
"summary": "开始时间"
|
||||
},
|
||||
"end_time": {
|
||||
"type": "datetime",
|
||||
"editable": true,
|
||||
"query": false,
|
||||
"summary": "结束时间"
|
||||
},
|
||||
"budget_amount": {
|
||||
"type": "double",
|
||||
"size": [
|
||||
18,
|
||||
2
|
||||
],
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"editable": true,
|
||||
"query": false,
|
||||
"summary": "本次投放预算金额(double(18,2))"
|
||||
},
|
||||
"status": {
|
||||
"type": "str",
|
||||
"size": 16,
|
||||
"notnull": true,
|
||||
"default": "published",
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"code": "status",
|
||||
"summary": "投放状态"
|
||||
},
|
||||
"remark": {
|
||||
"type": "str",
|
||||
"size": 512,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"editable": true,
|
||||
"query": false,
|
||||
"summary": "备注"
|
||||
},
|
||||
"create_user": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"default": "",
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "创建人"
|
||||
},
|
||||
"create_time": {
|
||||
"type": "datetime",
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "创建时间"
|
||||
},
|
||||
"update_time": {
|
||||
"type": "datetime",
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "更新时间"
|
||||
},
|
||||
"deleted": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "逻辑删除标记 0正常 1删除"
|
||||
}
|
||||
},
|
||||
"editable": [
|
||||
"api/pbl_blueprint_publish_list.dspy",
|
||||
"api/pbl_blueprint_publish_edit.dspy",
|
||||
"api/pbl_blueprint_publish_view.dspy"
|
||||
],
|
||||
"browserfields": [
|
||||
"blueprint_id",
|
||||
"version_no",
|
||||
"target_type",
|
||||
"target_id",
|
||||
"status",
|
||||
"start_time",
|
||||
"end_time"
|
||||
]
|
||||
}
|
||||
@ -1,58 +0,0 @@
|
||||
{
|
||||
"tblname": "pbl_blueprint_reflection_spec",
|
||||
"params": {
|
||||
"editable": [
|
||||
"blueprint_id",
|
||||
"task_id",
|
||||
"mission_id",
|
||||
"seq",
|
||||
"code",
|
||||
"name",
|
||||
"trigger_point",
|
||||
"reflection_type",
|
||||
"description",
|
||||
"spec",
|
||||
"rubric_ref",
|
||||
"weight",
|
||||
"status"
|
||||
],
|
||||
"browserfields": [
|
||||
"blueprint_id",
|
||||
"task_id",
|
||||
"mission_id",
|
||||
"seq",
|
||||
"code",
|
||||
"name",
|
||||
"trigger_point",
|
||||
"reflection_type",
|
||||
"weight",
|
||||
"status",
|
||||
"updated_at"
|
||||
],
|
||||
"searchfields": [
|
||||
"tenant_id",
|
||||
"blueprint_id",
|
||||
"task_id",
|
||||
"mission_id",
|
||||
"trigger_point",
|
||||
"reflection_type",
|
||||
"code",
|
||||
"name",
|
||||
"status"
|
||||
],
|
||||
"orderby": "seq asc",
|
||||
"defaultfilter": {
|
||||
"deleted": 0
|
||||
},
|
||||
"caption": "蓝图子对象-反思规格",
|
||||
"readonly": [
|
||||
"id",
|
||||
"tenant_id",
|
||||
"created_by",
|
||||
"created_at",
|
||||
"updated_by",
|
||||
"updated_at",
|
||||
"deleted"
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -1,47 +0,0 @@
|
||||
{
|
||||
"tblname": "pbl_blueprint_role",
|
||||
"params": {
|
||||
"editable": [
|
||||
"blueprint_id",
|
||||
"task_id",
|
||||
"seq",
|
||||
"code",
|
||||
"name",
|
||||
"description",
|
||||
"spec",
|
||||
"permissions",
|
||||
"status"
|
||||
],
|
||||
"browserfields": [
|
||||
"blueprint_id",
|
||||
"task_id",
|
||||
"seq",
|
||||
"code",
|
||||
"name",
|
||||
"status",
|
||||
"updated_at"
|
||||
],
|
||||
"searchfields": [
|
||||
"tenant_id",
|
||||
"blueprint_id",
|
||||
"task_id",
|
||||
"code",
|
||||
"name",
|
||||
"status"
|
||||
],
|
||||
"orderby": "seq asc",
|
||||
"defaultfilter": {
|
||||
"deleted": 0
|
||||
},
|
||||
"caption": "蓝图子对象-角色",
|
||||
"readonly": [
|
||||
"id",
|
||||
"tenant_id",
|
||||
"created_by",
|
||||
"created_at",
|
||||
"updated_by",
|
||||
"updated_at",
|
||||
"deleted"
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -1,46 +0,0 @@
|
||||
{
|
||||
"tblname": "pbl_blueprint_task",
|
||||
"params": {
|
||||
"editable": [
|
||||
"blueprint_id",
|
||||
"parent_id",
|
||||
"seq",
|
||||
"code",
|
||||
"name",
|
||||
"description",
|
||||
"spec",
|
||||
"status"
|
||||
],
|
||||
"browserfields": [
|
||||
"blueprint_id",
|
||||
"seq",
|
||||
"code",
|
||||
"name",
|
||||
"parent_id",
|
||||
"status",
|
||||
"updated_at"
|
||||
],
|
||||
"searchfields": [
|
||||
"tenant_id",
|
||||
"blueprint_id",
|
||||
"parent_id",
|
||||
"code",
|
||||
"name",
|
||||
"status"
|
||||
],
|
||||
"orderby": "seq asc",
|
||||
"defaultfilter": {
|
||||
"deleted": 0
|
||||
},
|
||||
"caption": "蓝图子对象-任务",
|
||||
"readonly": [
|
||||
"id",
|
||||
"tenant_id",
|
||||
"created_by",
|
||||
"created_at",
|
||||
"updated_by",
|
||||
"updated_at",
|
||||
"deleted"
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -1,49 +1,187 @@
|
||||
{
|
||||
"tblname": "pbl_blueprint_template",
|
||||
"params": {
|
||||
"editable": [
|
||||
"code",
|
||||
"name",
|
||||
"category",
|
||||
"subject",
|
||||
"grade",
|
||||
"duration_hours",
|
||||
"description",
|
||||
"content",
|
||||
"status"
|
||||
],
|
||||
"browserfields": [
|
||||
"code",
|
||||
"name",
|
||||
"category",
|
||||
"subject",
|
||||
"grade",
|
||||
"duration_hours",
|
||||
"status",
|
||||
"use_count",
|
||||
"updated_at"
|
||||
],
|
||||
"searchfields": [
|
||||
"tenant_id",
|
||||
"code",
|
||||
"name",
|
||||
"category",
|
||||
"status"
|
||||
],
|
||||
"orderby": "use_count desc, updated_at desc",
|
||||
"defaultfilter": {
|
||||
"deleted": 0
|
||||
"id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"editable": true,
|
||||
"query": false,
|
||||
"summary": "主键ID(str32,UUID去横线)"
|
||||
},
|
||||
"caption": "PBL蓝图模板",
|
||||
"readonly": [
|
||||
"id",
|
||||
"tenant_id",
|
||||
"use_count",
|
||||
"created_by",
|
||||
"created_at",
|
||||
"updated_by",
|
||||
"updated_at",
|
||||
"deleted"
|
||||
]
|
||||
}
|
||||
"tenant_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "租户ID(首业务字段,所有读写强制打头,缺失即 fail-closed 拒绝);空串=平台公共模板"
|
||||
},
|
||||
"code": {
|
||||
"type": "str",
|
||||
"size": 64,
|
||||
"notnull": true,
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "模板编码(租户内唯一)"
|
||||
},
|
||||
"name": {
|
||||
"type": "str",
|
||||
"size": 128,
|
||||
"notnull": true,
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "模板名称"
|
||||
},
|
||||
"category": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"default": "general",
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"code": "category",
|
||||
"summary": "模板分类"
|
||||
},
|
||||
"subject": {
|
||||
"type": "str",
|
||||
"size": 64,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "适用学科"
|
||||
},
|
||||
"grade": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "适用年级"
|
||||
},
|
||||
"scope": {
|
||||
"type": "str",
|
||||
"size": 16,
|
||||
"notnull": true,
|
||||
"default": "tenant",
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"code": "scope",
|
||||
"summary": "可见范围"
|
||||
},
|
||||
"payload_json": {
|
||||
"type": "text",
|
||||
"default": "",
|
||||
"editable": true,
|
||||
"query": false,
|
||||
"summary": "模板树载荷JSON(blueprint+nodes+edges)"
|
||||
},
|
||||
"node_count": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "模板节点数"
|
||||
},
|
||||
"edge_count": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "模板边数"
|
||||
},
|
||||
"price_amount": {
|
||||
"type": "double",
|
||||
"size": [
|
||||
18,
|
||||
2
|
||||
],
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"editable": true,
|
||||
"query": false,
|
||||
"summary": "模板定价金额(double(18,2),0=免费)"
|
||||
},
|
||||
"use_count": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "被实例化次数"
|
||||
},
|
||||
"status": {
|
||||
"type": "str",
|
||||
"size": 16,
|
||||
"notnull": true,
|
||||
"default": "enabled",
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"code": "status",
|
||||
"summary": "模板状态"
|
||||
},
|
||||
"summary": {
|
||||
"type": "str",
|
||||
"size": 512,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"editable": true,
|
||||
"query": false,
|
||||
"summary": "模板说明"
|
||||
},
|
||||
"create_user": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"default": "",
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "创建人"
|
||||
},
|
||||
"create_time": {
|
||||
"type": "datetime",
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "创建时间"
|
||||
},
|
||||
"update_user": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"default": "",
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "更新人"
|
||||
},
|
||||
"update_time": {
|
||||
"type": "datetime",
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "更新时间"
|
||||
},
|
||||
"deleted": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "逻辑删除标记 0正常 1删除"
|
||||
}
|
||||
},
|
||||
"editable": [
|
||||
"api/pbl_blueprint_template_list.dspy",
|
||||
"api/pbl_blueprint_template_edit.dspy",
|
||||
"api/pbl_blueprint_template_view.dspy"
|
||||
],
|
||||
"browserfields": [
|
||||
"code",
|
||||
"name",
|
||||
"category",
|
||||
"subject",
|
||||
"scope",
|
||||
"status",
|
||||
"use_count"
|
||||
]
|
||||
}
|
||||
|
||||
@ -1,43 +0,0 @@
|
||||
{
|
||||
"tblname": "pbl_blueprint_template_item",
|
||||
"params": {
|
||||
"editable": [
|
||||
"template_id",
|
||||
"obj_type",
|
||||
"parent_ref",
|
||||
"ref_key",
|
||||
"seq",
|
||||
"name",
|
||||
"spec"
|
||||
],
|
||||
"browserfields": [
|
||||
"template_id",
|
||||
"obj_type",
|
||||
"seq",
|
||||
"name",
|
||||
"ref_key",
|
||||
"parent_ref",
|
||||
"updated_at"
|
||||
],
|
||||
"searchfields": [
|
||||
"tenant_id",
|
||||
"template_id",
|
||||
"obj_type",
|
||||
"name"
|
||||
],
|
||||
"orderby": "obj_type asc, seq asc",
|
||||
"defaultfilter": {
|
||||
"deleted": 0
|
||||
},
|
||||
"caption": "PBL蓝图模板条目",
|
||||
"readonly": [
|
||||
"id",
|
||||
"tenant_id",
|
||||
"created_by",
|
||||
"created_at",
|
||||
"updated_by",
|
||||
"updated_at",
|
||||
"deleted"
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -1,43 +1,136 @@
|
||||
{
|
||||
"tblname": "pbl_blueprint_version",
|
||||
"params": {
|
||||
"editable": [
|
||||
"blueprint_id",
|
||||
"version_no",
|
||||
"snapshot",
|
||||
"change_delta",
|
||||
"quality_level",
|
||||
"status",
|
||||
"remark"
|
||||
],
|
||||
"browserfields": [
|
||||
"blueprint_id",
|
||||
"version_no",
|
||||
"quality_level",
|
||||
"status",
|
||||
"remark",
|
||||
"created_by",
|
||||
"created_at"
|
||||
],
|
||||
"searchfields": [
|
||||
"tenant_id",
|
||||
"blueprint_id",
|
||||
"version_no",
|
||||
"status"
|
||||
],
|
||||
"orderby": "version_no desc",
|
||||
"defaultfilter": {
|
||||
"deleted": 0
|
||||
"id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"editable": true,
|
||||
"query": false,
|
||||
"summary": "主键ID(str32,UUID去横线)"
|
||||
},
|
||||
"caption": "PBL蓝图版本快照",
|
||||
"readonly": [
|
||||
"id",
|
||||
"tenant_id",
|
||||
"created_by",
|
||||
"created_at",
|
||||
"updated_by",
|
||||
"updated_at",
|
||||
"deleted"
|
||||
]
|
||||
}
|
||||
"tenant_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "租户ID(首业务字段,所有读写强制打头,缺失即 fail-closed 拒绝)"
|
||||
},
|
||||
"blueprint_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "所属蓝图ID"
|
||||
},
|
||||
"version_no": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "版本号(蓝图内递增)"
|
||||
},
|
||||
"version_type": {
|
||||
"type": "str",
|
||||
"size": 16,
|
||||
"notnull": true,
|
||||
"default": "draft",
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"code": "version_type",
|
||||
"summary": "版本类型"
|
||||
},
|
||||
"change_summary": {
|
||||
"type": "str",
|
||||
"size": 512,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"editable": true,
|
||||
"query": false,
|
||||
"summary": "变更说明"
|
||||
},
|
||||
"snapshot_json": {
|
||||
"type": "text",
|
||||
"default": "",
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "蓝图整树快照JSON(主表字段+节点+边)"
|
||||
},
|
||||
"node_count": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "快照节点数"
|
||||
},
|
||||
"edge_count": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "快照边数"
|
||||
},
|
||||
"quality_status": {
|
||||
"type": "str",
|
||||
"size": 16,
|
||||
"notnull": true,
|
||||
"default": "q0",
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"code": "quality_status",
|
||||
"summary": "该版本质量状态"
|
||||
},
|
||||
"score_amount": {
|
||||
"type": "double",
|
||||
"size": [
|
||||
18,
|
||||
2
|
||||
],
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"editable": true,
|
||||
"query": false,
|
||||
"summary": "该版本评估得分(double(18,2),M6 回写)"
|
||||
},
|
||||
"create_user": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"default": "",
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "创建人"
|
||||
},
|
||||
"create_time": {
|
||||
"type": "datetime",
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "创建时间"
|
||||
},
|
||||
"deleted": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "逻辑删除标记 0正常 1删除"
|
||||
}
|
||||
},
|
||||
"editable": [
|
||||
"api/pbl_blueprint_version_list.dspy",
|
||||
"api/pbl_blueprint_version_edit.dspy",
|
||||
"api/pbl_blueprint_version_view.dspy"
|
||||
],
|
||||
"browserfields": [
|
||||
"version_no",
|
||||
"version_type",
|
||||
"change_summary",
|
||||
"node_count",
|
||||
"edge_count",
|
||||
"quality_status",
|
||||
"create_time"
|
||||
]
|
||||
}
|
||||
|
||||
137
pbl_blueprint/json/pbl_blueprint_version_delta.json
Normal file
137
pbl_blueprint/json/pbl_blueprint_version_delta.json
Normal file
@ -0,0 +1,137 @@
|
||||
{
|
||||
"tblname": "pbl_blueprint_version_delta",
|
||||
"params": {
|
||||
"id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"editable": true,
|
||||
"query": false,
|
||||
"summary": "主键ID(str32,UUID去横线)"
|
||||
},
|
||||
"tenant_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "租户ID(首业务字段,所有读写强制打头,缺失即 fail-closed 拒绝)"
|
||||
},
|
||||
"blueprint_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "所属蓝图ID"
|
||||
},
|
||||
"from_version": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "基准版本号"
|
||||
},
|
||||
"to_version": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "目标版本号"
|
||||
},
|
||||
"delta_type": {
|
||||
"type": "str",
|
||||
"size": 16,
|
||||
"notnull": true,
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"code": "delta_type",
|
||||
"summary": "差异对象类型"
|
||||
},
|
||||
"op_type": {
|
||||
"type": "str",
|
||||
"size": 16,
|
||||
"notnull": true,
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"code": "op_type",
|
||||
"summary": "操作类型"
|
||||
},
|
||||
"target_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"editable": true,
|
||||
"query": true,
|
||||
"summary": "差异对象ID(节点ID/边ID/蓝图ID)"
|
||||
},
|
||||
"target_name": {
|
||||
"type": "str",
|
||||
"size": 128,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"editable": true,
|
||||
"query": false,
|
||||
"summary": "差异对象名称(冗余便于展示)"
|
||||
},
|
||||
"before_json": {
|
||||
"type": "text",
|
||||
"default": "",
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "变更前JSON"
|
||||
},
|
||||
"after_json": {
|
||||
"type": "text",
|
||||
"default": "",
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "变更后JSON"
|
||||
},
|
||||
"sort_no": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"editable": true,
|
||||
"query": false,
|
||||
"summary": "排序号"
|
||||
},
|
||||
"create_user": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"default": "",
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "创建人"
|
||||
},
|
||||
"create_time": {
|
||||
"type": "datetime",
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "创建时间"
|
||||
},
|
||||
"deleted": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"editable": false,
|
||||
"query": false,
|
||||
"summary": "逻辑删除标记 0正常 1删除"
|
||||
}
|
||||
},
|
||||
"editable": [
|
||||
"api/pbl_blueprint_version_delta_list.dspy",
|
||||
"api/pbl_blueprint_version_delta_edit.dspy",
|
||||
"api/pbl_blueprint_version_delta_view.dspy"
|
||||
],
|
||||
"browserfields": [
|
||||
"from_version",
|
||||
"to_version",
|
||||
"delta_type",
|
||||
"op_type",
|
||||
"target_name"
|
||||
]
|
||||
}
|
||||
@ -1,55 +1,230 @@
|
||||
{
|
||||
"summary": "PBL 蓝图聚合根主表(M1a)。一行=一个项目式学习蓝图,承载学科/年级/课时与质量状态;所有子对象(task/mission/role/learning_goal/evidence_spec/artifact_spec/reflection_spec)通过 blueprint_id 挂靠。tenant_id 为强制打头字段,任何读写必须带租户条件,缺失即拒绝。",
|
||||
"fields": [
|
||||
{"name": "id", "type": "str", "len": 32, "primary": true, "notnull": true, "desc": "主键,32位无横线UUID"},
|
||||
{"name": "tenant_id", "type": "str", "len": 32, "notnull": true, "desc": "租户ID,强制打头,所有查询第一过滤条件"},
|
||||
{"name": "code", "type": "str", "len": 64, "notnull": true, "desc": "蓝图编码,租户内唯一,形如 PBL-{yyyyMMdd}-{seq4}"},
|
||||
{"name": "name", "type": "str", "len": 200, "notnull": true, "desc": "蓝图名称"},
|
||||
{"name": "subject", "type": "str", "len": 64, "desc": "学科"},
|
||||
{"name": "grade", "type": "str", "len": 32, "desc": "适用年级"},
|
||||
{"name": "duration_hours", "type": "int", "desc": "总课时数"},
|
||||
{"name": "version_no", "type": "int", "notnull": true, "default": "1", "desc": "当前版本号,每次发布快照自增"},
|
||||
{"name": "status", "type": "str", "len": 32, "notnull": true, "default": "draft", "desc": "蓝图状态,见 codes.status"},
|
||||
{"name": "quality_level", "type": "str", "len": 16, "default": "L1", "desc": "质量等级 L1-L5,由 pbl_validation 回写"},
|
||||
{"name": "summary", "type": "text", "desc": "蓝图简介/驱动性问题"},
|
||||
{"name": "config", "type": "json", "desc": "扩展配置(分组策略/评分权重/运行时参数)"},
|
||||
{"name": "source_blueprint_id", "type": "str", "len": 32, "desc": "fork 来源蓝图ID,非fork为空"},
|
||||
{"name": "template_id", "type": "str", "len": 32, "desc": "实例化来源模板ID,非模板创建为空"},
|
||||
{"name": "owner_id", "type": "str", "len": 32, "desc": "归属教师/创建者用户ID"},
|
||||
{"name": "published_at", "type": "datetime", "desc": "最近发布时间"},
|
||||
{"name": "remark", "type": "str", "len": 500, "desc": "备注"},
|
||||
{"name": "created_by", "type": "str", "len": 32, "desc": "创建人"},
|
||||
{"name": "created_at", "type": "datetime", "desc": "创建时间"},
|
||||
{"name": "updated_by", "type": "str", "len": 32, "desc": "最后修改人"},
|
||||
{"name": "updated_at", "type": "datetime", "desc": "最后修改时间"},
|
||||
{"name": "deleted", "type": "int", "notnull": true, "default": "0", "desc": "软删除标记 0正常 1已删除"}
|
||||
],
|
||||
"indexes": [
|
||||
{"name": "uk_pbl_blueprint_tenant_code", "fields": ["tenant_id", "code"], "unique": true},
|
||||
{"name": "idx_pbl_blueprint_tenant_status", "fields": ["tenant_id", "status", "deleted"], "unique": false},
|
||||
{"name": "idx_pbl_blueprint_owner", "fields": ["tenant_id", "owner_id"], "unique": false},
|
||||
{"name": "idx_pbl_blueprint_template", "fields": ["tenant_id", "template_id"], "unique": false}
|
||||
"summary": "PBL 蓝图聚合根表:一个租户下的一份项目式学习蓝图(学科/年级/学段/状态/质量状态/当前版本/预算)。所有读写 tenant_id 强制打头。",
|
||||
"primary": [
|
||||
"id"
|
||||
],
|
||||
"fields": {
|
||||
"id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"summary": "主键ID(str32,UUID去横线)"
|
||||
},
|
||||
"tenant_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"summary": "租户ID(首业务字段,所有读写强制打头,缺失即 fail-closed 拒绝)"
|
||||
},
|
||||
"code": {
|
||||
"type": "str",
|
||||
"size": 64,
|
||||
"notnull": true,
|
||||
"summary": "蓝图编码(租户内唯一,PBL-BP-序号)"
|
||||
},
|
||||
"name": {
|
||||
"type": "str",
|
||||
"size": 128,
|
||||
"notnull": true,
|
||||
"summary": "蓝图名称"
|
||||
},
|
||||
"subject": {
|
||||
"type": "str",
|
||||
"size": 64,
|
||||
"default": "",
|
||||
"summary": "学科"
|
||||
},
|
||||
"grade": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"default": "",
|
||||
"summary": "年级"
|
||||
},
|
||||
"phase": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"default": "",
|
||||
"summary": "学段"
|
||||
},
|
||||
"status": {
|
||||
"type": "str",
|
||||
"size": 16,
|
||||
"notnull": true,
|
||||
"default": "draft",
|
||||
"summary": "蓝图状态"
|
||||
},
|
||||
"quality_status": {
|
||||
"type": "str",
|
||||
"size": 16,
|
||||
"notnull": true,
|
||||
"default": "q0",
|
||||
"summary": "质量状态(5 级)"
|
||||
},
|
||||
"source": {
|
||||
"type": "str",
|
||||
"size": 16,
|
||||
"notnull": true,
|
||||
"default": "manual",
|
||||
"summary": "来源方式"
|
||||
},
|
||||
"source_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"default": "",
|
||||
"summary": "来源对象ID(模板ID或父蓝图ID)"
|
||||
},
|
||||
"owner_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"default": "",
|
||||
"summary": "负责人(教师)ID"
|
||||
},
|
||||
"class_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"default": "",
|
||||
"summary": "关联班级ID"
|
||||
},
|
||||
"current_version": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"summary": "当前版本号(save_version 回写)"
|
||||
},
|
||||
"duration_hours": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"summary": "预计课时数"
|
||||
},
|
||||
"budget_amount": {
|
||||
"type": "double",
|
||||
"size": [
|
||||
18,
|
||||
2
|
||||
],
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"summary": "预算金额(double(18,2))"
|
||||
},
|
||||
"summary": {
|
||||
"type": "str",
|
||||
"size": 512,
|
||||
"default": "",
|
||||
"summary": "蓝图简介"
|
||||
},
|
||||
"ext_json": {
|
||||
"type": "text",
|
||||
"default": "",
|
||||
"summary": "扩展属性JSON"
|
||||
},
|
||||
"publish_time": {
|
||||
"type": "datetime",
|
||||
"summary": "最近发布时间"
|
||||
},
|
||||
"create_user": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"default": "",
|
||||
"summary": "创建人"
|
||||
},
|
||||
"create_time": {
|
||||
"type": "datetime",
|
||||
"summary": "创建时间"
|
||||
},
|
||||
"update_user": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"default": "",
|
||||
"summary": "更新人"
|
||||
},
|
||||
"update_time": {
|
||||
"type": "datetime",
|
||||
"summary": "更新时间"
|
||||
},
|
||||
"deleted": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"summary": "逻辑删除标记 0正常 1删除"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"uk_pbl_blueprint_code": {
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"code"
|
||||
],
|
||||
"unique": true,
|
||||
"summary": "租户内蓝图编码唯一"
|
||||
},
|
||||
"idx_pbl_blueprint_tenant": {
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"deleted",
|
||||
"status"
|
||||
],
|
||||
"unique": false,
|
||||
"summary": "租户蓝图列表主索引(tenant_id 打头)"
|
||||
},
|
||||
"idx_pbl_blueprint_owner": {
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"owner_id"
|
||||
],
|
||||
"unique": false,
|
||||
"summary": "按负责人查蓝图"
|
||||
},
|
||||
"idx_pbl_blueprint_source": {
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"source",
|
||||
"source_id"
|
||||
],
|
||||
"unique": false,
|
||||
"summary": "按来源(模板/fork)追溯"
|
||||
},
|
||||
"idx_pbl_blueprint_class": {
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"class_id"
|
||||
],
|
||||
"unique": false,
|
||||
"summary": "按班级查蓝图"
|
||||
}
|
||||
},
|
||||
"codes": {
|
||||
"status": [
|
||||
["draft", "草稿"],
|
||||
["validating", "校验中"],
|
||||
["validated", "校验通过"],
|
||||
["compiling", "编译中"],
|
||||
["compiled", "已编译"],
|
||||
["published", "已发布"],
|
||||
["archived", "已归档"]
|
||||
],
|
||||
"quality_level": [
|
||||
["L1", "L1-初始"],
|
||||
["L2", "L2-基本完整"],
|
||||
["L3", "L3-结构合格"],
|
||||
["L4", "L4-可运行"],
|
||||
["L5", "L5-优质"]
|
||||
],
|
||||
"deleted": [
|
||||
["0", "正常"],
|
||||
["1", "已删除"]
|
||||
]
|
||||
"status": {
|
||||
"summary": "蓝图状态",
|
||||
"items": {
|
||||
"draft": "草稿",
|
||||
"validating": "校验中",
|
||||
"ready": "就绪",
|
||||
"published": "已发布",
|
||||
"archived": "已归档",
|
||||
"disabled": "已停用"
|
||||
}
|
||||
},
|
||||
"quality_status": {
|
||||
"summary": "质量状态(5 级,M2 校验引擎回写)",
|
||||
"items": {
|
||||
"q0": "未校验",
|
||||
"q1": "不合格",
|
||||
"q2": "基本合格",
|
||||
"q3": "合格",
|
||||
"q4": "优秀",
|
||||
"q5": "标杆"
|
||||
}
|
||||
},
|
||||
"source": {
|
||||
"summary": "来源方式",
|
||||
"items": {
|
||||
"manual": "手工创建",
|
||||
"template": "模板实例化",
|
||||
"fork": "蓝图fork派生",
|
||||
"agent": "Designer Agent 生成",
|
||||
"import": "离线包导入"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,36 +0,0 @@
|
||||
{
|
||||
"summary": "PBL 蓝图子对象-产出物规格(M1a,7类子对象之一)。一行=一种期望产出物的规格定义(作品/报告/模型/演示),供 pbl_evidence 归档产出物与 pbl_compiler 生成任务交付要求。统一泛化契约。tenant_id 强制打头。",
|
||||
"fields": [
|
||||
{"name": "id", "type": "str", "len": 32, "primary": true, "notnull": true, "desc": "主键,32位无横线UUID"},
|
||||
{"name": "tenant_id", "type": "str", "len": 32, "notnull": true, "desc": "租户ID,强制打头"},
|
||||
{"name": "blueprint_id", "type": "str", "len": 32, "notnull": true, "desc": "所属蓝图ID"},
|
||||
{"name": "task_id", "type": "str", "len": 32, "desc": "关联任务ID"},
|
||||
{"name": "mission_id", "type": "str", "len": 32, "desc": "关联关卡ID"},
|
||||
{"name": "seq", "type": "int", "notnull": true, "default": "0", "desc": "蓝图内排序号"},
|
||||
{"name": "code", "type": "str", "len": 64, "desc": "产出物规格编码,蓝图内唯一,缺省自动生成 ART-{seq}"},
|
||||
{"name": "name", "type": "str", "len": 200, "notnull": true, "desc": "产出物名称"},
|
||||
{"name": "artifact_type", "type": "str", "len": 32, "notnull": true, "default": "document", "desc": "产出物类型,见 codes.artifact_type"},
|
||||
{"name": "description", "type": "text", "desc": "产出物说明与要求"},
|
||||
{"name": "spec", "type": "json", "desc": "产出物规格(格式/尺寸/时长/字数/文件类型白名单)"},
|
||||
{"name": "accept_criteria", "type": "json", "desc": "验收标准条目数组"},
|
||||
{"name": "required", "type": "int", "notnull": true, "default": "1", "desc": "是否必交 0否 1是"},
|
||||
{"name": "weight", "type": "float", "default": "1.0", "desc": "评分权重"},
|
||||
{"name": "status", "type": "str", "len": 32, "notnull": true, "default": "draft", "desc": "子对象状态,见 codes.status"},
|
||||
{"name": "created_by", "type": "str", "len": 32, "desc": "创建人"},
|
||||
{"name": "created_at", "type": "datetime", "desc": "创建时间"},
|
||||
{"name": "updated_by", "type": "str", "len": 32, "desc": "最后修改人"},
|
||||
{"name": "updated_at", "type": "datetime", "desc": "最后修改时间"},
|
||||
{"name": "deleted", "type": "int", "notnull": true, "default": "0", "desc": "软删除标记 0正常 1已删除"}
|
||||
],
|
||||
"indexes": [
|
||||
{"name": "uk_pbl_bpart_tenant_bp_code", "fields": ["tenant_id", "blueprint_id", "code"], "unique": true},
|
||||
{"name": "idx_pbl_bpart_tenant_bp_seq", "fields": ["tenant_id", "blueprint_id", "seq"], "unique": false},
|
||||
{"name": "idx_pbl_bpart_task", "fields": ["tenant_id", "task_id"], "unique": false}
|
||||
],
|
||||
"codes": {
|
||||
"artifact_type": [["document", "文档报告"], ["model", "三维模型"], ["scene", "场景作品"], ["video", "视频"], ["audio", "音频"], ["image", "图片"], ["code", "程序脚本"], ["presentation", "演示汇报"], ["dataset", "数据集"]],
|
||||
"required": [["0", "选交"], ["1", "必交"]],
|
||||
"status": [["draft", "草稿"], ["active", "生效"], ["disabled", "停用"]],
|
||||
"deleted": [["0", "正常"], ["1", "已删除"]]
|
||||
}
|
||||
}
|
||||
190
pbl_blueprint/models/pbl_blueprint_audit.json
Normal file
190
pbl_blueprint/models/pbl_blueprint_audit.json
Normal file
@ -0,0 +1,190 @@
|
||||
{
|
||||
"summary": "蓝图操作审计表(append-only):记录蓝图/节点/边/版本/模板/发布/锁的关键写操作与结果,只增不改不删,供追溯与合规审计。",
|
||||
"primary": [
|
||||
"id"
|
||||
],
|
||||
"fields": {
|
||||
"id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"summary": "主键ID(str32,UUID去横线)"
|
||||
},
|
||||
"tenant_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"summary": "租户ID(首业务字段,所有读写强制打头,缺失即 fail-closed 拒绝)"
|
||||
},
|
||||
"blueprint_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"summary": "蓝图ID"
|
||||
},
|
||||
"target_type": {
|
||||
"type": "str",
|
||||
"size": 16,
|
||||
"notnull": true,
|
||||
"summary": "操作对象类型"
|
||||
},
|
||||
"target_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"summary": "操作对象ID"
|
||||
},
|
||||
"action": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"summary": "操作动作"
|
||||
},
|
||||
"version_no": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"summary": "关联版本号"
|
||||
},
|
||||
"before_json": {
|
||||
"type": "text",
|
||||
"default": "",
|
||||
"summary": "操作前数据JSON"
|
||||
},
|
||||
"after_json": {
|
||||
"type": "text",
|
||||
"default": "",
|
||||
"summary": "操作后数据JSON"
|
||||
},
|
||||
"result": {
|
||||
"type": "str",
|
||||
"size": 16,
|
||||
"notnull": true,
|
||||
"default": "success",
|
||||
"summary": "操作结果"
|
||||
},
|
||||
"err_code": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"summary": "失败错误码"
|
||||
},
|
||||
"cost_ms": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"summary": "耗时毫秒"
|
||||
},
|
||||
"op_user": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"summary": "操作人"
|
||||
},
|
||||
"op_source": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"default": "api",
|
||||
"summary": "操作来源(web/api/agent/offline)"
|
||||
},
|
||||
"client_ip": {
|
||||
"type": "str",
|
||||
"size": 64,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"summary": "客户端IP"
|
||||
},
|
||||
"create_time": {
|
||||
"type": "datetime",
|
||||
"summary": "操作时间"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"idx_pbl_audit_bp": {
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"blueprint_id",
|
||||
"create_time"
|
||||
],
|
||||
"unique": false,
|
||||
"summary": "按蓝图时间线查审计"
|
||||
},
|
||||
"idx_pbl_audit_target": {
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"target_type",
|
||||
"target_id"
|
||||
],
|
||||
"unique": false,
|
||||
"summary": "按对象查审计"
|
||||
},
|
||||
"idx_pbl_audit_user": {
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"op_user",
|
||||
"create_time"
|
||||
],
|
||||
"unique": false,
|
||||
"summary": "按操作人查审计"
|
||||
},
|
||||
"idx_pbl_audit_action": {
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"action",
|
||||
"result"
|
||||
],
|
||||
"unique": false,
|
||||
"summary": "按动作与结果统计"
|
||||
}
|
||||
},
|
||||
"codes": {
|
||||
"target_type": {
|
||||
"summary": "操作对象类型",
|
||||
"items": {
|
||||
"blueprint": "蓝图",
|
||||
"node": "节点",
|
||||
"edge": "边",
|
||||
"version": "版本",
|
||||
"delta": "版本差异",
|
||||
"template": "模板",
|
||||
"publish": "发布",
|
||||
"offline": "离线包",
|
||||
"fork": "血缘",
|
||||
"lock": "编辑锁"
|
||||
}
|
||||
},
|
||||
"action": {
|
||||
"summary": "操作动作",
|
||||
"items": {
|
||||
"create": "创建",
|
||||
"update": "更新",
|
||||
"delete": "删除",
|
||||
"fork": "fork派生",
|
||||
"save_version": "存版本",
|
||||
"rollback": "回滚",
|
||||
"publish": "发布",
|
||||
"revoke": "撤回发布",
|
||||
"instantiate": "模板实例化",
|
||||
"validate": "校验",
|
||||
"compile": "编译",
|
||||
"lock": "加锁",
|
||||
"unlock": "解锁",
|
||||
"export_offline": "导出离线包",
|
||||
"import_offline": "导入离线包"
|
||||
}
|
||||
},
|
||||
"result": {
|
||||
"summary": "操作结果",
|
||||
"items": {
|
||||
"success": "成功",
|
||||
"fail": "失败",
|
||||
"denied": "被拒绝"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
137
pbl_blueprint/models/pbl_blueprint_edge.json
Normal file
137
pbl_blueprint/models/pbl_blueprint_edge.json
Normal file
@ -0,0 +1,137 @@
|
||||
{
|
||||
"summary": "蓝图节点关系边表:描述子对象之间的依赖/解锁/产出/消耗/评估/包含关系,供 Compiler(M3)编译为 Game Definition 图结构。",
|
||||
"primary": [
|
||||
"id"
|
||||
],
|
||||
"fields": {
|
||||
"id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"summary": "主键ID(str32,UUID去横线)"
|
||||
},
|
||||
"tenant_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"summary": "租户ID(首业务字段,所有读写强制打头,缺失即 fail-closed 拒绝)"
|
||||
},
|
||||
"blueprint_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"summary": "所属蓝图ID"
|
||||
},
|
||||
"from_node_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"summary": "起点节点ID"
|
||||
},
|
||||
"to_node_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"summary": "终点节点ID"
|
||||
},
|
||||
"edge_type": {
|
||||
"type": "str",
|
||||
"size": 16,
|
||||
"notnull": true,
|
||||
"default": "depends",
|
||||
"summary": "边类型"
|
||||
},
|
||||
"weight": {
|
||||
"type": "double",
|
||||
"size": [
|
||||
18,
|
||||
2
|
||||
],
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"summary": "边权重(double(18,2))"
|
||||
},
|
||||
"condition_json": {
|
||||
"type": "text",
|
||||
"default": "",
|
||||
"summary": "触发条件JSON"
|
||||
},
|
||||
"sort_no": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"summary": "排序号"
|
||||
},
|
||||
"create_user": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"default": "",
|
||||
"summary": "创建人"
|
||||
},
|
||||
"create_time": {
|
||||
"type": "datetime",
|
||||
"summary": "创建时间"
|
||||
},
|
||||
"update_time": {
|
||||
"type": "datetime",
|
||||
"summary": "更新时间"
|
||||
},
|
||||
"deleted": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"summary": "逻辑删除标记 0正常 1删除"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"uk_pbl_edge": {
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"blueprint_id",
|
||||
"from_node_id",
|
||||
"to_node_id",
|
||||
"edge_type"
|
||||
],
|
||||
"unique": true,
|
||||
"summary": "同类型边唯一,防重复连边"
|
||||
},
|
||||
"idx_pbl_edge_bp": {
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"blueprint_id",
|
||||
"deleted"
|
||||
],
|
||||
"unique": false,
|
||||
"summary": "按蓝图取全部边"
|
||||
},
|
||||
"idx_pbl_edge_from": {
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"from_node_id"
|
||||
],
|
||||
"unique": false,
|
||||
"summary": "出边查询"
|
||||
},
|
||||
"idx_pbl_edge_to": {
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"to_node_id"
|
||||
],
|
||||
"unique": false,
|
||||
"summary": "入边查询(删节点前校验)"
|
||||
}
|
||||
},
|
||||
"codes": {
|
||||
"edge_type": {
|
||||
"summary": "边类型",
|
||||
"items": {
|
||||
"depends": "依赖",
|
||||
"unlocks": "解锁",
|
||||
"produces": "产出",
|
||||
"consumes": "消耗",
|
||||
"assesses": "评估",
|
||||
"contains": "包含"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,38 +0,0 @@
|
||||
{
|
||||
"summary": "PBL 蓝图子对象-证据规格(M1a,7类子对象之一)。一行=一条学习证据的采集规格,定义证据类型/采集方式/校验规则,供 pbl_evidence 幂等采集与 pbl_assessment 评分引用。统一泛化契约。tenant_id 强制打头。",
|
||||
"fields": [
|
||||
{"name": "id", "type": "str", "len": 32, "primary": true, "notnull": true, "desc": "主键,32位无横线UUID"},
|
||||
{"name": "tenant_id", "type": "str", "len": 32, "notnull": true, "desc": "租户ID,强制打头"},
|
||||
{"name": "blueprint_id", "type": "str", "len": 32, "notnull": true, "desc": "所属蓝图ID"},
|
||||
{"name": "task_id", "type": "str", "len": 32, "desc": "关联任务ID"},
|
||||
{"name": "mission_id", "type": "str", "len": 32, "desc": "关联关卡ID"},
|
||||
{"name": "learning_goal_id", "type": "str", "len": 32, "desc": "关联学习目标ID"},
|
||||
{"name": "seq", "type": "int", "notnull": true, "default": "0", "desc": "蓝图内排序号"},
|
||||
{"name": "code", "type": "str", "len": 64, "desc": "证据规格编码,蓝图内唯一,缺省自动生成 EVI-{seq}"},
|
||||
{"name": "name", "type": "str", "len": 200, "notnull": true, "desc": "证据规格名称"},
|
||||
{"name": "evidence_type", "type": "str", "len": 32, "notnull": true, "default": "auto", "desc": "证据类型,见 codes.evidence_type"},
|
||||
{"name": "collect_mode", "type": "str", "len": 32, "notnull": true, "default": "auto", "desc": "采集方式,见 codes.collect_mode"},
|
||||
{"name": "description", "type": "text", "desc": "证据说明"},
|
||||
{"name": "spec", "type": "json", "desc": "证据规格(字段schema/取值范围/单位/幂等键规则)"},
|
||||
{"name": "rule", "type": "json", "desc": "校验规则(阈值/表达式/必填项)"},
|
||||
{"name": "weight", "type": "float", "default": "1.0", "desc": "评分权重"},
|
||||
{"name": "status", "type": "str", "len": 32, "notnull": true, "default": "draft", "desc": "子对象状态,见 codes.status"},
|
||||
{"name": "created_by", "type": "str", "len": 32, "desc": "创建人"},
|
||||
{"name": "created_at", "type": "datetime", "desc": "创建时间"},
|
||||
{"name": "updated_by", "type": "str", "len": 32, "desc": "最后修改人"},
|
||||
{"name": "updated_at", "type": "datetime", "desc": "最后修改时间"},
|
||||
{"name": "deleted", "type": "int", "notnull": true, "default": "0", "desc": "软删除标记 0正常 1已删除"}
|
||||
],
|
||||
"indexes": [
|
||||
{"name": "uk_pbl_bpev_tenant_bp_code", "fields": ["tenant_id", "blueprint_id", "code"], "unique": true},
|
||||
{"name": "idx_pbl_bpev_tenant_bp_seq", "fields": ["tenant_id", "blueprint_id", "seq"], "unique": false},
|
||||
{"name": "idx_pbl_bpev_goal", "fields": ["tenant_id", "learning_goal_id"], "unique": false},
|
||||
{"name": "idx_pbl_bpev_mission", "fields": ["tenant_id", "mission_id"], "unique": false}
|
||||
],
|
||||
"codes": {
|
||||
"evidence_type": [["auto", "系统自动采集"], ["manual", "人工提交"], ["artifact", "产出物派生"], ["interaction", "交互行为"], ["assessment", "测评结果"]],
|
||||
"collect_mode": [["auto", "自动"], ["trigger", "事件触发"], ["manual", "手动上传"], ["scheduled", "定时采集"]],
|
||||
"status": [["draft", "草稿"], ["active", "生效"], ["disabled", "停用"]],
|
||||
"deleted": [["0", "正常"], ["1", "已删除"]]
|
||||
}
|
||||
}
|
||||
145
pbl_blueprint/models/pbl_blueprint_fork.json
Normal file
145
pbl_blueprint/models/pbl_blueprint_fork.json
Normal file
@ -0,0 +1,145 @@
|
||||
{
|
||||
"summary": "蓝图 fork 血缘表:记录蓝图之间的派生关系(源蓝图→新蓝图)与模板实例化来源,支持 fork 溯源与派生树查询。",
|
||||
"primary": [
|
||||
"id"
|
||||
],
|
||||
"fields": {
|
||||
"id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"summary": "主键ID(str32,UUID去横线)"
|
||||
},
|
||||
"tenant_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"summary": "租户ID(首业务字段,所有读写强制打头,缺失即 fail-closed 拒绝)"
|
||||
},
|
||||
"source_blueprint_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"summary": "源蓝图ID(模板实例化时为空串)"
|
||||
},
|
||||
"source_version": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"summary": "fork 时的源版本号"
|
||||
},
|
||||
"target_blueprint_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"summary": "派生出的新蓝图ID"
|
||||
},
|
||||
"fork_type": {
|
||||
"type": "str",
|
||||
"size": 16,
|
||||
"notnull": true,
|
||||
"default": "copy",
|
||||
"summary": "fork 方式"
|
||||
},
|
||||
"template_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"summary": "若由模板实例化,记录模板ID"
|
||||
},
|
||||
"node_count": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"summary": "复制的节点数"
|
||||
},
|
||||
"edge_count": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"summary": "复制的边数"
|
||||
},
|
||||
"cost_amount": {
|
||||
"type": "double",
|
||||
"size": [
|
||||
18,
|
||||
2
|
||||
],
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"summary": "本次 fork 产生的费用金额(double(18,2),付费模板)"
|
||||
},
|
||||
"remark": {
|
||||
"type": "str",
|
||||
"size": 512,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"summary": "备注"
|
||||
},
|
||||
"create_user": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"default": "",
|
||||
"summary": "创建人"
|
||||
},
|
||||
"create_time": {
|
||||
"type": "datetime",
|
||||
"summary": "创建时间"
|
||||
},
|
||||
"deleted": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"summary": "逻辑删除标记 0正常 1删除"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"uk_pbl_fork": {
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"source_blueprint_id",
|
||||
"target_blueprint_id"
|
||||
],
|
||||
"unique": true,
|
||||
"summary": "同一对蓝图血缘唯一,保证幂等"
|
||||
},
|
||||
"idx_pbl_fork_source": {
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"source_blueprint_id",
|
||||
"deleted"
|
||||
],
|
||||
"unique": false,
|
||||
"summary": "查某蓝图的派生列表"
|
||||
},
|
||||
"idx_pbl_fork_target": {
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"target_blueprint_id"
|
||||
],
|
||||
"unique": false,
|
||||
"summary": "反查某蓝图的来源"
|
||||
},
|
||||
"idx_pbl_fork_template": {
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"template_id"
|
||||
],
|
||||
"unique": false,
|
||||
"summary": "查模板被实例化记录"
|
||||
}
|
||||
},
|
||||
"codes": {
|
||||
"fork_type": {
|
||||
"summary": "fork 方式",
|
||||
"items": {
|
||||
"copy": "完整复制",
|
||||
"structure": "仅结构(不带载荷)",
|
||||
"template": "模板实例化",
|
||||
"branch": "分支派生"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
{
|
||||
"summary": "PBL 蓝图子对象-学习目标(M1a,7类子对象之一)。一行=蓝图的一条学习目标(知识/能力/素养/态度四维),可关联到任务并被评估量规引用。统一泛化契约。tenant_id 强制打头。",
|
||||
"fields": [
|
||||
{"name": "id", "type": "str", "len": 32, "primary": true, "notnull": true, "desc": "主键,32位无横线UUID"},
|
||||
{"name": "tenant_id", "type": "str", "len": 32, "notnull": true, "desc": "租户ID,强制打头"},
|
||||
{"name": "blueprint_id", "type": "str", "len": 32, "notnull": true, "desc": "所属蓝图ID"},
|
||||
{"name": "task_id", "type": "str", "len": 32, "desc": "关联任务ID,为空表示蓝图级目标"},
|
||||
{"name": "seq", "type": "int", "notnull": true, "default": "0", "desc": "蓝图内排序号"},
|
||||
{"name": "code", "type": "str", "len": 64, "desc": "目标编码,蓝图内唯一,缺省自动生成 GOAL-{seq}"},
|
||||
{"name": "name", "type": "str", "len": 200, "notnull": true, "desc": "目标名称"},
|
||||
{"name": "dimension", "type": "str", "len": 32, "notnull": true, "default": "knowledge", "desc": "目标维度,见 codes.dimension"},
|
||||
{"name": "description", "type": "text", "desc": "目标描述(可观测行为动词表述)"},
|
||||
{"name": "spec", "type": "json", "desc": "目标规格(课标对应/掌握层级/评价方式)"},
|
||||
{"name": "weight", "type": "float", "default": "1.0", "desc": "评估权重,供 pbl_assessment Rubric 使用"},
|
||||
{"name": "status", "type": "str", "len": 32, "notnull": true, "default": "draft", "desc": "子对象状态,见 codes.status"},
|
||||
{"name": "created_by", "type": "str", "len": 32, "desc": "创建人"},
|
||||
{"name": "created_at", "type": "datetime", "desc": "创建时间"},
|
||||
{"name": "updated_by", "type": "str", "len": 32, "desc": "最后修改人"},
|
||||
{"name": "updated_at", "type": "datetime", "desc": "最后修改时间"},
|
||||
{"name": "deleted", "type": "int", "notnull": true, "default": "0", "desc": "软删除标记 0正常 1已删除"}
|
||||
],
|
||||
"indexes": [
|
||||
{"name": "uk_pbl_bplg_tenant_bp_code", "fields": ["tenant_id", "blueprint_id", "code"], "unique": true},
|
||||
{"name": "idx_pbl_bplg_tenant_bp_seq", "fields": ["tenant_id", "blueprint_id", "seq"], "unique": false},
|
||||
{"name": "idx_pbl_bplg_dim", "fields": ["tenant_id", "blueprint_id", "dimension"], "unique": false}
|
||||
],
|
||||
"codes": {
|
||||
"dimension": [["knowledge", "知识"], ["ability", "能力"], ["literacy", "素养"], ["attitude", "态度"]],
|
||||
"status": [["draft", "草稿"], ["active", "生效"], ["disabled", "停用"]],
|
||||
"deleted": [["0", "正常"], ["1", "已删除"]]
|
||||
}
|
||||
}
|
||||
132
pbl_blueprint/models/pbl_blueprint_lock.json
Normal file
132
pbl_blueprint/models/pbl_blueprint_lock.json
Normal file
@ -0,0 +1,132 @@
|
||||
{
|
||||
"summary": "蓝图编辑锁表:多人协作时对蓝图/节点加编辑锁(悲观锁+rev 乐观修订号),锁带过期时间,过期可被抢占。",
|
||||
"primary": [
|
||||
"id"
|
||||
],
|
||||
"fields": {
|
||||
"id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"summary": "主键ID(str32,UUID去横线)"
|
||||
},
|
||||
"tenant_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"summary": "租户ID(首业务字段,所有读写强制打头,缺失即 fail-closed 拒绝)"
|
||||
},
|
||||
"blueprint_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"summary": "被锁蓝图ID"
|
||||
},
|
||||
"node_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"summary": "被锁节点ID(空串=整蓝图锁)"
|
||||
},
|
||||
"lock_type": {
|
||||
"type": "str",
|
||||
"size": 16,
|
||||
"notnull": true,
|
||||
"default": "edit",
|
||||
"summary": "锁类型"
|
||||
},
|
||||
"holder_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"summary": "持锁人ID"
|
||||
},
|
||||
"holder_name": {
|
||||
"type": "str",
|
||||
"size": 64,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"summary": "持锁人名称"
|
||||
},
|
||||
"rev": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"summary": "乐观锁修订号(每次抢占/续锁 +1)"
|
||||
},
|
||||
"expire_time": {
|
||||
"type": "datetime",
|
||||
"summary": "锁过期时间"
|
||||
},
|
||||
"status": {
|
||||
"type": "str",
|
||||
"size": 16,
|
||||
"notnull": true,
|
||||
"default": "holding",
|
||||
"summary": "锁状态"
|
||||
},
|
||||
"create_time": {
|
||||
"type": "datetime",
|
||||
"summary": "加锁时间"
|
||||
},
|
||||
"update_time": {
|
||||
"type": "datetime",
|
||||
"summary": "续锁时间"
|
||||
},
|
||||
"deleted": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"summary": "逻辑删除标记 0正常 1删除"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"uk_pbl_lock": {
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"blueprint_id",
|
||||
"node_id",
|
||||
"lock_type"
|
||||
],
|
||||
"unique": true,
|
||||
"summary": "同对象同类型仅一把锁记录"
|
||||
},
|
||||
"idx_pbl_lock_holder": {
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"holder_id",
|
||||
"status"
|
||||
],
|
||||
"unique": false,
|
||||
"summary": "查某人持有的锁"
|
||||
},
|
||||
"idx_pbl_lock_expire": {
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"status",
|
||||
"expire_time"
|
||||
],
|
||||
"unique": false,
|
||||
"summary": "过期锁清理扫描"
|
||||
}
|
||||
},
|
||||
"codes": {
|
||||
"lock_type": {
|
||||
"summary": "锁类型",
|
||||
"items": {
|
||||
"edit": "编辑锁",
|
||||
"publish": "发布锁",
|
||||
"compile": "编译锁"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"summary": "锁状态",
|
||||
"items": {
|
||||
"holding": "持有中",
|
||||
"released": "已释放",
|
||||
"expired": "已过期"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,30 +0,0 @@
|
||||
{
|
||||
"summary": "PBL 蓝图子对象-关卡(M1a,7类子对象之一)。一行=任务下的一个游戏化关卡,是 Compiler 生成 Game Definition 的直接来源。统一泛化契约(blueprint_id + task_id + seq + code + name + spec + status)。tenant_id 强制打头。",
|
||||
"fields": [
|
||||
{"name": "id", "type": "str", "len": 32, "primary": true, "notnull": true, "desc": "主键,32位无横线UUID"},
|
||||
{"name": "tenant_id", "type": "str", "len": 32, "notnull": true, "desc": "租户ID,强制打头"},
|
||||
{"name": "blueprint_id", "type": "str", "len": 32, "notnull": true, "desc": "所属蓝图ID"},
|
||||
{"name": "task_id", "type": "str", "len": 32, "notnull": true, "desc": "所属任务ID"},
|
||||
{"name": "seq", "type": "int", "notnull": true, "default": "0", "desc": "任务内排序号"},
|
||||
{"name": "code", "type": "str", "len": 64, "desc": "关卡编码,蓝图内唯一,缺省自动生成 MISSION-{seq}"},
|
||||
{"name": "name", "type": "str", "len": 200, "notnull": true, "desc": "关卡名称"},
|
||||
{"name": "description", "type": "text", "desc": "关卡说明"},
|
||||
{"name": "spec", "type": "json", "desc": "关卡规格(目标/规则/场景引用/通关条件/奖励)"},
|
||||
{"name": "unlock_condition", "type": "json", "desc": "解锁条件(前置关卡/分数门槛)"},
|
||||
{"name": "status", "type": "str", "len": 32, "notnull": true, "default": "draft", "desc": "子对象状态,见 codes.status"},
|
||||
{"name": "created_by", "type": "str", "len": 32, "desc": "创建人"},
|
||||
{"name": "created_at", "type": "datetime", "desc": "创建时间"},
|
||||
{"name": "updated_by", "type": "str", "len": 32, "desc": "最后修改人"},
|
||||
{"name": "updated_at", "type": "datetime", "desc": "最后修改时间"},
|
||||
{"name": "deleted", "type": "int", "notnull": true, "default": "0", "desc": "软删除标记 0正常 1已删除"}
|
||||
],
|
||||
"indexes": [
|
||||
{"name": "uk_pbl_bpms_tenant_bp_code", "fields": ["tenant_id", "blueprint_id", "code"], "unique": true},
|
||||
{"name": "idx_pbl_bpms_tenant_bp_seq", "fields": ["tenant_id", "blueprint_id", "seq"], "unique": false},
|
||||
{"name": "idx_pbl_bpms_task", "fields": ["tenant_id", "task_id", "seq"], "unique": false}
|
||||
],
|
||||
"codes": {
|
||||
"status": [["draft", "草稿"], ["active", "生效"], ["disabled", "停用"]],
|
||||
"deleted": [["0", "正常"], ["1", "已删除"]]
|
||||
}
|
||||
}
|
||||
190
pbl_blueprint/models/pbl_blueprint_node.json
Normal file
190
pbl_blueprint/models/pbl_blueprint_node.json
Normal file
@ -0,0 +1,190 @@
|
||||
{
|
||||
"summary": "蓝图子对象节点表(7 类子对象泛化契约):role/task/rule/artifact/scene/assessment/resource 统一存一张表,差异载荷放 spec_json,按 node_type 分派校验器(M2)。",
|
||||
"primary": [
|
||||
"id"
|
||||
],
|
||||
"fields": {
|
||||
"id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"summary": "主键ID(str32,UUID去横线)"
|
||||
},
|
||||
"tenant_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"summary": "租户ID(首业务字段,所有读写强制打头,缺失即 fail-closed 拒绝)"
|
||||
},
|
||||
"blueprint_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"summary": "所属蓝图ID"
|
||||
},
|
||||
"node_type": {
|
||||
"type": "str",
|
||||
"size": 16,
|
||||
"notnull": true,
|
||||
"summary": "子对象类型(7 类泛化)"
|
||||
},
|
||||
"parent_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"summary": "父节点ID(构成蓝图树,空串=根节点)"
|
||||
},
|
||||
"code": {
|
||||
"type": "str",
|
||||
"size": 64,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"summary": "节点编码(蓝图内同类型唯一)"
|
||||
},
|
||||
"name": {
|
||||
"type": "str",
|
||||
"size": 128,
|
||||
"notnull": true,
|
||||
"summary": "节点名称"
|
||||
},
|
||||
"sort_no": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"summary": "同级排序号"
|
||||
},
|
||||
"spec_json": {
|
||||
"type": "text",
|
||||
"default": "",
|
||||
"summary": "泛化载荷JSON(结构随 node_type 不同)"
|
||||
},
|
||||
"ref_module": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"summary": "引用模块名(scense/world/assessment 等)"
|
||||
},
|
||||
"ref_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"summary": "引用对象ID"
|
||||
},
|
||||
"status": {
|
||||
"type": "str",
|
||||
"size": 16,
|
||||
"notnull": true,
|
||||
"default": "active",
|
||||
"summary": "节点状态"
|
||||
},
|
||||
"required": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"summary": "是否必需节点 0否 1是"
|
||||
},
|
||||
"weight": {
|
||||
"type": "double",
|
||||
"size": [
|
||||
18,
|
||||
2
|
||||
],
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"summary": "权重/分值(double(18,2))"
|
||||
},
|
||||
"create_user": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"default": "",
|
||||
"summary": "创建人"
|
||||
},
|
||||
"create_time": {
|
||||
"type": "datetime",
|
||||
"summary": "创建时间"
|
||||
},
|
||||
"update_user": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"default": "",
|
||||
"summary": "更新人"
|
||||
},
|
||||
"update_time": {
|
||||
"type": "datetime",
|
||||
"summary": "更新时间"
|
||||
},
|
||||
"deleted": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"summary": "逻辑删除标记 0正常 1删除"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"uk_pbl_node_code": {
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"blueprint_id",
|
||||
"node_type",
|
||||
"code"
|
||||
],
|
||||
"unique": true,
|
||||
"summary": "蓝图内同类型节点编码唯一"
|
||||
},
|
||||
"idx_pbl_node_bp": {
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"blueprint_id",
|
||||
"deleted",
|
||||
"node_type"
|
||||
],
|
||||
"unique": false,
|
||||
"summary": "按蓝图+类型列节点(tenant_id 打头)"
|
||||
},
|
||||
"idx_pbl_node_parent": {
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"blueprint_id",
|
||||
"parent_id",
|
||||
"sort_no"
|
||||
],
|
||||
"unique": false,
|
||||
"summary": "构树查询"
|
||||
},
|
||||
"idx_pbl_node_ref": {
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"ref_module",
|
||||
"ref_id"
|
||||
],
|
||||
"unique": false,
|
||||
"summary": "按引用对象反查节点"
|
||||
}
|
||||
},
|
||||
"codes": {
|
||||
"node_type": {
|
||||
"summary": "子对象类型(7 类泛化契约)",
|
||||
"items": {
|
||||
"role": "角色",
|
||||
"task": "任务",
|
||||
"rule": "规则",
|
||||
"artifact": "产出物",
|
||||
"scene": "场景引用",
|
||||
"assessment": "评估项",
|
||||
"resource": "资源"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"summary": "节点状态",
|
||||
"items": {
|
||||
"active": "生效",
|
||||
"draft": "草稿",
|
||||
"invalid": "校验不通过",
|
||||
"disabled": "停用"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
141
pbl_blueprint/models/pbl_blueprint_offline.json
Normal file
141
pbl_blueprint/models/pbl_blueprint_offline.json
Normal file
@ -0,0 +1,141 @@
|
||||
{
|
||||
"summary": "蓝图离线兜底包表:把蓝图/模板打包为自描述 JSON 包(含 schema_version 与 sha256 校验和),网络或服务不可用时本地兜底实例化。",
|
||||
"primary": [
|
||||
"id"
|
||||
],
|
||||
"fields": {
|
||||
"id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"summary": "主键ID(str32,UUID去横线)"
|
||||
},
|
||||
"tenant_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"summary": "租户ID(首业务字段,所有读写强制打头,缺失即 fail-closed 拒绝)"
|
||||
},
|
||||
"blueprint_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"summary": "来源蓝图ID(与 template_id 二选一)"
|
||||
},
|
||||
"template_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"summary": "来源模板ID"
|
||||
},
|
||||
"pkg_code": {
|
||||
"type": "str",
|
||||
"size": 64,
|
||||
"notnull": true,
|
||||
"summary": "离线包编码"
|
||||
},
|
||||
"pkg_name": {
|
||||
"type": "str",
|
||||
"size": 128,
|
||||
"notnull": true,
|
||||
"summary": "离线包名称"
|
||||
},
|
||||
"pkg_version": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 1,
|
||||
"summary": "离线包版本"
|
||||
},
|
||||
"pkg_json": {
|
||||
"type": "text",
|
||||
"default": "",
|
||||
"summary": "离线包内容JSON(自描述:schema_version+蓝图+节点+边)"
|
||||
},
|
||||
"file_size": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"summary": "包体字节数"
|
||||
},
|
||||
"checksum": {
|
||||
"type": "str",
|
||||
"size": 64,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"summary": "内容校验和(sha256)"
|
||||
},
|
||||
"status": {
|
||||
"type": "str",
|
||||
"size": 16,
|
||||
"notnull": true,
|
||||
"default": "ready",
|
||||
"summary": "离线包状态"
|
||||
},
|
||||
"expire_time": {
|
||||
"type": "datetime",
|
||||
"summary": "过期时间"
|
||||
},
|
||||
"create_user": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"default": "",
|
||||
"summary": "创建人"
|
||||
},
|
||||
"create_time": {
|
||||
"type": "datetime",
|
||||
"summary": "创建时间"
|
||||
},
|
||||
"update_time": {
|
||||
"type": "datetime",
|
||||
"summary": "更新时间"
|
||||
},
|
||||
"deleted": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"summary": "逻辑删除标记 0正常 1删除"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"uk_pbl_offline_code": {
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"pkg_code",
|
||||
"pkg_version"
|
||||
],
|
||||
"unique": true,
|
||||
"summary": "离线包编码+版本唯一"
|
||||
},
|
||||
"idx_pbl_offline_bp": {
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"blueprint_id",
|
||||
"deleted"
|
||||
],
|
||||
"unique": false,
|
||||
"summary": "按蓝图查离线包"
|
||||
},
|
||||
"idx_pbl_offline_status": {
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"status",
|
||||
"expire_time"
|
||||
],
|
||||
"unique": false,
|
||||
"summary": "过期包清理扫描"
|
||||
}
|
||||
},
|
||||
"codes": {
|
||||
"status": {
|
||||
"summary": "离线包状态",
|
||||
"items": {
|
||||
"ready": "可用",
|
||||
"building": "构建中",
|
||||
"expired": "已过期",
|
||||
"invalid": "校验失败"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
168
pbl_blueprint/models/pbl_blueprint_publish.json
Normal file
168
pbl_blueprint/models/pbl_blueprint_publish.json
Normal file
@ -0,0 +1,168 @@
|
||||
{
|
||||
"summary": "蓝图发布/投放记录表:蓝图某版本投放到班级/团队/学生的一次发布,记录运行时句柄与编译产物,供 M11 runtime_ext 关联。",
|
||||
"primary": [
|
||||
"id"
|
||||
],
|
||||
"fields": {
|
||||
"id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"summary": "主键ID(str32,UUID去横线)"
|
||||
},
|
||||
"tenant_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"summary": "租户ID(首业务字段,所有读写强制打头,缺失即 fail-closed 拒绝)"
|
||||
},
|
||||
"blueprint_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"summary": "蓝图ID"
|
||||
},
|
||||
"version_no": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"summary": "发布的版本号"
|
||||
},
|
||||
"class_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"summary": "投放班级ID"
|
||||
},
|
||||
"target_type": {
|
||||
"type": "str",
|
||||
"size": 16,
|
||||
"notnull": true,
|
||||
"default": "class",
|
||||
"summary": "投放对象类型"
|
||||
},
|
||||
"target_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"summary": "投放对象ID"
|
||||
},
|
||||
"runtime_ref": {
|
||||
"type": "str",
|
||||
"size": 64,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"summary": "运行时引用(scense_runtime 会话/世界ID)"
|
||||
},
|
||||
"game_def_json": {
|
||||
"type": "text",
|
||||
"default": "",
|
||||
"summary": "编译产物 Game Definition JSON(M3 回写)"
|
||||
},
|
||||
"start_time": {
|
||||
"type": "datetime",
|
||||
"summary": "开始时间"
|
||||
},
|
||||
"end_time": {
|
||||
"type": "datetime",
|
||||
"summary": "结束时间"
|
||||
},
|
||||
"budget_amount": {
|
||||
"type": "double",
|
||||
"size": [
|
||||
18,
|
||||
2
|
||||
],
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"summary": "本次投放预算金额(double(18,2))"
|
||||
},
|
||||
"status": {
|
||||
"type": "str",
|
||||
"size": 16,
|
||||
"notnull": true,
|
||||
"default": "published",
|
||||
"summary": "投放状态"
|
||||
},
|
||||
"remark": {
|
||||
"type": "str",
|
||||
"size": 512,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"summary": "备注"
|
||||
},
|
||||
"create_user": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"default": "",
|
||||
"summary": "创建人"
|
||||
},
|
||||
"create_time": {
|
||||
"type": "datetime",
|
||||
"summary": "创建时间"
|
||||
},
|
||||
"update_time": {
|
||||
"type": "datetime",
|
||||
"summary": "更新时间"
|
||||
},
|
||||
"deleted": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"summary": "逻辑删除标记 0正常 1删除"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"uk_pbl_publish": {
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"blueprint_id",
|
||||
"version_no",
|
||||
"target_type",
|
||||
"target_id"
|
||||
],
|
||||
"unique": true,
|
||||
"summary": "同版本同对象只投放一次,保证幂等"
|
||||
},
|
||||
"idx_pbl_publish_bp": {
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"blueprint_id",
|
||||
"deleted"
|
||||
],
|
||||
"unique": false,
|
||||
"summary": "按蓝图查投放记录"
|
||||
},
|
||||
"idx_pbl_publish_class": {
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"class_id",
|
||||
"status"
|
||||
],
|
||||
"unique": false,
|
||||
"summary": "按班级查在跑的投放"
|
||||
}
|
||||
},
|
||||
"codes": {
|
||||
"target_type": {
|
||||
"summary": "投放对象类型",
|
||||
"items": {
|
||||
"class": "班级",
|
||||
"team": "团队",
|
||||
"student": "学生",
|
||||
"preview": "预览"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"summary": "投放状态",
|
||||
"items": {
|
||||
"published": "已发布",
|
||||
"running": "进行中",
|
||||
"finished": "已结束",
|
||||
"revoked": "已撤回"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,36 +0,0 @@
|
||||
{
|
||||
"summary": "PBL 蓝图子对象-反思规格(M1a,7类子对象之一)。一行=一条反思/自评提示的规格定义(触发时机、反思类型、引导问题、量规维度),供运行时按节点触发学习者反思并回流评估。统一泛化契约。tenant_id 强制打头。",
|
||||
"fields": [
|
||||
{"name": "id", "type": "str", "len": 32, "primary": true, "notnull": true, "desc": "主键,32位无横线UUID"},
|
||||
{"name": "tenant_id", "type": "str", "len": 32, "notnull": true, "desc": "租户ID,强制打头"},
|
||||
{"name": "blueprint_id", "type": "str", "len": 32, "notnull": true, "desc": "所属蓝图ID"},
|
||||
{"name": "task_id", "type": "str", "len": 32, "desc": "关联任务ID"},
|
||||
{"name": "mission_id", "type": "str", "len": 32, "desc": "关联关卡ID"},
|
||||
{"name": "seq", "type": "int", "notnull": true, "default": "0", "desc": "蓝图内排序号"},
|
||||
{"name": "code", "type": "str", "len": 64, "desc": "反思规格编码,蓝图内唯一,缺省自动生成 REF-{seq}"},
|
||||
{"name": "name", "type": "str", "len": 200, "notnull": true, "desc": "反思规格名称"},
|
||||
{"name": "trigger_point", "type": "str", "len": 32, "notnull": true, "default": "mission_end", "desc": "触发时机,见 codes.trigger_point"},
|
||||
{"name": "reflection_type", "type": "str", "len": 32, "notnull": true, "default": "self", "desc": "反思类型,见 codes.reflection_type"},
|
||||
{"name": "description", "type": "text", "desc": "反思说明"},
|
||||
{"name": "spec", "type": "json", "desc": "反思规格(引导问题数组/作答形式/字数下限)"},
|
||||
{"name": "rubric_ref", "type": "json", "desc": "关联量规维度引用(供 pbl_assessment 打分)"},
|
||||
{"name": "weight", "type": "float", "default": "1.0", "desc": "评分权重"},
|
||||
{"name": "status", "type": "str", "len": 32, "notnull": true, "default": "draft", "desc": "子对象状态,见 codes.status"},
|
||||
{"name": "created_by", "type": "str", "len": 32, "desc": "创建人"},
|
||||
{"name": "created_at", "type": "datetime", "desc": "创建时间"},
|
||||
{"name": "updated_by", "type": "str", "len": 32, "desc": "最后修改人"},
|
||||
{"name": "updated_at", "type": "datetime", "desc": "最后修改时间"},
|
||||
{"name": "deleted", "type": "int", "notnull": true, "default": "0", "desc": "软删除标记 0正常 1已删除"}
|
||||
],
|
||||
"indexes": [
|
||||
{"name": "uk_pbl_bpref_tenant_bp_code", "fields": ["tenant_id", "blueprint_id", "code"], "unique": true},
|
||||
{"name": "idx_pbl_bpref_tenant_bp_seq", "fields": ["tenant_id", "blueprint_id", "seq"], "unique": false},
|
||||
{"name": "idx_pbl_bpref_trigger", "fields": ["tenant_id", "blueprint_id", "trigger_point"], "unique": false}
|
||||
],
|
||||
"codes": {
|
||||
"trigger_point": [["task_start", "任务开始"], ["task_end", "任务结束"], ["mission_end", "关卡结束"], ["milestone", "里程碑"], ["project_end", "项目结题"], ["manual", "手动触发"]],
|
||||
"reflection_type": [["self", "自我反思"], ["peer", "同伴互评"], ["team", "团队复盘"], ["teacher", "教师点评"]],
|
||||
"status": [["draft", "草稿"], ["active", "生效"], ["disabled", "停用"]],
|
||||
"deleted": [["0", "正常"], ["1", "已删除"]]
|
||||
}
|
||||
}
|
||||
@ -1,30 +0,0 @@
|
||||
{
|
||||
"summary": "PBL 蓝图子对象-角色(M1a,7类子对象之一)。一行=蓝图内定义的一种学习者角色(如队长/记录员/工程师),供分组与权限分配使用。统一泛化契约。tenant_id 强制打头。",
|
||||
"fields": [
|
||||
{"name": "id", "type": "str", "len": 32, "primary": true, "notnull": true, "desc": "主键,32位无横线UUID"},
|
||||
{"name": "tenant_id", "type": "str", "len": 32, "notnull": true, "desc": "租户ID,强制打头"},
|
||||
{"name": "blueprint_id", "type": "str", "len": 32, "notnull": true, "desc": "所属蓝图ID"},
|
||||
{"name": "task_id", "type": "str", "len": 32, "desc": "关联任务ID,为空表示蓝图级角色"},
|
||||
{"name": "seq", "type": "int", "notnull": true, "default": "0", "desc": "蓝图内排序号"},
|
||||
{"name": "code", "type": "str", "len": 64, "desc": "角色编码,蓝图内唯一,缺省自动生成 ROLE-{seq}"},
|
||||
{"name": "name", "type": "str", "len": 200, "notnull": true, "desc": "角色名称"},
|
||||
{"name": "description", "type": "text", "desc": "角色职责说明"},
|
||||
{"name": "spec", "type": "json", "desc": "角色规格(人数上限/能力标签/可用工具/评分权重)"},
|
||||
{"name": "permissions", "type": "json", "desc": "角色在运行时的操作权限集合"},
|
||||
{"name": "status", "type": "str", "len": 32, "notnull": true, "default": "draft", "desc": "子对象状态,见 codes.status"},
|
||||
{"name": "created_by", "type": "str", "len": 32, "desc": "创建人"},
|
||||
{"name": "created_at", "type": "datetime", "desc": "创建时间"},
|
||||
{"name": "updated_by", "type": "str", "len": 32, "desc": "最后修改人"},
|
||||
{"name": "updated_at", "type": "datetime", "desc": "最后修改时间"},
|
||||
{"name": "deleted", "type": "int", "notnull": true, "default": "0", "desc": "软删除标记 0正常 1已删除"}
|
||||
],
|
||||
"indexes": [
|
||||
{"name": "uk_pbl_bprole_tenant_bp_code", "fields": ["tenant_id", "blueprint_id", "code"], "unique": true},
|
||||
{"name": "idx_pbl_bprole_tenant_bp_seq", "fields": ["tenant_id", "blueprint_id", "seq"], "unique": false},
|
||||
{"name": "idx_pbl_bprole_task", "fields": ["tenant_id", "task_id"], "unique": false}
|
||||
],
|
||||
"codes": {
|
||||
"status": [["draft", "草稿"], ["active", "生效"], ["disabled", "停用"]],
|
||||
"deleted": [["0", "正常"], ["1", "已删除"]]
|
||||
}
|
||||
}
|
||||
@ -1,36 +0,0 @@
|
||||
{
|
||||
"summary": "PBL 蓝图子对象-任务(M1a,7类子对象之一)。一行=蓝图下的一个学习任务,是关卡/角色/目标的挂载父级。采用统一泛化契约(blueprint_id + seq + name + spec + status),便于 Compiler 与 Validation 统一遍历。tenant_id 强制打头。",
|
||||
"fields": [
|
||||
{"name": "id", "type": "str", "len": 32, "primary": true, "notnull": true, "desc": "主键,32位无横线UUID"},
|
||||
{"name": "tenant_id", "type": "str", "len": 32, "notnull": true, "desc": "租户ID,强制打头"},
|
||||
{"name": "blueprint_id", "type": "str", "len": 32, "notnull": true, "desc": "所属蓝图ID"},
|
||||
{"name": "parent_id", "type": "str", "len": 32, "desc": "父任务ID,支持任务分层,顶层为空"},
|
||||
{"name": "seq", "type": "int", "notnull": true, "default": "0", "desc": "蓝图内排序号"},
|
||||
{"name": "code", "type": "str", "len": 64, "desc": "任务编码,蓝图内唯一,缺省自动生成 TASK-{seq}"},
|
||||
{"name": "name", "type": "str", "len": 200, "notnull": true, "desc": "任务名称"},
|
||||
{"name": "description", "type": "text", "desc": "任务描述/驱动性问题"},
|
||||
{"name": "spec", "type": "json", "desc": "任务规格(时长/难度/分组方式/前置条件/交付要求)"},
|
||||
{"name": "status", "type": "str", "len": 32, "notnull": true, "default": "draft", "desc": "子对象状态,见 codes.status"},
|
||||
{"name": "created_by", "type": "str", "len": 32, "desc": "创建人"},
|
||||
{"name": "created_at", "type": "datetime", "desc": "创建时间"},
|
||||
{"name": "updated_by", "type": "str", "len": 32, "desc": "最后修改人"},
|
||||
{"name": "updated_at", "type": "datetime", "desc": "最后修改时间"},
|
||||
{"name": "deleted", "type": "int", "notnull": true, "default": "0", "desc": "软删除标记 0正常 1已删除"}
|
||||
],
|
||||
"indexes": [
|
||||
{"name": "idx_pbl_bptask_tenant_bp_seq", "fields": ["tenant_id", "blueprint_id", "seq"], "unique": false},
|
||||
{"name": "uk_pbl_bptask_tenant_bp_code", "fields": ["tenant_id", "blueprint_id", "code"], "unique": true},
|
||||
{"name": "idx_pbl_bptask_parent", "fields": ["tenant_id", "parent_id"], "unique": false}
|
||||
],
|
||||
"codes": {
|
||||
"status": [
|
||||
["draft", "草稿"],
|
||||
["active", "生效"],
|
||||
["disabled", "停用"]
|
||||
],
|
||||
"deleted": [
|
||||
["0", "正常"],
|
||||
["1", "已删除"]
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -1,31 +1,191 @@
|
||||
{
|
||||
"summary": "PBL 蓝图模板表(M1a)。一行=一个可复用蓝图模板,用于一键实例化生成新蓝图;离线兜底时也可由模板生成最小可用蓝图。tenant_id 强制打头,平台内置模板 tenant_id 固定为 platform。",
|
||||
"fields": [
|
||||
{"name": "id", "type": "str", "len": 32, "primary": true, "notnull": true, "desc": "主键,32位无横线UUID"},
|
||||
{"name": "tenant_id", "type": "str", "len": 32, "notnull": true, "desc": "租户ID,强制打头;平台内置模板为 platform"},
|
||||
{"name": "code", "type": "str", "len": 64, "notnull": true, "desc": "模板编码,租户内唯一"},
|
||||
{"name": "name", "type": "str", "len": 200, "notnull": true, "desc": "模板名称"},
|
||||
{"name": "category", "type": "str", "len": 64, "desc": "模板分类,见 codes.category"},
|
||||
{"name": "subject", "type": "str", "len": 64, "desc": "适用学科"},
|
||||
{"name": "grade", "type": "str", "len": 32, "desc": "适用年级"},
|
||||
{"name": "duration_hours", "type": "int", "desc": "建议课时"},
|
||||
{"name": "description", "type": "text", "desc": "模板说明"},
|
||||
{"name": "content", "type": "json", "desc": "模板主体内容(主表默认值+子对象骨架)"},
|
||||
{"name": "status", "type": "str", "len": 32, "notnull": true, "default": "enabled", "desc": "模板状态,见 codes.status"},
|
||||
{"name": "use_count", "type": "int", "notnull": true, "default": "0", "desc": "被实例化次数"},
|
||||
{"name": "created_by", "type": "str", "len": 32, "desc": "创建人"},
|
||||
{"name": "created_at", "type": "datetime", "desc": "创建时间"},
|
||||
{"name": "updated_by", "type": "str", "len": 32, "desc": "最后修改人"},
|
||||
{"name": "updated_at", "type": "datetime", "desc": "最后修改时间"},
|
||||
{"name": "deleted", "type": "int", "notnull": true, "default": "0", "desc": "软删除标记 0正常 1已删除"}
|
||||
],
|
||||
"indexes": [
|
||||
{"name": "uk_pbl_bpt_tenant_code", "fields": ["tenant_id", "code"], "unique": true},
|
||||
{"name": "idx_pbl_bpt_tenant_cat", "fields": ["tenant_id", "category", "status", "deleted"], "unique": false}
|
||||
"summary": "蓝图模板表:可复用的蓝图骨架(payload_json 存模板树),支持实例化为新蓝图;离线兜底时作为本地模板源。",
|
||||
"primary": [
|
||||
"id"
|
||||
],
|
||||
"fields": {
|
||||
"id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"summary": "主键ID(str32,UUID去横线)"
|
||||
},
|
||||
"tenant_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"summary": "租户ID(首业务字段,所有读写强制打头,缺失即 fail-closed 拒绝);空串=平台公共模板"
|
||||
},
|
||||
"code": {
|
||||
"type": "str",
|
||||
"size": 64,
|
||||
"notnull": true,
|
||||
"summary": "模板编码(租户内唯一)"
|
||||
},
|
||||
"name": {
|
||||
"type": "str",
|
||||
"size": 128,
|
||||
"notnull": true,
|
||||
"summary": "模板名称"
|
||||
},
|
||||
"category": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"default": "general",
|
||||
"summary": "模板分类"
|
||||
},
|
||||
"subject": {
|
||||
"type": "str",
|
||||
"size": 64,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"summary": "适用学科"
|
||||
},
|
||||
"grade": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"summary": "适用年级"
|
||||
},
|
||||
"scope": {
|
||||
"type": "str",
|
||||
"size": 16,
|
||||
"notnull": true,
|
||||
"default": "tenant",
|
||||
"summary": "可见范围"
|
||||
},
|
||||
"payload_json": {
|
||||
"type": "text",
|
||||
"default": "",
|
||||
"summary": "模板树载荷JSON(blueprint+nodes+edges)"
|
||||
},
|
||||
"node_count": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"summary": "模板节点数"
|
||||
},
|
||||
"edge_count": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"summary": "模板边数"
|
||||
},
|
||||
"price_amount": {
|
||||
"type": "double",
|
||||
"size": [
|
||||
18,
|
||||
2
|
||||
],
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"summary": "模板定价金额(double(18,2),0=免费)"
|
||||
},
|
||||
"use_count": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"summary": "被实例化次数"
|
||||
},
|
||||
"status": {
|
||||
"type": "str",
|
||||
"size": 16,
|
||||
"notnull": true,
|
||||
"default": "enabled",
|
||||
"summary": "模板状态"
|
||||
},
|
||||
"summary": {
|
||||
"type": "str",
|
||||
"size": 512,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"summary": "模板说明"
|
||||
},
|
||||
"create_user": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"default": "",
|
||||
"summary": "创建人"
|
||||
},
|
||||
"create_time": {
|
||||
"type": "datetime",
|
||||
"summary": "创建时间"
|
||||
},
|
||||
"update_user": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"default": "",
|
||||
"summary": "更新人"
|
||||
},
|
||||
"update_time": {
|
||||
"type": "datetime",
|
||||
"summary": "更新时间"
|
||||
},
|
||||
"deleted": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"summary": "逻辑删除标记 0正常 1删除"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"uk_pbl_template_code": {
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"code"
|
||||
],
|
||||
"unique": true,
|
||||
"summary": "租户内模板编码唯一"
|
||||
},
|
||||
"idx_pbl_template_list": {
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"deleted",
|
||||
"status",
|
||||
"category"
|
||||
],
|
||||
"unique": false,
|
||||
"summary": "模板列表查询(tenant_id 打头)"
|
||||
},
|
||||
"idx_pbl_template_subject": {
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"subject",
|
||||
"grade"
|
||||
],
|
||||
"unique": false,
|
||||
"summary": "按学科年级筛模板"
|
||||
}
|
||||
},
|
||||
"codes": {
|
||||
"category": [["stem", "STEM跨学科"], ["humanity", "人文社科"], ["science", "自然科学"], ["engineering", "工程制作"], ["social", "社会服务"], ["general", "通用"]],
|
||||
"status": [["enabled", "启用"], ["disabled", "停用"]],
|
||||
"deleted": [["0", "正常"], ["1", "已删除"]]
|
||||
"scope": {
|
||||
"summary": "可见范围",
|
||||
"items": {
|
||||
"tenant": "本租户",
|
||||
"platform": "平台公共",
|
||||
"private": "仅创建人"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"summary": "模板状态",
|
||||
"items": {
|
||||
"enabled": "启用",
|
||||
"disabled": "停用",
|
||||
"draft": "草稿"
|
||||
}
|
||||
},
|
||||
"category": {
|
||||
"summary": "模板分类",
|
||||
"items": {
|
||||
"stem": "STEM",
|
||||
"science": "自然科学",
|
||||
"humanity": "人文社科",
|
||||
"art": "艺术",
|
||||
"labor": "劳动实践",
|
||||
"general": "通用"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,27 +0,0 @@
|
||||
{
|
||||
"summary": "PBL 蓝图模板条目表(M1a)。一行=模板下的一个子对象骨架条目,obj_type 取 7 类子对象类型之一;实例化模板时按 seq 顺序批量生成对应子对象记录,ref_key/parent_ref 做模板内相对引用。tenant_id 强制打头。",
|
||||
"fields": [
|
||||
{"name": "id", "type": "str", "len": 32, "primary": true, "notnull": true, "desc": "主键,32位无横线UUID"},
|
||||
{"name": "tenant_id", "type": "str", "len": 32, "notnull": true, "desc": "租户ID,强制打头"},
|
||||
{"name": "template_id", "type": "str", "len": 32, "notnull": true, "desc": "所属模板ID"},
|
||||
{"name": "obj_type", "type": "str", "len": 32, "notnull": true, "desc": "子对象类型,见 codes.obj_type"},
|
||||
{"name": "parent_ref", "type": "str", "len": 64, "desc": "父条目引用键(模板内相对引用,实例化时解析为真实ID)"},
|
||||
{"name": "ref_key", "type": "str", "len": 64, "desc": "本条目引用键,供子条目 parent_ref 指向"},
|
||||
{"name": "seq", "type": "int", "notnull": true, "default": "0", "desc": "同类型内排序号"},
|
||||
{"name": "name", "type": "str", "len": 200, "notnull": true, "desc": "条目名称"},
|
||||
{"name": "spec", "type": "json", "desc": "条目规格(对应子对象 spec 字段默认值)"},
|
||||
{"name": "created_by", "type": "str", "len": 32, "desc": "创建人"},
|
||||
{"name": "created_at", "type": "datetime", "desc": "创建时间"},
|
||||
{"name": "updated_by", "type": "str", "len": 32, "desc": "最后修改人"},
|
||||
{"name": "updated_at", "type": "datetime", "desc": "最后修改时间"},
|
||||
{"name": "deleted", "type": "int", "notnull": true, "default": "0", "desc": "软删除标记 0正常 1已删除"}
|
||||
],
|
||||
"indexes": [
|
||||
{"name": "idx_pbl_bpti_tenant_tpl", "fields": ["tenant_id", "template_id", "obj_type", "seq"], "unique": false},
|
||||
{"name": "idx_pbl_bpti_refkey", "fields": ["tenant_id", "template_id", "ref_key"], "unique": false}
|
||||
],
|
||||
"codes": {
|
||||
"obj_type": [["task", "任务"], ["mission", "关卡"], ["role", "角色"], ["learning_goal", "学习目标"], ["evidence_spec", "证据规格"], ["artifact_spec", "产出物规格"], ["reflection_spec", "反思规格"]],
|
||||
"deleted": [["0", "正常"], ["1", "已删除"]]
|
||||
}
|
||||
}
|
||||
@ -1,28 +1,139 @@
|
||||
{
|
||||
"summary": "PBL 蓝图版本快照表(M1a)。一行=蓝图某版本的完整快照 + 相对上一版本的变更增量 change_delta,支撑版本回溯/diff/回滚。snapshot 存全量子对象树,change_delta 存 added/updated/removed 三段。tenant_id 强制打头。",
|
||||
"fields": [
|
||||
{"name": "id", "type": "str", "len": 32, "primary": true, "notnull": true, "desc": "主键,32位无横线UUID"},
|
||||
{"name": "tenant_id", "type": "str", "len": 32, "notnull": true, "desc": "租户ID,强制打头"},
|
||||
{"name": "blueprint_id", "type": "str", "len": 32, "notnull": true, "desc": "所属蓝图ID"},
|
||||
{"name": "version_no", "type": "int", "notnull": true, "desc": "版本号,蓝图内递增"},
|
||||
{"name": "snapshot", "type": "json", "notnull": true, "desc": "该版本蓝图全量快照(主表字段+7类子对象树)"},
|
||||
{"name": "change_delta", "type": "json", "desc": "相对上一版本变更增量 {added:[],updated:[],removed:[]}"},
|
||||
{"name": "quality_level", "type": "str", "len": 16, "desc": "该版本质量等级快照 L1-L5"},
|
||||
{"name": "status", "type": "str", "len": 32, "notnull": true, "default": "saved", "desc": "版本状态,见 codes.status"},
|
||||
{"name": "remark", "type": "str", "len": 500, "desc": "版本说明"},
|
||||
{"name": "created_by", "type": "str", "len": 32, "desc": "创建人"},
|
||||
{"name": "created_at", "type": "datetime", "desc": "创建时间"},
|
||||
{"name": "updated_by", "type": "str", "len": 32, "desc": "最后修改人"},
|
||||
{"name": "updated_at", "type": "datetime", "desc": "最后修改时间"},
|
||||
{"name": "deleted", "type": "int", "notnull": true, "default": "0", "desc": "软删除标记 0正常 1已删除"}
|
||||
],
|
||||
"indexes": [
|
||||
{"name": "uk_pbl_bpv_tenant_bp_ver", "fields": ["tenant_id", "blueprint_id", "version_no"], "unique": true},
|
||||
{"name": "idx_pbl_bpv_tenant_status", "fields": ["tenant_id", "status", "deleted"], "unique": false}
|
||||
"summary": "蓝图版本表:每次 save_version 生成一个版本快照(snapshot_json 存整棵树),支持版本回溯、change_delta 对比与回滚。",
|
||||
"primary": [
|
||||
"id"
|
||||
],
|
||||
"fields": {
|
||||
"id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"summary": "主键ID(str32,UUID去横线)"
|
||||
},
|
||||
"tenant_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"summary": "租户ID(首业务字段,所有读写强制打头,缺失即 fail-closed 拒绝)"
|
||||
},
|
||||
"blueprint_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"summary": "所属蓝图ID"
|
||||
},
|
||||
"version_no": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"summary": "版本号(蓝图内递增)"
|
||||
},
|
||||
"version_type": {
|
||||
"type": "str",
|
||||
"size": 16,
|
||||
"notnull": true,
|
||||
"default": "draft",
|
||||
"summary": "版本类型"
|
||||
},
|
||||
"change_summary": {
|
||||
"type": "str",
|
||||
"size": 512,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"summary": "变更说明"
|
||||
},
|
||||
"snapshot_json": {
|
||||
"type": "text",
|
||||
"default": "",
|
||||
"summary": "蓝图整树快照JSON(主表字段+节点+边)"
|
||||
},
|
||||
"node_count": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"summary": "快照节点数"
|
||||
},
|
||||
"edge_count": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"summary": "快照边数"
|
||||
},
|
||||
"quality_status": {
|
||||
"type": "str",
|
||||
"size": 16,
|
||||
"notnull": true,
|
||||
"default": "q0",
|
||||
"summary": "该版本质量状态"
|
||||
},
|
||||
"score_amount": {
|
||||
"type": "double",
|
||||
"size": [
|
||||
18,
|
||||
2
|
||||
],
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"summary": "该版本评估得分(double(18,2),M6 回写)"
|
||||
},
|
||||
"create_user": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"default": "",
|
||||
"summary": "创建人"
|
||||
},
|
||||
"create_time": {
|
||||
"type": "datetime",
|
||||
"summary": "创建时间"
|
||||
},
|
||||
"deleted": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"summary": "逻辑删除标记 0正常 1删除"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"uk_pbl_version": {
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"blueprint_id",
|
||||
"version_no"
|
||||
],
|
||||
"unique": true,
|
||||
"summary": "蓝图内版本号唯一"
|
||||
},
|
||||
"idx_pbl_version_bp": {
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"blueprint_id",
|
||||
"deleted",
|
||||
"version_no"
|
||||
],
|
||||
"unique": false,
|
||||
"summary": "版本列表(倒序取最新)"
|
||||
}
|
||||
},
|
||||
"codes": {
|
||||
"status": [["saved", "已保存"], ["published", "已发布"], ["rolled_back", "已回滚"], ["archived", "已归档"]],
|
||||
"quality_level": [["L1", "L1-初始"], ["L2", "L2-基本完整"], ["L3", "L3-结构合格"], ["L4", "L4-可运行"], ["L5", "L5-优质"]],
|
||||
"deleted": [["0", "正常"], ["1", "已删除"]]
|
||||
"version_type": {
|
||||
"summary": "版本类型",
|
||||
"items": {
|
||||
"draft": "草稿版本",
|
||||
"minor": "小版本",
|
||||
"major": "大版本",
|
||||
"publish": "发布版本",
|
||||
"rollback": "回滚版本"
|
||||
}
|
||||
},
|
||||
"quality_status": {
|
||||
"summary": "质量状态(5 级,M2 校验引擎回写)",
|
||||
"items": {
|
||||
"q0": "未校验",
|
||||
"q1": "不合格",
|
||||
"q2": "基本合格",
|
||||
"q3": "合格",
|
||||
"q4": "优秀",
|
||||
"q5": "标杆"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
139
pbl_blueprint/models/pbl_blueprint_version_delta.json
Normal file
139
pbl_blueprint/models/pbl_blueprint_version_delta.json
Normal file
@ -0,0 +1,139 @@
|
||||
{
|
||||
"summary": "蓝图版本差异表:记录两个版本之间的 change_delta(新增/修改/删除的节点、边与主表字段),供版本对比与回滚审计。",
|
||||
"primary": [
|
||||
"id"
|
||||
],
|
||||
"fields": {
|
||||
"id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"summary": "主键ID(str32,UUID去横线)"
|
||||
},
|
||||
"tenant_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"summary": "租户ID(首业务字段,所有读写强制打头,缺失即 fail-closed 拒绝)"
|
||||
},
|
||||
"blueprint_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"summary": "所属蓝图ID"
|
||||
},
|
||||
"from_version": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"summary": "基准版本号"
|
||||
},
|
||||
"to_version": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"summary": "目标版本号"
|
||||
},
|
||||
"delta_type": {
|
||||
"type": "str",
|
||||
"size": 16,
|
||||
"notnull": true,
|
||||
"summary": "差异对象类型"
|
||||
},
|
||||
"op_type": {
|
||||
"type": "str",
|
||||
"size": 16,
|
||||
"notnull": true,
|
||||
"summary": "操作类型"
|
||||
},
|
||||
"target_id": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"summary": "差异对象ID(节点ID/边ID/蓝图ID)"
|
||||
},
|
||||
"target_name": {
|
||||
"type": "str",
|
||||
"size": 128,
|
||||
"notnull": true,
|
||||
"default": "",
|
||||
"summary": "差异对象名称(冗余便于展示)"
|
||||
},
|
||||
"before_json": {
|
||||
"type": "text",
|
||||
"default": "",
|
||||
"summary": "变更前JSON"
|
||||
},
|
||||
"after_json": {
|
||||
"type": "text",
|
||||
"default": "",
|
||||
"summary": "变更后JSON"
|
||||
},
|
||||
"sort_no": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"summary": "排序号"
|
||||
},
|
||||
"create_user": {
|
||||
"type": "str",
|
||||
"size": 32,
|
||||
"default": "",
|
||||
"summary": "创建人"
|
||||
},
|
||||
"create_time": {
|
||||
"type": "datetime",
|
||||
"summary": "创建时间"
|
||||
},
|
||||
"deleted": {
|
||||
"type": "int",
|
||||
"notnull": true,
|
||||
"default": 0,
|
||||
"summary": "逻辑删除标记 0正常 1删除"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"uk_pbl_delta": {
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"blueprint_id",
|
||||
"from_version",
|
||||
"to_version",
|
||||
"delta_type",
|
||||
"op_type",
|
||||
"target_id"
|
||||
],
|
||||
"unique": true,
|
||||
"summary": "同一版本对同一对象同一操作唯一,保证幂等写入"
|
||||
},
|
||||
"idx_pbl_delta_bp": {
|
||||
"fields": [
|
||||
"tenant_id",
|
||||
"blueprint_id",
|
||||
"deleted",
|
||||
"to_version"
|
||||
],
|
||||
"unique": false,
|
||||
"summary": "按蓝图+版本查差异"
|
||||
}
|
||||
},
|
||||
"codes": {
|
||||
"delta_type": {
|
||||
"summary": "差异对象类型",
|
||||
"items": {
|
||||
"blueprint": "蓝图主表字段",
|
||||
"node": "节点",
|
||||
"edge": "边"
|
||||
}
|
||||
},
|
||||
"op_type": {
|
||||
"summary": "操作类型",
|
||||
"items": {
|
||||
"add": "新增",
|
||||
"update": "修改",
|
||||
"delete": "删除"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1446
pbl_blueprint/service.py
Normal file
1446
pbl_blueprint/service.py
Normal file
File diff suppressed because it is too large
Load Diff
100
pbl_blueprint/tenant.py
Normal file
100
pbl_blueprint/tenant.py
Normal file
@ -0,0 +1,100 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""租户上下文(M1a):tenant_id 强制打头 + fail-closed 校验。
|
||||
|
||||
所有对外契约函数第一步必须 require_tenant(...);所有 SQL 条件第一个必须是 tenant_id=?。
|
||||
禁止任何“默认租户/全局租户”兜底——缺失即拒绝。
|
||||
|
||||
tenant_where() 返回 (TenantWhere, args):TenantWhere 是 str 子类并携带绑定参数,
|
||||
即使调用方漏传 args,db 层也能从片段上取回参数(防“有占位符无参数”的漏绑定缺陷)。
|
||||
"""
|
||||
|
||||
from .errors import ERR_TENANT_MISSING, ERR_FORBIDDEN, PblBlueprintError
|
||||
|
||||
MAX_TENANT_LEN = 32
|
||||
|
||||
|
||||
class TenantWhere(str):
|
||||
"""WHERE 片段字符串 + 绑定参数(不可变,随片段一起传递)。"""
|
||||
|
||||
__slots__ = ("args",)
|
||||
|
||||
def __new__(cls, sql, args=None):
|
||||
obj = str.__new__(cls, sql or "")
|
||||
obj.args = list(args or [])
|
||||
return obj
|
||||
|
||||
def __repr__(self):
|
||||
return "TenantWhere(%r, args=%r)" % (str(self), self.args)
|
||||
|
||||
|
||||
def _clean(v):
|
||||
if v is None:
|
||||
return ""
|
||||
if isinstance(v, bytes):
|
||||
try:
|
||||
v = v.decode("utf-8")
|
||||
except Exception:
|
||||
return ""
|
||||
return str(v).strip()
|
||||
|
||||
|
||||
def require_tenant(tenant_id, ctx_tenant_id=None):
|
||||
"""校验并返回规范化 tenant_id。
|
||||
|
||||
:param tenant_id: 调用方传入的租户ID(必填,非空,<=32 字符,不含通配符)
|
||||
:param ctx_tenant_id: 会话上下文租户ID(可选);给了就必须一致,否则判越权
|
||||
:raises PblBlueprintError: ERR_TENANT_MISSING / ERR_FORBIDDEN
|
||||
"""
|
||||
tid = _clean(tenant_id)
|
||||
if not tid:
|
||||
raise PblBlueprintError(ERR_TENANT_MISSING)
|
||||
if len(tid) > MAX_TENANT_LEN:
|
||||
raise PblBlueprintError(ERR_TENANT_MISSING, "tenant_id 超长(>%d)" % MAX_TENANT_LEN)
|
||||
if "*" in tid or "%" in tid:
|
||||
raise PblBlueprintError(ERR_TENANT_MISSING, "tenant_id 含通配符,拒绝")
|
||||
if "'" in tid or ";" in tid or "--" in tid:
|
||||
raise PblBlueprintError(ERR_TENANT_MISSING, "tenant_id 含非法字符,拒绝")
|
||||
if ctx_tenant_id is not None:
|
||||
ctx = _clean(ctx_tenant_id)
|
||||
if ctx and ctx != tid:
|
||||
raise PblBlueprintError(ERR_FORBIDDEN, "tenant_id 与会话上下文不一致")
|
||||
return tid
|
||||
|
||||
|
||||
def tenant_where(tenant_id, extra_sql="", extra_args=None):
|
||||
"""生成 tenant_id 打头的 WHERE 片段。
|
||||
|
||||
返回 (TenantWhere, args),片段一定以 `tenant_id=?` 开头,杜绝漏租户过滤。
|
||||
"""
|
||||
tid = require_tenant(tenant_id)
|
||||
sql = "tenant_id=?"
|
||||
args = [tid]
|
||||
if extra_sql:
|
||||
sql = sql + " and (" + extra_sql + ")"
|
||||
if extra_args:
|
||||
args.extend(list(extra_args))
|
||||
return TenantWhere(sql, args), args
|
||||
|
||||
|
||||
def inject_tenant(tenant_id, data, overwrite=True):
|
||||
"""把 tenant_id 注入待写入 dict 的首位(保持字段顺序:tenant_id 打头)。"""
|
||||
tid = require_tenant(tenant_id)
|
||||
src = dict(data or {})
|
||||
if not overwrite and src.get("tenant_id") and _clean(src.get("tenant_id")) != tid:
|
||||
raise PblBlueprintError(ERR_FORBIDDEN, "data.tenant_id 与上下文不一致")
|
||||
out = {"tenant_id": tid}
|
||||
for k, v in src.items():
|
||||
if k == "tenant_id":
|
||||
continue
|
||||
out[k] = v
|
||||
return out
|
||||
|
||||
|
||||
def assert_tenant_first(fields):
|
||||
"""校验字段序列中 tenant_id 是首个业务字段(紧跟主键 id 之后)。"""
|
||||
seq = [str(f) for f in (fields or [])]
|
||||
if not seq:
|
||||
return False
|
||||
if seq[0] == "id":
|
||||
return len(seq) > 1 and seq[1] == "tenant_id"
|
||||
return seq[0] == "tenant_id"
|
||||
@ -5,27 +5,16 @@ build-backend = "setuptools.build_meta"
|
||||
[project]
|
||||
name = "pbl_blueprint"
|
||||
version = "1.0.0"
|
||||
description = "PBL 蓝图聚合根与子对象、版本、模板模块(M1a)——蓝图 CRUD/树/fork、7 类子对象泛化契约、版本 change_delta、模板实例化与离线兜底;tenant_id 强制打头"
|
||||
description = "PBL 蓝图聚合根与子对象、版本、模板(M1a)——蓝图 CRUD/树/fork、7 类子对象泛化契约、版本 change_delta、模板实例化与离线兜底"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.8"
|
||||
license = { text = "Proprietary" }
|
||||
authors = [{ name = "sdlc agent.develop" }]
|
||||
keywords = ["pbl", "blueprint", "sage", "module"]
|
||||
authors = [{ name = "agent.develop" }]
|
||||
dependencies = []
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = ["pytest>=7.0"]
|
||||
|
||||
[tool.setuptools]
|
||||
packages = ["pbl_blueprint"]
|
||||
include-package-data = true
|
||||
|
||||
[tool.setuptools.package-data]
|
||||
pbl_blueprint = [
|
||||
"json/*.json",
|
||||
"models/*.json",
|
||||
"sql/*.sql",
|
||||
"wwwroot/*.ui",
|
||||
"init/*.json",
|
||||
"skill/*.md",
|
||||
]
|
||||
pbl_blueprint = ["models/*.json", "json/*.json", "skill/*.md"]
|
||||
|
||||
773
scripts/gen_defs.py
Normal file
773
scripts/gen_defs.py
Normal file
@ -0,0 +1,773 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""pbl_blueprint 表定义/CRUD定义生成器(M1a)。
|
||||
|
||||
单一事实源:本文件的 TABLES 声明 -> 生成
|
||||
pbl_blueprint/models/{tbl}.json 四段式表定义(summary/fields/indexes/codes)
|
||||
pbl_blueprint/json/{tbl}.json CRUD 定义(tblname/params/editable/browserfields)
|
||||
|
||||
用法(机构工作空间根目录):
|
||||
python3 modules/pbl_blueprint/scripts/gen_defs.py # 生成 + 清理陈旧定义
|
||||
python3 modules/pbl_blueprint/scripts/gen_defs.py --clean # 仅清理非 11 表的陈旧文件
|
||||
python3 modules/pbl_blueprint/scripts/gen_defs.py --dump-ddl # 打印建表 DDL
|
||||
|
||||
约定(QC 逐条核验点):
|
||||
* primary 恒为 ["id"];id 为 str(32) notnull
|
||||
* tenant_id 为首业务字段(紧跟 id),str(32) notnull
|
||||
* 金额/分值字段一律 double(18,2)
|
||||
* 所有索引 tenant_id 打头
|
||||
* CRUD 定义根键仅 tblname/params/editable/browserfields,editable 为 3 个 api/*.dspy
|
||||
"""
|
||||
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
MODULE_ROOT = os.path.dirname(HERE)
|
||||
PKG = os.path.join(MODULE_ROOT, "pbl_blueprint")
|
||||
MODEL_DIR = os.path.join(PKG, "models")
|
||||
JSON_DIR = os.path.join(PKG, "json")
|
||||
|
||||
TABLE_ORDER = [
|
||||
"pbl_blueprint",
|
||||
"pbl_blueprint_node",
|
||||
"pbl_blueprint_edge",
|
||||
"pbl_blueprint_version",
|
||||
"pbl_blueprint_version_delta",
|
||||
"pbl_blueprint_template",
|
||||
"pbl_blueprint_publish",
|
||||
"pbl_blueprint_offline",
|
||||
"pbl_blueprint_fork",
|
||||
"pbl_blueprint_lock",
|
||||
"pbl_blueprint_audit",
|
||||
]
|
||||
|
||||
AUDIT_FIELDS = ("create_user", "create_time", "update_user", "update_time")
|
||||
IMMUTABLE_FIELDS = AUDIT_FIELDS + ("deleted",)
|
||||
# 服务端写入的大字段:CRUD 定义里 editable=false(前端只读)
|
||||
SERVER_BLOB_FIELDS = ("snapshot_json", "pkg_json", "before_json", "after_json",
|
||||
"game_def_json", "checksum", "file_size", "use_count",
|
||||
"node_count", "edge_count", "current_version", "rev",
|
||||
"publish_time", "expire_time", "cost_ms", "err_code")
|
||||
|
||||
QUALITY_CODE = {
|
||||
"summary": "质量状态(5 级,M2 校验引擎回写)",
|
||||
"items": {
|
||||
"q0": "未校验",
|
||||
"q1": "不合格",
|
||||
"q2": "基本合格",
|
||||
"q3": "合格",
|
||||
"q4": "优秀",
|
||||
"q5": "标杆",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def F(name, typ, size=None, nn=False, default=None, summary=""):
|
||||
"""字段声明。"""
|
||||
spec = {"type": typ}
|
||||
if size is not None:
|
||||
spec["size"] = size
|
||||
if nn:
|
||||
spec["notnull"] = True
|
||||
if default is not None:
|
||||
spec["default"] = default
|
||||
spec["summary"] = summary
|
||||
return (name, spec)
|
||||
|
||||
|
||||
def ID():
|
||||
return F("id", "str", 32, True, None, "主键ID(str32,UUID去横线)")
|
||||
|
||||
|
||||
def TENANT(extra=""):
|
||||
return F("tenant_id", "str", 32, True, None,
|
||||
"租户ID(首业务字段,所有读写强制打头,缺失即 fail-closed 拒绝)" + extra)
|
||||
|
||||
|
||||
def DELETED():
|
||||
return F("deleted", "int", None, True, 0, "逻辑删除标记 0正常 1删除")
|
||||
|
||||
|
||||
def IDX(name, fields, unique, summary):
|
||||
return (name, {"fields": list(fields), "unique": bool(unique), "summary": summary})
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ 11 表定义
|
||||
TABLES = {}
|
||||
|
||||
TABLES["pbl_blueprint"] = {
|
||||
"summary": "PBL 蓝图聚合根表:一个租户下的一份项目式学习蓝图(学科/年级/学段/状态/质量状态/当前版本/预算)。所有读写 tenant_id 强制打头。",
|
||||
"fields": [
|
||||
ID(), TENANT(),
|
||||
F("code", "str", 64, True, None, "蓝图编码(租户内唯一,PBL-BP-序号)"),
|
||||
F("name", "str", 128, True, None, "蓝图名称"),
|
||||
F("subject", "str", 64, False, "", "学科"),
|
||||
F("grade", "str", 32, False, "", "年级"),
|
||||
F("phase", "str", 32, False, "", "学段"),
|
||||
F("status", "str", 16, True, "draft", "蓝图状态"),
|
||||
F("quality_status", "str", 16, True, "q0", "质量状态(5 级)"),
|
||||
F("source", "str", 16, True, "manual", "来源方式"),
|
||||
F("source_id", "str", 32, False, "", "来源对象ID(模板ID或父蓝图ID)"),
|
||||
F("owner_id", "str", 32, False, "", "负责人(教师)ID"),
|
||||
F("class_id", "str", 32, False, "", "关联班级ID"),
|
||||
F("current_version", "int", None, True, 0, "当前版本号(save_version 回写)"),
|
||||
F("duration_hours", "int", None, True, 0, "预计课时数"),
|
||||
F("budget_amount", "double", [18, 2], True, 0, "预算金额(double(18,2))"),
|
||||
F("summary", "str", 512, False, "", "蓝图简介"),
|
||||
F("ext_json", "text", None, False, "", "扩展属性JSON"),
|
||||
F("publish_time", "datetime", None, False, None, "最近发布时间"),
|
||||
F("create_user", "str", 32, False, "", "创建人"),
|
||||
F("create_time", "datetime", None, False, None, "创建时间"),
|
||||
F("update_user", "str", 32, False, "", "更新人"),
|
||||
F("update_time", "datetime", None, False, None, "更新时间"),
|
||||
DELETED(),
|
||||
],
|
||||
"indexes": [
|
||||
IDX("uk_pbl_blueprint_code", ["tenant_id", "code"], True, "租户内蓝图编码唯一"),
|
||||
IDX("idx_pbl_blueprint_tenant", ["tenant_id", "deleted", "status"], False, "租户蓝图列表主索引(tenant_id 打头)"),
|
||||
IDX("idx_pbl_blueprint_owner", ["tenant_id", "owner_id"], False, "按负责人查蓝图"),
|
||||
IDX("idx_pbl_blueprint_source", ["tenant_id", "source", "source_id"], False, "按来源(模板/fork)追溯"),
|
||||
IDX("idx_pbl_blueprint_class", ["tenant_id", "class_id"], False, "按班级查蓝图"),
|
||||
],
|
||||
"codes": {
|
||||
"status": {
|
||||
"summary": "蓝图状态",
|
||||
"items": {
|
||||
"draft": "草稿",
|
||||
"validating": "校验中",
|
||||
"ready": "就绪",
|
||||
"published": "已发布",
|
||||
"archived": "已归档",
|
||||
"disabled": "已停用",
|
||||
},
|
||||
},
|
||||
"quality_status": QUALITY_CODE,
|
||||
"source": {
|
||||
"summary": "来源方式",
|
||||
"items": {
|
||||
"manual": "手工创建",
|
||||
"template": "模板实例化",
|
||||
"fork": "蓝图fork派生",
|
||||
"agent": "Designer Agent 生成",
|
||||
"import": "离线包导入",
|
||||
},
|
||||
},
|
||||
},
|
||||
"browserfields": ["code", "name", "subject", "status", "quality_status", "owner_id", "update_time"],
|
||||
"queryfields": ["code", "name", "subject", "grade", "phase", "status", "quality_status",
|
||||
"source_id", "owner_id", "class_id"],
|
||||
}
|
||||
|
||||
TABLES["pbl_blueprint_node"] = {
|
||||
"summary": "蓝图子对象节点表(7 类子对象泛化契约):role/task/rule/artifact/scene/assessment/resource 统一存一张表,差异载荷放 spec_json,按 node_type 分派校验器(M2)。",
|
||||
"fields": [
|
||||
ID(), TENANT(),
|
||||
F("blueprint_id", "str", 32, True, None, "所属蓝图ID"),
|
||||
F("node_type", "str", 16, True, None, "子对象类型(7 类泛化)"),
|
||||
F("parent_id", "str", 32, True, "", "父节点ID(构成蓝图树,空串=根节点)"),
|
||||
F("code", "str", 64, True, "", "节点编码(蓝图内同类型唯一)"),
|
||||
F("name", "str", 128, True, None, "节点名称"),
|
||||
F("sort_no", "int", None, True, 0, "同级排序号"),
|
||||
F("spec_json", "text", None, False, "", "泛化载荷JSON(结构随 node_type 不同)"),
|
||||
F("ref_module", "str", 32, True, "", "引用模块名(scense/world/assessment 等)"),
|
||||
F("ref_id", "str", 32, True, "", "引用对象ID"),
|
||||
F("status", "str", 16, True, "active", "节点状态"),
|
||||
F("required", "int", None, True, 0, "是否必需节点 0否 1是"),
|
||||
F("weight", "double", [18, 2], True, 0, "权重/分值(double(18,2))"),
|
||||
F("create_user", "str", 32, False, "", "创建人"),
|
||||
F("create_time", "datetime", None, False, None, "创建时间"),
|
||||
F("update_user", "str", 32, False, "", "更新人"),
|
||||
F("update_time", "datetime", None, False, None, "更新时间"),
|
||||
DELETED(),
|
||||
],
|
||||
"indexes": [
|
||||
IDX("uk_pbl_node_code", ["tenant_id", "blueprint_id", "node_type", "code"], True, "蓝图内同类型节点编码唯一"),
|
||||
IDX("idx_pbl_node_bp", ["tenant_id", "blueprint_id", "deleted", "node_type"], False, "按蓝图+类型列节点(tenant_id 打头)"),
|
||||
IDX("idx_pbl_node_parent", ["tenant_id", "blueprint_id", "parent_id", "sort_no"], False, "构树查询"),
|
||||
IDX("idx_pbl_node_ref", ["tenant_id", "ref_module", "ref_id"], False, "按引用对象反查节点"),
|
||||
],
|
||||
"codes": {
|
||||
"node_type": {
|
||||
"summary": "子对象类型(7 类泛化契约)",
|
||||
"items": {
|
||||
"role": "角色",
|
||||
"task": "任务",
|
||||
"rule": "规则",
|
||||
"artifact": "产出物",
|
||||
"scene": "场景引用",
|
||||
"assessment": "评估项",
|
||||
"resource": "资源",
|
||||
},
|
||||
},
|
||||
"status": {
|
||||
"summary": "节点状态",
|
||||
"items": {
|
||||
"active": "生效",
|
||||
"draft": "草稿",
|
||||
"invalid": "校验不通过",
|
||||
"disabled": "停用",
|
||||
},
|
||||
},
|
||||
},
|
||||
"browserfields": ["code", "name", "node_type", "parent_id", "sort_no", "status"],
|
||||
"queryfields": ["blueprint_id", "node_type", "parent_id", "code", "name",
|
||||
"ref_module", "ref_id", "status"],
|
||||
}
|
||||
|
||||
TABLES["pbl_blueprint_edge"] = {
|
||||
"summary": "蓝图节点关系边表:描述子对象之间的依赖/解锁/产出/消耗/评估/包含关系,供 Compiler(M3)编译为 Game Definition 图结构。",
|
||||
"fields": [
|
||||
ID(), TENANT(),
|
||||
F("blueprint_id", "str", 32, True, None, "所属蓝图ID"),
|
||||
F("from_node_id", "str", 32, True, None, "起点节点ID"),
|
||||
F("to_node_id", "str", 32, True, None, "终点节点ID"),
|
||||
F("edge_type", "str", 16, True, "depends", "边类型"),
|
||||
F("weight", "double", [18, 2], True, 0, "边权重(double(18,2))"),
|
||||
F("condition_json", "text", None, False, "", "触发条件JSON"),
|
||||
F("sort_no", "int", None, True, 0, "排序号"),
|
||||
F("create_user", "str", 32, False, "", "创建人"),
|
||||
F("create_time", "datetime", None, False, None, "创建时间"),
|
||||
F("update_time", "datetime", None, False, None, "更新时间"),
|
||||
DELETED(),
|
||||
],
|
||||
"indexes": [
|
||||
IDX("uk_pbl_edge", ["tenant_id", "blueprint_id", "from_node_id", "to_node_id", "edge_type"], True, "同类型边唯一,防重复连边"),
|
||||
IDX("idx_pbl_edge_bp", ["tenant_id", "blueprint_id", "deleted"], False, "按蓝图取全部边"),
|
||||
IDX("idx_pbl_edge_from", ["tenant_id", "from_node_id"], False, "出边查询"),
|
||||
IDX("idx_pbl_edge_to", ["tenant_id", "to_node_id"], False, "入边查询(删节点前校验)"),
|
||||
],
|
||||
"codes": {
|
||||
"edge_type": {
|
||||
"summary": "边类型",
|
||||
"items": {
|
||||
"depends": "依赖",
|
||||
"unlocks": "解锁",
|
||||
"produces": "产出",
|
||||
"consumes": "消耗",
|
||||
"assesses": "评估",
|
||||
"contains": "包含",
|
||||
},
|
||||
},
|
||||
},
|
||||
"browserfields": ["from_node_id", "to_node_id", "edge_type", "weight", "sort_no"],
|
||||
"queryfields": ["blueprint_id", "from_node_id", "to_node_id", "edge_type"],
|
||||
}
|
||||
|
||||
TABLES["pbl_blueprint_version"] = {
|
||||
"summary": "蓝图版本表:每次 save_version 生成一个版本快照(snapshot_json 存整棵树),支持版本回溯、change_delta 对比与回滚。",
|
||||
"fields": [
|
||||
ID(), TENANT(),
|
||||
F("blueprint_id", "str", 32, True, None, "所属蓝图ID"),
|
||||
F("version_no", "int", None, True, None, "版本号(蓝图内递增)"),
|
||||
F("version_type", "str", 16, True, "draft", "版本类型"),
|
||||
F("change_summary", "str", 512, True, "", "变更说明"),
|
||||
F("snapshot_json", "text", None, False, "", "蓝图整树快照JSON(主表字段+节点+边)"),
|
||||
F("node_count", "int", None, True, 0, "快照节点数"),
|
||||
F("edge_count", "int", None, True, 0, "快照边数"),
|
||||
F("quality_status", "str", 16, True, "q0", "该版本质量状态"),
|
||||
F("score_amount", "double", [18, 2], True, 0, "该版本评估得分(double(18,2),M6 回写)"),
|
||||
F("create_user", "str", 32, False, "", "创建人"),
|
||||
F("create_time", "datetime", None, False, None, "创建时间"),
|
||||
DELETED(),
|
||||
],
|
||||
"indexes": [
|
||||
IDX("uk_pbl_version", ["tenant_id", "blueprint_id", "version_no"], True, "蓝图内版本号唯一"),
|
||||
IDX("idx_pbl_version_bp", ["tenant_id", "blueprint_id", "deleted", "version_no"], False, "版本列表(倒序取最新)"),
|
||||
],
|
||||
"codes": {
|
||||
"version_type": {
|
||||
"summary": "版本类型",
|
||||
"items": {
|
||||
"draft": "草稿版本",
|
||||
"minor": "小版本",
|
||||
"major": "大版本",
|
||||
"publish": "发布版本",
|
||||
"rollback": "回滚版本",
|
||||
},
|
||||
},
|
||||
"quality_status": QUALITY_CODE,
|
||||
},
|
||||
"browserfields": ["version_no", "version_type", "change_summary", "node_count",
|
||||
"edge_count", "quality_status", "create_time"],
|
||||
"queryfields": ["blueprint_id", "version_no", "version_type", "quality_status"],
|
||||
}
|
||||
|
||||
TABLES["pbl_blueprint_version_delta"] = {
|
||||
"summary": "蓝图版本差异表:记录两个版本之间的 change_delta(新增/修改/删除的节点、边与主表字段),供版本对比与回滚审计。",
|
||||
"fields": [
|
||||
ID(), TENANT(),
|
||||
F("blueprint_id", "str", 32, True, None, "所属蓝图ID"),
|
||||
F("from_version", "int", None, True, 0, "基准版本号"),
|
||||
F("to_version", "int", None, True, 0, "目标版本号"),
|
||||
F("delta_type", "str", 16, True, None, "差异对象类型"),
|
||||
F("op_type", "str", 16, True, None, "操作类型"),
|
||||
F("target_id", "str", 32, True, "", "差异对象ID(节点ID/边ID/蓝图ID)"),
|
||||
F("target_name", "str", 128, True, "", "差异对象名称(冗余便于展示)"),
|
||||
F("before_json", "text", None, False, "", "变更前JSON"),
|
||||
F("after_json", "text", None, False, "", "变更后JSON"),
|
||||
F("sort_no", "int", None, True, 0, "排序号"),
|
||||
F("create_user", "str", 32, False, "", "创建人"),
|
||||
F("create_time", "datetime", None, False, None, "创建时间"),
|
||||
DELETED(),
|
||||
],
|
||||
"indexes": [
|
||||
IDX("uk_pbl_delta", ["tenant_id", "blueprint_id", "from_version", "to_version",
|
||||
"delta_type", "op_type", "target_id"], True,
|
||||
"同一版本对同一对象同一操作唯一,保证幂等写入"),
|
||||
IDX("idx_pbl_delta_bp", ["tenant_id", "blueprint_id", "deleted", "to_version"], False, "按蓝图+版本查差异"),
|
||||
],
|
||||
"codes": {
|
||||
"delta_type": {
|
||||
"summary": "差异对象类型",
|
||||
"items": {"blueprint": "蓝图主表字段", "node": "节点", "edge": "边"},
|
||||
},
|
||||
"op_type": {
|
||||
"summary": "操作类型",
|
||||
"items": {"add": "新增", "update": "修改", "delete": "删除"},
|
||||
},
|
||||
},
|
||||
"browserfields": ["from_version", "to_version", "delta_type", "op_type", "target_name"],
|
||||
"queryfields": ["blueprint_id", "from_version", "to_version", "delta_type",
|
||||
"op_type", "target_id"],
|
||||
}
|
||||
|
||||
TABLES["pbl_blueprint_template"] = {
|
||||
"summary": "蓝图模板表:可复用的蓝图骨架(payload_json 存模板树),支持实例化为新蓝图;离线兜底时作为本地模板源。",
|
||||
"fields": [
|
||||
ID(), TENANT(";空串=平台公共模板"),
|
||||
F("code", "str", 64, True, None, "模板编码(租户内唯一)"),
|
||||
F("name", "str", 128, True, None, "模板名称"),
|
||||
F("category", "str", 32, True, "general", "模板分类"),
|
||||
F("subject", "str", 64, True, "", "适用学科"),
|
||||
F("grade", "str", 32, True, "", "适用年级"),
|
||||
F("scope", "str", 16, True, "tenant", "可见范围"),
|
||||
F("payload_json", "text", None, False, "", "模板树载荷JSON(blueprint+nodes+edges)"),
|
||||
F("node_count", "int", None, True, 0, "模板节点数"),
|
||||
F("edge_count", "int", None, True, 0, "模板边数"),
|
||||
F("price_amount", "double", [18, 2], True, 0, "模板定价金额(double(18,2),0=免费)"),
|
||||
F("use_count", "int", None, True, 0, "被实例化次数"),
|
||||
F("status", "str", 16, True, "enabled", "模板状态"),
|
||||
F("summary", "str", 512, True, "", "模板说明"),
|
||||
F("create_user", "str", 32, False, "", "创建人"),
|
||||
F("create_time", "datetime", None, False, None, "创建时间"),
|
||||
F("update_user", "str", 32, False, "", "更新人"),
|
||||
F("update_time", "datetime", None, False, None, "更新时间"),
|
||||
DELETED(),
|
||||
],
|
||||
"indexes": [
|
||||
IDX("uk_pbl_template_code", ["tenant_id", "code"], True, "租户内模板编码唯一"),
|
||||
IDX("idx_pbl_template_list", ["tenant_id", "deleted", "status", "category"], False, "模板列表查询(tenant_id 打头)"),
|
||||
IDX("idx_pbl_template_subject", ["tenant_id", "subject", "grade"], False, "按学科年级筛模板"),
|
||||
],
|
||||
"codes": {
|
||||
"scope": {
|
||||
"summary": "可见范围",
|
||||
"items": {"tenant": "本租户", "platform": "平台公共", "private": "仅创建人"},
|
||||
},
|
||||
"status": {
|
||||
"summary": "模板状态",
|
||||
"items": {"enabled": "启用", "disabled": "停用", "draft": "草稿"},
|
||||
},
|
||||
"category": {
|
||||
"summary": "模板分类",
|
||||
"items": {
|
||||
"stem": "STEM",
|
||||
"science": "自然科学",
|
||||
"humanity": "人文社科",
|
||||
"art": "艺术",
|
||||
"labor": "劳动实践",
|
||||
"general": "通用",
|
||||
},
|
||||
},
|
||||
},
|
||||
"browserfields": ["code", "name", "category", "subject", "scope", "status", "use_count"],
|
||||
"queryfields": ["code", "name", "category", "subject", "grade", "scope", "status"],
|
||||
}
|
||||
|
||||
TABLES["pbl_blueprint_publish"] = {
|
||||
"summary": "蓝图发布/投放记录表:蓝图某版本投放到班级/团队/学生的一次发布,记录运行时句柄与编译产物,供 M11 runtime_ext 关联。",
|
||||
"fields": [
|
||||
ID(), TENANT(),
|
||||
F("blueprint_id", "str", 32, True, None, "蓝图ID"),
|
||||
F("version_no", "int", None, True, 0, "发布的版本号"),
|
||||
F("class_id", "str", 32, True, "", "投放班级ID"),
|
||||
F("target_type", "str", 16, True, "class", "投放对象类型"),
|
||||
F("target_id", "str", 32, True, "", "投放对象ID"),
|
||||
F("runtime_ref", "str", 64, True, "", "运行时引用(scense_runtime 会话/世界ID)"),
|
||||
F("game_def_json", "text", None, False, "", "编译产物 Game Definition JSON(M3 回写)"),
|
||||
F("start_time", "datetime", None, False, None, "开始时间"),
|
||||
F("end_time", "datetime", None, False, None, "结束时间"),
|
||||
F("budget_amount", "double", [18, 2], True, 0, "本次投放预算金额(double(18,2))"),
|
||||
F("status", "str", 16, True, "published", "投放状态"),
|
||||
F("remark", "str", 512, True, "", "备注"),
|
||||
F("create_user", "str", 32, False, "", "创建人"),
|
||||
F("create_time", "datetime", None, False, None, "创建时间"),
|
||||
F("update_time", "datetime", None, False, None, "更新时间"),
|
||||
DELETED(),
|
||||
],
|
||||
"indexes": [
|
||||
IDX("uk_pbl_publish", ["tenant_id", "blueprint_id", "version_no", "target_type", "target_id"], True, "同版本同对象只投放一次,保证幂等"),
|
||||
IDX("idx_pbl_publish_bp", ["tenant_id", "blueprint_id", "deleted"], False, "按蓝图查投放记录"),
|
||||
IDX("idx_pbl_publish_class", ["tenant_id", "class_id", "status"], False, "按班级查在跑的投放"),
|
||||
],
|
||||
"codes": {
|
||||
"target_type": {
|
||||
"summary": "投放对象类型",
|
||||
"items": {"class": "班级", "team": "团队", "student": "学生", "preview": "预览"},
|
||||
},
|
||||
"status": {
|
||||
"summary": "投放状态",
|
||||
"items": {
|
||||
"published": "已发布",
|
||||
"running": "进行中",
|
||||
"finished": "已结束",
|
||||
"revoked": "已撤回",
|
||||
},
|
||||
},
|
||||
},
|
||||
"browserfields": ["blueprint_id", "version_no", "target_type", "target_id", "status",
|
||||
"start_time", "end_time"],
|
||||
"queryfields": ["blueprint_id", "version_no", "class_id", "target_type", "target_id",
|
||||
"runtime_ref", "status"],
|
||||
}
|
||||
|
||||
TABLES["pbl_blueprint_offline"] = {
|
||||
"summary": "蓝图离线兜底包表:把蓝图/模板打包为自描述 JSON 包(含 schema_version 与 sha256 校验和),网络或服务不可用时本地兜底实例化。",
|
||||
"fields": [
|
||||
ID(), TENANT(),
|
||||
F("blueprint_id", "str", 32, True, "", "来源蓝图ID(与 template_id 二选一)"),
|
||||
F("template_id", "str", 32, True, "", "来源模板ID"),
|
||||
F("pkg_code", "str", 64, True, None, "离线包编码"),
|
||||
F("pkg_name", "str", 128, True, None, "离线包名称"),
|
||||
F("pkg_version", "int", None, True, 1, "离线包版本"),
|
||||
F("pkg_json", "text", None, False, "", "离线包内容JSON(自描述:schema_version+蓝图+节点+边)"),
|
||||
F("file_size", "int", None, True, 0, "包体字节数"),
|
||||
F("checksum", "str", 64, True, "", "内容校验和(sha256)"),
|
||||
F("status", "str", 16, True, "ready", "离线包状态"),
|
||||
F("expire_time", "datetime", None, False, None, "过期时间"),
|
||||
F("create_user", "str", 32, False, "", "创建人"),
|
||||
F("create_time", "datetime", None, False, None, "创建时间"),
|
||||
F("update_time", "datetime", None, False, None, "更新时间"),
|
||||
DELETED(),
|
||||
],
|
||||
"indexes": [
|
||||
IDX("uk_pbl_offline_code", ["tenant_id", "pkg_code", "pkg_version"], True, "离线包编码+版本唯一"),
|
||||
IDX("idx_pbl_offline_bp", ["tenant_id", "blueprint_id", "deleted"], False, "按蓝图查离线包"),
|
||||
IDX("idx_pbl_offline_status", ["tenant_id", "status", "expire_time"], False, "过期包清理扫描"),
|
||||
],
|
||||
"codes": {
|
||||
"status": {
|
||||
"summary": "离线包状态",
|
||||
"items": {
|
||||
"ready": "可用",
|
||||
"building": "构建中",
|
||||
"expired": "已过期",
|
||||
"invalid": "校验失败",
|
||||
},
|
||||
},
|
||||
},
|
||||
"browserfields": ["pkg_code", "pkg_name", "pkg_version", "status", "file_size", "create_time"],
|
||||
"queryfields": ["blueprint_id", "template_id", "pkg_code", "pkg_name", "pkg_version", "status"],
|
||||
}
|
||||
|
||||
TABLES["pbl_blueprint_fork"] = {
|
||||
"summary": "蓝图 fork 血缘表:记录蓝图之间的派生关系(源蓝图→新蓝图)与模板实例化来源,支持 fork 溯源与派生树查询。",
|
||||
"fields": [
|
||||
ID(), TENANT(),
|
||||
F("source_blueprint_id", "str", 32, True, "", "源蓝图ID(模板实例化时为空串)"),
|
||||
F("source_version", "int", None, True, 0, "fork 时的源版本号"),
|
||||
F("target_blueprint_id", "str", 32, True, None, "派生出的新蓝图ID"),
|
||||
F("fork_type", "str", 16, True, "copy", "fork 方式"),
|
||||
F("template_id", "str", 32, True, "", "若由模板实例化,记录模板ID"),
|
||||
F("node_count", "int", None, True, 0, "复制的节点数"),
|
||||
F("edge_count", "int", None, True, 0, "复制的边数"),
|
||||
F("cost_amount", "double", [18, 2], True, 0, "本次 fork 产生的费用金额(double(18,2),付费模板)"),
|
||||
F("remark", "str", 512, True, "", "备注"),
|
||||
F("create_user", "str", 32, False, "", "创建人"),
|
||||
F("create_time", "datetime", None, False, None, "创建时间"),
|
||||
DELETED(),
|
||||
],
|
||||
"indexes": [
|
||||
IDX("uk_pbl_fork", ["tenant_id", "source_blueprint_id", "target_blueprint_id"], True, "同一对蓝图血缘唯一,保证幂等"),
|
||||
IDX("idx_pbl_fork_source", ["tenant_id", "source_blueprint_id", "deleted"], False, "查某蓝图的派生列表"),
|
||||
IDX("idx_pbl_fork_target", ["tenant_id", "target_blueprint_id"], False, "反查某蓝图的来源"),
|
||||
IDX("idx_pbl_fork_template", ["tenant_id", "template_id"], False, "查模板被实例化记录"),
|
||||
],
|
||||
"codes": {
|
||||
"fork_type": {
|
||||
"summary": "fork 方式",
|
||||
"items": {
|
||||
"copy": "完整复制",
|
||||
"structure": "仅结构(不带载荷)",
|
||||
"template": "模板实例化",
|
||||
"branch": "分支派生",
|
||||
},
|
||||
},
|
||||
},
|
||||
"browserfields": ["source_blueprint_id", "source_version", "target_blueprint_id",
|
||||
"fork_type", "node_count", "create_time"],
|
||||
"queryfields": ["source_blueprint_id", "target_blueprint_id", "fork_type", "template_id"],
|
||||
}
|
||||
|
||||
TABLES["pbl_blueprint_lock"] = {
|
||||
"summary": "蓝图编辑锁表:多人协作时对蓝图/节点加编辑锁(悲观锁+rev 乐观修订号),锁带过期时间,过期可被抢占。",
|
||||
"fields": [
|
||||
ID(), TENANT(),
|
||||
F("blueprint_id", "str", 32, True, None, "被锁蓝图ID"),
|
||||
F("node_id", "str", 32, True, "", "被锁节点ID(空串=整蓝图锁)"),
|
||||
F("lock_type", "str", 16, True, "edit", "锁类型"),
|
||||
F("holder_id", "str", 32, True, None, "持锁人ID"),
|
||||
F("holder_name", "str", 64, True, "", "持锁人名称"),
|
||||
F("rev", "int", None, True, 0, "乐观锁修订号(每次抢占/续锁 +1)"),
|
||||
F("expire_time", "datetime", None, False, None, "锁过期时间"),
|
||||
F("status", "str", 16, True, "holding", "锁状态"),
|
||||
F("create_time", "datetime", None, False, None, "加锁时间"),
|
||||
F("update_time", "datetime", None, False, None, "续锁时间"),
|
||||
DELETED(),
|
||||
],
|
||||
"indexes": [
|
||||
IDX("uk_pbl_lock", ["tenant_id", "blueprint_id", "node_id", "lock_type"], True, "同对象同类型仅一把锁记录"),
|
||||
IDX("idx_pbl_lock_holder", ["tenant_id", "holder_id", "status"], False, "查某人持有的锁"),
|
||||
IDX("idx_pbl_lock_expire", ["tenant_id", "status", "expire_time"], False, "过期锁清理扫描"),
|
||||
],
|
||||
"codes": {
|
||||
"lock_type": {
|
||||
"summary": "锁类型",
|
||||
"items": {"edit": "编辑锁", "publish": "发布锁", "compile": "编译锁"},
|
||||
},
|
||||
"status": {
|
||||
"summary": "锁状态",
|
||||
"items": {"holding": "持有中", "released": "已释放", "expired": "已过期"},
|
||||
},
|
||||
},
|
||||
"browserfields": ["blueprint_id", "node_id", "lock_type", "holder_name", "status", "expire_time"],
|
||||
"queryfields": ["blueprint_id", "node_id", "lock_type", "holder_id", "status"],
|
||||
}
|
||||
|
||||
TABLES["pbl_blueprint_audit"] = {
|
||||
"summary": "蓝图操作审计表(append-only):记录蓝图/节点/边/版本/模板/发布/锁的关键写操作与结果,只增不改不删,供追溯与合规审计。",
|
||||
"fields": [
|
||||
ID(), TENANT(),
|
||||
F("blueprint_id", "str", 32, True, "", "蓝图ID"),
|
||||
F("target_type", "str", 16, True, None, "操作对象类型"),
|
||||
F("target_id", "str", 32, True, "", "操作对象ID"),
|
||||
F("action", "str", 32, True, None, "操作动作"),
|
||||
F("version_no", "int", None, True, 0, "关联版本号"),
|
||||
F("before_json", "text", None, False, "", "操作前数据JSON"),
|
||||
F("after_json", "text", None, False, "", "操作后数据JSON"),
|
||||
F("result", "str", 16, True, "success", "操作结果"),
|
||||
F("err_code", "str", 32, True, "", "失败错误码"),
|
||||
F("cost_ms", "int", None, True, 0, "耗时毫秒"),
|
||||
F("op_user", "str", 32, True, "", "操作人"),
|
||||
F("op_source", "str", 32, True, "api", "操作来源(web/api/agent/offline)"),
|
||||
F("client_ip", "str", 64, True, "", "客户端IP"),
|
||||
F("create_time", "datetime", None, False, None, "操作时间"),
|
||||
],
|
||||
"indexes": [
|
||||
IDX("idx_pbl_audit_bp", ["tenant_id", "blueprint_id", "create_time"], False, "按蓝图时间线查审计"),
|
||||
IDX("idx_pbl_audit_target", ["tenant_id", "target_type", "target_id"], False, "按对象查审计"),
|
||||
IDX("idx_pbl_audit_user", ["tenant_id", "op_user", "create_time"], False, "按操作人查审计"),
|
||||
IDX("idx_pbl_audit_action", ["tenant_id", "action", "result"], False, "按动作与结果统计"),
|
||||
],
|
||||
"codes": {
|
||||
"target_type": {
|
||||
"summary": "操作对象类型",
|
||||
"items": {
|
||||
"blueprint": "蓝图",
|
||||
"node": "节点",
|
||||
"edge": "边",
|
||||
"version": "版本",
|
||||
"delta": "版本差异",
|
||||
"template": "模板",
|
||||
"publish": "发布",
|
||||
"offline": "离线包",
|
||||
"fork": "血缘",
|
||||
"lock": "编辑锁",
|
||||
},
|
||||
},
|
||||
"action": {
|
||||
"summary": "操作动作",
|
||||
"items": {
|
||||
"create": "创建",
|
||||
"update": "更新",
|
||||
"delete": "删除",
|
||||
"fork": "fork派生",
|
||||
"save_version": "存版本",
|
||||
"rollback": "回滚",
|
||||
"publish": "发布",
|
||||
"revoke": "撤回发布",
|
||||
"instantiate": "模板实例化",
|
||||
"validate": "校验",
|
||||
"compile": "编译",
|
||||
"lock": "加锁",
|
||||
"unlock": "解锁",
|
||||
"export_offline": "导出离线包",
|
||||
"import_offline": "导入离线包",
|
||||
},
|
||||
},
|
||||
"result": {
|
||||
"summary": "操作结果",
|
||||
"items": {"success": "成功", "fail": "失败", "denied": "被拒绝"},
|
||||
},
|
||||
},
|
||||
"browserfields": ["blueprint_id", "target_type", "action", "result", "op_user",
|
||||
"op_source", "create_time"],
|
||||
"queryfields": ["blueprint_id", "target_type", "target_id", "action", "version_no",
|
||||
"result", "op_user", "op_source", "create_time"],
|
||||
}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ 生成
|
||||
def build_model(tbl):
|
||||
d = TABLES[tbl]
|
||||
fields = {}
|
||||
for name, spec in d["fields"]:
|
||||
fields[name] = spec
|
||||
return {
|
||||
"summary": d["summary"],
|
||||
"primary": ["id"],
|
||||
"fields": fields,
|
||||
"indexes": dict(d["indexes"]),
|
||||
"codes": d["codes"],
|
||||
}
|
||||
|
||||
|
||||
def build_crud(tbl):
|
||||
d = TABLES[tbl]
|
||||
codes = d.get("codes") or {}
|
||||
params = {}
|
||||
for name, spec in d["fields"]:
|
||||
p = {"type": spec["type"]}
|
||||
if "size" in spec:
|
||||
p["size"] = spec["size"]
|
||||
if spec.get("notnull"):
|
||||
p["notnull"] = True
|
||||
if "default" in spec:
|
||||
p["default"] = spec["default"]
|
||||
if name in IMMUTABLE_FIELDS or name in SERVER_BLOB_FIELDS:
|
||||
p["editable"] = False
|
||||
else:
|
||||
p["editable"] = True
|
||||
p["query"] = name in (d.get("queryfields") or ()) or name == "tenant_id"
|
||||
if name in codes:
|
||||
p["code"] = name
|
||||
p["summary"] = spec["summary"]
|
||||
params[name] = p
|
||||
return {
|
||||
"tblname": tbl,
|
||||
"params": params,
|
||||
"editable": [
|
||||
"api/%s_list.dspy" % tbl,
|
||||
"api/%s_edit.dspy" % tbl,
|
||||
"api/%s_view.dspy" % tbl,
|
||||
],
|
||||
"browserfields": list(d.get("browserfields") or []),
|
||||
}
|
||||
|
||||
|
||||
def col_type(spec):
|
||||
t = (spec.get("type") or "str").lower()
|
||||
size = spec.get("size")
|
||||
if t in ("str", "string", "varchar", "char"):
|
||||
return "varchar(%d)" % int(size or 64)
|
||||
if t == "int":
|
||||
return "int"
|
||||
if t == "bigint":
|
||||
return "bigint"
|
||||
if t == "double":
|
||||
if isinstance(size, (list, tuple)) and len(size) == 2:
|
||||
return "decimal(%d,%d)" % (int(size[0]), int(size[1]))
|
||||
return "double"
|
||||
if t in ("text", "longtext", "json"):
|
||||
return "text"
|
||||
if t in ("datetime", "timestamp"):
|
||||
return "datetime"
|
||||
if t == "date":
|
||||
return "date"
|
||||
return "varchar(64)"
|
||||
|
||||
|
||||
def ddl():
|
||||
out = ["-- pbl_blueprint M1a DDL(%d 表,由 scripts/gen_defs.py 生成)" % len(TABLE_ORDER), ""]
|
||||
for tbl in TABLE_ORDER:
|
||||
m = build_model(tbl)
|
||||
cols = []
|
||||
for name, spec in m["fields"].items():
|
||||
nn = " not null" if spec.get("notnull") else ""
|
||||
df = ""
|
||||
if "default" in spec:
|
||||
dv = spec["default"]
|
||||
df = (" default '%s'" % dv) if isinstance(dv, str) else (" default %s" % dv)
|
||||
cm = " comment '%s'" % str(spec.get("summary") or "").replace("'", "")
|
||||
cols.append(" `%s` %s%s%s%s" % (name, col_type(spec), nn, df, cm))
|
||||
cols.append(" primary key (`%s`)" % "` , `".join(m["primary"]))
|
||||
for iname, ispec in m["indexes"].items():
|
||||
uq = "unique " if ispec.get("unique") else ""
|
||||
cols.append(" %skey `%s` (%s)" % (uq, iname,
|
||||
", ".join(["`%s`" % c for c in ispec["fields"]])))
|
||||
out.append("-- %s" % m["summary"])
|
||||
out.append("create table if not exists `%s` (" % tbl)
|
||||
out.append(",\n".join(cols))
|
||||
out.append(") engine=InnoDB default charset=utf8mb4 comment='%s';" % tbl.replace("'", ""))
|
||||
out.append("")
|
||||
return "\n".join(out)
|
||||
|
||||
|
||||
def clean_stale():
|
||||
"""删除 models/ 与 json/ 下不属于 11 表清单的陈旧定义文件。"""
|
||||
removed = []
|
||||
for d in (MODEL_DIR, JSON_DIR):
|
||||
if not os.path.isdir(d):
|
||||
continue
|
||||
for fn in sorted(os.listdir(d)):
|
||||
if not fn.endswith(".json"):
|
||||
continue
|
||||
if fn[:-5] not in TABLE_ORDER:
|
||||
p = os.path.join(d, fn)
|
||||
os.remove(p)
|
||||
removed.append(os.path.relpath(p, MODULE_ROOT))
|
||||
return removed
|
||||
|
||||
|
||||
def write_json(path, obj):
|
||||
d = os.path.dirname(path)
|
||||
if not os.path.isdir(d):
|
||||
os.makedirs(d)
|
||||
with io.open(path, "w", encoding="utf-8") as f:
|
||||
f.write(json.dumps(obj, ensure_ascii=False, indent=2) + "\n")
|
||||
|
||||
|
||||
def generate():
|
||||
removed = clean_stale()
|
||||
written = []
|
||||
for tbl in TABLE_ORDER:
|
||||
p1 = os.path.join(MODEL_DIR, "%s.json" % tbl)
|
||||
write_json(p1, build_model(tbl))
|
||||
written.append(os.path.relpath(p1, MODULE_ROOT))
|
||||
p2 = os.path.join(JSON_DIR, "%s.json" % tbl)
|
||||
write_json(p2, build_crud(tbl))
|
||||
written.append(os.path.relpath(p2, MODULE_ROOT))
|
||||
return removed, written
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
argv = list(sys.argv[1:] if argv is None else argv)
|
||||
if "--dump-ddl" in argv:
|
||||
print(ddl())
|
||||
return 0
|
||||
if "--clean" in argv:
|
||||
rm = clean_stale()
|
||||
print("已清理陈旧定义 %d 个: %s" % (len(rm), rm))
|
||||
return 0
|
||||
rm, wr = generate()
|
||||
print("清理陈旧定义 %d 个: %s" % (len(rm), rm))
|
||||
print("生成定义文件 %d 个(models 11 + json 11):" % len(wr))
|
||||
for w in wr:
|
||||
print(" %s" % w)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@ -1,132 +1,179 @@
|
||||
"""pbl_blueprint 模块 RBAC 路径注册脚本。
|
||||
# -*- coding: utf-8 -*-
|
||||
"""pbl_blueprint RBAC 路径注册清单(显式枚举,无 %/* 通配符)。
|
||||
|
||||
用途:把本模块全部新 API 路径 + 页面路径注册到平台 RBAC(权限路径表),
|
||||
使 owner.admin / 租户管理员可授权,未注册路径一律 403。
|
||||
本文件是 RBAC 路径的唯一声明源:init.py 的 RBAC_PATHS 必须与此处 PATHS 完全一致,
|
||||
selfcheck.py 会逐条比对(不一致即退出码非 0)。
|
||||
|
||||
执行方式(应用部署后一次性 / 幂等可重复执行):
|
||||
python -m pbl_blueprint.scripts.load_path
|
||||
或在应用 init() 后由部署脚本调用 load_paths(env)。
|
||||
用法:
|
||||
python3 modules/pbl_blueprint/scripts/load_path.py # 打印清单
|
||||
python3 modules/pbl_blueprint/scripts/load_path.py --check # 与 init.py 比对
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
MODULE_NAME = 'pbl_blueprint'
|
||||
MODULE_NAME = "pbl_blueprint"
|
||||
|
||||
# ---- 本模块全部新 API 路径(与 api.py 的 API_PATHS 保持一致,单一事实源)----
|
||||
API_PATHS = [
|
||||
('/pbl_blueprint/blueprint/create', 'POST', '新建蓝图'),
|
||||
('/pbl_blueprint/blueprint/update', 'POST', '更新蓝图'),
|
||||
('/pbl_blueprint/blueprint/delete', 'POST', '删除蓝图'),
|
||||
('/pbl_blueprint/blueprint/get', 'GET', '蓝图详情'),
|
||||
('/pbl_blueprint/blueprint/list', 'GET', '蓝图列表'),
|
||||
('/pbl_blueprint/blueprint/tree', 'GET', '蓝图子对象树'),
|
||||
('/pbl_blueprint/blueprint/fork', 'POST', '复制蓝图'),
|
||||
('/pbl_blueprint/version/save', 'POST', '保存版本快照'),
|
||||
('/pbl_blueprint/version/list', 'GET', '版本列表'),
|
||||
('/pbl_blueprint/version/diff', 'GET', '版本对比'),
|
||||
('/pbl_blueprint/version/rollback', 'POST', '版本回滚'),
|
||||
('/pbl_blueprint/subobject/create', 'POST', '新建子对象'),
|
||||
('/pbl_blueprint/subobject/update', 'POST', '更新子对象'),
|
||||
('/pbl_blueprint/subobject/delete', 'POST', '删除子对象'),
|
||||
('/pbl_blueprint/subobject/get', 'GET', '子对象详情'),
|
||||
('/pbl_blueprint/subobject/list', 'GET', '子对象列表'),
|
||||
('/pbl_blueprint/subobject/batch_upsert', 'POST', '批量写子对象'),
|
||||
('/pbl_blueprint/subobject/types', 'GET', '子对象类型枚举'),
|
||||
('/pbl_blueprint/template/create', 'POST', '新建模板'),
|
||||
('/pbl_blueprint/template/update', 'POST', '更新模板'),
|
||||
('/pbl_blueprint/template/delete', 'POST', '删除模板'),
|
||||
('/pbl_blueprint/template/get', 'GET', '模板详情'),
|
||||
('/pbl_blueprint/template/list', 'GET', '模板列表'),
|
||||
('/pbl_blueprint/template/instantiate', 'POST', '模板实例化为蓝图'),
|
||||
('/pbl_blueprint/template/offline_fallback', 'POST', '离线兜底生成蓝图'),
|
||||
]
|
||||
# 蓝图主表
|
||||
PATHS_BLUEPRINT = (
|
||||
"/api/pbl_blueprint/list.dspy",
|
||||
"/api/pbl_blueprint/create.dspy",
|
||||
"/api/pbl_blueprint/get.dspy",
|
||||
"/api/pbl_blueprint/update.dspy",
|
||||
"/api/pbl_blueprint/delete.dspy",
|
||||
"/api/pbl_blueprint/tree.dspy",
|
||||
"/api/pbl_blueprint/fork.dspy",
|
||||
"/api/pbl_blueprint/forks.dspy",
|
||||
"/api/pbl_blueprint/stats.dspy",
|
||||
)
|
||||
|
||||
# ---- 页面路径(wwwroot/*.ui 挂载点)----
|
||||
PAGE_PATHS = [
|
||||
('/pbl_blueprint/index', 'GET', 'PBL蓝图管理主页'),
|
||||
('/pbl_blueprint/blueprint/edit', 'GET', '蓝图编辑页'),
|
||||
('/pbl_blueprint/template/list', 'GET', '蓝图模板库页'),
|
||||
]
|
||||
# 子对象节点
|
||||
PATHS_NODE = (
|
||||
"/api/pbl_blueprint_node/list.dspy",
|
||||
"/api/pbl_blueprint_node/create.dspy",
|
||||
"/api/pbl_blueprint_node/get.dspy",
|
||||
"/api/pbl_blueprint_node/update.dspy",
|
||||
"/api/pbl_blueprint_node/delete.dspy",
|
||||
)
|
||||
|
||||
# ---- 表级数据权限路径(CRUD 定义 json/*.json 对应)----
|
||||
TABLE_PATHS = [
|
||||
('/pbl_blueprint/tbl/pbl_blueprint', 'CRUD', '蓝图主表数据权限'),
|
||||
('/pbl_blueprint/tbl/pbl_blueprint_version', 'CRUD', '蓝图版本表数据权限'),
|
||||
('/pbl_blueprint/tbl/pbl_blueprint_template', 'CRUD', '蓝图模板表数据权限'),
|
||||
('/pbl_blueprint/tbl/pbl_blueprint_template_item', 'CRUD', '蓝图模板条目表数据权限'),
|
||||
('/pbl_blueprint/tbl/pbl_blueprint_task', 'CRUD', '子对象-任务数据权限'),
|
||||
('/pbl_blueprint/tbl/pbl_blueprint_mission', 'CRUD', '子对象-关卡数据权限'),
|
||||
('/pbl_blueprint/tbl/pbl_blueprint_role', 'CRUD', '子对象-角色数据权限'),
|
||||
('/pbl_blueprint/tbl/pbl_blueprint_learning_goal', 'CRUD', '子对象-学习目标数据权限'),
|
||||
('/pbl_blueprint/tbl/pbl_blueprint_evidence_spec', 'CRUD', '子对象-证据规格数据权限'),
|
||||
('/pbl_blueprint/tbl/pbl_blueprint_artifact_spec', 'CRUD', '子对象-产出物规格数据权限'),
|
||||
('/pbl_blueprint/tbl/pbl_blueprint_reflection_spec', 'CRUD', '子对象-反思规格数据权限'),
|
||||
]
|
||||
# 关系边
|
||||
PATHS_EDGE = (
|
||||
"/api/pbl_blueprint_edge/list.dspy",
|
||||
"/api/pbl_blueprint_edge/create.dspy",
|
||||
"/api/pbl_blueprint_edge/get.dspy",
|
||||
"/api/pbl_blueprint_edge/update.dspy",
|
||||
"/api/pbl_blueprint_edge/delete.dspy",
|
||||
)
|
||||
|
||||
ALL_PATHS = API_PATHS + PAGE_PATHS + TABLE_PATHS
|
||||
# 版本与差异
|
||||
PATHS_VERSION = (
|
||||
"/api/pbl_blueprint_version/list.dspy",
|
||||
"/api/pbl_blueprint_version/get.dspy",
|
||||
"/api/pbl_blueprint_version/save.dspy",
|
||||
"/api/pbl_blueprint_version/rollback.dspy",
|
||||
"/api/pbl_blueprint_version_delta/list.dspy",
|
||||
"/api/pbl_blueprint_version_delta/get.dspy",
|
||||
)
|
||||
|
||||
# 模板
|
||||
PATHS_TEMPLATE = (
|
||||
"/api/pbl_blueprint_template/list.dspy",
|
||||
"/api/pbl_blueprint_template/create.dspy",
|
||||
"/api/pbl_blueprint_template/get.dspy",
|
||||
"/api/pbl_blueprint_template/update.dspy",
|
||||
"/api/pbl_blueprint_template/delete.dspy",
|
||||
"/api/pbl_blueprint_template/instantiate.dspy",
|
||||
)
|
||||
|
||||
# 发布
|
||||
PATHS_PUBLISH = (
|
||||
"/api/pbl_blueprint_publish/list.dspy",
|
||||
"/api/pbl_blueprint_publish/create.dspy",
|
||||
"/api/pbl_blueprint_publish/get.dspy",
|
||||
"/api/pbl_blueprint_publish/revoke.dspy",
|
||||
)
|
||||
|
||||
# 离线包
|
||||
PATHS_OFFLINE = (
|
||||
"/api/pbl_blueprint_offline/list.dspy",
|
||||
"/api/pbl_blueprint_offline/export.dspy",
|
||||
"/api/pbl_blueprint_offline/import.dspy",
|
||||
"/api/pbl_blueprint_offline/get.dspy",
|
||||
)
|
||||
|
||||
# 血缘
|
||||
PATHS_FORK = (
|
||||
"/api/pbl_blueprint_fork/list.dspy",
|
||||
"/api/pbl_blueprint_fork/get.dspy",
|
||||
)
|
||||
|
||||
# 编辑锁
|
||||
PATHS_LOCK = (
|
||||
"/api/pbl_blueprint_lock/lock.dspy",
|
||||
"/api/pbl_blueprint_lock/unlock.dspy",
|
||||
"/api/pbl_blueprint_lock/list.dspy",
|
||||
"/api/pbl_blueprint_lock/clean_expired.dspy",
|
||||
)
|
||||
|
||||
# 审计(只读)
|
||||
PATHS_AUDIT = (
|
||||
"/api/pbl_blueprint_audit/list.dspy",
|
||||
"/api/pbl_blueprint_audit/get.dspy",
|
||||
)
|
||||
|
||||
PATHS = (
|
||||
PATHS_BLUEPRINT + PATHS_NODE + PATHS_EDGE + PATHS_VERSION + PATHS_TEMPLATE +
|
||||
PATHS_PUBLISH + PATHS_OFFLINE + PATHS_FORK + PATHS_LOCK + PATHS_AUDIT
|
||||
)
|
||||
|
||||
# 只读路径(RBAC 上归为 read 权限组)
|
||||
READONLY_PATHS = tuple(p for p in PATHS if p.rsplit("/", 1)[-1].split(".")[0] in
|
||||
("list", "get", "tree", "forks", "stats"))
|
||||
WRITE_PATHS = tuple(p for p in PATHS if p not in READONLY_PATHS)
|
||||
|
||||
|
||||
def load_paths(env=None):
|
||||
"""把 ALL_PATHS 注册进 RBAC(幂等:存在则更新描述,不存在则新增)。
|
||||
|
||||
:return: {'registered': n, 'skipped': m, 'failed': k}
|
||||
"""
|
||||
result = {'registered': 0, 'skipped': 0, 'failed': 0, 'paths': []}
|
||||
if env is None:
|
||||
try:
|
||||
from sage import ServerEnv
|
||||
env = ServerEnv()
|
||||
except Exception:
|
||||
env = None
|
||||
|
||||
rbac = None
|
||||
if env is not None:
|
||||
for attr in ('rbac', 'RBAC'):
|
||||
rbac = getattr(env, attr, None)
|
||||
if rbac is not None:
|
||||
break
|
||||
if rbac is None:
|
||||
getter = getattr(env, 'get_rbac', None)
|
||||
if callable(getter):
|
||||
try:
|
||||
rbac = getter()
|
||||
except Exception:
|
||||
rbac = None
|
||||
|
||||
for path, method, caption in ALL_PATHS:
|
||||
item = {'path': path, 'method': method, 'caption': caption, 'module': MODULE_NAME}
|
||||
result['paths'].append(item)
|
||||
if rbac is None:
|
||||
result['skipped'] += 1
|
||||
def validate(paths=None):
|
||||
"""校验路径合法性:非空、以 / 开头、.dspy 结尾、无通配符、无重复。返回错误列表。"""
|
||||
ps = list(paths if paths is not None else PATHS)
|
||||
errs = []
|
||||
seen = set()
|
||||
for p in ps:
|
||||
if not p or not isinstance(p, str):
|
||||
errs.append("空路径或非字符串: %r" % (p,))
|
||||
continue
|
||||
try:
|
||||
registered = False
|
||||
for fn_name in ('register_path', 'add_path', 'load_path', 'register'):
|
||||
fn = getattr(rbac, fn_name, None)
|
||||
if callable(fn):
|
||||
try:
|
||||
fn(path, method, caption)
|
||||
except TypeError:
|
||||
fn(item)
|
||||
registered = True
|
||||
break
|
||||
if registered:
|
||||
result['registered'] += 1
|
||||
else:
|
||||
result['skipped'] += 1
|
||||
except Exception:
|
||||
result['failed'] += 1
|
||||
return result
|
||||
if not p.startswith("/api/"):
|
||||
errs.append("路径必须以 /api/ 开头: %s" % p)
|
||||
if not p.endswith(".dspy"):
|
||||
errs.append("路径必须以 .dspy 结尾: %s" % p)
|
||||
if "%" in p or "*" in p or "?" in p:
|
||||
errs.append("路径含通配符(禁止): %s" % p)
|
||||
if " " in p:
|
||||
errs.append("路径含空格: %s" % p)
|
||||
if p in seen:
|
||||
errs.append("路径重复: %s" % p)
|
||||
seen.add(p)
|
||||
return errs
|
||||
|
||||
|
||||
def main():
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
res = load_paths()
|
||||
print('[pbl_blueprint] load_path done: registered=%d skipped=%d failed=%d total=%d' % (
|
||||
res['registered'], res['skipped'], res['failed'], len(res['paths'])))
|
||||
def check_against_init():
|
||||
"""与 pbl_blueprint/init.py 的 RBAC_PATHS 逐条比对,返回 (ok, diff_msg)。"""
|
||||
root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
if root not in sys.path:
|
||||
sys.path.insert(0, root)
|
||||
try:
|
||||
from pbl_blueprint import init as _init # noqa
|
||||
except Exception as e:
|
||||
return False, "无法导入 pbl_blueprint.init: %s" % e
|
||||
a = list(PATHS)
|
||||
b = list(getattr(_init, "RBAC_PATHS", ()))
|
||||
if a == b:
|
||||
return True, "RBAC 路径一致(%d 条,顺序相同)" % len(a)
|
||||
only_a = [x for x in a if x not in b]
|
||||
only_b = [x for x in b if x not in a]
|
||||
return False, "RBAC 路径不一致: 仅 load_path=%s / 仅 init=%s / 顺序差异=%s" % (
|
||||
only_a, only_b, a != b and not only_a and not only_b)
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
argv = list(sys.argv[1:] if argv is None else argv)
|
||||
if "--check" in argv:
|
||||
errs = validate()
|
||||
good, msg = check_against_init()
|
||||
for e in errs:
|
||||
print("[PATH-ERR] %s" % e)
|
||||
print("[CHECK] %s" % msg)
|
||||
print("[STATS] total=%d readonly=%d write=%d" % (len(PATHS), len(READONLY_PATHS), len(WRITE_PATHS)))
|
||||
return 0 if (good and not errs) else 1
|
||||
print("# %s RBAC 路径清单(显式枚举,共 %d 条,无通配符)" % (MODULE_NAME, len(PATHS)))
|
||||
for p in PATHS:
|
||||
print(p)
|
||||
errs = validate()
|
||||
if errs:
|
||||
for e in errs:
|
||||
print("[PATH-ERR] %s" % e, file=sys.stderr)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user