初版把 data: 一律排除在「未上传」外——未落盘文档粘贴的图、导入 文档自带的图都是 data:image/...;base64 形态,批量/右键/状态栏全 检测不到。现 isUploadableImageSrc = 本地文件引用 ∪ base64 内嵌图: base64 图走新命令 s3_upload_base64(总数 31 对账),前端剥前缀传 纯 base64、Rust 解码后与文件图共用同一条 PUT 通路;对象名按内容 哈希(FNV-1a)生成,同图同名幂等。未落盘文档粘贴图也挂上自动上传。 进度/报错展示名对 base64 图用其哈希名,不再漏出 base64 串。 验证:独立 crate 对模拟 S3 实测 base64 解码 PUT;vitest 136+2、构建通过。
5.3 KiB
5.3 KiB
docs/30 — S3 图片上传
需求(2026-11-15 用户提出):①设置里能配 S3 并有「自动上传图片」开关; ②未上传的图片右键要有「上传到 S3」;③整个文档要有批量检测未上传图片 并上传的按钮。
设计
- 协议层在 Rust(
src-tauri/src/s3.rs):AWS SigV4 签名 + PUT Object, reqwest(blocking + rustls)。配置随每次调用从前端传入,Rust 不落盘 保存密钥。命令 2 个(总数 30 与 lib.rs 注册对账):s3_upload_image、s3_test_connection(对桶发签名 HEAD,按 200/403/404 给可读结论)。 - 寻址风格:Endpoint 留空 = AWS S3 虚拟主机风格
https://{bucket}.s3.{region}.amazonaws.com/{key};填了自定义 Endpoint(MinIO / R2 / OSS 等 S3 兼容服务)走路径风格{endpoint}/{bucket}/{key}。对象键 = 前缀 + 原文件名(同名覆盖, 重复上传幂等)。配了「自定义访问域名」时,回填文档的地址按 「域名/存储桶/前缀/文件名」拼——MinIO 等自建服务里 API 地址与对外 访问地址是两个域名、访问域名通常指向服务根,桶名必须出现在路径里 (2026-11-15 用户指正,原实现漏了桶名一段)。不填访问域名时回填的 就是上传请求本身的地址(路径风格已含桶名、AWS 虚拟主机风格桶名在 域名里)。 - 配置存储:进 UiState.s3(本机偏好库 SQLite/localStorage),字段 endpoint/region/bucket/accessKey/secretKey/prefix/publicBase/autoUpload; 加载时与默认值深合并。设置弹窗在 文件 → S3 上传设置…,带「测试连接」。 弹窗里已明示:密钥只保存在本机设置里,不写进文档、不会被 Git 同步带走。
- 「未上传」判定:本地文件引用(相对路径)或内嵌 base64 图片
(
data:image/...;base64,...)都算未上传(isUploadableImageSrc); http(s)/blob 不算。base64 形态来自未落盘文档的粘贴图与导入文档自带 图(2026-11-15 用户指正:初版把 data: 一律排除、检测不到)。base64 图没有文件可读,走单独的s3_upload_base64命令(命令总数 31): 前端剥前缀传纯 base64,Rust 解码后与文件图共用同一条 PUT 通路; 对象名按内容哈希生成(image-.<扩展名>),同图同名幂等。 上传成功后把编辑器里所有同 src 的图片节点 一次事务换成 S3 地址(replaceImageSrc,同图多处引用一并换),文档 随之变脏、走既有自动保存落盘。本地 .assets 文件保留不删(删了断掉 离线打开时的图,且用户可能还要本地备份)。
三个入口
- 自动上传(开关在 S3 设置里):粘贴/拖入(handleImageFile 落盘后) 与「插入图片」(copyToAssets 后)触发后台上传+回换;粘贴建节点是 异步的,第一次回换扑空时 900ms 后重试一次;再扑空或上传失败都给 提示(flash),不静默、本地引用保留。
- 右键单张:编辑区右键命中
时经
imageSrcForElement(posAtDOM 反查节点)拿到文档 src;本地图时菜单多一项「上传到 S3」。 未配置 S3 时点它先弹设置弹窗并提示。 - 批量:文件 →「上传文档图片到 S3…」+ 状态栏按钮(当前文档有未
上传本地图片时出现,带张数)。扫描走编辑器
listImages()(PM 文档 遍历、去重),逐张上传回换,进度弹窗实时显示 第 N/总 张与当前文件 名,结束汇总成功/失败明细。源码模式下不可用(菜单禁用、状态栏不 计数),提示切回即时渲染。
验证
- 签名算法:独立小 crate 里对照 AWS 官方文档示例(IAM ListUsers,
20150830T123600Z)——算出的 Signature 与官方值
5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400e06b5924a6f2b5d7逐字一致;UTC 时间换算(Hinnant civil 算法)与空负载哈希一并断言。 - base64 通路:同一独立 crate 里实测 upload_base64:解码后的字节 与原文一致地 PUT 到模拟 S3、Content-Type 按扩展名、非法 base64 在 发请求前就报错。
- 协议端到端:把随产品发货的同一份 s3.rs 放进独立 crate,对本地 TCP 模拟 S3 跑真实请求:PUT 路径风格、中文文件名百分号编码、 x-amz-content-sha256 与请求体哈希一致、Authorization 头结构、 自定义访问域名回填、HEAD 桶测试连接、空配置校验,2 例全过。
- 前端:vitest 133+2(新增 s3.test 路径/判定、editor-images 收集/ 回换/元素反查、s3-modal 保存载荷、右键菜单图片项);tsc 与构建通过; 设置弹窗无头浏览器截图核对(七个字段、开关、三按钮齐全)。
- Cargo.lock 已更新(reqwest 0.12.28 等,本机 cargo fetch 完成)。
待真机验证(Windows)
- Rust 完整构建(新增 reqwest/sha2/hmac 依赖)与真实 S3/兼容服务联调: 填真实配置 → 测试连接 → 粘贴图片自动上传 → 右键单张 → 批量上传, 并在桶里核对对象与文档回填地址可公开访问(桶需允许公开读或配 CDN)。
- 注意:签名时钟用系统 UTC 时间,本机时钟偏差过大会被 S3 拒绝 (报错会带回 S3 的 Code/Message,按提示校时即可)。