feat(域模型v2+工作台): 聚合API game_create/game_workbench/scene_add/entity_add + 4个dspy端点

This commit is contained in:
pipeline-agent 2026-08-30 14:13:12 +08:00
parent ade3b4be1e
commit b9d4a650c5
6 changed files with 159 additions and 0 deletions

140
scense_game/aggregation.py Normal file
View File

@ -0,0 +1,140 @@
# -*- coding: utf-8 -*-
"""域模型 v2 聚合 APIgame 为唯一聚合根。
- game_create建游戏 自动建 1:1 空世界world.game_id 回填
- game_workbenchgame 上下文全量查询world/scenes/entities/scripts
- scene_add / entity_add强归属创建scene 必属 game worldentity 必属该 world scene 或世界级
"""
import json
from appPublic.uniqueID import getID
from appPublic.log import info, error, exception
from sqlor.dbpools import DBPools
def _get_dbname():
from ahserver.serverenv import ServerEnv
return ServerEnv().get_module_dbname('scense_game')
def _now():
import time
return time.strftime('%Y-%m-%d %H:%M:%S')
async def game_create(ns):
"""建游戏并自动创建 1:1 空世界。ns: {name, code?, description?, icon?}"""
name = (ns or {}).get('name') or ''
if not str(name).strip():
return {'code': 'PARAM_REQUIRED', 'message': '游戏名称必填', 'field': 'name'}
gid = getID()
wid = getID()
now = _now()
dbname = _get_dbname()
try:
async with DBPools().sqlorContext(dbname) as sor:
await sor.C('game', {
'id': gid, 'name': str(name).strip(),
'code': str((ns or {}).get('code') or ('G-' + gid[:8].upper())),
'description': str((ns or {}).get('description') or ''),
'icon': str((ns or {}).get('icon') or 'fa-gamepad'),
'status': 'draft', 'created_at': now, 'updated_at': now})
await sor.C('world', {
'id': wid, 'game_id': gid,
'name': str(name).strip() + ' 世界',
'code': 'W-' + gid[:8].upper(),
'description': '游戏「%s」的世界1:1 自动创建)' % str(name).strip(),
'world_type': 'game', 'status': 'active',
'config_json': '{}', 'created_at': now, 'updated_at': now})
return {'success': True, 'data': {'game_id': gid, 'world_id': wid}}
except Exception as e:
exception('game_create: %s' % e)
return {'code': 'DB_ERROR', 'message': '创建游戏失败: %s' % e}
async def game_workbench(ns):
"""game 上下文全量:{game, world, scenes, entities, scripts}"""
gid = (ns or {}).get('game_id') or ''
if not gid:
return {'code': 'PARAM_REQUIRED', 'message': 'game_id 必填', 'field': 'game_id'}
dbname = _get_dbname()
try:
async with DBPools().sqlorContext(dbname) as sor:
g = await sor.R('game', {'id': gid})
if not g:
return {'code': 'NOT_FOUND', 'message': '游戏不存在'}
w = await sor.R('world', {'game_id': gid})
wid = w[0].id if w else None
scenes = await sor.R('scene', {'world_id': wid}) if wid else []
ents = await sor.R('entity', {'world_id': wid}) if wid else []
scripts = await sor.R('script_engine', {'game_id': gid}) if gid else []
return {'success': True, 'data': {
'game': dict(g[0]) if g else None,
'world': dict(w[0]) if w else None,
'scenes': [dict(s) for s in (scenes or [])],
'entities': [dict(e) for e in (ents or [])],
'scripts': [dict(s) for s in (scripts or [])]}}
except Exception as e:
exception('game_workbench: %s' % e)
return {'code': 'DB_ERROR', 'message': '查询失败: %s' % e}
async def scene_add(ns):
"""在游戏的世界下加场景。ns: {game_id, name, scene_type?}"""
gid = (ns or {}).get('game_id') or ''
name = (ns or {}).get('name') or ''
if not gid or not str(name).strip():
return {'code': 'PARAM_REQUIRED', 'message': 'game_id 与 name 必填'}
dbname = _get_dbname()
try:
async with DBPools().sqlorContext(dbname) as sor:
w = await sor.R('world', {'game_id': gid})
if not w:
return {'code': 'NOT_FOUND', 'message': '游戏无世界,先建游戏'}
sid = getID()
now = _now()
await sor.C('scene', {
'id': sid, 'world_id': w[0].id, 'name': str(name).strip(),
'code': 'S-' + sid[:8].upper(),
'description': str((ns or {}).get('description') or ''),
'scene_type': str((ns or {}).get('scene_type') or '0'),
'status': 'active', 'config_json': '{}',
'created_at': now, 'updated_at': now})
return {'success': True, 'data': {'scene_id': sid, 'world_id': w[0].id}}
except Exception as e:
exception('scene_add: %s' % e)
return {'code': 'DB_ERROR', 'message': '创建场景失败: %s' % e}
async def entity_add(ns):
"""在游戏世界内加实体。ns: {game_id, scene_id?(空=世界级实体), name, entity_type?, attributes_json?}"""
gid = (ns or {}).get('game_id') or ''
name = (ns or {}).get('name') or ''
if not gid or not str(name).strip():
return {'code': 'PARAM_REQUIRED', 'message': 'game_id 与 name 必填'}
dbname = _get_dbname()
try:
async with DBPools().sqlorContext(dbname) as sor:
w = await sor.R('world', {'game_id': gid})
if not w:
return {'code': 'NOT_FOUND', 'message': '游戏无世界'}
wid = w[0].id
scene_id = (ns or {}).get('scene_id') or ''
if scene_id:
sc = await sor.R('scene', {'id': scene_id})
if not sc or sc[0].world_id != wid:
return {'code': 'NOT_FOUND', 'message': '场景不属于该游戏的世界'}
eid = getID()
now = _now()
await sor.C('entity', {
'id': eid, 'world_id': wid, 'scene_id': scene_id or None,
'name': str(name).strip(), 'code': 'E-' + eid[:8].upper(),
'entity_type': str((ns or {}).get('entity_type') or '0'),
'status': 'active',
'attributes_json': (ns or {}).get('attributes_json') or '{}',
'created_at': now, 'updated_at': now})
return {'success': True, 'data': {'entity_id': eid, 'world_id': wid,
'scene_id': scene_id or None}}
except Exception as e:
exception('entity_add: %s' % e)
return {'code': 'DB_ERROR', 'message': '创建实体失败: %s' % e}

View File

@ -1,5 +1,8 @@
# -*- coding: utf-8 -*-
"""scense_game 游戏展示模块初始化:注册全部服务函数到 ServerEnv。"""
from scense_game.aggregation import (
game_create, game_workbench, scene_add, entity_add,
)
from scense_game.core import (
game_list_games, game_list_sessions, game_list_results,
game_start_session, game_pause_session, game_resume_session,
@ -14,6 +17,10 @@ def load_scense_game(env=None):
from ahserver.serverenv import ServerEnv
if env is None:
env = ServerEnv()
env.game_create = game_create
env.game_workbench = game_workbench
env.scene_add = scene_add
env.entity_add = entity_add
env.game_list_games = game_list_games
env.game_list_sessions = game_list_sessions
env.game_list_results = game_list_results

View File

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
result = await entity_add(dict(params_kw))
return json.dumps(result, ensure_ascii=False, default=str)

View File

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
result = await game_create(dict(params_kw))
return json.dumps(result, ensure_ascii=False, default=str)

View File

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
result = await game_workbench(dict(params_kw))
return json.dumps(result, ensure_ascii=False, default=str)

View File

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
result = await scene_add(dict(params_kw))
return json.dumps(result, ensure_ascii=False, default=str)