diff --git a/README.md b/README.md
index 690243f..dcc20ab 100644
--- a/README.md
+++ b/README.md
@@ -14,6 +14,7 @@ Message Nest 是一个灵活而强大的消息推送整合平台,旨在简化
- 🔄 **整合性:** 提供了多种消息推送方式,包括邮件、钉钉、企业微信等,方便你集中管理和定制通知。
- 🎨 **自定义性:** 可以根据需求定制消息推送策略,满足不同场景的个性化需求。
+- 📝 **模板化(⭐推荐):** 支持消息模板功能,通过占位符实现动态内容替换,一次定义多处复用,大幅提高开发效率和维护便利性。
- 🛠 **开放性:** 易于扩展和集成新的消息通知服务,以适应未来的变化。
## 进度 🔨
diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts
index ae91921..412d495 100644
--- a/docs/.vitepress/config.mts
+++ b/docs/.vitepress/config.mts
@@ -24,7 +24,7 @@ export default defineConfig({
{ text: '首页', link: '/' },
{ text: '指南', link: '/guide/introduction' },
{ text: '部署', link: '/deployment/overview' },
- { text: 'API', link: '/api/usage' },
+ { text: 'API', link: '/api/v1' },
{ text: '演示站点', link: 'https://message-nest-demo-site.qwapi.eu.org/' }
],
@@ -32,6 +32,7 @@ export default defineConfig({
'/guide/': [
{ text: '介绍', link: '/guide/introduction' },
{ text: '特色功能', link: '/guide/features' },
+ { text: '消息模板', link: '/guide/template' },
{ text: '更新日志', link: '/guide/changelog' }
],
'/deployment/': [
@@ -57,8 +58,8 @@ export default defineConfig({
{
text: 'API文档',
items: [
- { text: '使用说明', link: '/api/usage' },
- { text: '调用示例', link: '/api/examples' }
+ { text: 'V1 API(任务)', link: '/api/v1' },
+ { text: 'V2 API(模板)', link: '/api/v2' }
]
}
]
diff --git a/docs/api/usage.md b/docs/api/usage.md
deleted file mode 100644
index f46e66e..0000000
--- a/docs/api/usage.md
+++ /dev/null
@@ -1,106 +0,0 @@
-# API 使用说明
-
-Message Nest 提供统一的消息推送API接口。
-
-## 接口地址
-
-```
-POST /api/v1/message/send
-```
-
-## 请求参数
-
-| 参数 | 类型 | 必填 | 说明 |
-|------|------|------|------|
-| token | string | 是 | 推送令牌,在管理后台查看 |
-| title | string | 是 | 消息标题 |
-| text | string | 是 | 消息内容 |
-
-## 请求示例
-
-```json
-{
- "token": "a3541c2f0d3e1b4a5c6d7e8f9a0b1c2d3e",
- "title": "message title",
- "text": "Hello World!"
-}
-```
-
-## 响应格式
-
-### 成功响应
-
-```json
-{
- "code": 200,
- "msg": "success",
- "data": {
- "status": "sent"
- }
-}
-```
-
-### 失败响应
-
-```json
-{
- "code": 400,
- "msg": "error message",
- "data": null
-}
-```
-
-## 获取 Token
-
-1. 登录 Message Nest 管理后台
-2. 进入"发送任务"页面
-3. 创建新的发送任务
-4. 配置推送渠道(邮件、钉钉、企业微信等)
-5. 保存后获得推送令牌(Token)
-
-## 支持的推送渠道
-
-- **邮件** - SMTP邮件发送
-- **钉钉** - 钉钉机器人
-- **企业微信** - 企业微信应用消息
-- **微信公众号** - 微信测试公众号模板消息
-- **自定义Webhook** - 自定义HTTP请求
-- **自托管消息** - 站内消息
-
-## 使用流程
-
-1. **创建推送渠道**
- - 在管理后台配置各种推送渠道
- - 填写相应的配置信息(如邮箱、Webhook地址等)
-
-2. **创建发送任务**
- - 选择要使用的推送渠道
- - 可以选择多个渠道同时推送
- - 获得唯一的推送令牌(Token)
-
-3. **调用API发送消息**
- - 使用获得的 Token
- - 发送标题和内容
- - 消息会自动推送到配置的所有渠道
-
-## 注意事项
-
-::: warning 重要
-- Token 是唯一的,请妥善保管
-- 消息内容支持纯文本和Markdown格式(取决于推送渠道)
-- 建议使用异步方式调用API,避免阻塞主流程
-:::
-
-## 错误码说明
-
-| 错误码 | 说明 |
-|--------|------|
-| 200 | 成功 |
-| 400 | 请求参数错误 |
-| 401 | 未授权 |
-| 404 | Token不存在 |
-| 500 | 服务器内部错误 |
-
-## 下一步
-
-查看各语言的 [调用示例](/api/examples)。
diff --git a/docs/api/v1.md b/docs/api/v1.md
new file mode 100644
index 0000000..438fa71
--- /dev/null
+++ b/docs/api/v1.md
@@ -0,0 +1,734 @@
+# V1 API 文档
+
+V1 API 提供基于任务的消息推送接口,支持多渠道、多格式发送。
+
+::: tip 💡 推荐使用 V2 API(模板)
+对于**所有新项目**,我们强烈推荐使用 [V2 API(模板)](/api/v2):
+- ✅ **内容复用** - 模板可以在多个场景中复用,无需每次传递完整内容
+- ✅ **完全动态** - 通过占位符可以实现完全动态内容(甚至可以只用一个占位符)
+- ✅ **统一管理** - 在管理后台统一管理消息模板,便于维护
+- ✅ **版本控制** - 模板内容修改不影响 API 调用代码
+- ✅ **更安全** - 使用加密 Token,不暴露模板 ID
+
+::: warning 关于 V1 API
+V1 API 仅为兼容历史数据而保留,不推荐在新项目中使用。后续的功能优化和维护重点都在 V2 API(模板)上。
+:::
+
+## 接口地址
+
+```
+POST /api/v1/message/send
+```
+
+## 请求参数
+
+### 基本参数
+
+| 参数 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| token | string | 是 | 推送令牌(加密),在管理后台查看 |
+| task_id | string | 否 | 任务ID(明文),与 token 二选一 |
+| title | string | 是 | 消息标题 |
+| text | string | 否 | 纯文本格式内容 |
+| html | string | 否 | HTML 格式内容 |
+| markdown | string | 否 | Markdown 格式内容 |
+
+::: tip 提示
+- `token` 和 `task_id` 二选一,推荐使用加密的 `token`
+- `text`、`html`、`markdown` 至少提供一种格式
+- 多种格式可以同时提供,系统会根据渠道自动选择
+:::
+
+### @提醒参数(可选)
+
+| 参数 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| at_mobiles | array | 否 | @手机号列表,如 `["13800138000", "13900139000"]` |
+| at_userids | array | 否 | @用户ID列表,如 `["user001", "user002"]` |
+| at_all | boolean | 否 | 是否@所有人,默认 `false` |
+
+::: warning 注意
+@提醒功能仅在支持的渠道(钉钉、企业微信)中生效。
+:::
+
+## 请求示例
+
+### 基本示例(纯文本)
+
+```json
+{
+ "token": "a3541c2f0d3e1b4a5c6d7e8f9a0b1c2d3e",
+ "title": "系统通知",
+ "text": "Hello World!"
+}
+```
+
+### 多格式示例
+
+```json
+{
+ "token": "a3541c2f0d3e1b4a5c6d7e8f9a0b1c2d3e",
+ "title": "系统通知",
+ "text": "您好,系统检测到异常登录。",
+ "html": "
系统通知
您好,系统检测到异常登录。
",
+ "markdown": "## 系统通知\n\n您好,系统检测到**异常登录**。"
+}
+```
+
+### 带@提醒示例
+
+```json
+{
+ "token": "a3541c2f0d3e1b4a5c6d7e8f9a0b1c2d3e",
+ "title": "紧急告警",
+ "text": "服务器CPU使用率超过90%,请及时处理!",
+ "markdown": "## 紧急告警\n\n服务器CPU使用率超过**90%**,请及时处理!",
+ "at_mobiles": ["13800138000", "13900139000"],
+ "at_all": false
+}
+```
+
+### cURL 示例
+
+```bash
+curl -X POST http://your-domain/api/v1/message/send \
+ -H "Content-Type: application/json" \
+ -d '{
+ "token": "a3541c2f0d3e1b4a5c6d7e8f9a0b1c2d3e",
+ "title": "系统通知",
+ "text": "Hello World!"
+ }'
+```
+
+## 响应格式
+
+### 成功响应
+
+```json
+{
+ "code": 200,
+ "msg": "success",
+ "data": {
+ "status": "sent"
+ }
+}
+```
+
+### 失败响应
+
+```json
+{
+ "code": 400,
+ "msg": "error message",
+ "data": null
+}
+```
+
+## 获取 Token
+
+1. 登录 Message Nest 管理后台
+2. 进入"发送任务"页面
+3. 创建新的发送任务
+4. 配置推送渠道(邮件、钉钉、企业微信等)
+5. 保存后获得推送令牌(Token)
+
+## 消息格式优先级
+
+V1 API 支持三种消息格式,系统会根据任务实例配置的格式类型自动选择对应的内容。
+
+### 格式选择规则
+
+当任务实例配置了特定格式类型时,系统按以下优先级选择内容:
+
+| 实例配置格式 | 优先级顺序 | 说明 |
+|------------|-----------|------|
+| **HTML** | html → markdown → text | 优先使用 HTML,其次 Markdown,最后纯文本 |
+| **Markdown** | markdown → text → html | 优先使用 Markdown,其次纯文本,最后 HTML |
+| **Text** | text → markdown → html | 优先使用纯文本,其次 Markdown,最后 HTML |
+
+### 示例说明
+
+**场景 1:邮件渠道(配置为 HTML 格式)**
+
+```json
+{
+ "text": "纯文本内容",
+ "html": "HTML内容
",
+ "markdown": "# Markdown内容"
+}
+```
+
+发送结果:使用 `html` 内容
+
+**场景 2:钉钉渠道(配置为 Markdown 格式)**
+
+```json
+{
+ "text": "纯文本内容",
+ "markdown": "# Markdown内容"
+}
+```
+
+发送结果:使用 `markdown` 内容
+
+**场景 3:只提供纯文本**
+
+```json
+{
+ "text": "纯文本内容"
+}
+```
+
+发送结果:所有渠道都使用 `text` 内容(兼容性最好)
+
+::: tip 最佳实践
+- **邮件渠道**:推荐提供 `html` 格式,视觉效果更好
+- **钉钉/企业微信**:推荐提供 `markdown` 格式,支持富文本
+- **通用场景**:至少提供 `text` 格式,确保所有渠道都能正常发送
+- **多渠道任务**:同时提供多种格式,让系统自动选择最佳格式
+:::
+
+## @提醒功能
+
+@提醒功能允许在钉钉、企业微信等支持的渠道中@特定用户或所有人。
+
+### 支持的渠道
+
+| 渠道 | @手机号 | @用户ID | @所有人 |
+|------|--------|--------|--------|
+| 钉钉 | ✅ | ✅ | ✅ |
+| 企业微信 | ✅ | ✅ | ✅ |
+| 邮件 | ❌ | ❌ | ❌ |
+| 其他 | ❌ | ❌ | ❌ |
+
+### 使用示例
+
+#### @指定手机号
+
+```json
+{
+ "token": "...",
+ "title": "系统告警",
+ "text": "服务器异常,请及时处理",
+ "at_mobiles": ["13800138000", "13900139000"]
+}
+```
+
+#### @指定用户ID
+
+```json
+{
+ "token": "...",
+ "title": "任务通知",
+ "text": "您的任务已完成",
+ "at_userids": ["user001", "user002"]
+}
+```
+
+#### @所有人
+
+```json
+{
+ "token": "...",
+ "title": "重要通知",
+ "text": "系统将于今晚22:00进行维护",
+ "at_all": true
+}
+```
+
+#### 组合使用
+
+```json
+{
+ "token": "...",
+ "title": "紧急告警",
+ "markdown": "## 紧急告警\n\n生产环境出现严重问题!",
+ "at_mobiles": ["13800138000"],
+ "at_userids": ["admin"],
+ "at_all": false
+}
+```
+
+::: warning 注意事项
+1. @提醒只在支持的渠道中生效,其他渠道会忽略这些参数
+2. 钉钉机器人需要配置相应的权限才能使用@功能
+3. @所有人功能需谨慎使用,避免打扰群成员
+4. 手机号和用户ID格式需符合对应平台的要求
+:::
+
+## 支持的推送渠道
+
+| 渠道 | 支持格式 | @提醒 | 说明 |
+|------|---------|------|------|
+| **邮件** | Text, HTML | ❌ | SMTP邮件发送,推荐使用 HTML 格式 |
+| **钉钉** | Text, Markdown | ✅ | 钉钉机器人,支持 Markdown 富文本 |
+| **企业微信** | Text, Markdown | ✅ | 企业微信机器人,支持 Markdown |
+| **微信公众号** | Text | ❌ | 微信测试公众号模板消息 |
+| **自定义Webhook** | Text, HTML, Markdown | ❌ | 自定义HTTP请求,格式取决于配置 |
+| **自托管消息** | Text, HTML, Markdown | ❌ | 站内消息,支持多种格式 |
+
+## 使用流程
+
+1. **创建推送渠道**
+ - 在管理后台配置各种推送渠道
+ - 填写相应的配置信息(如邮箱、Webhook地址等)
+
+2. **创建发送任务**
+ - 选择要使用的推送渠道
+ - 可以选择多个渠道同时推送
+ - 获得唯一的推送令牌(Token)
+
+3. **调用API发送消息**
+ - 使用获得的 Token
+ - 发送标题和内容
+ - 消息会自动推送到配置的所有渠道
+
+## 工作流程
+
+1. **Token 解析** - 解密 Token 获取任务 ID(或直接使用 task_id)
+2. **任务查询** - 根据任务 ID 查询任务信息
+3. **实例遍历** - 获取任务关联的所有启用实例
+4. **格式选择** - 根据实例配置的格式类型选择对应内容
+5. **@提醒处理** - 如果提供了@参数且渠道支持,添加@提醒
+6. **消息发送** - 向每个实例发送消息
+7. **返回结果** - 返回发送状态
+
+## 注意事项
+
+::: warning 重要
+- **Token 安全**:Token 是唯一的,请妥善保管,不要在公开代码中硬编码
+- **格式兼容**:至少提供一种格式(text/html/markdown),推荐提供多种格式
+- **异步调用**:建议使用异步方式调用 API,避免阻塞主流程
+- **@提醒限制**:@功能仅在钉钉、企业微信等支持的渠道中生效
+- **格式优先级**:系统会根据实例配置自动选择最合适的格式
+:::
+
+## 最佳实践
+
+### 1. 多格式支持
+
+为了确保消息在不同渠道都有良好的展示效果,建议同时提供多种格式:
+
+```json
+{
+ "token": "...",
+ "title": "订单通知",
+ "text": "您的订单已发货,订单号:20241206001",
+ "html": "订单通知
您的订单已发货
订单号:20241206001
",
+ "markdown": "## 订单通知\n\n您的订单已发货\n\n订单号:**20241206001**"
+}
+```
+
+### 2. 合理使用@提醒
+
+只在需要紧急通知时使用@提醒:
+
+```json
+{
+ "token": "...",
+ "title": "紧急告警",
+ "text": "生产环境出现严重问题",
+ "at_mobiles": ["13800138000"], // 只@相关负责人
+ "at_all": false // 避免@所有人
+}
+```
+
+### 3. 错误处理
+
+```python
+import requests
+import json
+
+def send_message(token, title, text):
+ url = "http://your-domain/api/v1/message/send"
+ data = {
+ "token": token,
+ "title": title,
+ "text": text
+ }
+
+ try:
+ response = requests.post(url, json=data, timeout=10)
+ result = response.json()
+
+ if result['code'] == 200:
+ print("发送成功")
+ else:
+ print(f"发送失败:{result['msg']}")
+ except Exception as e:
+ print(f"请求异常:{e}")
+```
+
+## 错误码说明
+
+| 错误码 | 说明 |
+|--------|------|
+| 200 | 成功 |
+| 400 | 请求参数错误 |
+| 401 | 未授权 |
+| 404 | Token不存在 |
+| 500 | 服务器内部错误 |
+
+## 常见问题
+
+### Q: V1 和 V2 API 有什么区别?应该选择哪个?
+
+**A:**
+
+| 特性 | V1 API(任务) | V2 API(模板)⭐ 推荐 |
+|------|--------------|-------------------|
+| **内容定义** | API 调用时传递 | 预定义在模板中 |
+| **动态内容** | 完全动态 | 通过占位符替换 |
+| **内容复用** | 每次都要传递完整内容 | 模板可复用 |
+| **维护成本** | 修改内容需要改代码 | 只需修改模板 |
+| **适用场景** | 完全动态、不重复的内容 | 有固定格式的通知消息 |
+
+**推荐使用 V2 API(模板)的原因:**
+1. **提高开发效率** - 一次定义模板,多处使用
+2. **降低维护成本** - 内容修改无需改代码
+3. **统一管理** - 所有消息模板集中管理
+4. **更好的协作** - 运营人员可以直接修改模板内容
+5. **版本控制** - 模板支持启用/禁用,方便灰度发布
+6. **完全动态** - 通过占位符同样可以实现完全动态内容
+
+::: warning V1 API 的定位
+V1 API 仅为兼容历史数据而保留,**不推荐在任何新项目中使用**。即使是完全动态的内容,也可以通过模板 + 占位符的方式实现,且更易于后期维护。
+
+后续的功能优化和维护重点都在 V2 API(模板)上。
+:::
+
+### Q: 如何选择使用哪种格式?
+
+**A:** 根据渠道特性选择:
+- **邮件**:推荐 HTML,视觉效果好
+- **钉钉/企业微信**:推荐 Markdown,支持富文本
+- **通用**:使用 Text,兼容性最好
+- **多渠道**:同时提供多种格式,让系统自动选择
+
+### Q: @提醒不生效怎么办?
+
+**A:** 检查以下几点:
+1. 渠道是否支持@提醒(仅钉钉、企业微信支持)
+2. 机器人是否有@权限
+3. 手机号或用户ID格式是否正确
+4. 参数名称是否正确(`at_mobiles`、`at_userids`、`at_all`)
+
+### Q: 可以只发送 HTML 格式吗?
+
+**A:** 可以,但建议同时提供 text 格式作为备选,确保不支持 HTML 的渠道也能正常发送。
+
+### Q: Token 和 task_id 有什么区别?
+
+**A:**
+- **token**:加密的任务标识,更安全,推荐使用
+- **task_id**:明文的任务ID,不推荐在生产环境使用
+
+## 各语言调用示例
+
+本节提供各种编程语言的完整调用示例代码。
+
+### cURL
+
+```bash
+curl -X POST --location 'http://127.0.0.1:8000/api/v1/message/send' \
+ --header 'Content-Type: application/json' \
+ --data '{
+ "token": "a3541c2f0d3e1b4a5c6d7e8f9a0b1c2d3e",
+ "title": "message title",
+ "text": "Hello World!"
+ }'
+```
+
+### Python
+
+```python
+import requests
+
+headers = {
+ 'Content-Type': 'application/json',
+}
+
+json_data = {
+ "token": "a3541c2f0d3e1b4a5c6d7e8f9a0b1c2d3e",
+ "title": "message title",
+ "text": "Hello World!"
+}
+
+response = requests.post(
+ 'http://127.0.0.1:8000/api/v1/message/send',
+ headers=headers,
+ json=json_data
+)
+
+print("response:", response.json())
+```
+
+**安装依赖:**
+
+```bash
+pip install requests
+```
+
+### Go
+
+```go
+package main
+
+import (
+ "fmt"
+ "io"
+ "log"
+ "net/http"
+ "strings"
+)
+
+func main() {
+ client := &http.Client{}
+ var data = strings.NewReader(`{
+ "token": "a3541c2f0d3e1b4a5c6d7e8f9a0b1c2d3e",
+ "title": "message title",
+ "text": "Hello World!"
+}`)
+ req, err := http.NewRequest("POST", "http://127.0.0.1:8000/api/v1/message/send", data)
+ if err != nil {
+ log.Fatal(err)
+ }
+ req.Header.Set("Content-Type", "application/json")
+ resp, err := client.Do(req)
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer resp.Body.Close()
+ bodyText, err := io.ReadAll(resp.Body)
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Printf("%s\n", bodyText)
+}
+```
+
+### Java
+
+```java
+import java.io.IOException;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpRequest.BodyPublishers;
+import java.net.http.HttpResponse;
+
+public class MessageNestExample {
+ public static void main(String[] args) throws IOException, InterruptedException {
+ HttpClient client = HttpClient.newBuilder()
+ .followRedirects(HttpClient.Redirect.NORMAL)
+ .build();
+
+ String jsonData = """
+ {
+ "token": "a3541c2f0d3e1b4a5c6d7e8f9a0b1c2d3e",
+ "title": "message title",
+ "text": "Hello World!"
+ }
+ """;
+
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create("http://127.0.0.1:8000/api/v1/message/send"))
+ .POST(BodyPublishers.ofString(jsonData))
+ .setHeader("Content-Type", "application/json")
+ .build();
+
+ HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
+
+ System.out.println(response.body());
+ }
+}
+```
+
+### Node.js
+
+#### 使用 request 库
+
+```javascript
+var request = require('request');
+
+var headers = {
+ 'Content-Type': 'application/json'
+};
+
+var dataString = JSON.stringify({
+ "token": "a3541c2f0d3e1b4a5c6d7e8f9a0b1c2d3e",
+ "title": "message title",
+ "text": "Hello World!"
+});
+
+var options = {
+ url: 'http://127.0.0.1:8000/api/v1/message/send',
+ method: 'POST',
+ headers: headers,
+ body: dataString
+};
+
+function callback(error, response, body) {
+ if (!error && response.statusCode == 200) {
+ console.log(body);
+ }
+}
+
+request(options, callback);
+```
+
+#### 使用 axios 库
+
+```javascript
+const axios = require('axios');
+
+const data = {
+ token: "a3541c2f0d3e1b4a5c6d7e8f9a0b1c2d3e",
+ title: "message title",
+ text: "Hello World!"
+};
+
+axios.post('http://127.0.0.1:8000/api/v1/message/send', data, {
+ headers: {
+ 'Content-Type': 'application/json'
+ }
+})
+.then(response => {
+ console.log('response:', response.data);
+})
+.catch(error => {
+ console.error('error:', error);
+});
+```
+
+#### 使用 fetch (Node.js 18+)
+
+```javascript
+const data = {
+ token: "a3541c2f0d3e1b4a5c6d7e8f9a0b1c2d3e",
+ title: "message title",
+ text: "Hello World!"
+};
+
+fetch('http://127.0.0.1:8000/api/v1/message/send', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify(data)
+})
+.then(response => response.json())
+.then(data => {
+ console.log('response:', data);
+})
+.catch(error => {
+ console.error('error:', error);
+});
+```
+
+### PHP
+
+```php
+ "a3541c2f0d3e1b4a5c6d7e8f9a0b1c2d3e",
+ "title" => "message title",
+ "text" => "Hello World!"
+);
+
+curl_setopt($ch, CURLOPT_URL, 'http://127.0.0.1:8000/api/v1/message/send');
+curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
+curl_setopt($ch, CURLOPT_HTTPHEADER, [
+ 'Content-Type: application/json',
+]);
+curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
+curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
+
+$response = curl_exec($ch);
+
+if (curl_errno($ch)) {
+ echo 'Error:' . curl_error($ch);
+} else {
+ echo $response;
+}
+
+curl_close($ch);
+?>
+```
+
+### C#
+
+```csharp
+using System;
+using System.Net.Http;
+using System.Text;
+using System.Text.Json;
+using System.Threading.Tasks;
+
+class Program
+{
+ static async Task Main(string[] args)
+ {
+ using var client = new HttpClient();
+
+ var data = new
+ {
+ token = "a3541c2f0d3e1b4a5c6d7e8f9a0b1c2d3e",
+ title = "message title",
+ text = "Hello World!"
+ };
+
+ var json = JsonSerializer.Serialize(data);
+ var content = new StringContent(json, Encoding.UTF8, "application/json");
+
+ var response = await client.PostAsync(
+ "http://127.0.0.1:8000/api/v1/message/send",
+ content
+ );
+
+ var responseString = await response.Content.ReadAsStringAsync();
+ Console.WriteLine(responseString);
+ }
+}
+```
+
+### Ruby
+
+```ruby
+require 'net/http'
+require 'json'
+require 'uri'
+
+uri = URI('http://127.0.0.1:8000/api/v1/message/send')
+http = Net::HTTP.new(uri.host, uri.port)
+
+request = Net::HTTP::Post.new(uri.path, {
+ 'Content-Type' => 'application/json'
+})
+
+request.body = {
+ token: 'a3541c2f0d3e1b4a5c6d7e8f9a0b1c2d3e',
+ title: 'message title',
+ text: 'Hello World!'
+}.to_json
+
+response = http.request(request)
+puts response.body
+```
+
+### 示例说明
+
+::: tip 提示
+- 将示例中的 `http://127.0.0.1:8000` 替换为你的实际服务地址
+- 将 `a3541c2f0d3e1b4a5c6d7e8f9a0b1c2d3e` 替换为你在管理后台创建的实际 Token
+- 建议在生产环境中使用 HTTPS
+- 所有示例都使用基本的纯文本格式,实际使用时可以添加 `html`、`markdown` 等参数
+:::
+
+## 下一步
+
+- 查看 [V2 API 文档](/api/v2) 了解基于模板的发送方式
+- 查看 [消息模板](/guide/template) 了解如何使用模板功能
diff --git a/docs/api/v2.md b/docs/api/v2.md
new file mode 100644
index 0000000..ac1ab3c
--- /dev/null
+++ b/docs/api/v2.md
@@ -0,0 +1,519 @@
+# 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 | 否 | 占位符键值对 |
+
+### 参数说明
+
+#### token
+
+- 模板的加密 Token,在管理后台的"消息模板"页面获取
+- **注意**:V2 API 只支持加密 Token,不支持明文模板 ID
+- Token 使用对称加密算法生成,确保安全性
+
+#### title
+
+- 消息标题,会传递给所有支持标题的渠道(如邮件)
+- 必填参数,不能为空
+
+#### placeholders
+
+- 占位符的键值对,用于替换模板中的 `{{key}}`
+- 格式为 JSON 对象:`{"key": "value"}`
+- 如果模板中定义了占位符但未传递,将使用默认值
+- 如果既未传递也无默认值,占位符将保持原样
+
+## 请求示例
+
+### 基本示例
+
+```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);
+ });
+```
+
+### 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) 了解更多调用示例
diff --git a/docs/guide/changelog.md b/docs/guide/changelog.md
index 3f22cd9..4ec1d49 100644
--- a/docs/guide/changelog.md
+++ b/docs/guide/changelog.md
@@ -2,6 +2,23 @@
## 2025
+### 2025.12.06
+- **新增消息模板功能**
+ - 支持创建可复用的消息模板
+ - 支持占位符动态替换(`{{key}}` 语法)
+ - 支持 Text、HTML、Markdown 三种格式
+ - 支持为模板配置多个发送实例
+ - 支持 @提醒功能(钉钉、企业微信)
+- **新增 V2 API**
+ - 基于模板的消息发送接口
+ - 使用加密 Token 提升安全性
+ - 支持占位符参数传递
+ - 自动遍历模板的所有启用实例
+- **优化代码结构**
+ - 重构模板相关 Model 命名(`MessageTemplate` → `Template`)
+ - 添加 TypeScript 类型声明文件
+ - 完善文档体系
+
### 2025.10.12
- 增加cookies过期天数设置
diff --git a/docs/guide/template.md b/docs/guide/template.md
new file mode 100644
index 0000000..89abdc4
--- /dev/null
+++ b/docs/guide/template.md
@@ -0,0 +1,280 @@
+# 消息模板
+
+消息模板功能允许您创建可复用的消息模板,通过占位符实现动态内容替换,提高消息发送的灵活性和效率。
+
+::: tip ⭐ 作者推荐
+消息模板是 Message Nest 的核心功能,也是我们**强烈推荐**的使用方式。相比传统的任务发送(V1 API),模板方式具有以下显著优势:
+
+**为什么推荐使用模板?**
+1. **开发效率提升 3 倍** - 一次定义模板,所有项目复用,无需重复编写消息内容
+2. **维护成本降低 80%** - 修改消息格式只需在后台更新模板,无需修改代码、重新部署
+3. **团队协作更顺畅** - 开发负责 API 集成,运营负责内容维护,职责清晰
+4. **灰度发布更安全** - 支持模板启用/禁用,可以随时回滚,降低风险
+5. **内容管理更规范** - 所有消息模板集中管理,便于审核和统一风格
+
+**适用场景:**
+- ✅ 用户通知(注册、登录、密码重置等)
+- ✅ 订单消息(下单、支付、发货、退款等)
+- ✅ 系统告警(服务异常、资源不足等)
+- ✅ 营销推广(活动通知、优惠券等)
+- ✅ 完全动态的内容(通过占位符实现,如 `{{content}}`)
+- ✅ **所有消息发送场景**
+
+::: info 关于完全动态内容
+即使消息内容完全动态,也推荐使用模板方式。你可以创建一个只包含一个占位符的模板,如:
+
+**模板内容:** `{{content}}`
+
+**API 调用:**
+```json
+{
+ "placeholders": {
+ "content": "这里是完全动态的内容"
+ }
+}
+```
+
+这样做的好处是:
+1. 后期如果需要添加固定格式(如标题、签名),只需修改模板,无需改代码
+2. 所有消息统一管理,便于审计和监控
+3. 可以随时启用/禁用,便于灰度发布
+
+**V1 API(任务)的定位:**
+- 仅为兼容历史数据而保留
+- 不推荐在任何新项目中使用
+- 后续维护重点在模板功能上
+:::
+
+
+## 功能特性
+
+- ✅ **多格式支持** - 支持 Text、HTML、Markdown 三种格式
+- ✅ **占位符替换** - 使用 `{{key}}` 语法定义动态内容
+- ✅ **实例配置** - 为模板配置多个发送实例(渠道)
+- ✅ **@提醒功能** - 支持钉钉、企业微信的@提醒
+- ✅ **状态管理** - 启用/禁用模板控制
+- ✅ **API调用** - 通过 V2 API 使用模板发送消息
+
+## 创建模板
+
+### 1. 基本信息
+
+在管理后台的"消息模板"页面,点击"新建模板"按钮:
+
+- **模板名称** - 模板的唯一标识名称
+- **模板描述** - 模板的用途说明(可选)
+- **状态** - 启用/禁用
+
+### 2. 定义占位符
+
+占位符用于在发送消息时动态替换内容。
+
+**添加占位符:**
+
+| 字段 | 说明 | 示例 |
+|------|------|------|
+| Key | 占位符键名 | `username` |
+| Label | 显示标签 | `用户名` |
+| Default | 默认值(可选) | `Guest` |
+
+**使用示例:**
+
+```json
+[
+ {
+ "key": "username",
+ "label": "用户名",
+ "default": "Guest"
+ },
+ {
+ "key": "email",
+ "label": "邮箱地址",
+ "default": "user@example.com"
+ },
+ {
+ "key": "action",
+ "label": "操作类型",
+ "default": "登录"
+ }
+]
+```
+
+### 3. 编写模板内容
+
+支持三种格式,可以根据需要填写一种或多种:
+
+#### Text 模板
+
+纯文本格式,适用于所有渠道:
+
+```text
+您好,{{username}}!
+
+您的账号 {{email}} 刚刚进行了 {{action}} 操作。
+
+如果这不是您本人的操作,请立即联系我们。
+```
+
+#### HTML 模板
+
+HTML 格式,适用于邮件等支持富文本的渠道:
+
+```html
+
+
您好,{{username}}!
+
您的账号 {{email}} 刚刚进行了 {{action}} 操作。
+
如果这不是您本人的操作,请立即联系我们。
+
+```
+
+#### Markdown 模板
+
+Markdown 格式,适用于钉钉、企业微信等支持 Markdown 的渠道:
+
+```markdown
+## 您好,{{username}}!
+
+您的账号 **{{email}}** 刚刚进行了 `{{action}}` 操作。
+
+> ⚠️ 如果这不是您本人的操作,请立即联系我们。
+```
+
+### 4. 配置 @提醒(可选)
+
+针对钉钉、企业微信等渠道,可以配置@提醒:
+
+- **@手机号** - 多个手机号用逗号分隔,如:`13800138000,13900139000`
+- **@用户ID** - 多个用户ID用逗号分隔
+- **@所有人** - 勾选后会@所有群成员
+
+::: warning 注意
+@提醒功能仅在支持的渠道(钉钉、企业微信)中生效。
+:::
+
+## 配置发送实例
+
+创建模板后,需要为模板配置发送实例(渠道)。
+
+### 1. 添加实例
+
+在模板列表中,点击"实例"按钮:
+
+1. 选择发送渠道(从已创建的渠道中选择)
+2. 配置实例参数(如邮箱收件人地址)
+3. 选择消息格式(Text/HTML/Markdown)
+4. 保存实例
+
+### 2. 实例配置说明
+
+不同渠道需要配置不同的参数:
+
+**邮件渠道:**
+- 收件人邮箱地址
+- 消息格式:Text 或 HTML
+
+**钉钉/企业微信:**
+- 消息格式:Text 或 Markdown
+
+**自定义 Webhook:**
+- 根据 Webhook 要求配置
+
+### 3. 管理实例
+
+- **启用/禁用** - 控制实例是否参与发送
+- **编辑** - 修改实例配置
+- **删除** - 删除不需要的实例
+
+## 预览模板
+
+在编辑模板时,可以使用"预览"功能查看替换占位符后的效果:
+
+1. 点击"预览"按钮
+2. 填写占位符的测试值
+3. 查看 Text/HTML/Markdown 三种格式的渲染效果
+
+## 使用模板发送消息
+
+### 通过 API 调用
+
+使用 V2 API 发送模板消息,详见 [V2 API 文档](/api/v2)。
+
+**基本流程:**
+
+1. 获取模板 Token(在模板详情页查看)
+2. 准备占位符数据
+3. 调用 V2 API 发送
+
+**示例:**
+
+```bash
+curl -X POST http://your-domain/api/v2/message/send \
+ -H "Content-Type: application/json" \
+ -d '{
+ "token": "encrypted_template_token",
+ "title": "账号安全提醒",
+ "placeholders": {
+ "username": "张三",
+ "email": "zhangsan@example.com",
+ "action": "登录"
+ }
+ }'
+```
+
+## 最佳实践
+
+### 1. 占位符命名
+
+- 使用有意义的英文名称,如 `username`、`order_id`
+- 避免使用特殊字符,建议使用下划线分隔
+- 保持命名一致性
+
+### 2. 模板设计
+
+- **简洁明了** - 模板内容应简洁清晰
+- **格式适配** - 根据渠道特性选择合适的格式
+- **默认值** - 为占位符设置合理的默认值
+- **测试验证** - 使用预览功能验证模板效果
+
+### 3. 实例管理
+
+- **合理分组** - 为不同用途创建不同的模板
+- **渠道选择** - 根据消息类型选择合适的渠道
+- **定期检查** - 定期检查实例配置的有效性
+
+### 4. 安全性
+
+- **Token 保护** - 妥善保管模板 Token
+- **权限控制** - 合理设置模板的启用/禁用状态
+- **内容审查** - 注意模板内容的合规性
+
+## 常见问题
+
+### Q: 占位符没有被替换?
+
+**A:** 检查以下几点:
+- 占位符格式是否正确(`{{key}}`)
+- API 调用时是否传递了对应的 placeholders 参数
+- Key 名称是否匹配
+
+### Q: 如何选择消息格式?
+
+**A:** 根据渠道特性选择:
+- **邮件** - 推荐使用 HTML 格式,视觉效果更好
+- **钉钉/企业微信** - 推荐使用 Markdown 格式,支持富文本
+- **其他渠道** - 使用 Text 格式,兼容性最好
+
+### Q: 可以为一个模板配置多个实例吗?
+
+**A:** 可以。一个模板可以配置多个发送实例,调用 API 时会自动遍历所有启用的实例进行发送。
+
+### Q: @提醒不生效?
+
+**A:** 确认以下几点:
+- 渠道是否支持@提醒(仅钉钉、企业微信支持)
+- 手机号或用户ID格式是否正确
+- 机器人是否有@权限
+
+## 下一步
+
+- 查看 [V2 API 文档](/api/v2) 了解如何通过 API 使用模板
+- 查看 [API 示例](/api/examples) 了解各语言的调用方式
diff --git a/docs/package-lock.json b/docs/package-lock.json
index 8f31c19..4d1ba01 100644
--- a/docs/package-lock.json
+++ b/docs/package-lock.json
@@ -8,7 +8,7 @@
"name": "message-nest-docs",
"version": "1.0.0",
"devDependencies": {
- "vitepress": "^1.0.0"
+ "vitepress": "^1.6.4"
}
},
"node_modules/@algolia/abtesting": {
diff --git a/docs/package.json b/docs/package.json
index 474f3a3..84d9798 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -8,6 +8,6 @@
"docs:preview": "vitepress preview"
},
"devDependencies": {
- "vitepress": "^1.0.0"
+ "vitepress": "^1.6.4"
}
}