# HTML 转图片 / PDF 服务 — 外部对接文档 > 版本:2.0.1 | 更新日期:2026-03-17 --- ## 1. 概述 本服务提供 HTML/URL 转图片和 PDF 的能力,支持同步和异步两种调用模式: - **同步模式**:请求后直接返回文件流,适合简单、低并发场景 - **异步模式(推荐)**:提交任务后通过轮询或回调获取结果,适合高并发、大批量场景 **服务地址**:`https://pdf-service.example.com`(请替换为实际地址) --- ## 2. 认证 服务支持 API Key 认证。如果服务端开启了认证,所有业务接口都需要携带 Token。 ### 2.1 获取 Token ``` POST /api/auth/token Content-Type: application/json ``` **请求体:** ```json { "apiKey": "your-api-key", "userId": "your-user-id" } ``` **响应:** ```json { "accessToken": "eyJhbGciOiJIUzI1NiIs...", "tokenType": "Bearer", "expiresIn": 3600, "expiresAt": "2026-03-17T11:00:00Z" } ``` ### 2.2 请求认证方式 服务支持两种认证方式,任选其一即可: **方式一:JWT Token(通过 API Key 换取)** ``` Authorization: Bearer {accessToken} ``` **方式二:直接使用 API Key** ``` X-API-Key: your-api-key ``` 或: ``` Authorization: ApiKey your-api-key ``` 推荐使用 `X-API-Key` Header,Query String 方式(`?api_key=xxx`)存在 Key 泄露到日志的风险。 > 以下路径无需认证:`/health`、`/swagger`、`/metrics`、`/api/auth/token` ### 2.3 刷新 Token ``` POST /api/auth/refresh Authorization: Bearer {当前Token} ``` --- ## 3. 同步转换接口 适合简单场景,请求后直接返回文件二进制流。 ### 3.1 HTML 转图片 ``` POST /api/image/convert/html Content-Type: application/json ``` **请求体:** ```json { "html": "

Hello

", "options": { "format": "png", "quality": 90, "width": 1920, "height": 1080, "fullPage": true, "omitBackground": false }, "saveLocal": false } ``` **响应**:对应格式的图片文件流(`image/png`、`image/jpeg`、`image/webp`) ### 3.2 URL 转图片 ``` POST /api/image/convert/url Content-Type: application/json ``` **请求体:** ```json { "url": "https://example.com", "waitUntil": "networkidle0", "timeout": 30000, "options": { "format": "png", "width": 1920, "height": 1080, "fullPage": true }, "saveLocal": false } ``` ### 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,通过轮询状态或配置回调来获取结果。 ### 4.1 提交图片任务 ``` POST /api/tasks/image Content-Type: application/json Idempotency-Key: {可选,幂等键} ``` **请求体:** ```json { "source": { "type": "html", "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", "landscape": false, "printBackground": true, "margin": { "top": "10mm", "right": "10mm", "bottom": "10mm", "left": "10mm" } }, "waitUntil": "networkidle0", "timeout": 60000, "callback": { "url": "https://your-app.com/webhook/pdf-done", "includeFileData": false }, "saveLocal": true, "metadata": { "orderId": "12345" } } ``` ### 4.3 批量提交任务 ``` POST /api/tasks/batch Content-Type: application/json ``` **请求体:** ```json { "tasks": [ { "type": "image", "source": { "type": "html", "content": "

截图 1

