# office_read.dspy - 读取工作空间 docx 文件,转 markdown 返回给 univer-office 前端 import os import aiohttp file_id = (params_kw or {}).get('file', '').strip() 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 {"error": "未指定文件或无工作空间", "markdown": ""} 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 {"error": "非法路径", "markdown": ""} if not os.path.isfile(full_path): return {"error": "文件不存在", "markdown": ""} # 按扩展名分流:xlsx → IWorkbookData(表格),其余(docx)→ IDocumentData(文档) # (2026-09-18 xlsx 在线查看/编辑接入) _is_sheet = file_id.lower().endswith(('.xlsx', '.xlsm')) # 读文件 bytes with open(full_path, 'rb') as f: file_bytes = f.read() # 调 univer-office 转换(格式保真,跳过 markdown) try: timeout = aiohttp.ClientTimeout(total=60) async with aiohttp.ClientSession(timeout=timeout) as session: if _is_sheet: async with session.post( 'http://127.0.0.1:19091/convert/xlsx-to-workbookdata', data=file_bytes, headers={'Content-Type': 'application/octet-stream'}, ) as resp: if resp.status != 200: return {"error": "转换失败 HTTP " + str(resp.status), "workbookData": None} workbook_data = await resp.json() return {"workbookData": workbook_data} else: async with session.post( 'http://127.0.0.1:19091/convert/docx-to-docdata', data=file_bytes, headers={'Content-Type': 'application/octet-stream'}, ) as resp: if resp.status != 200: return {"error": "转换失败 HTTP " + str(resp.status), "documentData": None} doc_data = await resp.json() return {"documentData": doc_data} except Exception as e: return {"error": str(e), "documentData": None, "workbookData": None}