diff --git a/docs/外部对接文档.md b/docs/外部对接文档.md index b1c9cbf..240f592 100644 --- a/docs/外部对接文档.md +++ b/docs/外部对接文档.md @@ -1,12 +1,12 @@ -# HTML 转 PDF 服务 — 外部对接文档 +# HTML 转图片 / PDF 服务 — 外部对接文档 -> 版本:2.0.0 | 更新日期:2026-03-16 +> 版本:2.0.1 | 更新日期:2026-03-17 --- ## 1. 概述 -本服务提供 HTML/URL 转 PDF 和图片的能力,支持同步和异步两种调用模式: +本服务提供 HTML/URL 转图片和 PDF 的能力,支持同步和异步两种调用模式: - **同步模式**:请求后直接返回文件流,适合简单、低并发场景 - **异步模式(推荐)**:提交任务后通过轮询或回调获取结果,适合高并发、大批量场景 @@ -31,7 +31,7 @@ Content-Type: application/json ```json { "apiKey": "your-api-key", - "userId": "your-user-id" // 可选 + "userId": "your-user-id" } ``` @@ -42,7 +42,7 @@ Content-Type: application/json "accessToken": "eyJhbGciOiJIUzI1NiIs...", "tokenType": "Bearer", "expiresIn": 3600, - "expiresAt": "2026-03-16T11:00:00Z" + "expiresAt": "2026-03-17T11:00:00Z" } ``` @@ -52,38 +52,28 @@ Content-Type: application/json **方式一:JWT Token(通过 API Key 换取)** -先调用 `/api/auth/token` 获取 Token,然后在请求头中携带: - ``` Authorization: Bearer {accessToken} ``` -Token 有过期时间,过期后需要重新获取或刷新。 - -**方式二:直接使用 API Key(无需换 Token)** - -每次请求直接携带 API Key,支持以下三种传递方式: +**方式二:直接使用 API Key** ``` X-API-Key: your-api-key ``` +或: + ``` Authorization: ApiKey your-api-key ``` -``` -GET /api/tasks/xxx?api_key=your-api-key -``` - -推荐使用 `X-API-Key` Header,Query String 方式存在 Key 泄露到日志的风险。 +推荐使用 `X-API-Key` Header,Query String 方式(`?api_key=xxx`)存在 Key 泄露到日志的风险。 > 以下路径无需认证:`/health`、`/swagger`、`/metrics`、`/api/auth/token` ### 2.3 刷新 Token -使用 JWT Token 方式时,可在 Token 过期前刷新: - ``` POST /api/auth/refresh Authorization: Bearer {当前Token} @@ -95,58 +85,7 @@ Authorization: Bearer {当前Token} 适合简单场景,请求后直接返回文件二进制流。 -### 3.1 HTML 转 PDF - -``` -POST /api/pdf/convert/html -Content-Type: application/json -``` - -**请求体:** - -```json -{ - "html": "

Hello World

这是测试内容

", - "options": { - "format": "A4", - "landscape": false, - "printBackground": true, - "margin": { - "top": "10mm", - "right": "10mm", - "bottom": "10mm", - "left": "10mm" - } - }, - "saveLocal": false -} -``` - -**响应**:`application/pdf` 文件流 - -### 3.2 URL 转 PDF - -``` -POST /api/pdf/convert/url -Content-Type: application/json -``` - -**请求体:** - -```json -{ - "url": "https://example.com", - "waitUntil": "networkidle0", - "timeout": 30, - "options": { - "format": "A4", - "printBackground": true - }, - "saveLocal": false -} -``` - -### 3.3 HTML 转图片 +### 3.1 HTML 转图片 ``` POST /api/image/convert/html @@ -172,7 +111,7 @@ Content-Type: application/json **响应**:对应格式的图片文件流(`image/png`、`image/jpeg`、`image/webp`) -### 3.4 URL 转图片 +### 3.2 URL 转图片 ``` POST /api/image/convert/url @@ -185,7 +124,7 @@ Content-Type: application/json { "url": "https://example.com", "waitUntil": "networkidle0", - "timeout": 30, + "timeout": 30000, "options": { "format": "png", "width": 1920, @@ -196,16 +135,67 @@ Content-Type: application/json } ``` +### 3.3 HTML 转 PDF + +``` +POST /api/pdf/convert/html +Content-Type: application/json +``` + +**请求体:** + +```json +{ + "html": "

