Files

566 lines
14 KiB
Markdown
Raw Permalink 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.
# V2 API 文档
V2 API 提供基于消息模板的发送接口,支持占位符替换和多实例发送。
::: tip ⭐ 推荐使用
V2 API(模板)是我们推荐的消息发送方式,相比 V1 API 具有以下优势:
-**内容复用** - 一次定义,多处使用,大幅提高开发效率
-**灵活性** - 通过占位符实现动态内容,兼顾固定格式和动态数据
-**易维护** - 修改消息内容无需改代码,运营人员可直接操作
-**版本控制** - 支持模板启用/禁用,便于灰度发布和回滚
-**团队协作** - 开发和运营分工明确,提高协作效率
适用于 90% 的消息发送场景,特别是有固定格式的通知类消息。
:::
## 接口概述
V2 API 与 V1 API 的主要区别:
| 特性 | V1 API | V2 API |
|------|--------|--------|
| 发送方式 | 基于任务 | 基于模板 |
| 内容定义 | API 调用时传递 | 模板预定义 |
| 动态内容 | 不支持 | 支持占位符 |
| 多格式 | 单一格式 | Text/HTML/Markdown |
| 安全性 | Token 加密 | Token 加密 |
## 接口地址
```
POST /api/v2/message/send
```
## 请求参数
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| token | string | 是 | 加密的模板 Token |
| title | string | 是 | 消息标题 |
| placeholders | object | 否 | 占位符键值对 |
| recipients | array | 条件必填 | 动态接收者列表(群发模式)🆕 |
### 参数说明
#### token
- 模板的加密 Token,在管理后台的"消息模板"页面获取
- **注意**V2 API 只支持加密 Token,不支持明文模板 ID
- Token 使用对称加密算法生成,确保安全性
#### title
- 消息标题,会传递给所有支持标题的渠道(如邮件)
- 必填参数,不能为空
#### placeholders
- 占位符的键值对,用于替换模板中的 `{{key}}`
- 格式为 JSON 对象:`{"key": "value"}`
- 如果模板中定义了占位符但未传递,将使用默认值
- 如果既未传递也无默认值,占位符将保持原样
#### recipients 🆕
- 动态接收者列表,用于群发场景
- 格式为字符串数组:`["user1@example.com", "user2@example.com"]`
- **条件必填**:如果模板配置了动态接收实例,此参数为必填
- **支持的渠道**
- ✅ 邮件 - 多个收件人邮箱地址
- ✅ 微信公众号 - 多个用户 OpenID
- **使用限制**
- 一个模板只能配置一个动态接收实例
- 动态接收实例不能与固定接收实例混合使用
- 建议控制接收者数量,避免触发渠道限流
## 请求示例
### 基本示例
```bash
curl -X POST http://your-domain/api/v2/message/send \
-H "Content-Type: application/json" \
-d '{
"token": "a3541c2f0d3e1b4a5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b",
"title": "系统通知",
"placeholders": {
"username": "张三",
"action": "登录",
"time": "2024-12-06 12:00:00"
}
}'
```
### Python 示例
```python
import requests
import json
url = "http://your-domain/api/v2/message/send"
headers = {"Content-Type": "application/json"}
data = {
"token": "a3541c2f0d3e1b4a5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b",
"title": "系统通知",
"placeholders": {
"username": "张三",
"action": "登录",
"time": "2024-12-06 12:00:00"
}
}
response = requests.post(url, headers=headers, data=json.dumps(data))
print(response.json())
```
### Go 示例
```go
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
url := "http://your-domain/api/v2/message/send"
data := map[string]interface{}{
"token": "a3541c2f0d3e1b4a5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b",
"title": "系统通知",
"placeholders": map[string]string{
"username": "张三",
"action": "登录",
"time": "2024-12-06 12:00:00",
},
}
jsonData, _ := json.Marshal(data)
resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonData))
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Println(result)
}
```
### JavaScript/Node.js 示例
```javascript
const axios = require('axios');
const url = 'http://your-domain/api/v2/message/send';
const data = {
token: 'a3541c2f0d3e1b4a5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b',
title: '系统通知',
placeholders: {
username: '张三',
action: '登录',
time: '2024-12-06 12:00:00'
}
};
axios.post(url, data)
.then(response => {
console.log(response.data);
})
.catch(error => {
console.error('Error:', error);
});
```
### 动态接收者示例(群发)🆕
```javascript
const axios = require('axios');
const url = 'http://your-domain/api/v2/message/send';
const data = {
token: 'a3541c2f0d3e1b4a5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b',
title: '活动通知',
placeholders: {
activity_name: '双十二大促',
start_time: '2024-12-12 00:00:00',
discount: '全场8折'
},
recipients: [
'user1@example.com',
'user2@example.com',
'user3@example.com'
]
};
axios.post(url, data)
.then(response => {
console.log(response.data);
console.log(`成功发送给 ${response.data.data.count} 个接收者`);
})
.catch(error => {
console.error('Error:', error);
});
```
### Java 示例
```java
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
public class MessageSender {
public static void main(String[] args) throws Exception {
String url = "http://your-domain/api/v2/message/send";
String jsonData = """
{
"token": "a3541c2f0d3e1b4a5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b",
"title": "系统通知",
"placeholders": {
"username": "张三",
"action": "登录",
"time": "2024-12-06 12:00:00"
}
}
""";
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
conn.setDoOutput(true);
try (OutputStream os = conn.getOutputStream()) {
byte[] input = jsonData.getBytes(StandardCharsets.UTF_8);
os.write(input, 0, input.length);
}
int responseCode = conn.getResponseCode();
System.out.println("Response Code: " + responseCode);
}
}
```
## 响应格式
### 成功响应
```json
{
"code": 200,
"msg": "success",
"data": {
"token": "a3541c2f0d3e1b4a5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b",
"count": 3
}
}
```
**响应字段说明:**
- `code` - 状态码,200 表示成功
- `msg` - 响应消息
- `data.token` - 使用的模板 Token
- `data.count` - 成功发送的实例数量
### 失败响应
```json
{
"code": 400,
"msg": "token解析失败:invalid token format",
"data": null
}
```
## 工作流程
1. **Token 解析** - 解密 Token 获取模板 ID
2. **模板查询** - 根据模板 ID 查询模板信息
3. **状态检查** - 检查模板是否启用
4. **占位符替换** - 使用传入的 placeholders 替换模板中的占位符
5. **实例遍历** - 获取模板关联的所有启用实例
6. **格式匹配** - 根据实例的 ContentType 选择对应格式的内容
7. **消息发送** - 向每个实例发送消息
8. **返回结果** - 返回发送成功的实例数量
## 占位符替换规则
### 基本规则
模板中使用 `{{key}}` 定义占位符,API 调用时通过 `placeholders` 参数传递替换值。
**模板内容:**
```text
您好,{{username}}
您的订单 {{order_id}} 已经 {{status}}。
```
**API 调用:**
```json
{
"placeholders": {
"username": "张三",
"order_id": "20241206001",
"status": "发货"
}
}
```
**替换结果:**
```text
您好,张三!
您的订单 20241206001 已经发货。
```
### 默认值处理
如果占位符定义了默认值,未传递时使用默认值:
**占位符定义:**
```json
[
{
"key": "username",
"label": "用户名",
"default": "Guest"
}
]
```
**API 调用(未传递 username):**
```json
{
"placeholders": {}
}
```
**替换结果:**
```text
您好,Guest
```
### 未定义占位符
如果占位符既未传递也无默认值,将保持原样:
```text
您好,{{username}} // username 未传递且无默认值
```
## 内容格式选择
V2 API 支持三种内容格式,系统会根据实例配置自动选择:
| 格式 | 适用渠道 | 说明 |
|------|---------|------|
| Text | 所有渠道 | 纯文本格式,兼容性最好 |
| HTML | 邮件 | 富文本格式,支持样式 |
| Markdown | 钉钉、企业微信 | Markdown 格式,支持格式化 |
**示例:**
假设模板定义了三种格式的内容:
- Text: `您好,{{username}}`
- HTML: `<h2>您好,{{username}}</h2>`
- Markdown: `## 您好,{{username}}`
当发送到不同实例时:
- **邮件实例(ContentType=html** → 使用 HTML 格式
- **钉钉实例(ContentType=markdown** → 使用 Markdown 格式
- **其他实例(ContentType=text** → 使用 Text 格式
## @提醒功能
如果模板配置了@提醒,会自动应用到支持的渠道(钉钉、企业微信)。
**模板配置:**
- @手机号`13800138000,13900139000`
- @用户ID`user001,user002`
- @所有人:是
**发送效果:**
- 钉钉/企业微信会@指定的手机号或用户
- 如果启用@所有人,会@群内所有成员
## 错误码说明
| 错误码 | 说明 | 解决方案 |
|--------|------|---------|
| 200 | 成功 | - |
| 400 | 请求参数错误 | 检查请求参数格式 |
| 400 | token解析失败 | 检查 Token 是否正确 |
| 400 | 模板不存在 | 检查模板 ID 是否有效 |
| 400 | 模板已禁用 | 在管理后台启用模板 |
| 400 | 模板没有配置发送实例 | 为模板添加发送实例 |
| 400 | 模板没有启用的发送实例 | 启用至少一个实例 |
| 500 | 服务器内部错误 | 联系管理员 |
## 获取模板 Token
### 方式一:管理后台查看
1. 登录 Message Nest 管理后台
2. 进入"消息模板"页面
3. 点击模板的"接口"按钮
4. 查看并复制加密的 Token
### 方式二:API 代码示例
管理后台提供多种语言的 API 调用示例,包含真实的加密 Token。
## 安全性说明
### Token 加密
- V2 API 使用对称加密算法保护模板 ID
- Token 是确定性加密,相同的模板 ID 生成相同的 Token
- 加密密钥存储在服务器端,客户端无需关心加密细节
### 最佳实践
1. **Token 保护**
- 不要在公开代码中硬编码 Token
- 使用环境变量或配置文件存储 Token
- 定期检查 Token 的使用情况
2. **权限控制**
- 合理设置模板的启用/禁用状态
- 及时禁用不再使用的模板
- 定期审查模板配置
3. **内容安全**
- 注意模板内容的合规性
- 避免在模板中包含敏感信息
- 对用户输入进行验证和过滤
## 使用场景
### 1. 用户通知
```json
{
"token": "...",
"title": "账号安全提醒",
"placeholders": {
"username": "张三",
"action": "登录",
"ip": "192.168.1.100",
"time": "2024-12-06 12:00:00"
}
}
```
### 2. 订单通知
```json
{
"token": "...",
"title": "订单状态更新",
"placeholders": {
"order_id": "20241206001",
"status": "已发货",
"tracking_number": "SF1234567890",
"estimated_delivery": "2024-12-08"
}
}
```
### 3. 系统告警
```json
{
"token": "...",
"title": "系统告警",
"placeholders": {
"service": "API Server",
"level": "严重",
"message": "CPU使用率超过90%",
"time": "2024-12-06 12:00:00"
}
}
```
### 4. 营销推广
```json
{
"token": "...",
"title": "优惠活动通知",
"placeholders": {
"username": "张三",
"product": "VIP会员",
"discount": "8折",
"expire_date": "2024-12-31"
}
}
```
## 性能优化
### 异步发送
V2 API 采用异步发送机制,API 调用立即返回,实际发送在后台进行。
**优点:**
- 快速响应,不阻塞调用方
- 支持批量发送多个实例
- 自动重试失败的发送
### 批量发送
一次 API 调用可以发送到多个实例(渠道),系统自动遍历所有启用的实例。
**示例:**
模板配置了 3 个实例:
- 邮件实例(启用)
- 钉钉实例(启用)
- 企业微信实例(禁用)
调用 API 后,消息会发送到邮件和钉钉,企业微信实例被跳过。
## 常见问题
### Q: V1 和 V2 API 可以同时使用吗?
**A:** 可以。V1 和 V2 API 是独立的,可以根据需求选择使用。
### Q: 如何从 V1 迁移到 V2
**A:**
1. 创建消息模板,定义占位符
2. 为模板配置发送实例
3. 获取模板 Token
4. 修改 API 调用代码,使用 V2 接口
### Q: 占位符可以嵌套吗?
**A:** 不支持。占位符只支持一级替换,不支持嵌套或递归。
### Q: 可以动态添加占位符吗?
**A:** 不可以。占位符必须在模板中预先定义,API 调用时只能传递已定义的占位符。
### Q: 如何调试模板?
**A:** 使用管理后台的"预览"功能,可以填写测试数据查看替换效果。
## 下一步
- 查看 [消息模板文档](/guide/template) 了解如何创建和管理模板
- 查看 [V1 API 文档](/api/usage) 了解传统的发送方式
- 查看 [API 示例](/api/examples) 了解更多调用示例