# 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 文件保留不删(删了断掉 离线打开时的图,且用户可能还要本地备份)。 ## 三个入口 1. **自动上传**(开关在 S3 设置里):粘贴/拖入(handleImageFile 落盘后) 与「插入图片」(copyToAssets 后)触发后台上传+回换;粘贴建节点是 异步的,第一次回换扑空时 900ms 后重试一次;再扑空或上传失败都给 提示(flash),不静默、本地引用保留。 2. **右键单张**:编辑区右键命中 时经 `imageSrcForElement` (posAtDOM 反查节点)拿到文档 src;本地图时菜单多一项「上传到 S3」。 未配置 S3 时点它先弹设置弹窗并提示。 3. **批量**:文件 →「上传文档图片到 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,按提示校时即可)。