feat(s3): base64 内嵌图片纳入未上传检测与上传(用户指正)

初版把 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、构建通过。
This commit is contained in:
Muse
2026-11-15 09:16:18 +08:00
parent b3c22fd597
commit fb51276bc5
11 changed files with 156 additions and 27 deletions

1
src-tauri/Cargo.lock generated
View File

@@ -2029,6 +2029,7 @@ dependencies = [
name = "new-markdown"
version = "0.1.0"
dependencies = [
"base64 0.22.1",
"encoding_rs",
"hmac",
"reqwest 0.12.28",

View File

@@ -22,3 +22,4 @@ trash = "5"
reqwest = { version = "0.12", default-features = false, features = ["blocking", "rustls-tls"] }
sha2 = "0.10"
hmac = "0.12"
base64 = "0.22"

View File

@@ -941,6 +941,17 @@ pub async fn s3_upload_image(
.map_err(|e| format!("上传任务失败:{e}"))?
}
#[tauri::command]
pub async fn s3_upload_base64(
config: crate::s3::S3Config,
base64: String,
file_name: String,
) -> Result<String, String> {
tauri::async_runtime::spawn_blocking(move || crate::s3::upload_base64(&config, &base64, &file_name))
.await
.map_err(|e| format!("上传任务失败:{e}"))?
}
#[tauri::command]
pub async fn s3_test_connection(config: crate::s3::S3Config) -> Result<String, String> {
tauri::async_runtime::spawn_blocking(move || crate::s3::test_connection(&config))

View File

@@ -68,6 +68,7 @@ pub fn run() {
commands::git_set_remote,
commands::git_sync_now,
commands::s3_upload_image,
commands::s3_upload_base64,
commands::s3_test_connection,
commands::delete_to_trash,
commands::db_recent_list,

View File

@@ -161,6 +161,25 @@ pub fn upload(cfg: &S3Config, file_path: &str, file_name: &str) -> Result<String
return Err("图片超过 64MB,未上传。".into());
}
let bytes = std::fs::read(file_path).map_err(|e| format!("读取待上传图片失败:{e}"))?;
upload_bytes(cfg, &bytes, file_name)
}
/// 上传文档内嵌的 base64 图片(data:image/...;base64 数据,前端已剥掉前缀)。
/// 未落盘文档粘贴的图片只有这种形态,没有文件可读。
pub fn upload_base64(cfg: &S3Config, base64_str: &str, file_name: &str) -> Result<String, String> {
validate(cfg)?;
use base64::Engine;
let bytes = base64::engine::general_purpose::STANDARD
.decode(base64_str.trim())
.map_err(|e| format!("图片 base64 解码失败:{e}"))?;
if bytes.len() as u64 > MAX_IMAGE_BYTES {
return Err("图片超过 64MB,未上传。".into());
}
upload_bytes(cfg, &bytes, file_name)
}
fn upload_bytes(cfg: &S3Config, bytes: &[u8], file_name: &str) -> Result<String, String> {
let bytes = bytes.to_vec();
let key = object_key(cfg, file_name);
let (url, host, canonical_uri) = target(cfg, &key);
let payload_hash = sha256_hex(&bytes);