From 9a7dfef2049080a07caaf4102ba0a14f7c91f312 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Wed, 16 Sep 2026 14:50:49 +0800 Subject: [PATCH] =?UTF-8?q?fix(compose):=20=E6=A0=87=E4=B9=A6docx=E7=94=9F?= =?UTF-8?q?=E6=88=90=E4=B8=A4=E8=BF=9E=E4=BF=AE(2026-09-16=E7=94=98?= =?UTF-8?q?=E8=82=83=E9=A1=B9=E7=9B=AE=E5=AE=9E=E6=B5=8B)=E2=80=94?= =?UTF-8?q?=E2=80=94=E2=91=A0pyproject=E5=A3=B0=E6=98=8Epython-docx?= =?UTF-8?q?=E4=BE=9D=E8=B5=96(=E5=8E=9Fdependencies=3D[]:=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E6=9C=BA=E6=97=A0docx=E6=A8=A1=E5=9D=97=E2=86=92=5Fwr?= =?UTF-8?q?ite=5Fdocx=20ImportError=E8=AF=9A=E5=AE=9E=E9=99=8D=E7=BA=A7?= =?UTF-8?q?=E5=8F=AA=E5=87=BAmd,=E4=BD=86=E6=A0=87=E4=B9=A6=E6=9C=80?= =?UTF-8?q?=E7=BB=88=E4=BA=A4=E4=BB=98=E5=BF=85=E9=A1=BBdocx;=E4=B8=8Epric?= =?UTF-8?q?ing=E6=BC=8F=E5=A3=B0=E6=98=8Epyyaml=E5=90=8C=E6=AC=BE=E6=95=99?= =?UTF-8?q?=E8=AE=AD)=20=E2=91=A1=5Ffetch=5Fimage=5Fbytes=E5=AF=B9/idfile|?= =?UTF-8?q?/download=E7=BD=91=E5=85=B3URL=E7=9B=B4=E6=8E=A5=E6=98=A0?= =?UTF-8?q?=E5=B0=84=E6=9C=AC=E6=9C=BAfilesroot=E8=AF=BB=E7=9B=98(?= =?UTF-8?q?=E5=90=88=E6=88=90=E8=BF=9B=E7=A8=8B=E6=97=A0=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E6=80=81,HTTP=E6=8B=89logined-only=E7=9A=84/idfile=E5=BF=85401?= =?UTF-8?q?=E2=86=92=E9=85=8D=E5=9B=BE=E5=85=A8=E5=8F=98'=E4=B8=8B?= =?UTF-8?q?=E8=BD=BD=E5=A4=B1=E8=B4=A5'=E6=96=87=E5=AD=97=E5=8D=A0?= =?UTF-8?q?=E4=BD=8D;=E9=98=B2=E7=A9=BF=E8=B6=8Arealpath=E6=A0=A1=E9=AA=8C?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pipeline_bidding/bid_compose_capability.py | 46 +++++++++++++++++++--- pyproject.toml | 5 ++- 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/pipeline_bidding/bid_compose_capability.py b/pipeline_bidding/bid_compose_capability.py index e614579..4d9ca4e 100644 --- a/pipeline_bidding/bid_compose_capability.py +++ b/pipeline_bidding/bid_compose_capability.py @@ -40,27 +40,63 @@ def _fetch_image_bytes(url, timeout=30): URL 是 invoke_model 产物经 downloadfile2url 落地的本地持久地址 (/idfile 静态路径,上游 24h 时效 URL 已在推理层落地,这里拿到的是 - 平台自身地址)。http(s) 下载;本地绝对路径直读。失败返回 None(诚实 - 降级:docx 里保留文字说明,不假装成功)。 + 平台自身地址)。 + + 解析优先级(2026-09-16 修复): + 1. /idfile|/download 网关 URL → 直接映射本机 filesroot 读盘——合成进程无 + 登录态,HTTP 拉 logined-only 的 /idfile 必 401,图全变「下载失败」占位 + (甘肃项目 bid_v1 docx 实测根因)。 + 2. 本地绝对路径直读。 + 3. 其余 http(s) 下载。 + 失败返回 None(诚实降级:docx 里保留文字说明,不假装成功)。 """ try: u = (url or '').strip() if not u: return None + # 网关 URL → filesroot 本地映射 + if u.startswith('http://') or u.startswith('https://'): + from urllib.parse import urlparse, unquote + path = unquote(urlparse(u).path or '') + for lead in ('/idfile/', '/download/'): + if path.startswith(lead): + local = _filesroot_path(path[len(lead):]) + if local and os.path.isfile(local): + with open(local, 'rb') as f: + return f.read() + break + if u.startswith('/') and os.path.isfile(u): + with open(u, 'rb') as f: + return f.read() if u.startswith('http://') or u.startswith('https://'): import urllib.request req = urllib.request.Request(u, headers={'User-Agent': 'pipeline-bidding'}) with urllib.request.urlopen(req, timeout=timeout) as resp: data = resp.read() return data if data else None - if u.startswith('/') and os.path.isfile(u): - with open(u, 'rb') as f: - return f.read() except Exception as e: logger.warning("compose: 配图下载失败 url=%s: %s", (url or '')[:120], e) return None +def _filesroot_path(rel): + """/idfile 网关相对路径 → 本机 filesroot 绝对路径(防穿越:realpath 校验)。""" + try: + from appPublic.jsonConfig import getConfig + root = getattr(getConfig(), 'filesroot', '') or '' + if not root: + return '' + rel = (rel or '').lstrip('/') + if not rel or '..' in rel.split('/'): + return '' + full = os.path.realpath(os.path.join(root, rel)) + if full.startswith(os.path.realpath(root) + os.sep): + return full + except Exception: + pass + return '' + + # markdown 图片行:![alt](url)(章节写作者按角色 prompt 规范嵌入 invoke_model 产物) _MD_IMG_RE = None diff --git a/pyproject.toml b/pyproject.toml index 0c90dcd..754fab6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,10 @@ name = "pipeline_bidding" version = "0.1.0" description = "投标产线:招标信息采集→审批→立项→解析→资质准备→分章节编写→评审→合成→评分→交付" -dependencies = [] +# ⚠ python-docx 必须声明:compose_bid 合成标书 docx 依赖它,缺了走 ImportError +# 诚实降级只出 md(2026-09-16 甘肃项目实测:标书最终要 docx 交付,静默降级不达标; +# 与 pricing 漏声明 pyyaml 同款教训——非 editable 环境 pip install 不带依赖必炸) +dependencies = ["python-docx"] [tool.setuptools.packages.find] where = ["."]