feat(DingtalkBotService): remove YAML Front Matter from message content and add utility method

This commit is contained in:
2026-01-25 11:21:23 +08:00
parent 9d656645ed
commit f2b189b1b6
4 changed files with 211 additions and 26 deletions

View File

@@ -0,0 +1,157 @@
# 通知客户端调试接口文档
## 接口说明
手动发送通知给指定的客户端,用于测试和调试通知功能。
## 接口地址
```
POST /notifyClient/debugPush
```
## 请求参数
```json
{
"targetAccessKey": "ak-xxxx-xxxx-xxxx", // 必填:目标客户端的 AccessKey
"content": "这是一条测试消息", // 必填:消息内容
"metadata": { // 可选:元数据
"title": "测试通知",
"priority": "high",
"category": "system"
},
"deadline": 1737859200 // 可选截止时间Unix时间戳不填默认为1小时后
}
```
### 参数说明
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| targetAccessKey | String | 是 | 目标客户端的 AccessKey可以从客户端列表中获取 |
| content | String | 是 | 消息内容,支持纯文本或 Markdown 格式 |
| metadata | Object | 否 | 元数据键值对,用于传递额外信息 |
| deadline | Long | 否 | 消息截止时间Unix时间戳超过这个时间的消息不会被投递。不填默认为当前时间+1小时 |
## 响应结果
### 成功响应
```json
{
"success": true,
"messageId": "msg-xxxx-xxxx-xxxx",
"targetAccessKey": "ak-xxxx-xxxx-xxxx",
"targetClientName": "测试客户端",
"serverUrl": "grpc://localhost:50051",
"timestamp": 1737859200000
}
```
### 失败响应
```json
{
"success": false,
"error": "目标客户端不存在: ak-xxxx-xxxx-xxxx",
"targetAccessKey": "ak-xxxx-xxxx-xxxx",
"timestamp": 1737859200000
}
```
## 使用示例
### cURL 示例
```bash
curl -X POST http://localhost:8080/notifyClient/debugPush \
-H "Content-Type: application/json" \
-d '{
"targetAccessKey": "ak-123e4567-e89b-12d3-a456-426614174000",
"content": "这是一条测试消息",
"metadata": {
"title": "系统通知",
"priority": "high"
}
}'
```
### JavaScript 示例
```javascript
fetch('http://localhost:8080/notifyClient/debugPush', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
targetAccessKey: 'ak-123e4567-e89b-12d3-a456-426614174000',
content: '这是一条测试消息',
metadata: {
title: '系统通知',
priority: 'high'
}
})
})
.then(response => response.json())
.then(data => console.log('推送结果:', data))
.catch(error => console.error('推送失败:', error));
```
### Python 示例
```python
import requests
import time
url = 'http://localhost:8080/notifyClient/debugPush'
payload = {
'targetAccessKey': 'ak-123e4567-e89b-12d3-a456-426614174000',
'content': '这是一条测试消息',
'metadata': {
'title': '系统通知',
'priority': 'high'
},
'deadline': int(time.time()) + 3600 # 1小时后过期
}
response = requests.post(url, json=payload)
print('推送结果:', response.json())
```
## 常见问题
### Q1: 如何获取客户端的 AccessKey
A: 通过 `/notifyClient/list` 接口查询客户端列表,返回结果中包含每个客户端的 `ak` 字段。
### Q2: 消息推送失败怎么办?
A: 检查以下几点:
1. 目标客户端的 AccessKey 是否正确
2. 通知服务器地址是否正确配置
3. 目标客户端是否在线并已连接到通知服务器
4. 查看服务器日志获取详细错误信息
### Q3: deadline 参数应该填什么值?
A: deadline 是 Unix 时间戳(秒),表示消息的截止时间。可以使用:
- JavaScript: `Math.floor(Date.now() / 1000) + 3600` // 1小时后
- Python: `int(time.time()) + 3600` // 1小时后
- 不填则默认为当前时间+1小时
### Q4: metadata 可以放什么数据?
A: metadata 是一个键值对对象,所有的 key 和 value 都必须是字符串类型。常用的字段有:
- `title`: 消息标题
- `priority`: 优先级low/normal/high
- `category`: 分类system/business/alert等
- 其他自定义字段
## 注意事项
1. 此接口仅用于调试和测试,生产环境中应该通过正常的业务流程发送通知
2. 消息内容建议不超过 10KB
3. 如果目标客户端不在线消息会在客户端下次连接时投递deadline 未过期的情况下)
4. 建议在调用前先通过 `/notifyClient/getById` 接口确认目标客户端存在

View File

