pipeline-sdlc/wwwroot/api/office_save.dspy
ymq 964f475067 feat(workspace): xlsx在线查看/编辑接入(2026-09-18用户要求)
- office_read.dspy: 按扩展名分流——xlsx/xlsm调univer-office的xlsx-to-workbookdata
  返回workbookData;docx原链路不变(顺带f-string改拼接合规)
- office_save.dspy: workbookData/documentData双字段分流,xlsx调workbook-data-to-xlsx回写
- workspace_view/open.dspy: office_exts扩为['.docx','.xlsx','.xlsm'],
  xlsx与docx同享在线查看(mode=view只读)+在线编辑+下载按钮
- 配套: univer-office仓11d83bc(IWorkbookData双向转换器+sheets插件+44断言往返自测)
2026-09-18 10:59:51 +08:00

85 lines
3.3 KiB
Plaintext
Raw Permalink 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_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 -> xlsxdocx: 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)}