新闻详情

新闻详情

首页 / 资讯中心 / 详情

Epic Stack 图片存储架构:从 SQLite BLOB 迁移到 Tigris 对象存储的完整实践

发布时间:2026/9/18 7:48:50来源:尧图网络
Epic Stack 图片存储架构:从 SQLite BLOB 迁移到 Tigris 对象存储的完整实践
Epic Stack 图片存储架构从 SQLite BLOB 迁移到 Tigris 对象存储的完整实践【免费下载链接】epic-stackThis is a Full Stack app starter with the foundational things setup and configured for you to hit the ground running on your next EPIC idea.项目地址: https://gitcode.com/GitHub_Trending/ep/epic-stackEpic Stack 项目曾在 SQLite 中以 BLOB 形式直接存储上传图片本篇文章以决策文档 docs/decisions/040-tigris-image-storage.md 为主线完整还原这一架构决策的背景、迁移方案与落地实现。读完你将掌握TigrisS3 兼容对象存储的完整配置方式、无 SDK 的 AWS SigV4 签名请求实现原理、SQLite 仅存元数据的混合存储模型以及本地离线开发与测试的 Mock 机制。一、为什么放弃在 SQLite 中存放图片二进制在转向 Tigris 之前Epic Stack 的图片处理遵循 docs/decisions/018-images.md 中的决策将用户上传的图片以二进制 BLOB 形式直接存入 SQLite 数据库。当时的理由很充分——SQLite 官方甚至专门论证过从数据库读取小文件可能比文件系统更快配合 LiteFS 还能免费获得多节点复制存储。但该方案存在明确的硬伤也正是 040 号决策文档开篇列出的四条核心痛点数据库膨胀二进制数据显著增大 SQLite 文件体积备份复杂度随之上升性能衰减大块二进制数据会拖慢数据库整体读写性能备份变慢包含二进制数据的 SQLite 备份更大、耗时更长缺乏 CDN 能力SQLite 无法为图片分发提供任何边缘加速手段。此外 018 号文档还补充了一个量级参考SQLite LiteFS 的组合经过测试可支撑到 10GB 规模这对多数应用足够但足够不是 Epic Stack 的追求。同时旧的方案没有任何图片优化/压缩能力客户端请求什么原图就返回什么原图该问题随后由 041-image-optimization.md 引入的 openimg 按需优化解决。二、决策切换到 Tigris 对象存储2025-02-20Epic Stack 正式通过 040-tigris-image-storage.md 决策Status: accepted核心结论如下将图片二进制数据从 SQLite 中移出放入专用的对象存储SQLite 只保留图片的元数据引用关系、所有权等借助 Tigris 的 S3 兼容 API 完成图片的高效存取为大量图片上传的应用提供更好的扩展性。决策中有一个非常关键的实现取舍不引入任何 S3 SDK而是自己用经过认证的 fetch 请求管理上传与下载。这让依赖面保持极轻也正因为没有 SDK 抽象底层签名逻辑完全透明可控。积极影响SQLite 体积显著缩小备份效率提升关注点分离更清晰二进制数据 vs 关系数据借助 Tigris 基础设施获得潜在更优的图片服务性能对重图片应用更具扩展性未来接入 CDN 更容易数据库维护与备份流程简化Tigris 存储成本远低于 Fly volume 存储。需要接受的代价新增外部服务依赖但 Fly.io 原生集成无需额外注册账号需要管理 Tigris 配置部署配置略微复杂图片上传与检索逻辑复杂度上升。三、配置 Tigris环境变量与本地 Mock3.1 必需的环境变量根据 app/utils/env.server.ts 中的 Zod schema 校验以下五个变量缺一不可均为必填字符串AWS_ENDPOINT_URL_S3还需通过z.string().url()的 URL 格式校验AWS_ACCESS_KEY_IDmock-access-key AWS_SECRET_ACCESS_KEYmock-secret-key AWS_REGIONauto AWS_ENDPOINT_URL_S3https://fly.storage.tigris.dev BUCKET_NAMEmock-bucketAWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEYTigris 的访问凭据AWS_REGION区域Fly Tigris 集成场景固定为autoAWS_ENDPOINT_URL_S3Tigris 的 S3 兼容端点Fly 上默认为https://fly.storage.tigris.devBUCKET_NAME对象存储桶名称。在 Fly.io 上创建 Epic Stack 项目时会自动为应用创建 storage 并注入这些变量在本地开发时这些值写入.env文件配合 MSWMock Service WorkerMock整个开发过程可以完全离线运行。3.2 本地 Mock 的实现Mock 逻辑位于 tests/mocks/tigris.ts它拦截指向${AWS_ENDPOINT_URL_S3}/${BUCKET_NAME}/:key*的 PUT 与 GET 请求PUT校验请求头必须包含Authorization以AWS4-HMAC-SHA256开头、X-Amz-Date、X-Amz-Content-SHA256: UNSIGNED-PAYLOAD且 Credential 中携带正确的 Access Key校验通过后将请求体写落到tests/fixtures/uploaded目录GET优先从tests/fixtures/images读取测试夹具图片否则回落到tests/fixtures/uploaded并返回带Cache-Control: public, max-age31536000, immutable的响应。这意味着即使没有真实 Tigris 账号本地开发与端到端测试也能完整体验完整的上传 → 存储 → 读取链路。四、混合存储模型元数据在 SQLite二进制在 Tigris4.1 数据库 Schemaprisma/schema.prisma 中的UserImage与NoteImage模型与决策文档描述一致实际实现还比决策文档多了一个altText可选字段用于图片的替代文本model UserImage { id String id default(cuid()) altText String? objectKey String createdAt DateTime default(now()) updatedAt DateTime updatedAt user User relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade) userId String index([userId]) } model NoteImage { id String id default(cuid()) altText String? objectKey String createdAt DateTime default(now()) updatedAt DateTime updatedAt note Note relation(fields: [noteId], references: [id], onDelete: Cascade, onUpdate: Cascade) noteId String index([noteId]) }两个模型的关键设计objectKey是核心桥梁字段它指向对象在 Tigris 桶中的唯一路径是 SQLite 与对象存储之间的唯一关联级联删除onDelete: Cascade保证用户/笔记被删除时对应的图片元数据记录一并清除二进制对象需另行清理外键索引index([userId])与index([noteId])保证按所有者查询图片元数据的性能。从源码结构看图片本身不再以字节形式落库数据库只扮演引用登记表的角色这正是决策文档强调的混合存储hybrid approach。五、核心实现无 SDK 的 AWS SigV4 签名请求决策文档明确不使用 S3 SDK全部实现集中在 app/utils/storage.server.ts对 Node 内置crypto模块完成 AWS Signature Version 4SigV4手工签名。5.1 对象 Key 的组织策略上传函数使用cuid2生成唯一 ID并结合时间戳与原始扩展名构造可读、可归类的 Key用户头像users/${userId}/profile-images/${timestamp}-${fileId}.${fileExtension}笔记图片users/${userId}/notes/${noteId}/images/${timestamp}-${fileId}.${fileExtension}这种用户/模块/时间戳-随机ID.扩展名的层级结构天然支持按前缀列出/清理对象。5.2 签名流程AWS SigV4 一步步拆解getBaseSignedRequestInfo是签名核心它完全手工实现了 SigV4 四步流程构造规范化请求Canonical Request按HTTP方法 / 资源路径 / 规范化查询串 / 规范化头 / 签名头列表 / 载荷哈希拼接。本项目使用UNSIGNED-PAYLOAD对内容不做流式哈希适用于流式上传构造待签名字符串String to SignAWS4-HMAC-SHA256 amzDate credentialScope(dateStamp/region/s3/aws4_request) sha256(canonicalRequest)派生签名密钥Signing Key通过getSignatureKey对AWS4${secretKey}依次做 HMACdate → region → service → aws4_request生成签名对待签名字符串做最终 HMAC-SHA256组装进Authorization头。5.3 上传与下载两个方向的封装上传PUTuploadToStorage接受File或mjackson/form-data-parser的FileUpload对FileUpload直接使用.stream()流式发送决策与代码都在追求轻量并在头部附带Content-Type与X-Amz-Meta-Upload-Date元数据响应非 2xx 时记录状态码并抛出错误。下载GETgetSignedGetRequestInfo(key)复用同一签名基座生成带签名的 GET URL 与头信息供代理层拉取对象。5.4 上传在业务层的调用链签名上传被两处业务逻辑复用头像上传app/utils/auth.server.ts 在用户认证/同步流程中调用uploadProfileImage(user.id, imageFile)并把返回的objectKey写入UserImage记录笔记图片上传app/routes/users/$username/notes/shared/note-editor.server.tsx 在笔记表单处理时对每个带文件的图片字段调用uploadNoteImage(userId, noteId, file)将objectKey存入NoteImage头像设置页面 app/routes/settings/profile/photo.tsx 同样通过uploadProfileImage完成上传。六、图片服务出口本地代理 按需优化决策文档提到图片 URL 指向本地服务器由本地服务器代理到 Tigris这一层实现在 app/routes/resources/images.tsxloader 接收objectKey查询参数调用getSignedGetRequestInfo生成带签名 URL通过 openimg 的getImgResponse以fetch方式回源拉取并设置Cache-Control: public, max-age31536000, immutable的强缓存端点在服务图片的同时还基于 openimg sharp 提供w、h、format、fit等按需变换参数优化结果落盘到/data/images生产环境或tests/fixtures/openimg测试环境做文件缓存见 docs/image-optimization.md出于安全考虑回源域名通过allowlistedOrigins白名单限制仅允许当前应用域名与AWS_ENDPOINT_URL_S3。这套签名直连 本地代理 按需优化的组合既避免了将私有存储桶直接暴露给浏览器又为后续接入 CDN 留好了位置。七、迁移与向后兼容决策文档的实施清单明确包含数据库迁移 既有图片手工迁移 提供迁移工具三项工作设置 Tigris 配置修改图片上传处理器将文件存入 Tigris更新图片检索路由从 Tigris 提供服务迁移期间保持向后兼容需要数据库迁移以及既有图片的手工迁移为已有应用提供迁移工具。对正在升级的存量应用建议的迁移路径是先把既有 SQLite 中的 BLOB 图片逐个导出按新 Key 规则上传到 Tigris 桶再执行 Prisma 迁移将 BLOB 列替换为objectKey引用最后在验证图片可正常代理访问后释放数据库中的二进制数据。八、小结这套架构带来的工程启示从 018-images.md 的先存 SQLite到 040-tigris-image-storage.md 的迁往 TigrisEpic Stack 的图片存储演进完整呈现了一次典型的架构升级路径当数据形态二进制 vs 关系型与业务诉求扩展性、CDN、成本不匹配时果断把关注点拆开。最终形态值得直接借鉴关系数据库只存引用SQLite 表里只有一个objectKey字符串体积小、备份快、查询快对象存储只管二进制Tigris 负责海量字节的存取与分发无 SDK 的签名实现约 180 行代码完成 SigV4 签名app/utils/storage.server.ts零第三方依赖本地全离线可测MSW Mock 让开发者不配真实账号也能跑通全链路tests/mocks/tigris.ts。如果你的应用同样面临图片越来越多、数据库越来越重的问题可以直接复用这套模式选一个 S3 兼容存储用 SigV4 签名手写上传下载数据库只留objectKey再配一层按需优化代理即可获得一份轻量、可扩展、可离线开发验证的图片存储方案。相关文档docs/decisions/040-tigris-image-storage.md · docs/decisions/018-images.md · docs/decisions/041-image-optimization.md · docs/image-storage.md · docs/image-optimization.md【免费下载链接】epic-stackThis is a Full Stack app starter with the foundational things setup and configured for you to hit the ground running on your next EPIC idea.项目地址: https://gitcode.com/GitHub_Trending/ep/epic-stack创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

