From eebd79d9d757eaa9290fc2fb61c882a12b3383e1 Mon Sep 17 00:00:00 2001 From: pipeline-agent Date: Sun, 30 Aug 2026 13:26:14 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=9B=9E=E5=A1=AB=E7=9B=AE=E6=A0=87?= =?UTF-8?q?=E6=9C=BA=E7=8E=B0=E5=9C=BA=E5=A5=BD=E4=BB=A3=E7=A0=81=E2=80=94?= =?UTF-8?q?=E2=80=94=E8=BF=9C=E7=AB=AFmain=E6=AE=8B=E7=BC=BA/=E7=A9=BA?= =?UTF-8?q?=E5=AF=BC=E8=87=B4=E9=83=A8=E7=BD=B2=E5=90=8E=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=E4=B8=8D=E5=8F=AF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 11 +- README.md | 36 +++- __init__.py | 7 +- build.sh | 1 - docs/i18n.md | 1 - docs/work-log-2026-07-19.md | 1 - entity/__init__.py | 18 +- entity/__pycache__/__init__.cpython-310.pyc | Bin 657 -> 0 bytes entity/__pycache__/init.cpython-310.pyc | Bin 15231 -> 0 bytes entity/init.py | 176 +++++++++++++++++- init/data.json | 33 +++- json/entity.json | 34 +++- json/entity_import.json | 17 +- models/entity.json | 34 +++- models/entity_import.json | 30 ++- models/entity_import_log.json | 21 ++- pyproject.toml | 14 +- scripts/__pycache__/load_path.cpython-310.pyc | Bin 1910 -> 0 bytes scripts/load_path.py | 65 ++++++- skill/SKILL.md | 30 ++- wwwroot/api/entity_create.dspy | 3 +- wwwroot/api/entity_delete.dspy | 3 +- wwwroot/api/entity_get.dspy | 1 - wwwroot/api/entity_import.dspy | 4 +- wwwroot/api/entity_import_guard.dspy | 2 +- wwwroot/api/entity_import_list.dspy | 1 - wwwroot/api/entity_import_reject.dspy | 1 - wwwroot/api/entity_list.dspy | 1 - wwwroot/api/entity_update.dspy | 3 +- wwwroot/api/get_search_entity_type.dspy | 9 +- wwwroot/api/get_search_import_status.dspy | 1 - wwwroot/api/get_search_scene_id.dspy | 9 +- wwwroot/api/get_search_status.dspy | 9 +- wwwroot/api/get_search_world_id.dspy | 9 +- wwwroot/i18n/en-US.json | 1 - wwwroot/i18n/zh-CN.json | 1 - wwwroot/import_page.ui | 21 ++- wwwroot/index.ui | 23 ++- 38 files changed, 596 insertions(+), 35 deletions(-) delete mode 100644 build.sh delete mode 100644 docs/i18n.md delete mode 100644 docs/work-log-2026-07-19.md delete mode 100644 entity/__pycache__/__init__.cpython-310.pyc delete mode 100644 entity/__pycache__/init.cpython-310.pyc delete mode 100644 scripts/__pycache__/load_path.cpython-310.pyc delete mode 100644 wwwroot/api/entity_get.dspy delete mode 100644 wwwroot/api/entity_import_list.dspy delete mode 100644 wwwroot/api/entity_import_reject.dspy delete mode 100644 wwwroot/api/entity_list.dspy delete mode 100644 wwwroot/api/get_search_import_status.dspy delete mode 100644 wwwroot/i18n/en-US.json delete mode 100644 wwwroot/i18n/zh-CN.json diff --git a/.gitignore b/.gitignore index 2ab6fc2..6254bac 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,10 @@ -忽略生成物/CRUD 目录 \ No newline at end of file +__pycache__/ +*.py[cod] +*.egg-info/ +build/ +dist/ +models/mysql.ddl.sql +wwwroot/entity/ +wwwroot/entity_import/ +*.swp +*.swo diff --git a/README.md b/README.md index b73e34e..53bdf8f 100644 --- a/README.md +++ b/README.md @@ -1 +1,35 @@ -模块说明 \ No newline at end of file +# entity 模块 + +实体管理模块,含实体导入。依赖 `world`、`scene` 模块。 + +## 功能 +- 实体表(entity)CRUD:实体名称/编码/所属世界/所属场景/实体类型/状态/属性 JSON +- 实体导入:上传 CSV/JSON 文件 → 解析 → 逐条校验 → 批量落库 → 写导入记录(entity_import) + +## 数据表 +| 表 | 说明 | +|----|------| +| entity | 实体表 | +| entity_import | 实体导入记录表 | + +## 目录结构 +``` +entity/ +├── entity/ # Python 包(init.py 定义 load_entity 及业务函数) +├── models/ # 表定义(entity.json / entity_import.json) +├── json/ # CRUD 定义(entity.json / entity_import.json) +├── wwwroot/ # 前端(index.ui / import_page.ui / api/*.dspy) +├── init/data.json # appcodes 字典种子(entity_type / entity_status / import_status) +├── scripts/load_path.py # RBAC 权限注册 +└── skill/SKILL.md # 模块技能文档 +``` + +## 集成方式 +应用入口 `app/{应用名}.py` 中 `from entity.init import load_entity`,并在 `init()` 里调用 +`load_entity()`。库名由宿主应用的 `get_module_dbname('entity')` 决定,模块内不硬编码。 + +## 关键接口 +- `load_entity()`:挂载模块函数到 ServerEnv +- `wwwroot/api/entity_import.dspy`:实体导入(world_id + scene_id + file) +- `wwwroot/api/entity_create.dspy` / `entity_update.dspy` / `entity_delete.dspy`:实体 CRUD +- `wwwroot/api/get_search_*.dspy`:下拉数据源 diff --git a/__init__.py b/__init__.py index 7f63704..4e3446f 100644 --- a/__init__.py +++ b/__init__.py @@ -1 +1,6 @@ -仓库根标记(双层结构说明) \ No newline at end of file +"""entity 模块:实体管理,含实体导入。""" + +from entity.init import load_entity # noqa: F401 + +__all__ = ['load_entity'] +__version__ = '1.0.0' diff --git a/build.sh b/build.sh deleted file mode 100644 index c7761a6..0000000 --- a/build.sh +++ /dev/null @@ -1 +0,0 @@ -构建脚本(DDL+xls2ui+wwwroot链接) \ No newline at end of file diff --git a/docs/i18n.md b/docs/i18n.md deleted file mode 100644 index 38fb712..0000000 --- a/docs/i18n.md +++ /dev/null @@ -1 +0,0 @@ -i18n 提取说明 \ No newline at end of file diff --git a/docs/work-log-2026-07-19.md b/docs/work-log-2026-07-19.md deleted file mode 100644 index 3a9182b..0000000 --- a/docs/work-log-2026-07-19.md +++ /dev/null @@ -1 +0,0 @@ -工作日志(范围/决策/验证/遗留) \ No newline at end of file diff --git a/entity/__init__.py b/entity/__init__.py index 8b0ff6a..530e674 100644 --- a/entity/__init__.py +++ b/entity/__init__.py @@ -1 +1,17 @@ -包导出(防 dspy NameError) \ No newline at end of file +"""entity 模块包。""" + +from .init import ( # noqa: F401 + create_entity, + delete_entity, + entity_import, + load_entity, + update_entity, +) + +__all__ = [ + 'load_entity', + 'create_entity', + 'update_entity', + 'delete_entity', + 'entity_import', +] diff --git a/entity/__pycache__/__init__.cpython-310.pyc b/entity/__pycache__/__init__.cpython-310.pyc deleted file mode 100644 index 87ff22725b1393ded6df1087977d9eaf4034ac52..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 657 zcmY+Czi-qq6vv%h?q@D{Tp18s)?45oK&VUC3W?2&6`f5L6vvV66Uuf1Ri&syg^CbT z8HxltCOShAe*>#WDhE2TGvRkBcfqnh()T^TAM&$^q5*R2JA3(ftA)@di01O0*mg%f zaG(ey>QO)QxQ~6_!Yv-)fVXj*cW{SyahLaSkN0t(5AcA8IOIb- zx8L@!&c4{wWlTt#r<#yEMH$yYzb}sL@dx|-O-z+D{yKm5^JH1QcpvX3ob5;{;snWGbnKFYtcCAVi%_K`STi=`)v?(de*v30fc_CyY z)1*kG`WH+dr5Q65Nd82~j1p1kROG5QsTs@J+H`Bv0?}o$aolb=luqkj^R z?M^4AO9&M+LQDXUC}Ggj{Qx8bKpW5jbOAj;9{}G)hJYa;0*nA-2S}P^86h8$ya`6Y dpRM{;?>5F|diS`X^NihxStuwJc-!7^@)x>8;gJ9U diff --git a/entity/__pycache__/init.cpython-310.pyc b/entity/__pycache__/init.cpython-310.pyc deleted file mode 100644 index 1f3266c356c6ed7917ee1565a320cd4e32c85d31..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15231 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 diff --git a/entity/init.py b/entity/init.py index 8565ff6..100128f 100644 --- a/entity/init.py +++ b/entity/init.py @@ -1 +1,175 @@ -后端实现:create/update/delete/get/list_entities/import_entities/entity_import_guard/reject_import_write + load_entity() 注册;_err() 统一错误结构;两阶段导入+回滚 \ No newline at end of file +"""entity 模块:实体管理 + 实体导入。 + +通过 load_entity() 挂载到 ServerEnv,函数被 .dspy 以全局方式直接调用。 +""" + +import csv +import io +import json + +from appPublic.timeUtils import curDateString, timestampstr +from appPublic.uniqueID import getID +from sqlor.dbpools import DBPools + + +def _get_dbname(): + """取库名(由宿主应用决定,禁止硬编码)。""" + from ahserver.serverenv import ServerEnv + return ServerEnv().get_module_dbname('entity') + + +def _clean_ns(ns): + """清理 Tabular/Form 提交的 _text 后缀字段与占位值。""" + for k in list(ns.keys()): + if k.endswith('_text'): + ns.pop(k, None) + elif ns[k] in ('NaN', 'null', ''): + ns[k] = None + return ns + + +async def create_entity(request, params_kw): + dbname = _get_dbname() + ns = _clean_ns(dict(params_kw or {})) + if not ns.get('id'): + ns['id'] = getID() + ns['created_at'] = curDateString() + ns['updated_at'] = timestampstr() + ns.setdefault('entity_type', '0') + ns.setdefault('status', '0') + db = DBPools() + async with db.sqlorContext(dbname) as sor: + await sor.C('entity', ns) + return {'success': True, 'message': '保存成功'} + + +async def update_entity(request, params_kw): + dbname = _get_dbname() + ns = _clean_ns(dict(params_kw or {})) + eid = ns.pop('id', None) + if not eid: + return {'success': False, 'message': '缺少主键 id'} + ns['updated_at'] = timestampstr() + db = DBPools() + async with db.sqlorContext(dbname) as sor: + await sor.U('entity', {'id': eid, **ns}) + return {'success': True, 'message': '更新成功'} + + +async def delete_entity(request, params_kw): + dbname = _get_dbname() + eid = (params_kw or {}).get('id') + if not eid: + return {'success': False, 'message': '缺少主键 id'} + db = DBPools() + async with db.sqlorContext(dbname) as sor: + await sor.D('entity', {'id': eid}) + return {'success': True, 'message': '删除成功'} + + +def _parse_import_content(content): + """解析导入内容,返回记录列表。支持 JSON 数组/对象 或 CSV(首行表头)。""" + text = (content or '').strip() + if not text: + return [] + if text.startswith('[') or text.startswith('{'): + data = json.loads(text) + return data if isinstance(data, list) else [data] + reader = csv.DictReader(io.StringIO(text)) + return [dict(r) for r in reader] + + +async def entity_import(request, params_kw): + dbname = _get_dbname() + p = params_kw or {} + world_id = p.get('world_id', '') + scene_id = p.get('scene_id') or None + file_obj = p.get('file') + + if not world_id: + return {'success': False, 'message': '缺少目标世界 world_id'} + + file_name = '' + content = '' + if file_obj is not None: + file_name = getattr(file_obj, 'filename', '') or 'upload' + try: + raw = file_obj.read() + content = raw.decode('utf-8') if isinstance(raw, bytes) else raw + except Exception as e: + return {'success': False, 'message': '读取文件失败: %s' % str(e)} + elif p.get('file_content'): + content = p.get('file_content') + file_name = p.get('file_name', 'import') + + try: + rows = _parse_import_content(content) + except Exception as e: + return {'success': False, 'message': '文件解析失败: %s' % str(e)} + + if not rows: + return {'success': False, 'message': '文件内容为空'} + + total = len(rows) + success = 0 + fail = 0 + + db = DBPools() + async with db.sqlorContext(dbname) as sor: + for row in rows: + try: + name = str(row.get('name') or '').strip() + code = str(row.get('code') or '').strip() + if not name or not code: + fail += 1 + continue + ns = { + 'id': getID(), + 'world_id': world_id, + 'scene_id': scene_id, + 'name': name, + 'code': code, + 'entity_type': str(row.get('entity_type') or '0'), + 'status': str(row.get('status') or '0'), + 'attributes_json': row.get('attributes_json') or None, + 'created_at': curDateString(), + 'updated_at': timestampstr(), + } + await sor.C('entity', ns) + success += 1 + except Exception: + fail += 1 + + status = '2' if fail == 0 else ('3' if success == 0 else '2') + import_ns = { + 'id': getID(), + 'world_id': world_id, + 'scene_id': scene_id, + 'file_name': file_name, + 'total': total, + 'success': success, + 'fail': fail, + 'status': status, + 'created_at': curDateString(), + } + await sor.C('entity_import', import_ns) + + return { + 'success': True, + 'total': total, + 'success_count': success, + 'fail': fail, + 'file_name': file_name, + } + + +async def load_entity(): + from ahserver.serverenv import ServerEnv + env = ServerEnv() + env.create_entity = create_entity + env.create_entitys = create_entity + env.update_entity = update_entity + env.update_entitys = update_entity + env.delete_entity = delete_entity + env.delete_entitys = delete_entity + env.entity_import = entity_import diff --git a/init/data.json b/init/data.json index 6006290..9a97353 100644 --- a/init/data.json +++ b/init/data.json @@ -1 +1,32 @@ -entity_type/common_status/import_status 字典种子(Format B) \ No newline at end of file +{ + "appcodes": [ + { + "parentid": "entity_type", + "parentname": "实体类型", + "items": [ + {"k": "0", "v": "普通实体"}, + {"k": "1", "v": "NPC"}, + {"k": "2", "v": "物品"}, + {"k": "3", "v": "建筑"} + ] + }, + { + "parentid": "entity_status", + "parentname": "实体状态", + "items": [ + {"k": "0", "v": "启用"}, + {"k": "1", "v": "停用"} + ] + }, + { + "parentid": "import_status", + "parentname": "导入状态", + "items": [ + {"k": "0", "v": "待处理"}, + {"k": "1", "v": "处理中"}, + {"k": "2", "v": "完成"}, + {"k": "3", "v": "失败"} + ] + } + ] +} diff --git a/json/entity.json b/json/entity.json index 88f1bb2..b8409e9 100644 --- a/json/entity.json +++ b/json/entity.json @@ -1 +1,33 @@ -实体 CRUD 定义(editable + data_filter + alters 下拉) \ No newline at end of file +{ + "tblname": "entity", + "title": "实体管理", + "params": { + "sortby": ["created_at desc"], + "new_data_url": "{{entire_url('../api/entity_create.dspy')}}", + "update_data_url": "{{entire_url('../api/entity_update.dspy')}}", + "delete_data_url": "{{entire_url('../api/entity_delete.dspy')}}", + "browserfields": { + "exclouded": ["attributes_json", "updated_at"], + "alters": { + "world_id": {"uitype": "code", "dataurl": "{{entire_url('../api/get_search_world_id.dspy')}}"}, + "scene_id": {"uitype": "code", "dataurl": "{{entire_url('../api/get_search_scene_id.dspy')}}"}, + "entity_type": {"uitype": "code", "dataurl": "{{entire_url('../api/get_search_entity_type.dspy')}}"}, + "status": {"uitype": "code", "dataurl": "{{entire_url('../api/get_search_status.dspy')}}"} + } + }, + "editexclouded": ["id", "created_at", "updated_at"], + "data_filter": { + "AND": [ + {"field": "name", "op": "LIKE", "var": "name"}, + {"field": "world_id", "op": "=", "var": "world_id"}, + {"field": "scene_id", "op": "=", "var": "scene_id"} + ] + }, + "filter_labels": {"name": "实体名称", "world_id": "所属世界", "scene_id": "所属场景"}, + "editable": { + "new_data_url": "{{entire_url('../api/entity_create.dspy')}}", + "update_data_url": "{{entire_url('../api/entity_update.dspy')}}", + "delete_data_url": "{{entire_url('../api/entity_delete.dspy')}}" + } + } +} diff --git a/json/entity_import.json b/json/entity_import.json index 71d5a40..b14d094 100644 --- a/json/entity_import.json +++ b/json/entity_import.json @@ -1 +1,16 @@ -导入记录只读 CRUD 定义 \ No newline at end of file +{ + "tblname": "entity_import", + "title": "实体导入记录", + "params": { + "sortby": ["created_at desc"], + "browserfields": { + "exclouded": [] + }, + "editexclouded": ["id", "world_id", "scene_id", "file_name", "total", "success", "fail", "status", "created_at"], + "editable": { + "new_data_url": "{{entire_url('../api/entity_import_guard.dspy')}}", + "update_data_url": "{{entire_url('../api/entity_import_guard.dspy')}}", + "delete_data_url": "{{entire_url('../api/entity_import_guard.dspy')}}" + } + } +} diff --git a/models/entity.json b/models/entity.json index 4beccea..a504777 100644 --- a/models/entity.json +++ b/models/entity.json @@ -1 +1,33 @@ -实体表四段式定义(codes 逻辑关联 scene/world,entity_code 唯一) \ No newline at end of file +{ + "summary": [ + { + "name": "entity", + "title": "实体表", + "primary": ["id"], + "catelog": "entity" + } + ], + "fields": [ + {"name": "id", "title": "主键ID", "type": "str", "length": 32, "nullable": "no"}, + {"name": "world_id", "title": "所属世界", "type": "str", "length": 32, "nullable": "no"}, + {"name": "scene_id", "title": "所属场景", "type": "str", "length": 32, "nullable": "yes"}, + {"name": "name", "title": "实体名称", "type": "str", "length": 255, "nullable": "no"}, + {"name": "code", "title": "实体编码", "type": "str", "length": 64, "nullable": "no"}, + {"name": "entity_type", "title": "实体类型", "type": "str", "length": 16, "nullable": "no", "default": "0"}, + {"name": "status", "title": "状态", "type": "str", "length": 16, "nullable": "no", "default": "0"}, + {"name": "attributes_json", "title": "实体属性JSON", "type": "text", "nullable": "yes"}, + {"name": "created_at", "title": "创建时间", "type": "timestamp", "nullable": "no"}, + {"name": "updated_at", "title": "更新时间", "type": "timestamp", "nullable": "yes"} + ], + "indexes": [ + {"name": "idx_entity_code", "idxtype": "unique", "idxfields": ["code"]}, + {"name": "idx_entity_world", "idxtype": "index", "idxfields": ["world_id"]}, + {"name": "idx_entity_scene", "idxtype": "index", "idxfields": ["scene_id"]} + ], + "codes": [ + {"field": "world_id", "table": "world", "valuefield": "id", "textfield": "name"}, + {"field": "scene_id", "table": "scene", "valuefield": "id", "textfield": "name"}, + {"field": "entity_type", "table": "appcodes_kv", "valuefield": "k", "textfield": "v", "cond": "parentid='entity_type'"}, + {"field": "status", "table": "appcodes_kv", "valuefield": "k", "textfield": "v", "cond": "parentid='entity_status'"} + ] +} diff --git a/models/entity_import.json b/models/entity_import.json index e6d01ec..d5f0fdb 100644 --- a/models/entity_import.json +++ b/models/entity_import.json @@ -1 +1,29 @@ -导入批次表定义 \ No newline at end of file +{ + "summary": [ + { + "name": "entity_import", + "title": "实体导入记录表", + "primary": ["id"], + "catelog": "entity" + } + ], + "fields": [ + {"name": "id", "title": "主键ID", "type": "str", "length": 32, "nullable": "no"}, + {"name": "world_id", "title": "目标世界", "type": "str", "length": 32, "nullable": "no"}, + {"name": "scene_id", "title": "目标场景", "type": "str", "length": 32, "nullable": "yes"}, + {"name": "file_name", "title": "导入文件名", "type": "str", "length": 255, "nullable": "no"}, + {"name": "total", "title": "总条数", "type": "int", "nullable": "no", "default": "0"}, + {"name": "success", "title": "成功条数", "type": "int", "nullable": "no", "default": "0"}, + {"name": "fail", "title": "失败条数", "type": "int", "nullable": "no", "default": "0"}, + {"name": "status", "title": "导入状态", "type": "str", "length": 16, "nullable": "no", "default": "0"}, + {"name": "created_at", "title": "导入时间", "type": "timestamp", "nullable": "no"} + ], + "indexes": [ + {"name": "idx_entity_import_world", "idxtype": "index", "idxfields": ["world_id"]} + ], + "codes": [ + {"field": "world_id", "table": "world", "valuefield": "id", "textfield": "name"}, + {"field": "scene_id", "table": "scene", "valuefield": "id", "textfield": "name"}, + {"field": "status", "table": "appcodes_kv", "valuefield": "k", "textfield": "v", "cond": "parentid='import_status'"} + ] +} diff --git a/models/entity_import_log.json b/models/entity_import_log.json index cff470c..5b427f0 100644 --- a/models/entity_import_log.json +++ b/models/entity_import_log.json @@ -1 +1,20 @@ -导入明细日志表定义 \ No newline at end of file +{ + "summary": [{"name": "entity_import_log", "title": "实体导入日志表", "primary": ["id"], "catelog": "relation"}], + "fields": [ + {"name": "id", "title": "主键ID", "type": "str", "length": 32, "nullable": "no"}, + {"name": "world_id", "title": "所属世界ID", "type": "str", "length": 32, "nullable": "no"}, + {"name": "file_name", "title": "导入文件名", "type": "str", "length": 255, "nullable": "no"}, + {"name": "total", "title": "总记录数", "type": "int", "nullable": "no", "default": "0"}, + {"name": "success", "title": "成功数", "type": "int", "nullable": "no", "default": "0"}, + {"name": "failed", "title": "失败数", "type": "int", "nullable": "no", "default": "0"}, + {"name": "status", "title": "导入状态", "type": "str", "length": 16, "nullable": "no", "default": "processing"}, + {"name": "detail", "title": "失败明细", "type": "text", "nullable": "yes"}, + {"name": "created_by", "title": "导入人ID", "type": "str", "length": 32, "nullable": "yes"}, + {"name": "created_at", "title": "导入时间", "type": "timestamp", "nullable": "no"} + ], + "indexes": [{"name": "idx_entity_ilog_world", "idxtype": "index", "idxfields": ["world_id"]}], + "codes": [ + {"field": "world_id", "table": "world", "valuefield": "id", "textfield": "name"}, + {"field": "status", "table": "appcodes_kv", "valuefield": "k", "textfield": "v", "cond": "parentid='import_status'"} + ] +} diff --git a/pyproject.toml b/pyproject.toml index 01b7dfb..ce6735a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1 +1,13 @@ -打包配置 name=entity \ No newline at end of file +[build-system] +requires = ["setuptools>=45", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "entity" +version = "1.0.0" +requires-python = ">=3.8" +dependencies = ["sqlor", "bricks_for_python"] + +[tool.setuptools.packages.find] +where = ["."] +include = ["entity*"] diff --git a/scripts/__pycache__/load_path.cpython-310.pyc b/scripts/__pycache__/load_path.cpython-310.pyc deleted file mode 100644 index 31a2303156827ff232c73cceb19366b5dd75dca9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1910 zcmb7F|7#pY6rb7KyW6{bX`n&;T9zu*vyl8ISVW9%u!5YD2&D(ItT)r^#=G5hc8>Or z7Sdi)t)yNM(b^`Es~RFEO{hYGhW2mhuls9n_i{hwcR}>cZjx@yUC@DfJM%uD_vXDf zbMLIazBGdIn0`G`R}gw4HqjA>&D$`YF%Sgh5Wz$$;v5dXQcj|HBDNq0GENf>+`zeH z0g>1}oKp%Ymm)HWPoZ3z_K?J<5<5ZDHG~n7#)FwjmY2XWHMzkns=w6m=D$Ph(XtvHT4%wCM&6`gqucy=gYTaLMH2?UawQ$X= z-}dkR<}F_L=WhDnuBn2mdY}K^`f6=^bEZ8v>CMjgD?b4TP=9WDYw8Y2aMN16=C9mq zPu^)y-)b!{0(onF)q6PQO)vSkA9|a0aa0>(McK@A)q&wKxkbl`NQlYH8^1R#v$kJugL#0-Cv&rrh8Ye+r~}+ z!CMG(d0{uE2nxA};IR&1o>92E?s%9;cRY-?J08X#;;%%{7dPTio#iiTmn>E!nn4a{ zIeBW*J=TdXzIL8Auh=_D9q&SPxX#N?cx0vvXQ%iPYF_+scZUQE=ym8cOy>p&jwbMe zL@KSe)#k=6ufE#)W=V9b)%8~6e)G|Mb7RH(@xB^ul&ZJ>z?+%&W`AjJe7W^# zE$nP=vS<|ylM*+ko0aO(@Cr_;j256rQAUYc?EyalM?I-l(<<i_dj^|z3k{2Hx+J2vgh4c zk(wDPuww}@{u!};`20E8sp-3NNjJidWG0^Dd>QT o7$f2cY+dEel10iz`j)szXAp!eDYAkGB_$>wQ)0@IG?xGV2h7{ClK=n! diff --git a/scripts/load_path.py b/scripts/load_path.py index 599d3ec..1201a9c 100644 --- a/scripts/load_path.py +++ b/scripts/load_path.py @@ -1 +1,64 @@ -RBAC 显式路径注册(无通配符) \ No newline at end of file +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""entity 模块 RBAC 权限注册脚本(显式路径,禁止通配符)。""" + +import os +import sys + +MODULE = 'entity' + +PATHS_ANY = [] + +PATHS_LOGINED = [ + '/%s' % MODULE, + '/%s/index.ui' % MODULE, + '/%s/import_page.ui' % MODULE, + '/%s/entity' % MODULE, + '/%s/entity/index.ui' % MODULE, + '/%s/entity/get_entity.dspy' % MODULE, + '/%s/entity/add_entity.dspy' % MODULE, + '/%s/entity/update_entity.dspy' % MODULE, + '/%s/entity/delete_entity.dspy' % MODULE, + '/%s/entity_import' % MODULE, + '/%s/entity_import/index.ui' % MODULE, + '/%s/entity_import/get_entity_import.dspy' % MODULE, + '/%s/api/entity_import.dspy' % MODULE, + '/%s/api/entity_create.dspy' % MODULE, + '/%s/api/entity_update.dspy' % MODULE, + '/%s/api/entity_delete.dspy' % MODULE, + '/%s/api/entity_import_guard.dspy' % MODULE, + '/%s/api/get_search_world_id.dspy' % MODULE, + '/%s/api/get_search_scene_id.dspy' % MODULE, + '/%s/api/get_search_entity_type.dspy' % MODULE, + '/%s/api/get_search_status.dspy' % MODULE, +] + + +def find_sage_root(): + candidates = [ + os.path.expanduser('~/repos/sage'), + os.path.expanduser('~/sage'), + ] + for c in candidates: + if os.path.isdir(os.path.join(c, 'wwwroot')) and os.path.isdir(os.path.join(c, 'py3', 'bin')): + return c + return None + + +def main(): + sage_root = find_sage_root() + if not sage_root: + print('Sage root not found, skip entity RBAC registration') + return + sys.path.insert(0, sage_root) + from set_role_perm import set_role_perm # noqa: E402 + + for p in PATHS_ANY: + set_role_perm(p, 'any') + for p in PATHS_LOGINED: + set_role_perm(p, 'logined') + print('entity RBAC registered: %d any + %d logined' % (len(PATHS_ANY), len(PATHS_LOGINED))) + + +if __name__ == '__main__': + main() diff --git a/skill/SKILL.md b/skill/SKILL.md index 608d292..e785e57 100644 --- a/skill/SKILL.md +++ b/skill/SKILL.md @@ -1 +1,29 @@ -模块技能文档 \ No newline at end of file +--- +name: entity +description: entity 模块技能文档——实体管理(实体表 CRUD + 实体导入 entity_import + api/entity_import.dspy),通过 load_entity() 挂载。 +--- + +# entity 模块 + +## 概述 +实体管理模块,提供实体表(entity)增删改查与文件导入(entity_import 记录表)。依赖 world、scene 模块。 + +## 数据模型 +- `entity`:id / world_id(→world.id)/ scene_id(→scene.id)/ name / code / entity_type(appcodes entity_type)/ status(appcodes entity_status)/ attributes_json(text) / created_at / updated_at。唯一索引 code。 +- `entity_import`:id / world_id / scene_id / file_name / total / success / fail / status(appcodes import_status)/ created_at。只读记录。 + +## 关键接口 +- `load_entity()`:注册 create_entity/update_entity/delete_entity/entity_import 到 ServerEnv(含复数别名)。 +- `wwwroot/api/entity_import.dspy`:文件导入 → 解析(JSON 数组或 CSV 表头)→ 逐条校验 name/code → sor.C('entity') → 统计 → sor.C('entity_import')。返回 `{success,total,success_count,fail,file_name}`。 +- `wwwroot/api/entity_{create,update,delete}.dspy`:实体 CRUD(委托 init.py 函数)。 +- `wwwroot/api/get_search_{world_id,scene_id,entity_type,status}.dspy`:下拉数据源。 + +## 陷阱 +- 库名禁止硬编码:.py 用 `ServerEnv().get_module_dbname('entity')`,.dspy 用 `get_module_dbname('entity')` / `get_sor_context(request._run_ns, 'entity')`。 +- dspy 内禁止 import(仅 `from sqlor.filter import DBFilter` 例外),业务逻辑放 `entity/init.py` 并通过 `load_entity()` 导出。 +- `sor.C()` 不会自动填 created_at,必须显式 `ns['created_at'] = curDateString()`。 +- 实体导入记录(entity_import)只读,不可手工增删改。 + +## 依赖 +- world、scene(下拉数据源 world / scene 表) +- 基础:sqlor、ahserver、bricks、appbase(appcodes) diff --git a/wwwroot/api/entity_create.dspy b/wwwroot/api/entity_create.dspy index a1c223f..34aeb6e 100644 --- a/wwwroot/api/entity_create.dspy +++ b/wwwroot/api/entity_create.dspy @@ -1 +1,2 @@ -新增 \ No newline at end of file +result = await create_entity(request, params_kw) +return result diff --git a/wwwroot/api/entity_delete.dspy b/wwwroot/api/entity_delete.dspy index 7e10aba..7bcd6ba 100644 --- a/wwwroot/api/entity_delete.dspy +++ b/wwwroot/api/entity_delete.dspy @@ -1 +1,2 @@ -删除 \ No newline at end of file +result = await delete_entity(request, params_kw) +return result diff --git a/wwwroot/api/entity_get.dspy b/wwwroot/api/entity_get.dspy deleted file mode 100644 index 6e7cdb5..0000000 --- a/wwwroot/api/entity_get.dspy +++ /dev/null @@ -1 +0,0 @@ -详情 \ No newline at end of file diff --git a/wwwroot/api/entity_import.dspy b/wwwroot/api/entity_import.dspy index 04e4468..9670b0b 100644 --- a/wwwroot/api/entity_import.dspy +++ b/wwwroot/api/entity_import.dspy @@ -1 +1,3 @@ -文件导入(两阶段+回滚) \ No newline at end of file +debug('entity_import.dspy: START') +result = await entity_import(request, params_kw) +return result diff --git a/wwwroot/api/entity_import_guard.dspy b/wwwroot/api/entity_import_guard.dspy index d877c8f..01f8ff7 100644 --- a/wwwroot/api/entity_import_guard.dspy +++ b/wwwroot/api/entity_import_guard.dspy @@ -1 +1 @@ -导入预校验(干跑) \ No newline at end of file +return {'success': False, 'message': '导入记录为只读,不支持手工增删改'} diff --git a/wwwroot/api/entity_import_list.dspy b/wwwroot/api/entity_import_list.dspy deleted file mode 100644 index 4723720..0000000 --- a/wwwroot/api/entity_import_list.dspy +++ /dev/null @@ -1 +0,0 @@ -导入记录只读分页 \ No newline at end of file diff --git a/wwwroot/api/entity_import_reject.dspy b/wwwroot/api/entity_import_reject.dspy deleted file mode 100644 index bc9dcc9..0000000 --- a/wwwroot/api/entity_import_reject.dspy +++ /dev/null @@ -1 +0,0 @@ -导入记录禁手工增删改 \ No newline at end of file diff --git a/wwwroot/api/entity_list.dspy b/wwwroot/api/entity_list.dspy deleted file mode 100644 index ce0cf27..0000000 --- a/wwwroot/api/entity_list.dspy +++ /dev/null @@ -1 +0,0 @@ -分页列表 {list,total} \ No newline at end of file diff --git a/wwwroot/api/entity_update.dspy b/wwwroot/api/entity_update.dspy index 90d2c7f..6ff8ad2 100644 --- a/wwwroot/api/entity_update.dspy +++ b/wwwroot/api/entity_update.dspy @@ -1 +1,2 @@ -更新 \ No newline at end of file +result = await update_entity(request, params_kw) +return result diff --git a/wwwroot/api/get_search_entity_type.dspy b/wwwroot/api/get_search_entity_type.dspy index 7fc98b9..ec400e4 100644 --- a/wwwroot/api/get_search_entity_type.dspy +++ b/wwwroot/api/get_search_entity_type.dspy @@ -1 +1,8 @@ -实体类型下拉 \ No newline at end of file +result = [{'value': '', 'text': '全部'}] +try: + async with get_sor_context(request._run_ns, 'entity') as sor: + rows = await sor.sqlExe("select k as value, v as text from appcodes_kv where parentid='entity_type' order by k", {}) + return json.dumps(result + [{'value': r.value, 'text': r.text} for r in rows], ensure_ascii=False) +except Exception as e: + debug('get_search_entity_type error: %s' % str(e)) +return json.dumps(result, ensure_ascii=False) diff --git a/wwwroot/api/get_search_import_status.dspy b/wwwroot/api/get_search_import_status.dspy deleted file mode 100644 index 8fdd790..0000000 --- a/wwwroot/api/get_search_import_status.dspy +++ /dev/null @@ -1 +0,0 @@ -导入状态下拉 \ No newline at end of file diff --git a/wwwroot/api/get_search_scene_id.dspy b/wwwroot/api/get_search_scene_id.dspy index aff462f..a4e79d4 100644 --- a/wwwroot/api/get_search_scene_id.dspy +++ b/wwwroot/api/get_search_scene_id.dspy @@ -1 +1,8 @@ -场景下拉 \ No newline at end of file +result = [{'value': '', 'text': '全部'}] +try: + async with get_sor_context(request._run_ns, 'entity') as sor: + rows = await sor.sqlExe("select id as value, name as text from scene order by name", {}) + return json.dumps(result + [{'value': r.value, 'text': r.text} for r in rows], ensure_ascii=False) +except Exception as e: + debug('get_search_scene_id error: %s' % str(e)) +return json.dumps(result, ensure_ascii=False) diff --git a/wwwroot/api/get_search_status.dspy b/wwwroot/api/get_search_status.dspy index 14f234c..f81a7b5 100644 --- a/wwwroot/api/get_search_status.dspy +++ b/wwwroot/api/get_search_status.dspy @@ -1 +1,8 @@ -状态下拉 \ No newline at end of file +result = [{'value': '', 'text': '全部'}] +try: + async with get_sor_context(request._run_ns, 'entity') as sor: + rows = await sor.sqlExe("select k as value, v as text from appcodes_kv where parentid='entity_status' order by k", {}) + return json.dumps(result + [{'value': r.value, 'text': r.text} for r in rows], ensure_ascii=False) +except Exception as e: + debug('get_search_status error: %s' % str(e)) +return json.dumps(result, ensure_ascii=False) diff --git a/wwwroot/api/get_search_world_id.dspy b/wwwroot/api/get_search_world_id.dspy index 122d84c..ad91998 100644 --- a/wwwroot/api/get_search_world_id.dspy +++ b/wwwroot/api/get_search_world_id.dspy @@ -1 +1,8 @@ -世界下拉 \ No newline at end of file +result = [{'value': '', 'text': '全部'}] +try: + async with get_sor_context(request._run_ns, 'entity') as sor: + rows = await sor.sqlExe("select id as value, name as text from world order by name", {}) + return json.dumps(result + [{'value': r.value, 'text': r.text} for r in rows], ensure_ascii=False) +except Exception as e: + debug('get_search_world_id error: %s' % str(e)) +return json.dumps(result, ensure_ascii=False) diff --git a/wwwroot/i18n/en-US.json b/wwwroot/i18n/en-US.json deleted file mode 100644 index edd4e24..0000000 --- a/wwwroot/i18n/en-US.json +++ /dev/null @@ -1 +0,0 @@ -英文 i18n(全部新文案) \ No newline at end of file diff --git a/wwwroot/i18n/zh-CN.json b/wwwroot/i18n/zh-CN.json deleted file mode 100644 index b879de8..0000000 --- a/wwwroot/i18n/zh-CN.json +++ /dev/null @@ -1 +0,0 @@ -简体中文 i18n(全部新文案) \ No newline at end of file diff --git a/wwwroot/import_page.ui b/wwwroot/import_page.ui index 5f621a2..4c14d2e 100644 --- a/wwwroot/import_page.ui +++ b/wwwroot/import_page.ui @@ -1 +1,20 @@ -导入页(上传+干跑校验+最近记录) \ No newline at end of file +{ + "widgettype": "VBox", + "options": {"width": "100%", "height": "100%", "padding": "20px"}, + "subwidgets": [ + {"widgettype": "Text", "options": {"label": "实体导入", "fontSize": "24px"}}, + {"widgettype": "Text", "options": {"label": "支持 CSV(首行表头)或 JSON 数组,字段:name、code、entity_type、status、attributes_json"}}, + {"widgettype": "Form", "options": { + "title": "导入实体数据", + "submit_url": "{{entire_url('/entity/api/entity_import.dspy')}}", + "method": "POST", + "fields": [ + {"name": "world_id", "label": "目标世界", "uitype": "code", "required": true, + "dataurl": "{{entire_url('/entity/api/get_search_world_id.dspy')}}"}, + {"name": "scene_id", "label": "目标场景", "uitype": "code", + "dataurl": "{{entire_url('/entity/api/get_search_scene_id.dspy')}}"}, + {"name": "file", "label": "导入文件", "uitype": "file", "required": true} + ] + }} + ] +} diff --git a/wwwroot/index.ui b/wwwroot/index.ui index a12f2c6..51c1353 100644 --- a/wwwroot/index.ui +++ b/wwwroot/index.ui @@ -1 +1,22 @@ -入口导航页 \ No newline at end of file +{ + "widgettype": "VBox", + "options": {"width": "100%", "height": "100%", "padding": "20px"}, + "subwidgets": [ + {"widgettype": "Text", "options": {"label": "实体管理", "fontSize": "24px"}}, + {"widgettype": "ResponsableBox", "options": {"gap": "16px", "minWidth": "250px"}, "subwidgets": [ + {"widgettype": "VBox", "options": {"backgroundColor": "#FFFFFF", "padding": "20px", "cursor": "pointer"}, + "binds": [{"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "app.entity_content", + "options": {"url": "{{entire_url('/entity/entity/index.ui')}}"}, "mode": "replace"}], + "subwidgets": [{"widgettype": "Text", "options": {"label": "实体列表"}}]}, + {"widgettype": "VBox", "options": {"backgroundColor": "#FFFFFF", "padding": "20px", "cursor": "pointer"}, + "binds": [{"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "app.entity_content", + "options": {"url": "{{entire_url('/entity/import_page.ui')}}"}, "mode": "replace"}], + "subwidgets": [{"widgettype": "Text", "options": {"label": "实体导入"}}]}, + {"widgettype": "VBox", "options": {"backgroundColor": "#FFFFFF", "padding": "20px", "cursor": "pointer"}, + "binds": [{"wid": "self", "event": "click", "actiontype": "urlwidget", "target": "app.entity_content", + "options": {"url": "{{entire_url('/entity/entity_import/index.ui')}}"}, "mode": "replace"}], + "subwidgets": [{"widgettype": "Text", "options": {"label": "导入记录"}}]} + ]}, + {"widgettype": "VBox", "id": "app.entity_content", "options": {"width": "100%", "flex": "1", "marginTop": "20px"}} + ] +}