From 65387ca846ce5f499ad4a047525c6a09bdb0492d Mon Sep 17 00:00:00 2001 From: yuyx <237899745@qq.com> Date: Sun, 8 Feb 2026 00:12:27 +0800 Subject: [PATCH] feat(compress): improve quality guardrails and format conversion --- README.md | 4 +- docs/api.md | 7 +- docs/ui.md | 2 +- frontend/src/pages/DocsPage.vue | 44 ++++---- frontend/src/pages/HomePage.vue | 38 ++++++- src/api/compress.rs | 38 ++++--- src/api/tasks.rs | 10 +- src/services/compress.rs | 192 +++++++++++++++++++++++--------- 8 files changed, 233 insertions(+), 102 deletions(-) diff --git a/README.md b/README.md index 5b79f48..97df58d 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,9 @@ ### 核心功能 -- **图片压缩**:支持 PNG/JPG/JPEG/WebP/AVIF/GIF/BMP/TIFF/ICO(GIF 仅静态) +- **图片压缩**:支持 PNG/JPG/JPEG/WebP/AVIF/GIF/BMP/TIFF/ICO(GIF 仅静态,支持格式转换) - **批量处理**:支持多图片同时上传和处理 -- **压缩率**:1-100(数值越大压缩越强) +- **压缩率**:1-100(数值越小压缩越强,100 为不压缩) - **用户系统**:注册、登录、API Key 管理 - **计费与用量**:套餐/订阅/配额/发票 - **管理员后台**:用户管理、系统监控、配置管理 diff --git a/docs/api.md b/docs/api.md index da333ea..8239eab 100644 --- a/docs/api.md +++ b/docs/api.md @@ -274,9 +274,10 @@ Idempotency-Key: # 建议 | `file` | File | 是 | 图片文件 | | `compression_rate` | Integer | 否 | 压缩率 1-100(压缩后体积占原图比例,数值越小压缩越强,100 表示不压缩),优先级高于 `level` | | `level` | String | 否 | `high` / `medium` / `low`(兼容参数,默认 `medium`) | -| `output_format` | String | 否 | 已停用,仅支持保持原格式 | +| `output_format` | String | 否 | 输出格式:`png/jpeg/webp/avif/gif/bmp/tiff/ico`(默认保持原格式) | | `max_width` | Integer | 否 | 最大宽度(等比缩放) | | `max_height` | Integer | 否 | 最大高度(等比缩放) | +| `target_size_bytes` | Integer | 否 | 目标体积(字节),仅 `jpeg/webp/avif` 输出支持;会优先保清晰度并在必要时小幅缩放 | | `preserve_metadata` | Boolean | 否 | 是否保留元数据(默认 `false`) | 响应: @@ -309,6 +310,8 @@ X-API-Key: # 或 Bearer token(不建议匿名) Idempotency-Key: # 建议 ``` +表单字段与 `/compress` 一致(包括 `output_format`、`target_size_bytes`)。 + 成功响应: - HTTP `200` - Body:压缩后的图片二进制 @@ -339,7 +342,7 @@ Idempotency-Key: # 建议 | `files[]` | File[] | 是 | 图片文件数组(上限由套餐决定) | | `compression_rate` | Integer | 否 | 压缩率 1-100(压缩后体积占原图比例,数值越小压缩越强,100 表示不压缩),优先级高于 `level` | | `level` | String | 否 | `high` / `medium` / `low`(兼容参数) | -| `output_format` | String | 否 | 已停用,仅支持保持原格式 | +| `output_format` | String | 否 | 输出格式:`png/jpeg/webp/avif/gif/bmp/tiff/ico`(默认保持原格式) | | `preserve_metadata` | Boolean | 否 | 是否保留元数据(默认 `false`) | 响应: diff --git a/docs/ui.md b/docs/ui.md index 96c3ad7..bf2426f 100644 --- a/docs/ui.md +++ b/docs/ui.md @@ -44,7 +44,7 @@ 核心组件: - 顶栏:Logo + `/pricing` `/docs` + 登录/注册(登录后显示头像菜单) - 上传区:拖拽 + 点击选择 + 限制提示(格式/大小/数量) -- 参数区:压缩率(压缩后体积占比)、尺寸限制、元数据开关(输出格式保持原图) +- 参数区:压缩率(压缩后体积占比)/目标体积、输出格式、尺寸限制、元数据开关 - 结果区:文件列表(缩略图、大小、节省%、状态、下载/重试) - 汇总区:总节省、下载 ZIP、清空 - 信任区:隐私说明(默认去 EXIF)、保留期说明、状态页/联系入口 diff --git a/frontend/src/pages/DocsPage.vue b/frontend/src/pages/DocsPage.vue index 4413122..7013620 100644 --- a/frontend/src/pages/DocsPage.vue +++ b/frontend/src/pages/DocsPage.vue @@ -19,7 +19,7 @@
  • Base URL:https://ys.workyai.cn/api/v1
  • 认证方式:X-API-Key(推荐)或 Authorization: Bearer <token>
  • -
  • 支持格式:PNG / JPG / JPEG / WebP / AVIF / GIF(静态)/ BMP / TIFF / ICO
  • +
  • 支持格式:PNG / JPG / JPEG / WebP / AVIF / GIF(静态)/ BMP / TIFF / ICO(支持 output_format 转码)
  • 压缩率:compression_rate 1-100,表示压缩后体积占原图比例,100 为不压缩
  • 计量:成功压缩 1 个文件计 1 次;若体积未变小或压缩率为 100,则不扣额度