STM32实战进阶:穿透CubeMX抽象层的工业级开发指南 2026/9/18 8:28:15

STM32实战进阶:穿透CubeMX抽象层的工业级开发指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
open-code-review:开放式代码审查机制,让团队真正受益 2026/9/18 8:28:15

open-code-review:开放式代码审查机制,让团队真正受益

打从开始带团队做代码审查,我就一直在琢磨一个问题:代码审查这件事,到底应该做成什么样,才能真正让团队受益,而不是变成每月一次的形式主义考核。后来我主导推进了内部代号为open-code-review的项目,把过去…

阅读更多 →
Java基础试卷深度拆解:语法陷阱、循环与面向对象考点 2026/9/18 8:28:15

Java基础试卷深度拆解:语法陷阱、循环与面向对象考点

简介:这是一份Java语言基础测试试卷(第六套),面向正在复习Java核心语法、准备课程考试或面试笔试的初学者与自学者。内容以单项选择题为主,覆盖Java版本划分、main方法定义、构造方法重载、变量命名规范、boolean赋值、…

阅读更多 →
SeaTunnel 多表 Transform 能力边界解析:从 TableMerge 到跨表 JOIN 的完整实战指南 2026/9/18 8:28:15

SeaTunnel 多表 Transform 能力边界解析:从 TableMerge 到跨表 JOIN 的完整实战指南

SeaTunnel 多表 Transform 能力边界解析:从 TableMerge 到跨表 JOIN 的完整实战指南 【免费下载链接】seatunnel SeaTunnel is a multimodal, high-performance, distributed, massive data integration tool. 项目地址: https://gitcode.com/GitHub_Trending/se/…

阅读更多 →
VSCode终端打不开?新版JSON配置快速修复指南 2026/9/18 8:28:15

VSCode终端打不开?新版JSON配置快速修复指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
手写最小 ReAct 循环:看透 Agent 工具调用底层机制 2026/9/18 8:25:14

手写最小 ReAct 循环:看透 Agent 工具调用底层机制

上周有个做后端的同事跑来问我,Agent 里的工具调用到底是怎么转起来的。他的项目已经用上了某个封装得很厚的 Agent 框架,能跑通 demo,但一旦模型不按套路出牌、或者工具返回了脏数据,他就完全不知道该从哪下手。我给他的建议是&a…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