# 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) 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": ""} # 读 docx bytes with open(full_path, 'rb') as f: docx_bytes = f.read() # 调 univer-office 转 IDocumentData(格式保真,跳过 markdown) try: timeout = aiohttp.ClientTimeout(total=60) async with aiohttp.ClientSession(timeout=timeout) as session: async with session.post( 'http://127.0.0.1:19091/convert/docx-to-docdata', data=docx_bytes, headers={'Content-Type': 'application/octet-stream'}, ) as resp: if resp.status != 200: return {"error": f"转换失败 HTTP {resp.status}", "documentData": None} doc_data = await resp.json() return {"documentData": doc_data} except Exception as e: return {"error": str(e), "documentData": None}