业务系统里,用户上传的合同、图片、报表,以及系统自动生成的附件,放在哪里、怎么访问、怎么保证安全,看起来是小事,实际很容易出问题。阿里云附件的思路是把文件从业务数据库里拿出来,交给对象存储和权限体系去管。

为什么不建议把附件直接存进数据库
早期不少项目把文件以 BLOB 字段写进关系型数据库。文件少的时候没什么问题,文件一多,数据库备份变慢,主库体积膨胀,查询也跟着变慢。数据库本来就不擅长支撑静态资源的高并发下载,这一点在图片多的业务里最明显。
更常见的做法是:文件内容放在阿里云对象存储 OSS里,数据库只保存元信息,比如文件名、大小、类型、存储路径、上传人和上传时间。业务表通过外键关联这些记录即可。
典型架构:OSS 加业务元数据
一套稳妥的附件方案通常包含三部分:
- 存储层:OSS Bucket 设为私有读写,避免文件被人随意枚举访问。
- 元数据层:一张 attachment 表,记录 object_key、原始文件名、MIME 类型、大小、所属业务 ID 和状态。
- 服务层:后端负责鉴权,并生成上传与下载凭证。前端不直接接触存储密钥。
object_key 的命名要慢慢定。不要直接用用户上传的原始文件名,可以采用“业务前缀加日期加随机串加扩展名”的格式,例如 contract/2024/06/a3f9c2.pdf。原始文件名单独存入数据库,页面展示和下载时再用它。这样既保留了展示信息,又避开了路径冲突和中文、空格、特殊符号带来的麻烦。
上传流程:先传到 OSS,再由后端确认
大文件让客户端直接传到 OSS,应用服务器只负责发凭证和记录结果,带宽压力会小很多。流程大致如下:
- 客户端请求上传凭证,后端检查用户权限和文件类型、大小,然后返回预签名 URL。
- 客户端把文件直接 PUT 到 OSS。浏览器直传需要在 Bucket 上配置跨域规则,只开放实际用到的域名和请求方法,不要直接写成 *。
- 上传完成后,客户端调用后端接口确认。后端把记录从“待确认”改为“有效”。
这里最容易漏掉的是第三步。用户可能在上传中途关掉页面,OSS 里就会留下没有业务记录的文件。建议给“待确认”记录设置过期时间,并用定时任务清理超时的孤儿文件,否则存储空间会慢慢被无效文件占满,而且很难事后查清来源。
上传与下载的安全要点
附件场景最常见的风险有两个:越权访问和密钥泄露。处理时可以参考以下做法:
- 校验文件类型时,不能只看扩展名。把 .exe 改成 .jpg 就能绕过前端判断,后端要同时检查 MIME 类型,必要时读取文件头确认真实格式。
- 大小限制也要在后端做。前端的限制可以被绕过,后端再次校验才算数。
- 下载时不要把 Bucket 设为公开。由后端先确认当前用户有权查看该业务记录,再生成有效期较短的签名 URL,例如 10 到 15 分钟。链接过期后,别人拿到的地址就没用了。
- 下载响应里带上 Content-Disposition 头,让浏览器用原始文件名保存,否则用户拿到的通常是一串随机路径。
- AccessKey 不要写进前端代码或代码仓库。为应用单独创建 RAM 子账号,只授予它需要的 Bucket 和操作权限。一旦泄露,影响范围也能控制在一个 Bucket 之内。
一个典型场景:售后退款凭证管理
下面是售后系统里很常见的一次改造过程。用户提交退款申请时要上传截图。最初,图片存在应用服务器的本地目录里。订单量上来以后,磁盘很快告急,而且多台服务器之间的文件无法同步,客服在后台经常打不开图片。
后来团队做了三件事。第一,把存储迁到 OSS,所有凭证写入私有 Bucket。第二,新增附件表,每张图片对应一条记录,关联到售后工单编号。第三,客服后台查看图片时,由后端校验工单归属,再生成 15 分钟有效的签名链接。
改造后,应用服务器不再承担文件存储,图片加载稳定,越权查看的问题也被挡在了后端。团队还设置了生命周期规则:工单结束超过 180 天的凭证自动转为低频存储。历史凭证仍能查,存储费用却低了不少。这里的 15 分钟和 180 天只是示例值,实际取值要结合业务的查看频率和合规要求来定。
实施时的几点建议
如果准备在项目里落地阿里云附件方案,可以分步走。先梳理哪些业务需要附件,再统一 object_key 规则和元数据结构,接着接入鉴权和签名链接,最后补充监控和生命周期策略。
文件大小限制、跨域配置、签名参数这类细节,以阿里云官方文档和你所用的 SDK 版本为准。不同版本的接口写法可能有差异,照着旧教程抄,最容易在上线后才暴露问题。
附件看起来是小功能,却同时牵涉存储、权限、成本和体验。把文件和业务数据分开,把权限放在后端,用临时凭证控制访问,大多数场景都能覆盖。