中国标书大文件存储与解析技术方案(阿里云 OSS)¶
- 版本:v1.1
- 日期:2026-04-09
- 作者:Codex
- 适用项目:
Tender_Documents
1. 背景与问题定义¶
业务场景是中国标书用户,单文件普遍几十 MB,且存在 100MB+ 文件。当前链路在大文件场景下存在稳定性与成本风险:
- 标书主链路文件大小限制为 50MB。
代码位置:app/services/tender/parser_service.py(MAX_FILE_SIZE = 50 * 1024 * 1024)。 - 对象存储由
OBJECT_STORAGE_*通用配置驱动(S3 兼容端点,如阿里云 OSS)。
代码位置:app/services/oss_service.py、app/core/config.py。 - 同一个对象会被下载两次:
resolve_enqueue下载一次做校验,worker 解析时再下载一次。
代码位置:app/services/tender/parser_service.py、app/services/ingestion/index.py。 - 前端大文件上传目前为单次 PUT 预签名思路,对 100MB+ 文件容错性不够(弱网、断线、浏览器中断恢复差)。
2. 目标与非目标¶
2.1 目标¶
- 支持 100MB+ 标书稳定上传(建议上限先到 200MB,再按观测扩到 512MB)。
- 上传链路具备断点续传、失败重试、上传恢复能力。
- 后端避免重复下载同一对象,降低带宽与内存消耗。
- 存储类型与生命周期可控,兼顾性能、成本、合规。
- 保持现有解析业务(ARQ + ingestion)最小侵入迁移。
2.2 非目标¶
- 本期不改解析算法核心(PyMuPDF/Docx Parser 逻辑)。
- 本期不做跨云多活,只做单云主存储切换(可预留扩展位)。
3. OSS 选型结论(上线建议)¶
3.1 存储类型¶
对“源标书文件(上传后可能在短期内重复读取)”:
- 默认:标准存储(建议 ZRS)。
- 生命周期:上传后 30 天转低频(IA);180 天转归档(如业务确实很少回读)。
理由:
- 标准存储适合频繁读取与低延迟解析。
- IA/Archive 有最低存储时长和取回成本,不适合刚上传立即解析的热数据。
- 标书体积大(几十 MB 到百 MB),低频 64KB 最小计量对你影响不大,但最低时长与取回时延要考虑。
3.2 地域与网络¶
- Bucket 与解析服务放同地域(优先同城)。
- 后端访问 OSS 走同地域内网 Endpoint。
- 对公网用户下载(如需)走 CDN/加速域名,不影响后端解析链路。
- 当前项目已确认采用跨地域方案:ECS 在广州,OSS 在深圳(见 3.5 决策记录)。
3.3 上传模式¶
- 前端上传建议改为:OSS Browser.js SDK 分片上传 + 断点续传。
- 鉴权建议:STS 临时凭证(短时、最小权限、按前缀限制)。
- 对于 100MB+ 文件,不建议继续单次 PUT 签名 URL 作为主路径。
3.4 安全模型¶
- Bucket 默认私有(Private)。
- STS 角色权限限定到前缀(例如
tenders/source/${user_id}/)。 - 服务端
confirm/parse阶段二次校验对象元数据(size/content-type/key 规则)。
3.5 已确认部署参数(2026-04-09)¶
根据当前控制台创建结果,生产目标参数如下:
- Bucket:
itender - 地域:
华南1(深圳)/cn-shenzhen - Endpoint:
oss-cn-shenzhen.aliyuncs.com - 存储类型:
标准存储 - 冗余类型:
同城冗余存储(ZRS) - 读写权限:
私有 - 阻止公共访问:
开启
说明:
- 广州 ECS 访问深圳 OSS 属于跨地域访问,可用但会有额外时延。
- 同城冗余(ZRS)资源包仅抵扣匹配计费项;请在费用中心确认资源包为“中国内地通用”或覆盖深圳地域。
- 如果后续改回广州同地域低时延,需要重新建 Bucket(地域创建后不可变更)。
4. 总体架构(目标态)¶
sequenceDiagram
participant U as Browser
participant API as FastAPI
participant STS as STS/RAM
participant OSS as Aliyun OSS
participant Q as ARQ Queue
participant W as Parse Worker
U->>API: 1) 请求上传凭证(upload-token)
API->>STS: 2) AssumeRole(短时凭证)
STS-->>API: 3) 临时AK/SK/Token + 过期时间
API-->>U: 4) 返回凭证 + object key + bucket/region
U->>OSS: 5) 分片上传 + 断点续传 + complete
U->>API: 6) confirm-upload(file_key,size,etag...)
API->>OSS: 7) HeadObject 校验(不下载文件体)
API->>Q: 8) 入队(task_id,file_key,filename,...)
Q->>W: 9) 消费任务
W->>OSS: 10) 下载一次文件体并解析
W-->>API: 11) 写回解析结果与状态
5. 后端详细技术方案¶
5.1 存储抽象层¶
对象存储统一使用 OBJECT_STORAGE_* 与 OSSService(aioboto3,阿里云 OSS virtual-host 风格)。业务层只依赖 key 与公网 URL。
能力扩展(如 head_object)以代码实现为准。
5.2 去掉“重复下载”问题(关键)¶
当前¶
- API 在
resolve_enqueue下载全文件做 size 校验。 - Worker 在
process_document再下载全文件解析。
改造¶
- API 侧只做
HeadObject校验(对象存在、大小、类型)。 - 仅 worker 下载一次文件体并解析。
伪代码¶
# parser_service.resolve_enqueue
meta = await OSSService.head_object(file_key)
size = meta.size
_ensure_file_size(size)
await submit_document_parse(..., file_size=size, file_key=file_key)
5.3 文件大小策略¶
建议由固定常量改配置:
OBJECT_STORAGE_MAX_FILE_SIZE=209715200(字节,约 200 MiB;首发)- 观测 2 周后,若成功率与时延可接受,可调高
OBJECT_STORAGE_MAX_FILE_SIZE(字节)。
同时联动:
- ARQ soft timeout / hard timeout 适配大文件解析时间。
- worker 资源规格(CPU/内存)按 P95 文件大小评估。
5.4 上传鉴权接口设计(建议)¶
1) 获取上传凭证(新)¶
POST /tenders/{tender_id}/document/upload-token- 返回:
- STS
accessKeyId/accessKeySecret/securityToken/expiration bucket/region/endpointobject_key- 限制参数(
max_size、允许 MIME)
2) 上传完成确认(保留/增强)¶
POST /tenders/{tender_id}/document/confirm-upload- 入参补充:
file_keysizeetagcontent_typefilename
服务端执行:
- 校验 key 前缀合法。
HeadObject对齐 size/etag。- 创建 task 并入队。
5.5 幂等与防覆盖¶
file_key采用task_id + 原始文件名已较安全,建议再加用户维度前缀:
tenders/source/{user_id}/{task_id}/{filename}。- confirm-upload 做幂等(同 task_id 重复调用直接返回已创建任务)。
- 采用“唯一 object key + 后端幂等 + 禁止覆盖策略(可选)”降低误覆盖风险。
6. 前端上传技术方案(100MB+)¶
6.1 建议实现¶
使用 OSS Browser.js SDK:
- 大文件走 multipart upload。
- 断点续传开启本地 checkpoint(localStorage / IndexedDB)。
- 并发分片数按网络动态调整(例如 2~6)。
- 分片大小建议 4MB~16MB(默认 8MB,弱网降并发)。
6.2 交互流程¶
- 前端请求 upload-token。
- 前端直传 OSS(分片 + 重试 + 进度条)。
- 上传完成调用 confirm-upload。
- 页面轮询/订阅任务状态。
6.3 失败重试策略¶
- 上传失败:指数退避(1s/2s/4s,最多 5 次)。
- 完成提交失败:可重放 confirm-upload(服务端幂等)。
- 超过阈值提示用户“继续上传/重新上传”。
7. 成本与容量策略¶
7.1 成本构成(重点)¶
- 存储容量费(标准/低频/归档不同)。
- 请求费(PUT、Multipart、ListParts、GET/HEAD)。
- 流量费(公网下行、跨区域复制、加速链路)。
- 传输加速附加费(如启用)。
7.2 降本策略¶
- API 侧改
HeadObject,去掉一次全量下载,直接降带宽成本。 - 生命周期:30 天后转 IA,长期归档按业务回读率再定。
- 清理未完成分片(Lifecycle Abort Incomplete Multipart Upload)。
- 服务端与 OSS 同地域内网访问,避免不必要公网流量。
7.3 估算公式(用于预算)¶
- 月存储费 ≈
平均存储容量(GB) * 存储单价 - 请求费 ≈
PUT类请求数 * PUT单价 + GET/HEAD请求数 * GET单价 - 流量费 ≈
公网下行流量(GB) * 下行单价 + 加速流量(GB) * 加速单价
8. 安全与合规方案¶
- Bucket ACL:Private。
- RAM 最小权限:仅允许指定前缀的
Put/Get/Head/ListParts/AbortMultipartUpload。 - STS 凭证时效:建议 15~30 分钟。
- 服务端禁止信任前端 size/etag,必须
HeadObject二次核验。 - 若涉及敏感资料,开启服务端加密(SSE-KMS)并记录审计日志。
- 中国内地自定义域名对公网访问需满足备案要求。
9. 可观测性与 SLO¶
9.1 关键指标¶
- 上传成功率(按文件数)
- 上传中位/P95 耗时(按文件大小分桶)
- confirm->入队成功率
- 解析成功率、解析 P95 时延
- OSS 请求错误率(4xx/5xx 分开)
- 未完成分片数量与体积
9.2 报警建议¶
- 上传成功率 < 98%(5 分钟窗口)告警。
- confirm 接口 5xx > 1% 告警。
- ARQ 队列堆积超过阈值(例如 > 200)告警。
- 单日 OSS 请求成本异常波动告警。
10. 分阶段上线计划¶
Phase 0:基础设施准备(1~2 天)¶
- 创建 OSS Bucket(私有,标准存储)。
- 配置 RAM 角色 + STS 发放策略。
- 配置内网 Endpoint、域名、生命周期规则。
Phase 1:后端改造(2~3 天)¶
OSSServiceprovider 化配置。- 新增
head_object,resolve_enqueue改 Head 校验。 - 文件大小限制改配置项(先 200MB)。
Phase 2:前端大文件上传改造(2~4 天)¶
- 接 upload-token + multipart + checkpoint。
- 上传进度、失败重试、断点恢复 UI。
Phase 3:灰度(3~7 天)¶
- 10% 用户灰度。
- 观测成功率、上传耗时、解析队列、成本曲线。
- 达标后扩大到 50% -> 100%。
Phase 4:收敛与优化¶
- 生命周期策略微调(30/180 天)。
- 成本复盘,必要时上存储包/预留。
- 清理遗留专用存储配置与重复逻辑。
11. 风险清单与回滚¶
- OSS 与 S3 兼容差异导致 SDK 行为不一致。
处理:先在测试环境验证PutObject/Multipart/ListParts/HeadObject全链路。 - 弱网下分片失败率高。
处理:动态并发、分片重试、断点续传。 - 误配置导致前端可写超范围 key。
处理:RAM policy 前缀约束 + 后端再次校验 key。 - 灰度期解析积压。
处理:限制并发上传入口、弹性扩 worker、延长队列超时。
回滚策略:
- 部署时保证
OBJECT_STORAGE_*与目标地域 endpoint 一致。 - 发现异常时切回旧 provider,并暂停新 token 发放接口。
12. 验收标准(上线门槛)¶
- 200MB 文件上传成功率 >= 98%(办公网络+普通家庭宽带样本)。
- 100MB 文件上传+入队成功率 >= 99%。
- API 侧不再发生“为校验而下载整文件”行为。
- worker 解析链路对比改造前无功能回退。
- 安全检查通过(私有桶、STS 最小权限、服务端二次校验)。
13. 对当前代码的落地点(建议改动清单)¶
app/services/oss_service.py- provider 配置化
- 新增
head_object app/core/config.py- 新增 OSS 通用配置项与
OBJECT_STORAGE_MAX_FILE_SIZE app/services/tender/parser_service.pyresolve_enqueue用HeadObject替代download_bytes- 文件大小限制改配置
app/routes/tenders/parser.py- 新增
upload-token接口(或替换 presigned-url) - 前端上传模块
- 切 multipart + checkpoint + confirm 幂等
14. 关键参数建议(初始值)¶
OBJECT_STORAGE_MAX_FILE_SIZE=209715200- 前端分片大小
8MB - 前端并发分片
4 - STS 过期时间
1800s - 生命周期:
30d -> IA,180d -> Archive
14.1 .env.dev 推荐配置(深圳 ZRS 方案)¶
# Object Storage(阿里云 OSS)
OBJECT_STORAGE_REGION=cn-shenzhen
OBJECT_STORAGE_ENDPOINT=https://oss-cn-shenzhen.aliyuncs.com
OBJECT_STORAGE_BUCKET=itender
OBJECT_STORAGE_ACCESS_KEY_ID=REPLACE_WITH_OSS_ACCESS_KEY_ID
OBJECT_STORAGE_ACCESS_KEY_SECRET=REPLACE_WITH_OSS_ACCESS_KEY_SECRET
OBJECT_STORAGE_PUBLIC_DOMAIN=https://assets.neouu.com
# Parse size limit (initial online gate)
OBJECT_STORAGE_MAX_FILE_SIZE=209715200
上线前核对:
OBJECT_STORAGE_REGION与OBJECT_STORAGE_ENDPOINT必须同时为深圳。- AccessKey 建议使用 RAM 子账号并最小权限授权到业务前缀。
- 若后续切换到 STS 模式,保持接口不变,只替换 token 发放实现。
15. 参考资料(官方)¶
- OSS 选型指导:https://help.aliyun.com/zh/oss/user-guide/selection-guidance-selection-guidance
- OSS 存储费用计费项:https://help.aliyun.com/zh/oss/storage-fees
- 流量费用与计费:https://help.aliyun.com/zh/oss/traffic-fees
- 传输加速费用:https://help.aliyun.com/zh/oss/transfer-acceleration-fees
- Browser.js SDK 上传方式(含 >100MB 分片建议):https://help.aliyun.com/zh/oss/developer-reference/upload-objects-17/
- 分片上传说明:https://help.aliyun.com/zh/oss/user-guide/multipart-upload
- 使用 AWS SDK 访问 OSS:https://help.aliyun.com/zh/oss/developer-reference/use-aws-sdks-to-access-oss
- OSS 与 S3 差异:https://help.aliyun.com/zh/oss/developer-reference/compatibility-with-amazon-s3
- 生命周期规则执行机制:https://help.aliyun.com/zh/oss/analysis-of-the-reasons-why-the-oss-configuration-file-does-not-take-effect-after-its-lifecycle
- 中国内地备案与 OSS 使用说明:https://help.aliyun.com/zh/icp-filing/basic-icp-service/product-overview/use-oss