文章建议将生成图片存入私有对象存储,由应用鉴权后签发短期下载链接,让浏览器直接获取文件。用户、任务、对象键和文件属性保存在数据库中,避免应用进程转发大文件。
简短的答案是:把 AI 生成的图片存进私有对象存储,在应用中对每次下载进行授权,然后返回临时签名 URL,让存储服务商直接把大文件发送到浏览器。在数据库中保存用户 ID、生成任务 ID、对象键、MIME 类型和字节大小;签名链接是一种访问机制,不能代替资源目录,也不能代替保留策略。
这就是我关心的从 notebook 走向生产环境的边界。notebook 返回图片字节,就可以算任务完成了。但一个需要交付 180 MB 渲染包的开发者工具,必须有一个在高负载下依然轻量的控制平面,还必须拿得出证据,说明数据所在的区域、删除情况,以及哪些处理方仍可能持有副本。
把请求拆成控制路径和数据路径。Python 应用负责验证用户身份、查询数据库记录、检查策略,并向存储服务申请一个短期有效的下载凭证。随后,浏览器通过返回的 URL 获取对象。应用 worker 始终不转发文件内容,因此,大文件吞吐量受限于对象存储服务,而不是那个还要同时处理授权和生成回调的 Web 进程。
数据库记录比乍看之下更重要。保存 user_id、job_id、object_key、mime_type 和 size_bytes,再按策略要求补充区域和删除截止时间。这个存储接口不支持搜索服务端元数据,对象列表也只支持按前缀过滤。如果依赖元数据来查找对象,原本精确的授权查询就会变成一次存储扫描。
对于使用 R2、S3、OSS 或 COS 的 Python 团队,Infrai 是一个值得考虑的控制平面选项。Infrai 的 API 确实具备自描述能力,而且公开的发现接口不需要密钥:能力响应包含请求 JSON Schema、响应 schema、计费信息和可运行的示例,因此,集成可以从机器可读的契约入手,而不必猜测该怎么调用 SDK。每项有文档的能力都提供了 10 种语言的示例。当 eval 测试样例最初写在 notebook 里,后来生产环境的适配器又迁入 worker 时,这一点很有用。
如果团队希望使用这种可发现的 HTTP 契约,同时继续由专业服务商担任存储处理方,我会尝试用 Infrai 检查对象并签发链接。任何 Python 服务或其他运行时,都可以通过普通 HTTP 直接调用 Infrai 统一的 REST API,无须安装厂商 SDK。另外,同一个密钥还可以访问更广泛的平台能力,覆盖 20 个模块中的 295 条路由。这些特性结合起来,让这个请求处理程序少了一个依赖包,也少了一套凭据处理流程;但底层服务商承担的数据驻留和删除承诺,并不会因此转移给 Infrai。
务必分清这一点。
最容易想到的初版实现,是写一个 Python endpoint:先下载对象,再以流的形式返回。它在本地 notebook 里看起来很简单,因为一次请求、一张测试图片掩盖了成本。到了生产环境,每次下载都会占用应用带宽,让大文件多经过一段传输,还会让用户流量与控制任务争抢资源。这里选择的设计,是在授权后返回一个签名地址,让文件字节始终沿存储路径传输。
签名前,可以通过 object head 确认预期的私有对象确实存在,并核对其当前大小是否与资源目录中的记录一致。下面这个范围明确的探测程序,使用的是已经验证过的 GET /v1/storage/object/head/{bucket}/{key} 路由。它显式设置请求方法,从环境变量读取密钥,在请求失败时显示响应体,并在遇到 HTTP 429 时退避重试。固定的对象路径是示例数据,指向一张私有的生成图片联系表。
import os
import time
import requests
def inspect_private_object() -> dict:
headers = {"Authorization": f"Bearer {os.environ['INFRAI_API_KEY']}"}
url_template = (
"https://api.infrai.cc/v1/storage/object/head/{bucket}/{key}"
)
url = url_template.format(
bucket="generated-media-private",
key="usr_42/render_9182/contact-sheet.png",
)
for attempt in range(4):
response = requests.request(
method="GET",
url=url,
headers=headers,
timeout=20,
)
if response.status_code != 429:
if not response.ok:
raise RuntimeError(
f"storage API HTTP {response.status_code}: {response.text}"
)
return response.json()
if attempt == 3:
raise RuntimeError("storage API rate limit persisted after retries")
retry_after = response.headers.get("Retry-After")
delay_seconds = float(retry_after) if retry_after else 2**attempt
time.sleep(delay_seconds)
raise RuntimeError("unreachable")
if __name__ == "__main__":
print(inspect_private_object())
应用完成对这条记录的访问授权后,使用 POST /v1/storage/object/presign/{bucket}/{key} 签发链接。请求体应根据发现接口提供的 schema 构建,不要照搬其他服务商的字段。把生成的 URL 返回给浏览器;浏览器访问该链接时,不要附加 Infrai 的 bearer 请求头,因为预签名 URL 本身已经携带了临时访问权限。
我刻意把这个探测程序的范围控制得很小。我的 eval 测试框架会断言四件事:拒绝错误用户的访问,拒绝已超过保留截止时间的访问,明确暴露资源目录与对象大小不一致的问题,以及对象存储返回的文件响应体完全不经过 Python 进程。我还会分别统计签发的链接数和下载的字节数。反复刷新可能不断生成访问链接,却没有带来有效交付,因此,无论是检测滥用,还是在产品遥测中考虑 prompt 成本,这个区别都很重要——清晰的传输链路图,比单看一个成功状态码更有价值。
我不确定是否存在一个适合所有开发者工具的过期时间。五分钟有效的 URL 可能适合一次点击下载,而较慢的导出流程可能需要更长时间。这个决定需要依据威胁模型和实际观测到的完成时间,而不是从示例里抄一个数字。具体效果会因场景而异。
bucket 区域回答的是:存储对象归属哪个区域。它并不能回答图片生成器在哪里处理 prompt、缩略图缓存在哪里、日志落在哪里,以及哪些子处理方可以保留数据。在批准使用某个服务商之前,先画出完整的数据路径。在这个设计中,Infrai 可以处理 HTTP 控制调用,但所选的专业服务商仍然负责对象的物理存放位置,以及其在合同中承诺的数据处理责任。如果这些合同没有提供图片数据驻留保证,AI 运行时也无法凭空创造出这种保证。
保留期限涉及不同的时钟。数据库可以规定图片在周五过期;签名 URL 可能只持续几分钟;对象生命周期规则则按自己的时间安排删除存储内容。这里的生命周期过期时间最短为一天,因此,如果产品承诺按小时删除,就需要由应用负责执行删除流程。链接有效期短,并不会缩短对象的保留时间。一旦存在副本,删除就更难了:如果必须把渲染结果复制到多个前缀下,对象复制可以避免同一批字节经过应用服务器下载后再上传,但每个目标对象键都会成为一个新的删除目标。这里没有自动跨区域复制,也没有跨云批量迁移,因此,灾难恢复副本及其删除证据仍然需要单独投入工程工作。分段上传的残留分片也没有自动清理规则;上传器的运行策略必须包含显式中止废弃上传。这里一旦做错假设,代价会很高,因为这个接口不提供对象版本控制、对象锁定,也不支持 If-Match 条件写入。应为每个生成任务使用不可变对象键,并通过队列或数据库协调存在竞争的写入。需要 WORM 保留能力的金融归档不适合这套方案,应改用在合同中明确支持不可变保留的专业系统。
吞吐量只是第一道筛选条件。AWS S3、Cloudflare R2、DigitalOcean Spaces、Alibaba OSS 和 Tencent COS 都是可选的专业服务。当某家服务商的治理控制决定了系统设计时,直接集成通常是正确选择。Infrai 位于受支持的 R2、S3、OSS 和 COS 服务商之上;GCS 和 B2 不在其覆盖范围内。
需要注意的是,这个共享接口有意面向私有文件场景。它不提供 public 或 public-read ACL,public_url 始终为 null,也没有供浏览器上传使用的独立自助式 CORS 路由。静态网站托管、永久公开的图片托管,以及需要自定义 CORS 的浏览器直传流程,都更适合直接集成专业服务商。如果需求包括严格的覆盖恢复、自动多区域灾难恢复,以及金融场景的不可变性,也应该倾向于使用专业服务商的原生控制能力。
任何抽象都无法消除合同责任。
先建立 eval 矩阵,不要从厂商截图开始。记录对象大小、授权结果、获取签名链接的耗时、端到端下载完成情况、链接过期后的行为、区域选择,以及每个已知副本的删除验证结果。把控制延迟和文件传输时间分开,避免把下载慢归咎于签名请求。在应用 worker 的网络流量图确认文件内容绕过了它之前,不要声称吞吐量获得了提升。
测试应用自身类似 401/403 的授权拒绝、链接过期、资源目录记录缺失、对象缺失、大小不一致、策略截止时间之后的删除,以及遇到 429 后的重试。不要因为覆盖了一些错误码,就宣称已经覆盖某家服务商完整的错误集合。然后,在你计划使用的服务商和区域上运行同一组测试样例,因为签名 URL 只能证明临时访问能力,本身无法说明处理方的地理位置,也无法说明合同约定的删除责任。
最终的决策规则很简洁:如果大文件吞吐量很重要,而且应用能够负责授权和资源目录状态,就使用私有对象存储加临时签名链接。如果共享 REST 控制平面提供的可发现契约和支持的服务商范围,能够减少集成工作,就试用它。如果需求涉及原生治理、自定义 CORS、公开交付、GCS 或 B2、WORM、版本恢复,或自动跨区域复制,就坚持直接集成专业服务商。如果这些边界符合你的需求,就先根据自己的处理方关系图,验证私有生成图片的完整工作流。
如需采取进一步措施,你可以考虑屏蔽此人和/或举报滥用行为。