" }, "imageOptions": { "format": "png", "width": 1920, "height": 1080 } }, { "type": "image", "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": { "url": "https://your-app.com/webhook/batch-done" }, "onEachComplete": false, "onAllComplete": true } ``` **响应(202 Accepted):** ```json { "batchId": "batch-xxxx-xxxx", "taskIds": ["task-1", "task-2", "task-3"], "totalTasks": 3, "successCount": 3, "failedCount": 0, "links": { "status": "/api/tasks/batch/batch-xxxx-xxxx" } } ``` ### 4.4 查询任务详情 ``` GET /api/tasks/{taskId} ``` **响应:** ```json { "taskId": "550e8400-...", "type": "image", "source": { "type": "html", "content": "..." }, "status": "completed", "createdAt": "2026-03-17T10:00:00Z", "startedAt": "2026-03-17T10:00:01Z", "completedAt": "2026-03-17T10:00:03Z", "duration": 2000, "retryCount": 0, "result": { "fileSize": 204800, "fileType": "png", "downloadUrl": "/api/tasks/550e8400-.../download" }, "error": null, "links": { "self": "/api/tasks/550e8400-...", "download": "/api/tasks/550e8400-.../download" } } ``` ### 4.5 轻量级状态查询 适合高频轮询场景: ``` GET /api/tasks/{taskId}/status ``` **响应:** ```json { "taskId": "550e8400-...", "status": "processing", "createdAt": "2026-03-17T10:00:00Z", "startedAt": "2026-03-17T10:00:01Z", "completedAt": null } ``` ### 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 根据任务类型自动设置(`image/png`、`image/jpeg`、`image/webp`、`application/pdf`)。 响应头包含: - `X-Task-Id`: 任务 ID - `X-Expires-At`: 文件过期时间(如有) > 任务未完成时返回 `409 Conflict`。 ### 4.8 取消任务 ``` DELETE /api/tasks/{taskId} ``` > 仅 `pending` 状态的任务可取消,其他状态返回 `409 Conflict`。 ### 4.9 重试任务 ``` POST /api/tasks/{taskId}/retry ``` > 仅 `failed` 状态的任务可重试,返回 `202 Accepted`。 ### 4.10 查询批量任务状态 ``` GET /api/tasks/batch/{batchId} ``` ### 4.11 预检(Dry-run) 提交前检查内容是否可渲染、是否存在 SSRF 风险: ``` POST /api/tasks/validate Content-Type: application/json ``` **请求体:** ```json { "source": { "type": "url", "content": "https://example.com" } } ``` **响应:** ```json { "ok": true, "canRender": true, "suggestedQueue": "normal", "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. 配额查询 ### 5.1 查看当前配额 ``` GET /api/quota ``` ### 5.2 检查配额是否足够 ``` GET /api/quota/check ``` **响应:** ```json { "allowed": true, "denyReason": null, "dailyRemaining": 950, "monthlyRemaining": 9800, "concurrentRemaining": 8 } ``` --- ## 6. 健康检查 ``` GET /health ``` --- ## 7. 回调机制 异步任务支持配置回调 URL,任务完成后服务会主动 POST 通知你的系统。 ### 7.1 回调请求格式 ``` POST https://your-app.com/webhook/image-done Content-Type: application/json X-Callback-Signature: {HMAC签名} ``` 回调 Body 包含任务完成信息(taskId、status、result 等)。如果 `includeFileData` 设为 `true`,回调中会包含 Base64 编码的文件数据。 ### 7.2 回调重试 回调失败时,服务会自动进行指数退避重试(最多 3 次)。也可通过 4.12 接口手动重放。 --- ## 8. 参数参考 ### 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 | — | 自定义页面宽度(如 `"210mm"`),设置后忽略 format | | `height` | string | — | 自定义页面高度(如 `"297mm"`),设置后忽略 format | | `viewportWidth` | int | — | 浏览器视口宽度(像素) | | `viewportHeight` | int | — | 浏览器视口高度(像素) | | `margin.top/right/bottom/left` | string | `"10mm"` | 页面边距 | ### 8.3 waitUntil 参数 | 值 | 说明 | |----|------| | `load` | 等待 `load` 事件触发 | | `domcontentloaded` | 等待 `DOMContentLoaded` 事件 | | `networkidle0` | 等待 500ms 内无网络请求(推荐 SPA 页面) | | `networkidle2` | 等待 500ms 内不超过 2 个网络请求(默认) | ### 8.4 任务状态 | 状态 | 说明 | |------|------| | `pending` | 排队中 | | `processing` | 处理中 | | `completed` | 已完成 | | `failed` | 失败 | | `cancelled` | 已取消 | | `stalled` | 卡死(超时未完成) | --- ## 9. 错误处理 所有错误响应遵循 RFC 7807 ProblemDetails 格式: ```json { "status": 400, "title": "请求参数无效", "detail": "HTML 内容不能为空" } ``` | HTTP 状态码 | 含义 | 处理建议 | |-------------|------|----------| | 400 | 参数错误 | 检查请求体格式和必填字段 | | 401 | 认证失败 | 检查 Token 或 API Key | | 404 | 任务不存在 | 确认 taskId 是否正确 | | 409 | 状态冲突 | 任务状态不允许当前操作 | | 429 | 请求限流 | 降低请求频率,稍后重试 | | 503 | 服务繁忙 | 队列已满或服务过载,稍后重试 | --- ## 10. .NET SDK 接入 ### 10.1 安装 ```bash dotnet add package HtmlToPdfService.Client ``` ### 10.2 注册服务 ```csharp builder.Services.AddHtmlToPdfClient(options => { options.BaseUrl = "https://pdf-service.example.com"; options.ApiKey = "your-api-key"; options.TimeoutSeconds = 120; options.EnableRetry = true; options.RetryCount = 3; }); ``` ### 10.3 使用示例 ```csharp // 同步转换 — 直接拿到图片字节数组 public async Task ScreenshotAsync(string html) { return await _client.ConvertHtmlToImageAsync(html, new ImageOptions { Format = "png", Width = 1920, Height = 1080, FullPage = 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 }); return await _client.WaitAndDownloadAsync(result.TaskId); } ``` ### 10.4 SDK 配置项 | 配置项 | 类型 | 默认值 | 说明 | |--------|------|--------|------| | `BaseUrl` | string | — | 服务地址(必填) | | `ApiKey` | string | null | API Key | | `TimeoutSeconds` | int | 120 | HTTP 请求超时 | | `EnableRetry` | bool | false | 是否启用自动重试 | | `RetryCount` | int | 3 | 重试次数 | --- ## 11. 其他语言接入(curl 示例) ### 同步 HTML 转图片 ```bash curl -X POST https://pdf-service.example.com/api/image/convert/html \ -H "Content-Type: application/json" \ -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/image \ -H "Content-Type: application/json" \ -H "X-API-Key: your-api-key" \ -d '{ "source": {"type": "html", "content": "

