HtmlToPdf/docs/外部对接文档.md
2026-03-17 19:52:27 +08:00

802 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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` HeaderQuery 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": "<html><body><h1>Hello</h1></body></html>",
"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": "<html><body><h1>Hello World</h1></body></html>",
"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": "<h1>Hello World</h1>"
},
"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": "<h1>Hello World</h1>"
},
"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": "<h1>截图 1</h1>" },
"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": "<h1>文档</h1>" },
"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<byte[]> ScreenshotAsync(string html)
{
return await _client.ConvertHtmlToImageAsync(html, new ImageOptions
{
Format = "png",
Width = 1920,
Height = 1080,
FullPage = true
});
}
// 异步任务 — 提交后等待完成再下载
public async Task<byte[]> 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": "<h1>Hello</h1>", "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": "<h1>Hello</h1>"},
"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` 等待动画完成