--- name: entity description: 实体管理模块(W-03)——实体表(entity)CRUD、列表查询、实体文件导入(entity_import 记录),通过 load_entity() 挂载。 --- # entity 实体管理模块 ## 概述 元景项目 W-03 实体管理:管理实体(entity)数据。实体归属于场景(scene_id→scene.id,逻辑关联,不加外键)与世界(world_id→world.id)。提供实体表 CRUD、分页列表查询、CSV/JSON 文件导入(两阶段校验+批量插入+失败回滚)。 ## 数据模型 | 表 | 说明 | |----|------| | `entity` | 实体表。`entity_code` 唯一索引;`entity_type`/`status` 引用 appcodes_kv 字典;`scene_id`/`world_id` 为逻辑外键(codes 段,无物理外键);`import_id` 标记导入批次;`org_id` 机构隔离 | | `entity_import` | 导入批次记录(系统生成,只读)。`import_no` 唯一;`status` ∈ importing/success/fail | | `entity_import_log` | 导入逐行明细(系统生成,只读)。`result` 1=成功 0=失败 | 编码字典(init/data.json 幂等落库 appcodes/appcodes_kv): - `entity_type`:character/prop/vehicle/building/faction/other - `common_status`:1=启用 / 0=停用 - `import_status`:importing/success/fail ## 关键接口(REST 统一前缀 /api/*) | 接口 | 方法 | 说明 | 返回 | |------|------|------|------| | `/entity/api/entity_list.dspy` | GET/POST | 分页列表(keyword/entity_type/status/scene_id 过滤) | `{list, total, page, rows}` | | `/entity/api/entity_create.dspy` | POST | 新增实体(校验失败 100% 拦截不落库) | `{success, data:{id}}` | | `/entity/api/entity_update.dspy` | POST | 更新实体(校验+存在性检查) | `{success, data:{id}}` | | `/entity/api/entity_delete.dspy` | POST | 删除实体 | `{success, data:{id}}` | | `/entity/api/entity_get.dspy` | GET/POST | 实体详情 | `{success, data}` | | `/entity/api/entity_import.dspy` | POST | 文件导入(file 域 / file_content) | `{success, data:{import_id,total,success,fail}}` | | `/entity/api/entity_import_guard.dspy` | POST | 导入预校验(干跑,只校验不落库) | `{valid, errors, total}` | | `/entity/api/entity_import_list.dspy` | GET/POST | 导入记录只读分页 | `{list, total, page, rows}` | 错误结构统一:`{code, message, field, detail}`。分页结构统一:`{list, total}`。 ## 导入处理逻辑 1. **解析**:CSV(首行表头,字段 entity_code/entity_name/entity_type/scene_id/world_id/description/status/sort_no)或 JSON 数组。 2. **全量校验(第一阶段,不落库)**:编码必填/长度/文件内唯一/库内唯一;名称必填;类型/状态字典白名单;scene_id/world_id 存在性。任一行非法 → 整批拦截,只写失败批次+逐行日志,不导入任何实体。 3. **批量插入(第二阶段)**:同批实体以同一 `import_id` 标记插入;任一行异常 → 按 `import_id` 补偿 DELETE 已插入实体,保证失败回滚无脏数据。 4. 成功/失败均写 `entity_import` 批次与 `entity_import_log` 明细。 ## 陷阱 - **sor.I() 只用于表结构**:插入用 `sor.C(table, ns)`,不要 `sor.I(table, data)`(I 只收 1 参)。 - **sor.U() 只收 2 参**:`sor.U('entity', {**upd, 'id': id})`,id 必须在 data 内;仅主键字段时先校验非空字段。 - **dspy 中禁 import**:全部函数经 `load_entity()` 注册为全局(`create_entity/update_entity/delete_entity/get_entity/list_entities/import_entities/entity_import_guard/reject_import_write`),dspy 直接调用;`__init__.py` 必须导出 init.py 的 async 函数,否则 NameError。 - **取库名不硬编码**:.py 用 `get_sor_context(ServerEnv(), 'entity')`(内部解析 `get_module_dbname('entity')`);.dspy 用 `get_sor_context(request._run_ns, 'entity')`。禁止写 `DBNAME='...'`。 - **请求上下文**:.py 内取用户/机构用 `request._run_ns`(get_user/get_userorgid),不要用 `ServerEnv()`(进程级单例)。 - **`_text` 后缀字段**:表格提交会带 `xxx_text`,`_clean_ns` 会剔除后再写库。 - **curDateString()**:sqlor 不自动写时间戳,插入必须显式 `created_at`,否则记录静默丢失。 - **删除校验**:entity 可能被 world_snapshot 等引用,删除前可扩展引用检查(当前实现仅校验存在性)。 ## 依赖 - 基础:sqlor、ahserver、appPublic - 数据依赖(逻辑关联,不物理外键):`scene`(scene.id)、`world`(world.id)——codes 段引用,表缺失时仅下拉不渲染,不影响 CRUD - 字典:appbase 的 appcodes / appcodes_kv - 被 `world_snapshot`(W-03 快照)聚合读取