@@ -160,12 +160,15 @@ public class DingtalkBotService extends BaseService<DingtalkBotEntity, DingtalkB
body.put("msgtype", "markdown");
// 移除 YAML Front Matter
String cleanedContent = removeYamlFrontMatter(content);
body.put(
"markdown",
new HashMap<String, Object>() {
{
put("title", title);
put("text", content);
put("text", cleanedContent);
}
});
try {
@@ -185,6 +188,38 @@ public class DingtalkBotService extends BaseService<DingtalkBotEntity, DingtalkB
return sendMsgResult;
}
/**
* 移除 YAML Front Matter
* YAML Front Matter 格式为文件开头的 ---...--- 包围的内容
* @param content 原始内容
* @return 移除 YAML Front Matter 后的内容
*/
private String removeYamlFrontMatter(String content) {
if (content == null || content.trim().isEmpty()) {
return content;
}
// YAML Front Matter 必须从文件开头开始
String trimmed = content.trim();
if (!trimmed.startsWith("---")) {
return content;
}
// 查找第二个 --- 的位置
int endIndex = trimmed.indexOf("---", 3);
if (endIndex == -1) {
// 如果没有找到结束标记,返回原内容
return content;
}
// 移除 YAML Front Matter 并去除前后空白
String result = trimmed.substring(endIndex + 3).trim();
log.debug("移除 YAML Front Matter: 原长度={}, 新长度={}", content.length(), result.length());
return result;
}
/**
* 发送钉钉消息(兼容旧接口,内部进行模板渲染)
* @param dingtalkBotEntity 钉钉机器人实体

View File

@@ -93,14 +93,13 @@ public class NotifyClientService extends BaseService<NotifyClientEntity, NotifyC
*
* 注意:
* 1. title 会放到 metadata 中
* 2. content 应该已包含完整的 YAML Front Matter 格式(由调用方在模板中处理
* 3. 本方法不处理 YAML Front Matter 的特殊字段
* 4. 服务器地址从配置文件读取notify.server-url支持环境变量 NOTIFY_SERVER_URL
* 5. 所有推送都是 push 模式(推送到指定 AK 的客户端)
* 2. content 保留完整内容(包括 YAML Front Matter,通知客户端支持解析
* 3. 服务器地址从配置文件读取notify.server-url支持环境变量 NOTIFY_SERVER_URL
* 4. 所有推送都是 push 模式(推送到指定 AK 的客户端)
*
* @param client 通知客户端实体(使用其 AK 作为目标客户端标识)
* @param title 已渲染的标题(放入 metadata
* @param content 已渲染的内容(应包含完整的 YAML Front Matter
* @param content 已渲染的内容(保留完整的 YAML Front Matter
* @param params 原始参数(保留用于扩展)
*/
public void doSend(NotifyClientEntity client, String title, String content, Map<String, Object> params) {
@@ -121,7 +120,7 @@ public class NotifyClientService extends BaseService<NotifyClientEntity, NotifyC
// 计算截止时间(默认当前时间 + 30分钟
long deadline = calculateDeadline(params);
// 推送模式:发送到指定 AK 的客户端
// 推送模式:发送到指定 AK 的客户端(保留完整内容,包括 YAML Front Matter
log.debug("推送到客户端: serverUrl={}, targetAK={}", serverUrl, client.getAk());
String messageId = NotifyGrpcClient.pushMessage(
serverUrl,

View File

@@ -4,8 +4,8 @@ import cn.lihongjie.coal.base.dto.CommonQuery;
import cn.lihongjie.coal.base.dto.IdRequest;
import cn.lihongjie.coal.base.service.BaseService;
import cn.lihongjie.coal.dbFunctions.DbFunctionService;
import cn.lihongjie.coal.dingtalkBot.entity.DingtalkBotEntity;
import cn.lihongjie.coal.dingtalkBot.service.DingtalkBotService;
import cn.lihongjie.coal.dingtalkBotScene.service.DingtalkBotSceneService;
import cn.lihongjie.coal.exception.BizException;
import cn.lihongjie.coal.weightDeviceData.entity.WeightDeviceDataEntity;
import cn.lihongjie.coal.weightDeviceData.repository.WeightDeviceDataRepository;
@@ -56,6 +56,8 @@ public class WeightDeviceDataAlertService
@Autowired private DingtalkBotService dingtalkBotService;
@Autowired private DingtalkBotSceneService dingtalkBotSceneService;
@Autowired private ObjectMapper objectMapper;
public WeightDeviceDataAlertDto create(CreateWeightDeviceDataAlertDto request) {
@@ -420,26 +422,18 @@ public class WeightDeviceDataAlertService
notificationData.put("queryJson", valueObject);
log.info("notificationData: {}", objectMapper.writeValueAsString(notificationData));
// 如果配置了钉钉场景,发送钉钉通知
// 如果配置了场景,使用统一的场景服务发送通知(支持钉钉机器人和通知客户端)
if (alert.getScene() != null) {
List<DingtalkBotEntity> bots = alert.getScene().getDingtalkBots();
if (bots != null && !bots.isEmpty()) {
for (DingtalkBotEntity bot : bots) {
try {
dingtalkBotService.doSend(
bot,
alert.getScene().getDingtalkBotTemplate(),
notificationData);
log.info("钉钉通知发送成功: 规则={}, Bot={}", alert.getName(), bot.getName());
} catch (Exception e) {
log.error("发送钉钉通知失败: Bot={}, 错误={}", bot.getName(), e.getMessage(), e);
}
}
} else {
log.warn("规则 {} 配置了场景但没有可用的钉钉机器人", alert.getName());
try {
dingtalkBotSceneService.sendMessage(alert.getScene().getId(), notificationData);
log.info("场景通知发送成功: 规则={}, 场景={}", alert.getName(), alert.getScene().getName());
} catch (Exception e) {
log.error("发送场景通知失败: 规则={}, 场景={}, 错误={}",
alert.getName(), alert.getScene().getName(), e.getMessage(), e);
}
} else {
log.debug("规则 {} 未配置场景,跳过通知发送", alert.getName());
}
log.info("发送告警通知: 规则={}, 实际吨数={}, 进度={}%, 记录数={}",