# 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: `

您好,{{username}}!

` - 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) 了解更多调用示例