# office_save.dspy - univer-office 编辑后保存:IDocumentData -> docx 写回工作空间 import os import json as _json import aiohttp file_id = (params_kw or {}).get('file', '').strip() docdata_str = (params_kw or {}).get('documentData', '') or '' # xlsx 保存:前端传 workbookData 字段(2026-09-18 xlsx 在线编辑接入) workbookdata_str = (params_kw or {}).get('workbookData', '') or '' uid = await get_user() if not uid: uid = 'user-01' session_id = (params_kw or {}).get('session_id', '') or '' pipeline_id = (params_kw or {}).get('pipeline_id', '') or '' dbname = get_module_dbname('pipeline-sdlc') async with DBPools().sqlorContext(dbname) as sor: # 产线隔离:跨产线项目视为无项目(与弹窗入口一致,防绕过) project_dir, _ = await get_project_dir_pl(sor, uid, session_id, pipeline_id) space_dir, _ = await get_space_dir(sor, uid, session_id) # 通用会话(pipeline_id=_generic):锁用户专属目录 _general/{uid} # (2026-09-17 补齐:此前缺该分支,通用助手在线编辑 docx 保存失败) if pipeline_id == '_generic': _gd = generic_workspace_dir(uid) os.makedirs(_gd, exist_ok=True) project_dir = _gd space_dir = _gd if not file_id or not space_dir: return {"success": False, "error": "未指定文件或无工作空间"} # 按扩展名分流:xlsx 用 workbookData 字段,docx 用 documentData 字段 _is_sheet = file_id.lower().endswith(('.xlsx', '.xlsm')) _payload_str = workbookdata_str if _is_sheet else docdata_str if not _payload_str: return {"success": False, "error": "缺少文档数据"} try: _payload = _json.loads(_payload_str) except Exception as e: return {"success": False, "error": "文档数据解析失败: " + str(e)} full_path = resolve_workspace_path(project_dir, space_dir, file_id) # 路径穿越校验 real_ws = os.path.realpath(space_dir) real_full = os.path.realpath(full_path) if not real_full.startswith(real_ws + os.sep): return {"success": False, "error": "非法路径"} # 只允许覆盖已存在的 office 文件(防任意写入) if not os.path.isfile(full_path): return {"success": False, "error": "目标文件不存在"} title = os.path.basename(full_path) # 调 univer-office 生成 office 文件(按扩展名分流,保留格式) # xlsx: IWorkbookData -> xlsx;docx: IDocumentData -> docx try: timeout = aiohttp.ClientTimeout(total=120) async with aiohttp.ClientSession(timeout=timeout) as session: if _is_sheet: _url = 'http://127.0.0.1:19091/convert/workbook-data-to-xlsx' _req_json = {'workbookData': _payload} else: _url = 'http://127.0.0.1:19091/convert/doc-data-to-docx' _req_json = {'documentData': _payload, 'title': title} async with session.post(_url, json=_req_json) as resp: if resp.status != 200: return {"success": False, "error": "导出失败 HTTP " + str(resp.status)} out_bytes = await resp.read() except Exception as e: return {"success": False, "error": str(e)} # 写回工作空间 try: with open(full_path, 'wb') as f: f.write(out_bytes) return {"success": True, "bytes": len(out_bytes)} except Exception as e: return {"success": False, "error": str(e)}