27 KiB
资源访问与 OSS/CDN 分发架构重构
状态:已实施核心重构,待真实 OSS/CDN 链路与线上流量验收。
已落地:统一资源访问合同、存储分发策略、批量访问 API、共享 HTTP 执行器、用户/管理员/公开/分享/公告/外观/日志媒体迁移,以及前端 provider 输入、Blob 读取和画布复制地址迁移。未声称真实 CDN 命中率、CORS、Range 或账单收益已在本地验证。
1. 决策摘要与完成边界
这不是「把几个 URL 改成 OSS 地址」,而是收回散落在页面、API helper、handler、缓存和存储实现中的分发决策权。目标是:资源身份稳定、授权按业务场景执行、分发只有一个决策入口、供应商差异封装在适配器内。
- 用户展示、播放、下载、复制地址,以及分享、公开素材、后台预览和模型输入,必须使用同一资源访问合同;不同授权场景不能变成不同分发策略。
- 对具备直连条件的云资源,浏览器直接请求 CDN 或对象存储,平台只返回访问描述或重定向,不转发媒体正文。配置了合格 CDN 时优先 CDN;对象存储直连不能计作 CDN 命中。
- 本地资源、不可公网访问且没有可用 CDN 的资源,以及获准的特殊字节处理,是明确、有监控的例外;不能用静默代理掩盖配置错误。
- 画布右键复制地址签发原始资源的新鲜直链,不复制平台展示入口或浏览器 Blob URL。实际为本地资源时如实提示平台地址;没有可用地址时明确失败。
- 展示路径不默认下载完整 Blob;浏览器缓存成功不再是云端资源或生成任务成功的条件。
- 新增统一服务不是完成。全部调用方迁入、旧策略删除、权限及跨入口验收通过,才算完成整体重构。 可分阶段验证,但不把阶段性补丁作为最终交付。
本轮不重写任务引擎、计费、模型协议或转码算法,不引入通用插件平台、事件总线或为每次签名建立数据库记录。
2. 已确认问题与证据边界
| 现状 | 代码依据(仓库相对位置) | 架构影响 |
|---|---|---|
节点复制地址优先取 metadata.content,否则拼平台文件入口,没有使用已有直链接口 |
web/src/pages/canvas/project.tsx 的 copyNodeMediaUrlToClipboard |
交互层在解释媒体身份并选择出口 |
前端文件地址带 direct=1,后端生成的稳定文件地址不带;另有 proxy=1 |
web/src/services/api/resources.ts;backend/internal/assets/urls.go |
相同资源可能因调用来源而采用不同策略 |
| S3 默认代理判断在 CDN 判断前;直链签发与文件分发又是两套分支 | backend/internal/app/resource.go 的 prepareResourceDelivery、directResourceURL、signedOSSObjectURL |
供应商、部署能力与业务用途混在一起 |
/file 有重定向和流式读取两条路径 |
backend/internal/handler/user_data.go 的资源文件路由 |
平台 URL 不等于平台转发,必须看最终请求与正文发送量 |
| 显式 Blob 消费者在部分直连失败后允许走代理 | web/src/services/api/resources.ts 的 getResourceBlob;web/src/services/resource-blob-cache.ts |
CORS、过期或网络问题可能被代理流量掩盖;不是所有失败、所有缓存入口都会回退 |
| 视频生成同步等待完整 Blob;播放后还会计划缓存整文件,展示仍使用远程 URL | web/src/lib/canvas/canvas-generation-task-sync.ts;web/src/services/project-asset-sync.ts;web/src/services/resource-blob-cache.ts;web/src/components/canvas/canvas-node-content.tsx |
播放器读取与后台完整下载可能并存,缓存失败还会影响业务成功状态 |
| 管理预览、外观、公告、公开签名读取有直接流式路径;分享和日志媒体使用默认分发选项 | backend/internal/handler/admin_storage.go;backend/internal/app/appearance.go、announcement.go、analytics.go;backend/internal/canvas/canvas_share.go |
仅修改用户文件接口不能覆盖全站 |
| 普通资源写入存在 OSS 失败后落本地的路径,任务媒体新链路则强校验 | backend/internal/app/resource.go 的 storeResourceObject;backend/internal/app/task_media_storage.go |
当前配置不代表每个对象的实际位置,不能推断所有资源已经在 OSS |
| 对象保存实际 provider、endpoint、bucket 和历史存储关联;已有位置解析逻辑 | backend/internal/model/models_project.go;backend/internal/app/resource.go 的 ossSettingForResource |
不能把历史对象统一套到当前全局 CDN 域名 |
| 播放副本当前只针对本地原件生成,handler 自行选择副本和 ETag | backend/internal/app/video_transcode.go;backend/internal/handler/user_data.go |
变体选择必须进入合同,不能在重定向之后才选择;不能承诺云资源已经支持云端转码 |
| 存储连接测试会清空 CDN 配置,只验证源站;现有配置没有独立的 CDN 用户访问鉴权合同 | backend/internal/app/storage_location.go 的 verifyOSSConnection;backend/internal/app/settings.go |
「存储连接成功」不能作为 CDN、浏览器跨域和私有内容访问策略可用的证明 |
以上来自代码调查。尚未读取生产存储配置、实际请求日志、CDN 日志或账单,不能据此认定全部媒体都在代理,也不能量化已经多花了多少费用。线上归因需要同时记录最终域名、307/200/206、平台正文发送字节和 CDN 回源/命中情况;它用于确认收益,不替代架构重构。
3. 目标结构:身份、授权、分发、字节读取分离
UI / 画布 / 素材 / 编辑器
-> media-access:资源引用解析、批量请求、有效期、请求去重、账号隔离
-> resources API(现有 http 入口)
-> handler -> service 别名 -> app 组合根
-> 用户 / 分享 / 管理员 / 公开槽位的授权规则
-> assets.ResourceAccessService(唯一访问决策入口)
-> 变体及实际存储位置解析
-> DeliveryPolicy(用途、能力、鉴权、期限)
-> OriginSigner / CDNSigner
-> Redirect 或有明确原因的 StreamPlan
需要字节的编辑 / 导出 / 缩略图计算
-> 浏览器 media-bytes:先取得访问描述,再按需读取与缓存
服务端转码 / 导出 / 导入
-> 授权与业务编排 -> OriginReader:直接读实际存储,不套浏览器 CDN 策略
3.1 后端职责
| 边界 | 负责 | 不负责 |
|---|---|---|
handler |
入参、会话上下文、业务响应、执行已决定的重定向或流式 HTTP 响应 | 查存储配置、选择 provider、解释 CDN、决定强制代理 |
app |
场景授权编排、依赖注入、配置装配;向现有 canvas.Host 提供收敛后的能力 |
再保留一套直连与代理分支 |
assets |
ResourceRef、访问合同、变体解析、分发策略、结构化失败原因 |
HTTP、React、第三方 SDK 细节、业务提示文案 |
storage(从现有实现提取) |
按实际存储位置执行 Put/OpenRange/Delete、源站签名;封装 local、阿里云、腾讯云、七牛及 S3 差异 | 判断当前用户是否能看画布、选择显示还是复制 |
| 分发签名适配器 | CDN 路径映射与用户访问签名;与源站签名分开实现、分开测试 | 默认把私有内容当公开内容,或用对象存储签名算法猜测 CDN 算法 |
repository/model |
资源、位置、引用、配置与状态持久化 | 签发临时 URL、控制浏览器缓存 |
复用现有 assets 域,不再造一个包罗上传、任务、权限和 UI 逻辑的大 service。internal/service 继续只做别名;域包不得反向 import app/service。存储适配器不依赖 assets 业务策略,所需接口由组合根装配,避免循环依赖。
3.2 前端职责
services/api/resources.ts:只保留业务接口与类型,通过现有http调用,不负责选择媒体出口。services/media-access.ts(拟):统一访问描述生命周期,处理批量、去重、刷新、取消、账号切换;不依赖 JSX、路由或 AntD。- UI hook:订阅访问状态,适配图片加载和视频恢复;组件只声明资源引用、用途和变体。
services/media-bytes.ts(拟):承接真实需要 Blob/ArrayBuffer 的操作;原缓存模块收敛为其内部缓存能力,不能自行拼代理地址或签名地址。lib中的引用解析为纯函数。页面、store、生成同步和编辑器不得自行拼/file、判断云厂商或提取签名参数。
4. 统一访问合同
4.1 持久身份与临时访问描述
持久数据继续以资源 ID / resource:<id> storageKey 等稳定身份关联对象;临时访问 URL 只在内存中使用,不写回画布、任务结果、素材记录或 localStorage。文本节点的 content 语义不受影响,不全局删除同名字段。
现有稳定 /file 引用通过已有引用解析边界提取资源 ID;它可以作为稳定入口保留,但不能保留第二套分发策略。解析只识别受控站点的资源格式,不因外部 URL 包含相似路径就将其认作受管对象。不为替换域名批量重写所有历史画布,也不以整库迁移作为本次收敛的前提。
前端明确区分受管资源、外部 URL、浏览器本地内容。外部 URL 不冒充 OSS 对象;本地内容在上传成功前没有云端可复制地址。已有资源 ID 时不得优先采用过期的 content URL。
建议新增 POST /api/resources/access,单项和批量使用同一实现;批量上限初始为 100 项。分享等场景提供自己的授权适配入口,内部调用同一访问服务,不能让客户端自行声明管理员或分享授权上下文。
type ResourceAccessRequest = {
resourceId: string;
variant: 'original' | 'playback';
purpose: 'display' | 'download' | 'copy' | 'browser-process';
};
type ResourceAccess = {
resourceId: string;
requestedVariant: 'original' | 'playback';
actualVariant: 'original' | 'playback';
url: string;
delivery: 'cdn' | 'origin' | 'platform-local' | 'platform-proxy';
issuedAt: string;
expiresAt: string | null;
refreshAt: string;
revision: string;
fallbackReason?: string;
};
- 返回仍遵守
{ code, data, msg, reason }。批量成功受理后,data.items按项返回access或机器可读错误;顶层认证失败返回真实 HTTP/业务错误。逐项校验权限,不因同一批次有一个合法资源而放行其他资源。 purpose是策略输入,不是授权。provider-input只允许可信服务端调用,不开放一个供浏览器获取长效凭据的用途枚举。copy/download默认原件;播放可请求兼容副本。副本不就绪时按集中规则返回原件并说明actualVariant/fallbackReason,不能静默伪装副本就绪。源站与副本使用各自真实位置、MIME 和 ETag。- 时间字段使用 UTC RFC 3339。
expiresAt为实际签名到期时间,只有明确公开资源可为null;客户端结合issuedAt、请求耗时和本地单调时钟计算剩余期限,不能假定设备时钟准确。refreshAt同时受授权剩余时间、配置刷新周期和安全余量约束,不能固定缓存 4 分钟后猜测有效期。 revision表示资源变体与分发配置版本,不含密钥。访问描述响应默认no-store,客户端仅做受控内存缓存;媒体正文的浏览器缓存与 CDN 缓存另行配置。- 内部决策使用互斥类型
Redirect/LocalStream/ApprovedProxy,每种类型只携带必要字段;删除ForceDirect/ForceProxy和多个可空字段并存的隐式状态机。 - 返回平台 URL 时使用绑定资源、变体、期限及授权范围的受控读取入口;该入口执行已验证的流式授权,不再次解析成自己的重定向目标,防止循环重定向。
4.2 期限、复制与长视频
当前直链为 5 分钟、模型输入为 4 小时。重构后由单一 policy 按用途管理期限,复用这两个值作为已有行为基线,而不是让 UI 各自写常量;分享签名不得超过分享有效期,私有资源不得因用途不同扩大授权范围。
- 复制操作在用户点击时取得新鲜原件地址,并显示「CDN/对象存储临时地址及有效期」;浏览器剪贴板失败必须报错,不能吞掉异常再提示成功。
- 短期直链是可转交的访问凭据,不是永不过期的分享链接。需要长期分享时使用既有分享授权能力,不把 Bucket 改为公开以满足复制需求。
- 已签发地址在剩余有效期内的撤权限制必须明示;不能承诺撤销分享后所有外部签名立即失效。已经下载的内容也无法收回。
- 视频续期不能简单定时替换正在播放的
src。媒体控制器维护播放位置、暂停状态和当前请求版本;在需要续期或过期恢复时受控换源、恢复位置,最多刷新重试一次,失败可见。 - 验收必须覆盖播放、暂停后继续、跨过签名有效期后的拖动与新 Range 请求;不能以短视频能开始播放作为长视频通过的证明。
- 下载文件名、Content-Disposition 等由访问策略和适配器统一处理;不能仅为更改文件名而退回平台整文件转发。
5. 唯一分发策略与配置模型
分发顺序固定为:校验业务授权 → 校验资源 ready → 解析变体 → 解析该对象实际位置 → 校验用途能力和访问保护 → 签发访问描述或返回结构化错误。
| 实际条件 | 决策 | 限制 |
|---|---|---|
| 本地对象 | platform-local |
如实呈现;不能生成看起来像 OSS 的假地址 |
| 对象位置绑定有效 CDN,路径映射与用户访问保护适用于该用途 | cdn |
先判断可用 CDN,再考虑源站是否私网或 S3 |
| 无 CDN,源站可公网安全访问且可签发读取凭据 | origin |
计作源站直连,不宣称使用 CDN |
| 私网源站、无可用 CDN,配置明确允许平台读取 | platform-proxy |
必须记录原因、流量、大小/并发限制;复制时不能称为 OSS 直链 |
| 私有内容的 CDN 用户鉴权缺失、配置损坏、签名失败或凭据失效 | 结构化失败 | 不发布无保护地址,不因捕获异常就自动转代理 |
| 字节处理需要 CORS,但分发端未满足要求 | 明确失败;仅在服务端策略授权时采用受控代理 | 浏览器不能用 proxy=1 自行扩大权限或绕过流量约束 |
CDN 是分发配置,不是存储厂商别名。源站权限、CDN 回源权限、用户访问 CDN 的权限分别建模;本项目不得把「能回源」当作「私有资源可以匿名分发」。
复用 StorageLocation 与现有历史位置解析,为位置增加结构化 delivery 配置,区分域名/路径映射、用户访问鉴权方式、代理例外、期限和浏览器能力。优先沿用现有配置存储与加密边界,不为了抽象额外新建一套存储中心;签名密钥不返回给浏览器。
原有 cdnBaseUrl 不足以推导私有 CDN 鉴权能力。实施时盘点实际 CDN 产品和鉴权模式,补对应签名适配器;未验证配置保持不可用,不默认公开或自动猜算法。选择「必须走 CDN」时配置失败应报错,不能自动降为源站;允许源站降级则必须由同一 policy 明确配置并记录原因。
连接测试拆成源站读写检查与分发检查。后者覆盖浏览器 GET/HEAD/Range、所需 CORS、响应头、签名过期和未授权访问,并关联配置版本。保存配置不等于已经验证可用;不擅自修改云端 ACL、CORS、DNS 或 CDN 配置。
历史资源按其绑定位置寻址;改默认厂商只影响新写入。相同位置的 CDN/密钥变更可由位置解析统一生效,并使访问缓存失效;不得把另一 Bucket 的对象拼到新域名。
6. 缓存与写入成功语义
6.1 展示缓存和字节缓存分离
- 普通图片、视频、音频展示直接使用访问描述,交给浏览器/CDN 按媒体协议读取。删除生成完成、进入画布、开始播放后默认整文件缓存的流程。
- Blob 只为图片编辑、像素读取、合成、导出、复制图片内容及明确离线操作等字节消费者产生。下载原件优先原生直链下载,不一律先在 JS 中抓取全量文件。
media-bytes统一检查response.ok,传递取消信号;云直连不附带平台 Cookie。凭据过期可重新解析一次,不把所有网络/CORS 错误都判为过期,更不能无声启动代理。- 字节缓存按用户 scope、资源、实际变体及内容版本隔离,有大小/总量限制、请求去重与 Blob URL 回收。清晰区分缓存成功、云端保存成功和生成成功。
- 访问描述缓存另外按 scope、授权场景、资源、变体、用途去重,按服务端时间信息留余量刷新;公开/分享会话不得复用登录用户缓存。
- 切换账号时取消在途请求、清理访问与字节缓存引用;响应提交前核对发起时的 scope,禁止旧请求写入新账号缓存。签名 URL 不落入 localforage/localStorage。
- 采用微批量解析避免一个画布逐节点产生 N+1 请求;无视图消费者的资源不提前签发或下载。
6.2 写入与变体
资源成功以服务端实际对象和资源记录持久化成功为准。任务同步、项目素材同步不再等待浏览器拿到完整视频 Blob;元信息来自服务端或按需探测,读取失败不得捏造宽高、时长或成功状态。
资源写入的本地降级从隐式 catch 改为明确存储策略:选择 OSS 且未允许本地降级时写入失败向上抛出;允许降级时记录实际 local 位置、返回可见状态并计入例外指标。任务媒体已有的强校验不被削弱。此处只收敛存储成功语义,不修改任务计费规则。
已有本地资源不会因修改分发策略自动变为云资源。若要迁移,另行执行带预检、复制、完整性校验、引用切换与延迟清理的可回滚作业;未经确认不搬迁或删除现有对象。
现有本地播放副本纳入统一变体解析,不改变转码范围。未来新增云端副本时必须记录副本的真实位置并复用同一存储与分发接口,不能重新在播放器/handler 写一个云端特例。
7. 全入口迁移与旧实现退场清单
| 入口/旧职责 | 目标归属 | 删除或收敛要求 |
|---|---|---|
| 画布、素材库、项目封面、任务预览、图片预览、音视频播放器、编辑器媒体展示 | media-access + 展示 hook |
页面不得直接拼平台或 OSS 文件地址 |
| 节点「复制地址」、复制图片失败后的地址操作、下载入口 | 统一 copy/download 用途 | 不优先复制 metadata.content,不继续调用旧 oss-url helper |
resourceFileUrl、resourceProxyFileUrl、playbackVariantUrl、resolveResourceUrl 等 |
引用解析与访问合同 | 删除出口选择;稳定文件路由仅作为同一 policy 的 HTTP 适配 |
getResourceOSSUrl、独立直链缓存、DirectResourceURL |
唯一访问签发服务 | 调用方清零后删除 /oss-url 及独立分支,不长期并行 |
resolveImageUrl、resolveMediaUrl、文件/图片存储服务、CachedResourceImage |
统一访问生命周期 | 存储与展示不再各自解析直链或续期 |
| 生成结果恢复、任务输出、素材同步 | 持久资源引用 + 访问描述 | 不保存签名 URL,不依赖完整 Blob 才标成功 |
| 缓存、图片编辑、导出、剪贴板内容操作 | media-bytes |
删除自动整文件预取和 allowProxyFallback 隐式回退策略 |
| 用户文件、公开签名文件、管理员存储预览、调用日志媒体 | 各自授权 + 统一访问服务 + HTTP 执行器 | 删除 handler 自选 provider、直接调用云读取实现的旁路 |
| 画布分享、站点外观、公告资源 | 现有业务授权后使用同一分发策略 | 公开入口只能访问业务实际绑定资源,不能成为任意 resourceId 下载器 |
| 模型参考输入、服务端导出/转码/导入 | 外部读取签发或内部 OriginReader | 输入 URL 只在出站前解析;内部字节处理不绕经浏览器 CDN 再代理 |
ForceDirect/ForceProxy、direct/proxy 查询参数 |
服务端集中 policy | 不再接受调用方强制选择出口,OpenAPI 与测试同步删除 |
| 存储配置、历史位置解析、连通性测试 | 位置解析 + 分发配置/能力检查 | 一次改配置影响全部入口,不修改页面,不为每个路由新增开关 |
上述是必须覆盖的入口族,不声称静态列举已经穷尽所有调用点。实施第一阶段生成逐调用点清单,覆盖直接 import、封装转调、动态路由及外部 SDK 输入;清单中每项都要有迁入位置和验证用例,不能只搜索一个函数名就宣布完成。
8. 错误、安全与可观测性
- 权限仍在业务 service/域内校验,授权后的内部资源描述不得由客户端构造。管理员、分享、公开槽位各有授权规则,但只有一套分发规则。
- 非所属资源、不存在资源、未 ready、变体不可用、分发配置无效、签名失败、分发端不可读、代理不允许分别有机器可读 reason;沿用项目错误信封,不解析错误文案。
- 代理只读取已授权的受管对象,不接受任意 URL;保留出站 SSRF、私网精确放行、超时和取消约束,不为 S3 内网部署放开全网访问。
- Range、206、416、If-Range、条件缓存、MIME、Content-Disposition 在统一 HTTP 执行器及签名适配器中验证;读取失败不返回伪成功空文件。
- 每次访问决策记录场景、用途、实际位置类别、变体、delivery、结构化原因和配置版本。日志不记录签名完整 URL、查询参数、Cookie、密钥或分享凭据。
- 平台正文发送字节按 local/proxy 及原因分类;签发接口和重定向响应单独计量。资源 ID 可作受控日志关联,但不作高基数指标标签。
- 观察直连/代理占比、代理发送字节、重复整文件下载、签发失败、续期失败、CDN 命中与回源字节。账单比较按同等资源量/访问量归一化,不以平台请求数下降代替成本证明。
9. 实施阶段与落地顺序
这些是同一架构的实施阶段,不是「先只止血,后续再说」。阶段内允许未完成的代码演进,最终不保留新旧两套策略。
| 阶段 | 交付物 | 进入下一阶段的门槛 |
|---|---|---|
| A:合同与回归基线 | 全调用点清单、资源引用/访问/错误合同、位置与授权矩阵、现有测试映射 | 能明确每个入口的授权方、策略方、字节读取方;生产未知项单列 |
| B:后端纵向收敛 | assets 访问服务、存储/分发适配器、批量 API、共享 HTTP 执行器、位置配置与 OpenAPI |
local、各云厂商、S3 公网/私网、CDN 私有访问、变体和所有授权场景的契约测试通过 |
| C:全部消费者迁入 | 前端访问/字节层、生成同步解耦、分享/后台/公开/模型输入迁入 | 迁移清单清零;删除旧接口、布尔策略、旁路与自动整文件缓存;前后端版本配套 |
| D:架构与真实链路验收 | 依赖约束测试、最小专项回归、真实浏览器网络记录、经授权的云配置小流量验证 | 下节验收项完成;例外流量有明确来源;真实费用收益另用线上观察确认 |
实施同步维护 HTTP API、资源存储、代码地图及部署专题;如新增数据库字段,同步数据库说明。已实现但未获得用户确认的结果进入待验收记录,不把本设计登记为已完成功能。
本轮不更改数据库、云配置或生产路由。若实施涉及新增分发配置,先预检现有配置并说明变更影响;保留版本化配置/发布产物用于回滚。原则上不改变既有稳定引用编码;如确需改变持久合同,必须先给出备份、迁移与旧版本可读性方案,不能只回滚二进制。
10. 硬性验收门槛
架构约束
- 分发决策仅存在于
DeliveryPolicy;供应商签名只存在于对应适配器。 - 新增 CDN 或更换签名实现只改分发配置/适配器及其测试,不改画布、播放器、复制按钮和业务 handler。
- 增加一个媒体展示入口只消费统一合同,不引入新的地址拼装或 provider 判断。
- 通过依赖约束/静态扫描检查旧导出、旧查询参数及直接读取旁路;许可例外集中列出,禁止用模糊忽略规则绕过。
- 迁移清单无未处理调用方,没有长期保留的兼容分发链。
行为与安全
- CDN、源站直连、local、明确批准的私网代理覆盖原件/播放副本;配置变化后历史资源仍能按实际位置读取。
- 所有页面展示与右键复制均遵守同一策略;云资源复制为新鲜原件直链,过期提示准确;Blob/local 不被伪装为云资源。
- 本地播放副本、副本尚未就绪/失败、Range/拖动、签名过期、重新登录及账号切换均有针对真实实现的测试。
- 用户越权、管理员校验、分享期限/撤销边界、公开槽位越界、私有 CDN 未鉴权访问均验证拒绝语义。
- CORS/网络失败不会静默变平台代理;必需字节读取失败和剪贴板失败有可见错误,取消不被当成普通失败重试。
- 打开画布、生成成功、开始播放不再触发无业务必要的整视频下载;编辑/导出等真正需要字节的路径仍可用。
流量与上线
- 对已验证可直连的云资源样本,逐入口检查最终 CDN/源站域名;平台不发送该媒体的 200/206 正文,仅允许访问描述或重定向。
- 平台所有媒体正文发送都有可解释的 local/approved-proxy 原因;不能因路径叫
/api就统计成代理,也不能把 CDN 回源当命中。 - 浏览器真实请求与服务端计量一致,未产生播放 Range 加后台完整下载的重复流量。
- 小流量上线、配置异常和发布回滚有记录;没有擅自公开私有存储、删除旧对象或修改生产 ACL。
11. 本次评估结论与经验约束
方案具备落地基础:仓库已有资源 ID、实际位置/历史配置、权限边界和部分直链能力;主要工作是重置职责与合同、迁移全部调用方、删除重复策略,而不是重新发明对象存储系统。改动横跨资源核心流程、权限、缓存和部署配置,应按 L2 完整实施,不适合将其承诺为几个文件的小修复。
尚需实际环境验证的是 CDN 产品及用户访问鉴权方式、CORS/Range、历史 local 占比与各路径流量成本。它们决定具体配置、签名适配与例外范围,不改变「单一策略入口」的架构选择;本轮不把未知项当作已验证事实。
上一版评估虽然识别了分散决策,但没有把策略的唯一归属、旧实现删除和未来变更影响面作为硬完成条件,容易让止血成为终点。本次已按 症状 → 职责图 → 单一合同 → 全调用方迁移 → 旧路径删除 → 防回归约束 推进;剩余工作只属于真实云链路验收、供应商能力补齐和调用方继续收敛,不再新增第二套资源访问策略。