Hello World

", + "options": { + "format": "A4", + "landscape": false, + "printBackground": true, + "margin": { + "top": "10mm", + "right": "10mm", + "bottom": "10mm", + "left": "10mm" + } + }, + "saveLocal": false +} +``` + +**响应**:`application/pdf` 文件流 + +### 3.4 URL 转 PDF + +``` +POST /api/pdf/convert/url +Content-Type: application/json +``` + +**请求体:** + +```json +{ + "url": "https://example.com", + "waitUntil": "networkidle0", + "timeout": 30000, + "options": { + "format": "A4", + "printBackground": true + }, + "saveLocal": false +} +``` + --- ## 4. 异步任务接口(推荐) -异步模式下,提交任务后会立即返回任务 ID,通过轮询状态或配置回调来获取结果。 +异步模式下,提交任务后立即返回任务 ID,通过轮询状态或配置回调来获取结果。 -### 4.1 提交 PDF 任务 +### 4.1 提交图片任务 ``` -POST /api/tasks/pdf +POST /api/tasks/image Content-Type: application/json Idempotency-Key: {可选,幂等键} ``` @@ -216,7 +206,70 @@ Idempotency-Key: {可选,幂等键} { "source": { "type": "html", - "content": "

Hello World

这是测试内容

" + "content": "

Hello World

" + }, + "options": { + "format": "png", + "quality": 90, + "width": 1920, + "height": 1080, + "fullPage": true, + "omitBackground": false + }, + "waitUntil": "networkidle2", + "timeout": 60000, + "delayAfterLoad": 1000, + "callback": { + "url": "https://your-app.com/webhook/image-done", + "headers": { + "X-Custom-Header": "value" + }, + "includeFileData": false + }, + "saveLocal": true, + "metadata": { + "orderId": "12345", + "source": "your-system" + } +} +``` + +> `source.type` 为 `"html"` 时,`content` 填 HTML 字符串;为 `"url"` 时,`content` 填页面 URL。 +> `delayAfterLoad`:页面加载完成后额外等待的毫秒数,适合有延迟动画的页面。 + +**响应(202 Accepted):** + +```json +{ + "taskId": "550e8400-e29b-41d4-a716-446655440000", + "status": "pending", + "message": "任务已创建,正在排队处理", + "createdAt": "2026-03-17T10:00:00Z", + "estimatedWaitTime": 3, + "queuePosition": 2, + "links": { + "self": "/api/tasks/550e8400-...", + "status": "/api/tasks/550e8400-.../status", + "download": "/api/tasks/550e8400-.../download" + } +} +``` + +### 4.2 提交 PDF 任务 + +``` +POST /api/tasks/pdf +Content-Type: application/json +Idempotency-Key: {可选} +``` + +**请求体:** + +```json +{ + "source": { + "type": "html", + "content": "

Hello World

" }, "options": { "format": "A4", @@ -230,75 +283,18 @@ Idempotency-Key: {可选,幂等键} } }, "waitUntil": "networkidle0", - "timeout": 30, + "timeout": 60000, "callback": { "url": "https://your-app.com/webhook/pdf-done", - "headers": { - "X-Custom-Header": "value" - }, "includeFileData": false }, "saveLocal": true, "metadata": { - "orderId": "12345", - "source": "billing-system" + "orderId": "12345" } } ``` -> `source.type` 为 `"html"` 时,`content` 填 HTML 字符串;为 `"url"` 时,`content` 填页面 URL。 - -**响应(202 Accepted):** - -```json -{ - "taskId": "550e8400-e29b-41d4-a716-446655440000", - "status": "pending", - "message": "任务已创建,正在排队处理", - "createdAt": "2026-03-16T10:00:00Z", - "estimatedWaitTime": 3, - "queuePosition": 2, - "links": { - "self": "/api/tasks/550e8400-e29b-41d4-a716-446655440000", - "status": "/api/tasks/550e8400-e29b-41d4-a716-446655440000/status", - "download": "/api/tasks/550e8400-e29b-41d4-a716-446655440000/download", - "cancel": "/api/tasks/550e8400-e29b-41d4-a716-446655440000" - } -} -``` - -### 4.2 提交图片任务 - -``` -POST /api/tasks/image -Content-Type: application/json -Idempotency-Key: {可选} -``` - -**请求体:** - -```json -{ - "source": { - "type": "url", - "content": "https://example.com" - }, - "options": { - "format": "png", - "quality": 90, - "width": 1920, - "height": 1080, - "fullPage": true, - "omitBackground": false - }, - "delayAfterLoad": 1000, - "callback": { - "url": "https://your-app.com/webhook/image-done" - }, - "saveLocal": true -} -``` - ### 4.3 批量提交任务 ``` @@ -312,18 +308,20 @@ Content-Type: application/json { "tasks": [ { - "type": "pdf", - "source": { "type": "html", "content": "

