feat(office): 补提交 univer-office 办公代理(已部署上线)
/office/* 反向代理到本机 univer-office Node 服务(9091),承接 markdown<->IDocumentData 转换 + docx/xlsx/ppt 导出。含 office_proxy.py + config.json 路由 + pipeline_app 注册 + restart-pipeline.sh
This commit is contained in:
parent
c973a20fd1
commit
b849eb3e92
51
app/office_proxy.py
Normal file
51
app/office_proxy.py
Normal file
@ -0,0 +1,51 @@
|
||||
"""office_proxy - 把 /office/* 请求转发到本机 univer-office Node 服务(9091)。
|
||||
|
||||
univer-office 承担 markdown<->IDocumentData 转换与 docx/xlsx/ppt 导出,
|
||||
这些能力是 Node 生态(Univer + docx/exceljs/pptxgenjs),Python 侧无法直接实现,
|
||||
故通过 ahserver 的 startswiths 机制做反向代理。
|
||||
|
||||
路由映射:
|
||||
/office/render -> http://127.0.0.1:9091/render (markdown -> IDocumentData)
|
||||
/office/extract -> http://127.0.0.1:9091/extract (IDocumentData -> 纯文本)
|
||||
/office/export/docx -> http://127.0.0.1:9091/export/docx (markdown -> docx 二进制)
|
||||
"""
|
||||
import aiohttp
|
||||
from aiohttp import web
|
||||
from appPublic.registerfunction import registerFunction
|
||||
from appPublic.log import error, debug
|
||||
|
||||
OFFICE_BACKEND = 'http://127.0.0.1:9091'
|
||||
|
||||
# hop-by-hop 头,转发时需剔除
|
||||
_HOP_BY_HOP = {
|
||||
'host', 'content-length', 'transfer-encoding', 'connection',
|
||||
'keep-alive', 'upgrade', 'proxy-authenticate', 'proxy-authorization',
|
||||
'te', 'trailers',
|
||||
}
|
||||
|
||||
|
||||
async def office_proxy(request, params_kw, *args, **env):
|
||||
"""把 /office/* 转发到 9091。args = leading 之后的路径段,如 ('render',) 或 ('export','docx')。"""
|
||||
path = '/'.join(args)
|
||||
url = f'{OFFICE_BACKEND}/{path}'
|
||||
debug(f'office_proxy: {request.method} {url}')
|
||||
|
||||
body = await request.read()
|
||||
headers = {k: v for k, v in request.headers.items() if k.lower() not in _HOP_BY_HOP}
|
||||
|
||||
try:
|
||||
timeout = aiohttp.ClientTimeout(total=180)
|
||||
async with aiohttp.ClientSession(timeout=timeout) as session:
|
||||
async with session.request(request.method, url, headers=headers, data=body) as resp:
|
||||
resp_body = await resp.read()
|
||||
resp_headers = {
|
||||
k: v for k, v in resp.headers.items()
|
||||
if k.lower() not in _HOP_BY_HOP and k.lower() != 'content-encoding'
|
||||
}
|
||||
return web.Response(body=resp_body, status=resp.status, headers=resp_headers)
|
||||
except Exception as e:
|
||||
error(f'office_proxy failed: {e}')
|
||||
return web.Response(body=f'office backend error: {e}'.encode(), status=502)
|
||||
|
||||
|
||||
registerFunction('office_proxy', office_proxy)
|
||||
@ -39,6 +39,7 @@ def init():
|
||||
from tenant.init import load_tenant
|
||||
from ktv_adapter import load_ktv_adapter
|
||||
from global_func import set_globalvariable
|
||||
import office_proxy # 注册 /office/* 转发函数(univer-office Node 服务代理)
|
||||
try:
|
||||
from smssend import load_smssend
|
||||
except ImportError:
|
||||
|
||||
@ -51,6 +51,10 @@
|
||||
{
|
||||
"leading": "/i18n_getmsgs",
|
||||
"registerfunction": "i18n"
|
||||
},
|
||||
{
|
||||
"leading": "/office/",
|
||||
"registerfunction": "office_proxy"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
27
restart-pipeline.sh
Normal file
27
restart-pipeline.sh
Normal file
@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
# 重启 pipeline-app(避免 pkill -f pipeline_app 自杀:精确匹配 python 主进程路径)
|
||||
cd /d/pipeline/pipeline-app
|
||||
|
||||
echo "=== 杀旧进程 ==="
|
||||
if [ -f pipeline.pid ]; then
|
||||
kill $(cat pipeline.pid) 2>/dev/null
|
||||
fi
|
||||
pkill -f "app/pipeline_app.py" 2>/dev/null
|
||||
sleep 3
|
||||
|
||||
if pgrep -f "app/pipeline_app.py" > /dev/null; then
|
||||
echo "still running, force kill"
|
||||
pkill -9 -f "app/pipeline_app.py" 2>/dev/null
|
||||
sleep 2
|
||||
fi
|
||||
rm -f pipeline.pid pipeline-web.pid pipeline-worker-*.pid
|
||||
|
||||
echo "=== 启动 ==="
|
||||
bash start.sh
|
||||
sleep 6
|
||||
|
||||
echo "=== 进程 ==="
|
||||
ps aux | grep "app/pipeline_app.py" | grep -v grep | head -2
|
||||
|
||||
echo "=== 日志 tail ==="
|
||||
tail -15 logs/pipeline.log 2>/dev/null
|
||||
Loading…
x
Reference in New Issue
Block a user