pipeline-sdlc/wwwroot/api/office_read.dspy
ymq b02ee5f345 fix(workspace): 二级URL与office端点透传pipeline_id+_generic分支——通用助手查看/编辑文件报'文件不存在'根因
与 f0c4c10(upload_url) 同族缺陷全量排查修复,共5处:
1. workspace_view.dspy: md_url(file_url)只拼session_id漏pipeline_id→
   通用助手(_generic无session_id)下MdWidget二次请求workspace_file.dspy
   回退全局项目指针→在别产线空间找文件→弹'文件不存在: projects/调研1/docs/xxx.md'
   (2026-09-17实测复现+修复验证)
2. workspace_open.dspy: file_url/ws_url(xterm)/univer_url三处同病灶,统一_fq_s双透传
3. workspace_edit.xterm: 根本不读pipeline_id且用无隔离get_project_dir→
   改get_project_dir_pl+_generic锁_general/{uid}(与9个子端点对齐)
4. office_read.dspy/office_save.dspy: 读了pipeline_id但缺_generic分支→
   通用助手在线编辑docx报'未指定文件或无工作空间',补齐分支
univer-office前端main.ts配套透传pipeline_id(独立仓库另行部署)
2026-09-17 23:27:19 +08:00

61 lines
2.2 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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": ""}
# 读 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}