文档 1

" }, - "pdfOptions": { "format": "A4", "printBackground": true } - }, - { - "type": "pdf", - "source": { "type": "url", "content": "https://example.com" } + "type": "image", + "source": { "type": "html", "content": "

截图 1

" }, + "imageOptions": { "format": "png", "width": 1920, "height": 1080 } }, { "type": "image", - "source": { "type": "html", "content": "

截图

" }, - "imageOptions": { "format": "png", "width": 1920 } + "source": { "type": "url", "content": "https://example.com" }, + "imageOptions": { "format": "jpeg", "quality": 85 }, + "waitUntil": "networkidle0" + }, + { + "type": "pdf", + "source": { "type": "html", "content": "

文档

" }, + "pdfOptions": { "format": "A4", "printBackground": true } } ], "callback": { @@ -360,17 +358,17 @@ GET /api/tasks/{taskId} ```json { "taskId": "550e8400-...", - "type": "pdf", + "type": "image", "source": { "type": "html", "content": "..." }, "status": "completed", - "createdAt": "2026-03-16T10:00:00Z", - "startedAt": "2026-03-16T10:00:01Z", - "completedAt": "2026-03-16T10:00:04Z", - "duration": 3000, + "createdAt": "2026-03-17T10:00:00Z", + "startedAt": "2026-03-17T10:00:01Z", + "completedAt": "2026-03-17T10:00:03Z", + "duration": 2000, "retryCount": 0, "result": { - "fileSize": 102400, - "fileType": "pdf", + "fileSize": 204800, + "fileType": "png", "downloadUrl": "/api/tasks/550e8400-.../download" }, "error": null, @@ -383,7 +381,7 @@ GET /api/tasks/{taskId} ### 4.5 轻量级状态查询 -适合高频轮询场景,返回数据量小: +适合高频轮询场景: ``` GET /api/tasks/{taskId}/status @@ -395,19 +393,36 @@ GET /api/tasks/{taskId}/status { "taskId": "550e8400-...", "status": "processing", - "createdAt": "2026-03-16T10:00:00Z", - "startedAt": "2026-03-16T10:00:01Z", + "createdAt": "2026-03-17T10:00:00Z", + "startedAt": "2026-03-17T10:00:01Z", "completedAt": null } ``` -### 4.6 下载结果文件 +### 4.6 查询任务列表 + +``` +GET /api/tasks?status=completed&type=image&page=1&pageSize=20 +``` + +**Query 参数:** + +| 参数 | 类型 | 说明 | +|------|------|------| +| `status` | string | 按状态筛选:pending / processing / completed / failed / cancelled | +| `type` | string | 按类型筛选:image / pdf | +| `startDate` | datetime | 创建时间起始 | +| `endDate` | datetime | 创建时间截止 | +| `page` | int | 页码,默认 1 | +| `pageSize` | int | 每页数量,默认 20,最大 100 | + +### 4.7 下载结果文件 ``` GET /api/tasks/{taskId}/download ``` -**响应**:文件二进制流,Content-Type 根据任务类型自动设置。 +**响应**:文件二进制流,Content-Type 根据任务类型自动设置(`image/png`、`image/jpeg`、`image/webp`、`application/pdf`)。 响应头包含: - `X-Task-Id`: 任务 ID @@ -415,75 +430,29 @@ GET /api/tasks/{taskId}/download > 任务未完成时返回 `409 Conflict`。 -### 4.7 取消任务 +### 4.8 取消任务 ``` DELETE /api/tasks/{taskId} ``` -**响应:** - -```json -{ - "taskId": "550e8400-...", - "status": "cancelled", - "message": "任务已取消" -} -``` - > 仅 `pending` 状态的任务可取消,其他状态返回 `409 Conflict`。 -### 4.8 重试任务 +### 4.9 重试任务 ``` POST /api/tasks/{taskId}/retry ``` -**响应(202 Accepted):** +> 仅 `failed` 状态的任务可重试,返回 `202 Accepted`。 -```json -{ - "taskId": "550e8400-...", - "status": "pending", - "message": "任务已重新加入队列" -} -``` - -> 仅 `failed` 状态的任务可重试。 - -### 4.9 查询批量任务状态 +### 4.10 查询批量任务状态 ``` GET /api/tasks/batch/{batchId} ``` -**响应:** - -```json -{ - "batchId": "batch-xxxx", - "status": "completed", - "totalTasks": 3, - "completedTasks": 3, - "failedTasks": 0, - "processingTasks": 0, - "pendingTasks": 0, - "createdAt": "2026-03-16T10:00:00Z", - "completedAt": "2026-03-16T10:00:10Z", - "tasks": [ - { - "taskId": "task-1", - "status": "completed", - "type": "pdf", - "duration": 3000, - "downloadUrl": "/api/tasks/task-1/download", - "errorMessage": null - } - ] -} -``` - -### 4.10 预检(Dry-run) +### 4.11 预检(Dry-run) 提交前检查内容是否可渲染、是否存在 SSRF 风险: @@ -510,12 +479,55 @@ Content-Type: application/json "ok": true, "canRender": true, "suggestedQueue": "normal", - "estimatedRenderTimeMs": 3000, + "estimatedRenderTimeMs": 2000, "issues": [], "ssrfBlocked": false } ``` +### 4.12 回调重放 + +手动重新触发某个任务的回调通知: + +``` +POST /api/tasks/{taskId}/callback/replay +Content-Type: application/json +``` + +**请求体(可选):** + +```json +{ + "attempts": 3 +} +``` + +### 4.13 查询回调日志 + +``` +GET /api/tasks/{taskId}/callback/logs +``` + +**响应:** + +```json +[ + { + "id": "log-xxx", + "taskId": "550e8400-...", + "callbackUrl": "https://your-app.com/webhook", + "attempt": 1, + "responseStatus": 200, + "success": true, + "errorMessage": null, + "sentAt": "2026-03-17T10:00:05Z", + "responseAt": "2026-03-17T10:00:05Z", + "durationMs": 120, + "isReplay": false + } +] +``` + --- ## 5. 配额查询 @@ -526,35 +538,8 @@ Content-Type: application/json GET /api/quota ``` -**响应:** - -```json -{ - "userId": "user-123", - "daily": { - "used": 50, - "limit": 1000, - "remaining": 950, - "resetAt": "2026-03-17T00:00:00Z" - }, - "monthly": { - "used": 200, - "limit": 10000, - "remaining": 9800, - "resetAt": "2026-04-01T00:00:00Z" - }, - "concurrent": { - "used": 2, - "limit": 10, - "remaining": 8 - } -} -``` - ### 5.2 检查配额是否足够 -提交任务前可先调用此接口: - ``` GET /api/quota/check ``` @@ -579,8 +564,6 @@ GET /api/quota/check GET /health ``` -可用于监控服务是否正常运行。 - --- ## 7. 回调机制 @@ -589,10 +572,8 @@ GET /health ### 7.1 回调请求格式 -服务会向你配置的 `callback.url` 发送 POST 请求: - ``` -POST https://your-app.com/webhook/pdf-done +POST https://your-app.com/webhook/image-done Content-Type: application/json X-Callback-Signature: {HMAC签名} ``` @@ -601,54 +582,46 @@ X-Callback-Signature: {HMAC签名} ### 7.2 回调重试 -回调失败时,服务会自动进行指数退避重试。 +回调失败时,服务会自动进行指数退避重试(最多 3 次)。也可通过 4.12 接口手动重放。 --- ## 8. 参数参考 -### 8.1 PDF 选项(options) +### 8.1 图片选项(options) + +| 字段 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `format` | string | `"png"` | 图片格式:`png`、`jpeg`、`webp` | +| `quality` | int | `90` | 图片质量(1-100),仅 jpeg/webp 有效 | +| `width` | int | `1920` | 浏览器视口宽度(像素) | +| `height` | int | `1080` | 浏览器视口高度(像素) | +| `fullPage` | bool | `true` | 是否截取整个页面(含滚动区域) | +| `omitBackground` | bool | `false` | 是否透明背景,仅 png 有效 | + +> `width`/`height` 直接控制浏览器窗口大小,页面会在该分辨率下渲染后截图。 + +### 8.2 PDF 选项(options) | 字段 | 类型 | 默认值 | 说明 | |------|------|--------|------| | `format` | string | `"A4"` | 纸张格式:A4, Letter, Legal, A3 等 | | `landscape` | bool | `false` | 是否横向 | | `printBackground` | bool | `true` | 是否打印背景色/图 | -| `width` | string | — | 自定义页面宽度(如 `"1309px"`, `"210mm"`),设置后忽略 format | -| `height` | string | — | 自定义页面高度(如 `"926px"`, `"297mm"`),设置后忽略 format | -| `viewportWidth` | int | — | 浏览器视口宽度(像素),控制页面渲染时的窗口宽度 | -| `viewportHeight` | int | — | 浏览器视口高度(像素),控制页面渲染时的窗口高度 | -| `margin.top` | string | `"10mm"` | 上边距 | -| `margin.right` | string | `"10mm"` | 右边距 | -| `margin.bottom` | string | `"10mm"` | 下边距 | -| `margin.left` | string | `"10mm"` | 左边距 | - -> `width`/`height` 和 `format` 二选一。设置了 `width`+`height` 后 `format` 会被忽略。 -> `viewportWidth`/`viewportHeight` 用于控制浏览器渲染窗口大小,适合需要在固定分辨率下打开才能正常显示的页面。 - -### 8.2 图片选项(options) - -| 字段 | 类型 | 默认值 | 说明 | -|------|------|--------|------| -| `format` | string | `"png"` | 图片格式:png, jpeg, webp | -| `quality` | int | `90` | 图片质量(1-100),仅 jpeg/webp 有效 | -| `width` | int | `1920` | 浏览器视口宽度(像素),即页面渲染分辨率的宽 | -| `height` | int | `1080` | 浏览器视口高度(像素),即页面渲染分辨率的高 | -| `fullPage` | bool | `true` | 是否截取整个页面(超出视口的滚动区域也会截取) | -| `omitBackground` | bool | `false` | 是否忽略背景色(透明背景,仅 png 有效) | - -> `width`/`height` 直接控制浏览器窗口大小。比如传 `"width": 1309, "height": 926`,页面就会在 1309×926 的分辨率下渲染后再截图。 +| `width` | string | — | 自定义页面宽度(如 `"210mm"`),设置后忽略 format | +| `height` | string | — | 自定义页面高度(如 `"297mm"`),设置后忽略 format | +| `viewportWidth` | int | — | 浏览器视口宽度(像素) | +| `viewportHeight` | int | — | 浏览器视口高度(像素) | +| `margin.top/right/bottom/left` | string | `"10mm"` | 页面边距 | ### 8.3 waitUntil 参数 -控制页面加载等待策略,URL 转换时特别有用: - | 值 | 说明 | |----|------| | `load` | 等待 `load` 事件触发 | | `domcontentloaded` | 等待 `DOMContentLoaded` 事件 | | `networkidle0` | 等待 500ms 内无网络请求(推荐 SPA 页面) | -| `networkidle2` | 等待 500ms 内不超过 2 个网络请求 | +| `networkidle2` | 等待 500ms 内不超过 2 个网络请求(默认) | ### 8.4 任务状态 @@ -675,22 +648,18 @@ X-Callback-Signature: {HMAC签名} } ``` -### 常见错误码 - | HTTP 状态码 | 含义 | 处理建议 | |-------------|------|----------| | 400 | 参数错误 | 检查请求体格式和必填字段 | | 401 | 认证失败 | 检查 Token 或 API Key | | 404 | 任务不存在 | 确认 taskId 是否正确 | -| 409 | 状态冲突 | 任务状态不允许当前操作(如取消已完成的任务) | +| 409 | 状态冲突 | 任务状态不允许当前操作 | | 429 | 请求限流 | 降低请求频率,稍后重试 | | 503 | 服务繁忙 | 队列已满或服务过载,稍后重试 | --- -## 10. .NET SDK 接入(推荐) - -项目提供了 .NET 客户端 SDK,支持依赖注入,开箱即用。 +## 10. .NET SDK 接入 ### 10.1 安装 @@ -701,115 +670,43 @@ dotnet add package HtmlToPdfService.Client ### 10.2 注册服务 ```csharp -// Program.cs -using HtmlToPdfService.Client.Extensions; - builder.Services.AddHtmlToPdfClient(options => { options.BaseUrl = "https://pdf-service.example.com"; options.ApiKey = "your-api-key"; options.TimeoutSeconds = 120; - options.EnableRetry = true; // 启用指数退避重试 + options.EnableRetry = true; options.RetryCount = 3; }); ``` -或从配置文件读取: - -```json -// appsettings.json -{ - "HtmlToPdfClient": { - "BaseUrl": "https://pdf-service.example.com", - "ApiKey": "your-api-key", - "TimeoutSeconds": 120, - "EnableRetry": true, - "RetryCount": 3 - } -} -``` - -```csharp -builder.Services.AddHtmlToPdfClient(builder.Configuration); -``` - ### 10.3 使用示例 ```csharp -public class ReportService +// 同步转换 — 直接拿到图片字节数组 +public async Task ScreenshotAsync(string html) { - private readonly IHtmlToPdfClient _pdfClient; - - public ReportService(IHtmlToPdfClient pdfClient) + return await _client.ConvertHtmlToImageAsync(html, new ImageOptions { - _pdfClient = pdfClient; - } + Format = "png", + Width = 1920, + Height = 1080, + FullPage = true + }); +} - // 同步转换 — 直接拿到 PDF 字节数组 - public async Task GenerateSimplePdfAsync(string html) - { - return await _pdfClient.ConvertHtmlToPdfAsync(html, new PdfOptions - { - Format = "A4", - PrintBackground = true - }); - } +// 异步任务 — 提交后等待完成再下载 +public async Task ScreenshotAsyncTask(string html) +{ + var result = await _client.SubmitImageTaskAsync( + source: new SourceInfo { Type = "html", Content = html }, + options: new ImageOptions { Format = "png", Width = 1920 }); - // 异步任务 — 提交后等待完成再下载 - public async Task GeneratePdfAsync(string html) - { - var result = await _pdfClient.SubmitPdfTaskAsync( - source: new SourceInfo { Type = "html", Content = html }, - options: new PdfOptions { Format = "A4" }); - - // 等待任务完成并下载(内部自动轮询) - return await _pdfClient.WaitAndDownloadAsync(result.TaskId); - } - - // 异步任务 + 回调 — 提交后不等待,由回调通知 - public async Task SubmitPdfTaskAsync(string html, string callbackUrl) - { - var result = await _pdfClient.SubmitPdfTaskAsync( - source: new SourceInfo { Type = "html", Content = html }, - callback: new CallbackInfo { Url = callbackUrl }); - - return result.TaskId; - } + return await _client.WaitAndDownloadAsync(result.TaskId); } ``` -### 10.4 异常处理 - -```csharp -using HtmlToPdfService.Client.Exceptions; - -try -{ - var pdf = await _pdfClient.ConvertHtmlToPdfAsync(html); -} -catch (ValidationException ex) // 400 参数错误 -{ - logger.LogWarning("参数错误: {Message}", ex.Message); -} -catch (AuthenticationException ex) // 401 认证失败 -{ - logger.LogWarning("认证失败: {Message}", ex.Message); -} -catch (TaskNotFoundException ex) // 404 任务不存在 -{ - logger.LogWarning("任务不存在: {TaskId}", ex.TaskId); -} -catch (ServiceUnavailableException ex) // 503 服务繁忙 -{ - logger.LogWarning("服务繁忙,稍后重试: {Message}", ex.Message); -} -catch (HtmlToPdfClientException ex) // 其他错误 -{ - logger.LogError("转换失败: {Message}, 状态码: {StatusCode}", ex.Message, ex.StatusCode); -} -``` - -### 10.5 SDK 配置项 +### 10.4 SDK 配置项 | 配置项 | 类型 | 默认值 | 说明 | |--------|------|--------|------| @@ -818,34 +715,30 @@ catch (HtmlToPdfClientException ex) // 其他错误 | `TimeoutSeconds` | int | 120 | HTTP 请求超时 | | `EnableRetry` | bool | false | 是否启用自动重试 | | `RetryCount` | int | 3 | 重试次数 | -| `RetryBaseDelayMs` | int | 500 | 重试基础延迟(指数退避) | -| `CustomHeaders` | Dictionary | null | 自定义请求头 | --- -## 11. 其他语言接入 +## 11. 其他语言接入(curl 示例) -非 .NET 项目直接调用 HTTP API 即可,以下是 curl 示例: - -### 同步 HTML 转 PDF +### 同步 HTML 转图片 ```bash -curl -X POST https://pdf-service.example.com/api/pdf/convert/html \ +curl -X POST https://pdf-service.example.com/api/image/convert/html \ -H "Content-Type: application/json" \ - -H "Authorization: Bearer {token}" \ - -d '{"html": "

