176 lines
5.4 KiB
Python
176 lines
5.4 KiB
Python
"""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 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 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
|