@@ -28,14 +28,15 @@
同步压缩(返回 JSON + 下载链接)
- POST /compress,表单字段:filecompression_rate、 - max_widthmax_heightpreserve_metadata + POST /compress,表单字段:filecompression_rate/target_size_bytes、 + output_formatmax_widthmax_heightpreserve_metadata
-
curl -X POST \\
-  -H \"X-API-Key: if_live_xxx\" \\
-  -F \"file=@./demo.jpg\" \\
-  -F \"compression_rate=20\" \\
-  -F \"max_width=2000\" \\
+      
curl -X POST \
+  -H "X-API-Key: if_live_xxx" \
+  -F "file=@./demo.jpg" \
+  -F "compression_rate=20" \
+  -F "output_format=webp" \
+  -F "max_width=2000" \
   https://ys.workyai.cn/api/v1/compress
返回字段包含 download_urlsaved_percentbilling.units_charged。 @@ -45,12 +46,13 @@
同步压缩(直接返回二进制)
- POST /compress/direct,响应头包含原/压缩大小与扣费信息。 + POST /compress/direct,支持同样参数(含 output_format、target_size_bytes),响应头包含原/压缩大小与扣费信息。
-
curl -X POST \\
-  -H \"X-API-Key: if_live_xxx\" \\
-  -F \"file=@./demo.jpg\" \\
-  -F \"compression_rate=20\" \\
+      
curl -X POST \
+  -H "X-API-Key: if_live_xxx" \
+  -F "file=@./demo.jpg" \
+  -F "target_size_bytes=120000" \
+  -F "output_format=jpeg" \
   https://ys.workyai.cn/api/v1/compress/direct -o out.jpg -D headers.txt
重点响应头:ImageForge-Original-Size、 @@ -64,16 +66,16 @@
POST /compress/batch,上传多文件后返回任务 ID。
-
curl -X POST \\
-  -H \"X-API-Key: if_live_xxx\" \\
-  -F \"files[]=@./a.jpg\" \\
-  -F \"files[]=@./b.jpg\" \\
-  -F \"compression_rate=30\" \\
+      
curl -X POST \
+  -H "X-API-Key: if_live_xxx" \
+  -F "files[]=@./a.jpg" \
+  -F "files[]=@./b.jpg" \
+  -F "compression_rate=30" \
   https://ys.workyai.cn/api/v1/compress/batch
轮询任务:GET /compress/tasks/<task_id>,响应包含每个文件的 download_urldownload_all_url(ZIP)。
-
curl -H \"X-API-Key: if_live_xxx\" \\
+      
curl -H "X-API-Key: if_live_xxx" \
   https://ys.workyai.cn/api/v1/compress/tasks/550e8400-e29b-41d4-a716-446655440200
@@ -82,10 +84,10 @@
下载地址不在 /api/v1 下,需带同样的认证头。
-
curl -H \"X-API-Key: if_live_xxx\" \\
+      
curl -H "X-API-Key: if_live_xxx" \
   -L https://ys.workyai.cn/downloads/<file_id> -o result.jpg
 
-curl -H \"X-API-Key: if_live_xxx\" \\
+curl -H "X-API-Key: if_live_xxx" \
   -L https://ys.workyai.cn/downloads/tasks/<task_id> -o batch.zip
diff --git a/frontend/src/pages/HomePage.vue b/frontend/src/pages/HomePage.vue index f1e47bc..db40342 100644 --- a/frontend/src/pages/HomePage.vue +++ b/frontend/src/pages/HomePage.vue @@ -21,12 +21,14 @@ interface UploadItem { const auth = useAuthStore() type CompressionMode = 'percent' | 'size' +type OutputFormatOption = 'auto' | 'png' | 'jpeg' | 'webp' | 'avif' | 'gif' | 'bmp' | 'tiff' | 'ico' const options = reactive({ mode: 'percent' as CompressionMode, // 压缩模式:百分比 / 目标大小 compressionRate: 60, targetSize: '' as string, // 目标大小数值 targetUnit: 'KB' as 'KB' | 'MB', // 目标大小单位 + outputFormat: 'auto' as OutputFormatOption, maxWidth: '' as string, maxHeight: '' as string, }) @@ -114,7 +116,9 @@ function getTargetSizeBytes(): number | undefined { if (options.mode !== 'size') return undefined const size = Number(options.targetSize) if (!Number.isFinite(size) || size <= 0) return undefined - return options.targetUnit === 'MB' ? size * 1024 * 1024 : size * 1024 + const bytes = options.targetUnit === 'MB' ? size * 1024 * 1024 : size * 1024 + if (!Number.isFinite(bytes) || bytes < 1024) return undefined + return Math.round(bytes) } async function runOne(item: UploadItem) { @@ -122,14 +126,25 @@ async function runOne(item: UploadItem) { item.error = undefined try { + const targetSizeBytes = getTargetSizeBytes() + if (options.mode === 'size' && !targetSizeBytes) { + item.status = 'error' + item.error = '请填写有效目标大小(至少 1KB)' + return + } + + const outputFormat = options.outputFormat === 'auto' ? undefined : options.outputFormat + const compressOptions = options.mode === 'percent' ? { compression_rate: options.compressionRate, + output_format: outputFormat, max_width: toInt(options.maxWidth), max_height: toInt(options.maxHeight), } : { - target_size_bytes: getTargetSizeBytes(), + target_size_bytes: targetSizeBytes, + output_format: outputFormat, max_width: toInt(options.maxWidth), max_height: toInt(options.maxHeight), } @@ -542,6 +557,25 @@ async function resendVerification() {
直接指定压缩后的目标大小,系统会自动调整质量以逼近目标(仅 JPEG/WebP/AVIF 支持)。
+ +