1. 文档信息
- 文档名称:统一下载网关技术方案
- 文档类型:正式技术方案
- 适用范围:
cmclink-ibs-web-1前端及对应后端下载服务 - 当前状态:Draft
- 目标读者:前端开发、后端开发、架构评审、测试、运维
2. 背景与问题
当前系统内存在多种文件下载来源,主要包括以下几类:
- 第三方平台下载链接
- 内部 COS 平台下载链接
- 业务系统动态生成文件流
- 后续可能扩展的新下载来源
现状中,不同来源的下载能力不一致,前端下载链路逐步暴露出以下问题:
2.1 来源能力不一致
- 第三方链接通常不开放跨域,浏览器端无法稳定
fetch文件内容。 - 内部 COS 若支持 CORS,则前端可以
fetch -> blob -> download,但实现与第三方完全不同。 - 新来源接入时,前端往往需要继续新增条件分支,导致下载逻辑扩散。
2.2 文件名不可控
- 对于第三方跨域链接,前端无法稳定自定义下载文件名。
- 浏览器对跨域资源的
download属性支持有限,很多情况下会优先采用服务端响应头或 URL 中原始文件名。 - 不同页面重复拼装文件名,存在维护成本和规则不一致问题。
2.3 安全与治理能力不足
- 前端直接暴露真实第三方 URL、签名参数、桶名、路径结构。
- 不利于统一鉴权、审计、失败追踪和访问控制。
- 一旦上游下载规则变化,多个页面可能同时受影响。
2.4 前端复杂度持续上升
- 页面层和业务 composable 容易出现大量下载分支逻辑。
- 公共下载能力需要兼容越来越多来源类型,职责边界不清晰。
- 下载失败原因难以定位,测试成本较高。
3. 方案目标
本方案旨在通过"后端统一下载网关"解决多来源下载问题,目标如下:
- 为前端提供统一、稳定、可扩展的下载协议。
- 将跨域、来源识别、鉴权、文件名控制等复杂性收敛到后端。
- 统一下载文件名、响应头、错误码、审计日志和监控指标。
- 支持未来新增下载来源时按适配器方式扩展,不影响前端主流程。
- 保持前端业务页面调用方式简洁,避免继续扩散来源差异逻辑。
4. 适用范围与非目标
4.1 适用范围
- 保函下载
- 发票下载
- 舱单下载
- 提单及其他导出文件下载
- 内部 COS / 第三方平台 / 动态生成文件流等多来源文件场景
4.2 非目标
- 本方案不覆盖文件上传能力治理。
- 本方案不强制所有历史接口一次性切换,允许分阶段迁移。
- 本方案不强制引入断点续传;如后续存在大文件诉求,可作为增强项单独评估。
5. 关键结论
5.1 结论
推荐采用"后端统一抹平下载来源差异,前端统一消费二进制流"的架构方案。
5.2 原因
- 浏览器跨域限制只作用于前端页面脚本,不适用于服务端到服务端调用。
- 后端更适合承接第三方下载鉴权、地址适配、签名保护和文件名统一控制。
- 前端只需处理统一 blob 下载或标准流下载,逻辑更稳定、可维护性更强。
6. 总体方案
6.1 总体思路
前端不再直接面向第三方下载 URL 作为主链路,而是统一调用平台后端提供的下载接口。后端根据文件来源选择对应适配器,从上游获取文件流并流式转发给前端,同时统一设置响应头。
6.2 架构图
text
前端业务页面 / Composable
|
v
统一下载调用层(useFileDownload / API)
|
v
后端统一下载网关
|
+-------------------+
| |
v v
第三方平台适配器 内部 COS 适配器
| |
+---------+---------+
|
v
上游文件来源
6.3 角色分工
- 前端:发起统一下载请求、展示 loading、消费 blob、处理用户提示。
- 后端下载网关:统一鉴权、来源识别、文件名生成、流式转发、错误映射、日志记录。
- 来源适配器:屏蔽不同上游文件来源的调用差异。
7. 技术选型与设计原则
7.1 设计原则
- 单一入口:前端只认统一下载协议。
- 后端抹平:来源差异由服务端处理。
- 流式转发:避免大文件整包落内存。
- 文件名统一:优先由后端通过响应头控制。
- 兼容渐进迁移:允许保留历史直链下载能力作为临时兜底。
7.2 推荐技术路径
- 前端:继续使用统一下载 Hook,例如
useFileDownload - 后端:新增统一下载网关接口
- 网关实现:基于流式响应透传上游文件
- 来源扩展:采用适配器模式实现来源隔离
8. 详细设计
8.1 前端调用模型
前端统一通过平台 API 下载文件,不再直接使用第三方 URL 作为主下载方式。
推荐调用方式如下:
ts
await download(async () => api.downloadIndemnityFile({
bizId: '123',
bizType: 'indemnity',
}), {
fileName: 'GUARANTEE_LETTER_BL001.pdf',
fileType: 'pdf',
})
前端职责收敛为:
- 调用统一下载 API
- 展示下载中的 loading
- 解析 blob 或响应头中的文件名
- 统一错误提示
前端不再负责:
- 判断第三方链接是否可跨域
- 判断当前来源类型
- 决定是否走
fetch + blob或直链下载 - 拼接第三方签名 URL
8.2 后端统一下载接口设计
统一下载接口建议支持两种形式,项目可按业务现状择一或并行使用。
方案 A:按资源 ID 下载
http
GET /api/file-center/download/{resourceId}
适用于平台已经具备统一文件资源模型的场景。
方案 B:按业务语义下载
http
POST /api/export/download
Content-Type: application/json
请求示例:
json
{
"bizType": "indemnity",
"bizId": "123",
"templateType": "guaranteeLetter",
"expectedFileName": "GUARANTEE_LETTER_BL001.pdf"
}
适用于导出即下载、按业务上下文动态获取文件的场景。
8.3 后端请求模型建议
ts
interface DownloadRequest {
bizType: string
bizId: string
templateType?: string
expectedFileName?: string
locale?: string
}
8.4 后端响应规范
成功时直接返回二进制流,推荐响应头如下:
http
HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Disposition: attachment; filename*=UTF-8''GUARANTEE_LETTER_BL001.pdf
Content-Length: 245678
Cache-Control: private, no-store
X-Download-Source: third-party
失败时返回统一 JSON 错误体:
json
{
"code": "DOWNLOAD_UPSTREAM_TIMEOUT",
"message": "文件下载失败,请稍后重试",
"traceId": "xxx"
}
8.5 下载网关内部流程
统一下载网关建议按以下顺序处理:
- 校验登录态与业务权限
- 根据业务参数定位文件来源
- 选择适配器获取上游文件流
- 生成或覆盖最终下载文件名
- 设置标准响应头
- 以流式方式回传前端
- 记录审计日志、耗时、结果码与 traceId
8.6 来源适配器设计
建议在后端引入来源适配器层。
接口示例如下:
ts
interface DownloadAdapter {
canHandle(sourceType: string): boolean
getFileStream(input: DownloadRequest): Promise<{
stream: NodeJS.ReadableStream
fileName?: string
contentType?: string
contentLength?: number
}>
}
推荐适配器划分:
ThirdPartyDownloadAdapterInternalCosDownloadAdapterGeneratedFileAdapterLegacyUrlAdapter
8.7 文件名规则
文件名控制推荐以下优先级:
- 业务明确传入的标准文件名
- 后端按业务规则动态生成文件名
- 上游响应头中的文件名
- 系统兜底文件名
推荐统一使用 UTF-8 编码的 filename* 响应头,避免中文文件名乱码。
8.8 前端统一下载能力定位
前端仍保留统一下载 Hook,但职责调整为"消费层"而非"来源治理层"。
保留职责:
- loading 管理
- blob 下载触发
- 响应头解析文件名
- 通用异常提示
弱化职责:
- 来源识别
- 跨域能力判断
- 第三方链接命名控制
9. 安全设计
9.1 安全收益
- 隐藏第三方真实下载地址、签名参数和存储桶信息
- 下载前统一执行权限校验
- 便于增加下载频控、黑白名单、操作审计
- 降低前端直接暴露外部存储结构的风险
9.2 安全要求
- 后端下载接口必须校验登录态和业务权限
- 禁止通过简单拼接 URL 直接访问任意外部文件
- 记录下载行为审计日志,包括用户、时间、业务主键、来源、结果、traceId
- 对敏感文件下载场景支持额外鉴权或审批
10. 性能与稳定性设计
10.1 推荐实现方式
- 使用流式透传,不将整文件一次性加载到内存
- 透传或补充
Content-Length - 根据来源设置合理超时与重试策略
- 支持连接中断时及时释放上游请求
10.2 关注点
- 大文件下载会提升后端带宽与连接压力
- 第三方平台可能存在超时、限流或证书问题
- 统一下载网关应纳入监控治理范围
10.3 推荐监控指标
- 下载成功率
- 下载平均耗时
- 各来源失败率
- 平均文件大小
- 网关并发数
- 上游超时次数
11. 错误码建议
建议为下载网关定义统一错误码:
| 错误码 | 含义 | 说明 |
|---|---|---|
DOWNLOAD_UNAUTHORIZED |
未授权下载 | 登录失效或无权限 |
DOWNLOAD_NOT_FOUND |
文件不存在 | 业务记录不存在或上游文件不存在 |
DOWNLOAD_UPSTREAM_TIMEOUT |
上游超时 | 第三方或 COS 响应超时 |
DOWNLOAD_UPSTREAM_FORBIDDEN |
上游拒绝访问 | 上游鉴权失败 |
DOWNLOAD_SOURCE_UNSUPPORTED |
不支持的来源 | 未匹配到适配器 |
DOWNLOAD_INTERNAL_ERROR |
内部异常 | 网关内部错误 |
12. 兼容与迁移方案
12.1 迁移原则
- 新增业务优先接入统一下载网关
- 存量业务逐步迁移,不要求一次性替换全部直链下载
- 前端统一下载 Hook 尽量保持 API 不变,降低改造面
12.2 分阶段迁移
Phase 1:保函试点
- 后端为保函场景新增统一下载接口
- 前端
useIndemnityGenerate改为调用后端统一下载接口 - 保留原
downloadUrl模式作为短期回退方案
Phase 2:通用下载网关沉淀
- 将发票、舱单、提单等相似下载场景迁入统一网关
- 沉淀标准错误码、日志字段和响应头规范
Phase 3:下线历史直链主链路
- 新增需求不再允许页面直接消费第三方下载 URL
- 历史页面逐步移除来源判断逻辑
- 统一收敛到后端 blob/流下载模式
12.3 前端改造建议
- 页面层不再直接处理第三方 URL
- 业务 composable 不再判断来源类型
useFileDownload只保留统一消费能力
13. 风险与对策
| 风险 | 描述 | 对策 |
|---|---|---|
| 后端带宽压力上升 | 下载链路集中到网关 | 流式转发、网关限流、监控扩容 |
| 第三方不稳定 | 上游超时、限流、证书问题 | 超时控制、重试、熔断、降级提示 |
| 大文件内存占用 | 错误实现可能整包读入内存 | 强制流式透传 |
| 文件名乱码 | 中文文件名或特殊字符处理不一致 | 统一 filename* UTF-8 编码 |
| 历史改造成本 | 存量页面较多 | 分阶段迁移、优先试点 |
14. 验收标准
统一下载网关方案上线后,应至少满足以下验收标准:
- 前端新增下载场景无需识别具体来源类型。
- 第三方不可跨域来源可通过统一下载接口正常下载。
- 中文和英文文件名均可稳定下载且不乱码。
- 后端可记录完整下载日志并支持 traceId 排查。
- 保函场景完成试点迁移且无业务回退事故。
- 网关具备基础监控能力,可统计成功率和失败原因。
15. 推荐实施结论
对于当前"第三方不可跨域、内部 COS、未来多来源扩展"的下载场景,推荐采用"后端统一下载网关 + 前端统一消费二进制流"的方案。
该方案具备以下长期价值:
- 前端调用方式稳定
- 文件名控制能力稳定
- 来源扩展成本低
- 安全治理能力强
- 日志与异常排查更清晰
从项目演进角度看,该方案是当前下载能力治理中最优先、最具长期收益的基础设施改造方向。
16. 附录
16.1 前端调用示例
ts
async function handleDownload() {
await download(async () => api.downloadIndemnityFile({
bizType: 'indemnity',
bizId: '123',
}), {
fileName: 'GUARANTEE_LETTER_BL001.pdf',
fileType: 'pdf',
})
}
16.2 后端返回示例
http
HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Disposition: attachment; filename*=UTF-8''GUARANTEE_LETTER_BL001.pdf