21
This commit is contained in:
parent
f7495e5fd4
commit
578d84cb08
673
docs/外部对接文档.md
673
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": "<html><body><h1>Hello World</h1><p>这是测试内容</p></body></html>",
|
||||
"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": "<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,通过轮询状态或配置回调来获取结果。
|
||||
异步模式下,提交任务后立即返回任务 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": "<h1>Hello World</h1><p>这是测试内容</p>"
|
||||
"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",
|
||||
|
|
@ -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": "<h1>文档 1</h1>" },
|
||||
"pdfOptions": { "format": "A4", "printBackground": true }
|
||||
},
|
||||
{
|
||||
"type": "pdf",
|
||||
"source": { "type": "url", "content": "https://example.com" }
|
||||
"type": "image",
|
||||
"source": { "type": "html", "content": "<h1>截图 1</h1>" },
|
||||
"imageOptions": { "format": "png", "width": 1920, "height": 1080 }
|
||||
},
|
||||
{
|
||||
"type": "image",
|
||||
"source": { "type": "html", "content": "<h1>截图</h1>" },
|
||||
"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": "<h1>文档</h1>" },
|
||||
"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<byte[]> ScreenshotAsync(string html)
|
||||
{
|
||||
private readonly IHtmlToPdfClient _pdfClient;
|
||||
|
||||
public ReportService(IHtmlToPdfClient pdfClient)
|
||||
return await _client.ConvertHtmlToImageAsync(html, new ImageOptions
|
||||
{
|
||||
_pdfClient = pdfClient;
|
||||
}
|
||||
|
||||
// 同步转换 — 直接拿到 PDF 字节数组
|
||||
public async Task<byte[]> GenerateSimplePdfAsync(string html)
|
||||
{
|
||||
return await _pdfClient.ConvertHtmlToPdfAsync(html, new PdfOptions
|
||||
{
|
||||
Format = "A4",
|
||||
PrintBackground = true
|
||||
Format = "png",
|
||||
Width = 1920,
|
||||
Height = 1080,
|
||||
FullPage = true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 异步任务 — 提交后等待完成再下载
|
||||
public async Task<byte[]> GeneratePdfAsync(string html)
|
||||
{
|
||||
var result = await _pdfClient.SubmitPdfTaskAsync(
|
||||
// 异步任务 — 提交后等待完成再下载
|
||||
public async Task<byte[]> ScreenshotAsyncTask(string html)
|
||||
{
|
||||
var result = await _client.SubmitImageTaskAsync(
|
||||
source: new SourceInfo { Type = "html", Content = html },
|
||||
options: new PdfOptions { Format = "A4" });
|
||||
options: new ImageOptions { Format = "png", Width = 1920 });
|
||||
|
||||
// 等待任务完成并下载(内部自动轮询)
|
||||
return await _pdfClient.WaitAndDownloadAsync(result.TaskId);
|
||||
}
|
||||
|
||||
// 异步任务 + 回调 — 提交后不等待,由回调通知
|
||||
public async Task<string> 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": "<h1>Hello World</h1>"}' \
|
||||
-o document.pdf
|
||||
-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/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": "<h1>Hello</h1>"},
|
||||
"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` 等待动画完成
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user