从存 URL 到存 objectKey:一次 OSS 上传链路重构背后的抽象反思

张开发
2026/4/22 6:52:15 15 分钟阅读

分享文章

从存 URL 到存 objectKey:一次 OSS 上传链路重构背后的抽象反思
我一开始以为自己只是在修一个文件上传链路。表面上看问题很具体OSS 上传接口不够清晰Excel 上传有一致性窗口下载时还要生成临时访问凭证代码里甚至出现了“是不是阿里云 OSS”的判断。可当我一路往下挖才慢慢意识到我真正修的根本不是某个接口、某个字段或者某次 RPC而是系统里一个更深层的问题资源身份和访问方式被长期混在了一起。而这次重构让我越来越相信一句话加中间状态复杂度不会变少而是被放回了它本来该在的地方。很多时候系统不是突然变复杂了而是我们终于开始正视那些原本就存在、只是早期被隐藏起来的复杂度。最开始的设计为什么完全合理最开始这个系统的设计其实非常正常甚至可以说是最自然的那种。上传完文件返回一个 URL数据库里也直接存这个 URL前端拿到以后直接展示或者下载。头像是这样背景图是这样Excel 文件一开始其实也是这样。站在小系统、教学案例、甚至很多线上业务的早期阶段来看这种做法完全没有问题。它直接、简单、低认知成本也很符合“先跑起来”的工程直觉。所以我从来不觉得“用户表直接存头像 URL”这件事本身是错的。恰恰相反在系统复杂度还不高的时候这就是一种非常合理的建模方式。问题不是它一开始错了而是系统后来长大了。第一个警报对象名居然是 UUID真正让我警觉的不是“存 URL”这件事本身而是我在修上传链路时发现了一个更早的信号对象名居然是 UUID。这一下问题的味道就不对了。如果对象名是 OSS 服务内部随机生成的 UUID那上游业务实际上就失去了最关键的东西稳定命名权。而一旦业务侧拿不到稳定对象身份很多以后一定会遇到的事情都会变得极其难搞很难做稳定重试因为下一次重试写进去的未必还是同一个对象很难做补偿因为无法可靠定位“上一次到底成功上传了哪个文件”很难做对账因为业务主键和对象存储里的实体之间没有稳定映射很难做最终一致性治理因为连“谁是谁”都不够稳定这时候我才意识到上传链路里最先要收回来的不是什么 URL而是资源命名权本身。于是上传接口被拆开了不再是一个模糊的“上传文件”而是回到明确的业务语义uploadExceluploadAvataruploadBackground这一步看起来只是接口拆分实际上意义很大。因为它代表着一个边界开始被纠正对象路径和对象名的业务语义不应该由 OSS 服务靠猜而应该由业务服务显式决定。第二个警报临时访问凭证居然要判断是不是阿里云但更深的问题是我后来在做临时访问凭证时才真正看清楚的。当时为了下载 Excel 文件我要从存储里生成一个带过期时间的访问凭证。按理说这应该是很正常的一步调用 OSS 服务根据文件定位信息生成一个预签名下载 URL 就行了。结果代码里却出现了一个让我非常别扭的场景上层居然要判断这个地址是不是阿里云 OSS。我当时的第一反应是困惑我不是用策略模式封装了 MinIO 和阿里云的差异吗为什么上层还要判断aliyuncs.com原因很快就清楚了。因为系统里传来传去的不是稳定的资源身份而是完整 URL。于是为了生成下载访问凭证我还得先把 URL 反解回来拆出 bucket、objectName再根据 URL 结构去判断if(host.contains(aliyuncs.com)){// 阿里云 OSSbucket 在域名前缀里// eaqb.oss-cn-beijing.aliyuncs.com → bucket eaqbStringbucketNamehost.substring(0,host.indexOf(.));returnnewParsedFileLocation(bucketName,normalizedPath);}else{// MinIObucket 在路径第一段里// 127.0.0.1:9000/eaqb/excel/... → bucket eaqbStringbucketNamepathSegments[0];returnnewParsedFileLocation(bucketName,normalizedPath.substring(bucketName.length()1));}策略模式封装了写入方向——上传时调用哪个 SDK、怎么认证、怎么传文件这些都藏在策略实现里上层感知不到。但只要数据库里存的是完整 URL一旦需要反向操作从 URL 推回资源定位差异就从策略层漏回来了。封装在写入时成立在读取时就破了。就在那一刻我突然明白了一件事如果我为了生成临时访问凭证还得在上层判断它是不是阿里云 OSS那我拿到的根本不是资源身份而只是资源的一种访问表现。这就是抽象泄漏。真正稳定的东西不该是https://bucket.endpoint.aliyuncs.com/excel/123/9001.xlsxhttp://127.0.0.1:9000/eaqb/excel/123/9001.xlsx真正稳定的应该是excel/123/9001.xlsx也就是objectKey。URL 是访问方式objectKey才更接近资源本体。第三个警报同一个抽象层返回了两种语义也是从这里开始我慢慢意识到Excel 这条链路之所以先把问题逼出来不是偶然而是因为它天然比头像、背景图更像资源。Excel 文件后面会经历很多事情上传状态流转解析重试下载补偿对账一旦链路里出现这些动作你就很难继续把 URL 当成资源本体去存。因为 URL 太像“某个时间点、某个厂商、某种配置下的访问结果”了而不是“这个文件自己是谁”。所以 Excel 这条链路最后被迫完成了一次抽象升级不再存厂商 URL而是存objectKey不再把下载地址当资源本体而是按需生成临时访问凭证。但也正是在这个过程中我又发现了第三个信号同一个上传抽象层里返回值语义居然不一致。Excel 上传返回的是objectKey而头像、背景图上传返回的却还是 URL。这不是某个方法写坏了而是系统演进到中途时最典型的一种状态抽象升级做了一半。Excel 先完成了资源建模因为它的复杂度先把问题逼到了明面上图片链路则还停留在用户资料展示字段的旧语义里因为它在过去一直没有被迫升级。于是同一个 OSS 抽象层里开始出现两套返回语义。系统能跑但会越来越别扭代码能通但抽象已经开始撕裂。我后来越来越觉得最难受的系统状态不是简单也不是彻底复杂而是这种复杂度已经来了但抽象还没来得及完全跟上。为什么早期直接存 URL 没错但后来不够用了所以如果现在让我回头总结这次重构真正教会我的不是“应该把某个字段从oss_url改成object_key”而是更底层的一件事资源和访问方式应该分开建模。业务库里应该尽量存资源定位信息比如objectKey至于访问 URL无论是公开 URL 还是预签名下载访问凭证都应该在读取时根据当前配置动态生成。这样做的好处不只是“看起来规范”而是它会直接决定系统后续很多能力的上限更容易做补偿因为拿到的是稳定资源身份更容易做对账因为知道该查哪个对象更容易切换 MinIO / 阿里云 OSS而不污染业务数据更容易统一访问控制因为访问方式可以独立演进更容易给同一个资源生成不同形式的访问凭证而不改表结构从这个角度看早期直接存 URL 并没有错。它只是更适合“结果导向”的低复杂度阶段。而当系统开始进入“状态管理、补偿恢复、资源治理”的阶段时原来的抽象就会开始失效。这不是打脸过去而是系统在成长。这里还有一个边界必须说清楚不是所有项目都值得为了“资源建模”立刻把 URL 改成 objectKey。如果一个系统本身非常小资源类型单一访问方式长期稳定没有跨环境迁移、跨厂商切换、补偿对账、预签名访问、统一权限控制这些需求那么直接存 URL 依然可能是成本最低、最合理的方案。站在这类系统的上下文里简单方案不是“抽象不够高级”而是更符合真实约束。所以这篇文章想表达的不是“存 URL 一定错存 objectKey 一定对”而是抽象要和系统复杂度匹配。当资源开始承载状态、补偿、切换、治理这些职责时URL 才会逐渐不够用如果这些问题根本不存在就没必要为了“看起来更规范”而提前做过度设计。和之前另外两个问题放在一起看我也很喜欢把这次反思和之前另外两个问题放在一起看。一个是 outbox。很多人第一次加 outbox 或中间状态的时候会觉得系统怎么反而更复杂了。可事实上复杂度从来没有消失只是以前被藏在“不可靠的跨边界操作”里现在你把它显式放到了状态、重试和补偿里。另一个是验证码登录里的login_ticket。一开始如果试图把复杂度全压在验证码本身上看起来很直接但很多状态衔接、一次性消费、登录过程控制的问题都会混在验证码这一个点上最后既别扭又不稳定。后来引入login_ticket之后系统表面上确实多了一层状态和流转但这层复杂度恰恰被放回了它真正该在的位置验证码负责证明ticket 负责承接登录过程。文件上传这件事其实也是一样。最早的时候我们把复杂度藏在“直接存 URL”里后来当 UUID、补偿、预签名访问、厂商差异一个个冒出来复杂度终于回到了它真正该在的地方资源身份、状态管理、边界抽象和最终一致性。所以我现在越来越相信那句话加中间状态复杂度不会变少而是被放回了它本来该在的地方。如果再让我从头设计一次如果再让我从头设计一次我会更早地统一这个模型所有上传接口都围绕资源语义设计而不是围绕“上传完返回一个地址”设计所有文件都统一持久化objectKey用户头像、背景图长期看也应该按资源来建模而不是直接把访问 URL 固化在业务表里访问地址和预签名访问凭证都应该在读取时由存储服务根据当前配置生成业务服务负责“这是什么资源”存储服务负责“这个资源怎么访问”说到底这次我以为自己是在修一个 OSS 上传链路后来才发现我真正修的是系统里对“资源”这件事的理解方式。被重构的不只是代码。还有我自己的抽象。

更多文章