--- name: scene description: 场景管理模块(W-02)——场景表 CRUD、列表查询、场景导入。提供 scene/scene_import 两张表、CRUD、分页列表接口、文件导入(api/scene_import.dspy),通过 load_scene() 挂载。枚举字段用 appcodes 字典(cond parentid=)。 --- # scene 模块 ## 概述 场景管理,含场景导入。依赖 world(world_id 逻辑关联,不加物理外键)、appbase(appcodes 字典)、rbac(权限)。 ## 数据模型 ### 表 `scene`(models/scene.json) | 字段 | 类型 | 说明 | |------|------|------| | id | str(32) | 主键 | | world_id | str(32) | 所属世界(→world.id,逻辑关联) | | name | str(255) | 场景名称 | | code | str(64) | 场景编码(唯一,idx_scene_code) | | description | text | 描述 | | scene_type | str(16) | 场景类型(appcodes scene_type,默认 '0') | | status | str(16) | 状态(appcodes scene_status,默认 '0') | | config_json | text | 场景配置 JSON | | created_at | timestamp | 创建时间 | | updated_at | timestamp | 更新时间 | - 主键 `["id"]`;唯一索引 `idx_scene_code(code)`;索引 `idx_scene_world(world_id)` - codes:world_id → world(id/name)、scene_type → appcodes_kv(parentid='scene_type')、status → appcodes_kv(parentid='scene_status') ### 表 `scene_import`(models/scene_import.json,只读) | 字段 | 类型 | 说明 | |------|------|------| | id | str(32) | 主键 | | world_id | str(32) | 目标世界 | | file_name | str(255) | 导入文件名 | | total / success / fail | int | 总数 / 成功 / 失败(默认 0) | | status | str(16) | 导入状态(appcodes import_status:0 进行中/1 成功/2 失败) | | created_at | timestamp | 导入时间 | - 主键 `["id"]`;索引 `idx_scene_import_world(world_id)` ## 关键接口(load_scene() 挂载到 ServerEnv) - `list_scenes(params)` → `{list, total}`(sqlPaging 分页,支持 world_id/name/code/scene_type/status 过滤 + sort/order) - `get_scene({id})` → `{data}` 或错误 - `create_scene(ns)` → 校验 + world 存在校验 + 自动 id/created_at/updated_at;返回 `{success, id}` - `update_scene(ns)` → 写 updated_at、剔除 `_text` 后缀、world 校验 - `delete_scene({id})` - `import_scenes({world_id, file_name, rows})` → `{success, total, success_count, fail}` (**事务批量**:先全量解析+校验,再逐条插入;任一条失败整批回滚,无脏数据;success_count 表示成功条数,避免与布尔 success 冲突) - `parse_scene_file(content, filename)` → `{rows: [...]}`(JSON 数组 / JSON Lines / CSV) - `get_world_options()` / `get_scene_type_options()` / `get_scene_status_options()` / `get_import_status_options()` → `[{value, text}]` ### wwwroot/api/*.dspy(REST 统一前缀 /api/*,宿主挂载后为 /scene/api/*.dspy) - `list_scenes.dspy`(分页 `{list,total}`)、`get_scene.dspy`、`create_scene.dspy`、`scene_update.dspy`、`scene_delete.dspy` - `get_search_world_id.dspy` / `get_search_scene_type.dspy` / `get_search_status.dspy`(字典下拉) - `scene_import.dspy`(world_id + file → parse_scene_file → import_scenes) ### 错误结构(统一) `{code, message, field, detail}`;code:PARAM_REQUIRED / FIELD_REQUIRED / FIELD_TOO_LONG / WORLD_NOT_FOUND / DUPLICATE_CODE / PARSE_ERROR / DB_ERROR / NOT_FOUND。非法输入 100% 拦截不落库。 ## 陷阱 - 库名禁止硬编码:.py 用 `ServerEnv().get_module_dbname("scene")`,.dspy 用 `get_module_dbname("scene")`(全局);world 表在 world 模块库(`get_module_dbname("world")`) - dspy 无 import(json/get_sor_context/debug/params_kw 均预加载全局);函数经 load_scene() 注册后为全局,直接调用 - `create_scene` / `scene_import` 必须设置 `created_at = curDateString()`,否则 sor.C 静默丢记录 - `scene_import` 记录 total/success/fail 必须为 int - `import_scenes` 返回用 `success_count`(避免与布尔 `success` 键冲突) - CRUD json `new_data_url` 指向自定义 `api/create_scene.dspy`;scene_import 为只读列表,无 editable - 三处同步注册:scene/__init__.py 导出 ← scene/init.py 实现 ← load_scene() 注册 - 返回 `{list,total}` 分页用 `sor.sqlPaging`,不要硬编码 LIMIT/OFFSET ## 依赖 - world(world_id 逻辑关联 + 世界下拉)、appbase(appcodes 字典)、rbac(权限)