Hello World

"}' \ - -o document.pdf + -H "X-API-Key: your-api-key" \ + -d '{"html": "

Hello

", "options": {"format": "png", "width": 1920, "height": 1080}}' \ + -o screenshot.png ``` -### 异步提交任务 +### 异步提交图片任务 ```bash -curl -X POST https://pdf-service.example.com/api/tasks/pdf \ +curl -X POST https://pdf-service.example.com/api/tasks/image \ -H "Content-Type: application/json" \ - -H "Authorization: Bearer {token}" \ + -H "X-API-Key: your-api-key" \ -d '{ "source": {"type": "html", "content": "

Hello

"}, - "options": {"format": "A4"} + "options": {"format": "png", "width": 1920, "height": 1080} }' ``` @@ -853,15 +746,15 @@ curl -X POST https://pdf-service.example.com/api/tasks/pdf \ ```bash curl https://pdf-service.example.com/api/tasks/{taskId}/status \ - -H "Authorization: Bearer {token}" + -H "X-API-Key: your-api-key" ``` ### 下载文件 ```bash curl https://pdf-service.example.com/api/tasks/{taskId}/download \ - -H "Authorization: Bearer {token}" \ - -o result.pdf + -H "X-API-Key: your-api-key" \ + -o result.png ``` --- @@ -871,17 +764,17 @@ curl https://pdf-service.example.com/api/tasks/{taskId}/download \ ### 方式一:同步调用(简单直接) ``` -客户端 → POST /api/pdf/convert/html → 等待 → 返回 PDF 文件流 +客户端 → POST /api/image/convert/html → 等待 → 返回图片文件流 ``` -适合:单次转换、低并发、对延迟不敏感。 +适合:单次截图、低并发、对延迟不敏感。 ### 方式二:异步轮询 ``` -客户端 → POST /api/tasks/pdf → 返回 taskId +客户端 → POST /api/tasks/image → 返回 taskId 客户端 → GET /api/tasks/{taskId}/status → 轮询直到 completed -客户端 → GET /api/tasks/{taskId}/download → 下载文件 +客户端 → GET /api/tasks/{taskId}/download → 下载图片 ``` 适合:需要异步处理但不方便接收回调。 @@ -889,9 +782,9 @@ curl https://pdf-service.example.com/api/tasks/{taskId}/download \ ### 方式三:异步回调(推荐) ``` -客户端 → POST /api/tasks/pdf(带 callback.url)→ 返回 taskId +客户端 → POST /api/tasks/image(带 callback.url)→ 返回 taskId 服务端 → 处理完成后 POST 回调到 callback.url -客户端 → 收到回调后 GET /api/tasks/{taskId}/download → 下载文件 +客户端 → 收到回调后 GET /api/tasks/{taskId}/download → 下载图片 ``` 适合:高并发、批量处理、生产环境。 @@ -901,8 +794,8 @@ curl https://pdf-service.example.com/api/tasks/{taskId}/download \ ## 13. 注意事项 1. **幂等性**:异步接口支持 `Idempotency-Key` 请求头,相同 Key 不会重复创建任务 -2. **文件有效期**:生成的文件有保留时间限制(默认 7 天),请及时下载 +2. **文件有效期**:生成的文件默认保留 7 天,请及时下载 3. **SSRF 防护**:URL 模式下,服务会阻止访问内网地址(10.x、192.168.x、127.x 等) 4. **请求限流**:服务有 IP/用户维度的速率限制,超限返回 429 -5. **超时设置**:复杂页面建议适当增大 `timeout` 参数 -6. **SPA 页面**:转换 React/Vue/Angular 等 SPA 页面时,建议 `waitUntil` 设为 `networkidle0` +5. **超时设置**:复杂页面建议适当增大 `timeout` 参数(单位:毫秒) +6. **SPA 页面**:转换 React/Vue/Angular 等 SPA 页面时,建议 `waitUntil` 设为 `networkidle0`,并配合 `delayAfterLoad` 等待动画完成