Hello

"}, "options": {"format": "png", "width": 1920, "height": 1080} }' ``` ### 查询状态 ```bash curl https://pdf-service.example.com/api/tasks/{taskId}/status \ -H "X-API-Key: your-api-key" ``` ### 下载文件 ```bash curl https://pdf-service.example.com/api/tasks/{taskId}/download \ -H "X-API-Key: your-api-key" \ -o result.png ``` --- ## 12. 典型接入流程 ### 方式一:同步调用(简单直接) ``` 客户端 → POST /api/image/convert/html → 等待 → 返回图片文件流 ``` 适合:单次截图、低并发、对延迟不敏感。 ### 方式二:异步轮询 ``` 客户端 → POST /api/tasks/image → 返回 taskId 客户端 → GET /api/tasks/{taskId}/status → 轮询直到 completed 客户端 → GET /api/tasks/{taskId}/download → 下载图片 ``` 适合:需要异步处理但不方便接收回调。 ### 方式三:异步回调(推荐) ``` 客户端 → POST /api/tasks/image(带 callback.url)→ 返回 taskId 服务端 → 处理完成后 POST 回调到 callback.url 客户端 → 收到回调后 GET /api/tasks/{taskId}/download → 下载图片 ``` 适合:高并发、批量处理、生产环境。 --- ## 13. 注意事项 1. **幂等性**:异步接口支持 `Idempotency-Key` 请求头,相同 Key 不会重复创建任务 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`,并配合 `delayAfterLoad` 等待动画完成