# 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` 等待动画完成