啊鑫
5 天以前 1f963e2344833ff02087c05411b112147492bd00
添加钉钉推送消息功能
已添加18个文件
已修改4个文件
1518 ■■■■■ 文件已修改
API_DOCUMENTATION.md 204 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
PROJECT_SUMMARY.md 233 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
README.md 204 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/xky/config/DataAcquisitionConfiguration.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/xky/config/ResultMessage.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/xky/controller/DingtalkController.java 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/xky/dto/DingTalkMessage.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/xky/dto/DingTalkResponse.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/xky/dto/NumbericalDto.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/xky/dto/Result.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/xky/entity/DingtalkInfo.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/xky/entity/DingtalkMsg.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/xky/mapper/DingtalkInfoMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/xky/mapper/DingtalkMsgMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/xky/service/DingtalkInfoService.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/xky/service/Impl/DingtalkInfoServiceImpl.java 155 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/xky/service/SimpleExample.java 239 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/DingtalkInfoMapper.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/DingtalkMsgMapper.xml 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/test/java/com/gs/xky/XkyApplicationTests.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/test/java/com/gs/xky/controller/DingtalkControllerTest.java 108 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
API_DOCUMENTATION.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,204 @@
# é’‰é’‰æ¶ˆæ¯å‘送API文档
## æ¦‚è¿°
本文档描述了采购订单管理系统中钉钉消息发送功能的REST API接口。
## åŸºç¡€ä¿¡æ¯
- **基础URL**: `http://localhost:9095`
- **API版本**: v1.0
- **数据格式**: JSON
- **字符编码**: UTF-8
## æŽ¥å£åˆ—表
### 1. å‘送钉钉消息
#### æŽ¥å£ä¿¡æ¯
- **接口名称**: å‘送钉钉消息
- **请求方法**: POST
- **接口路径**: `/api/dingtalk/sendMessage`
- **功能描述**: æ ¹æ®æ£€éªŒå•号发送不合格检验单的钉钉通知消息
#### è¯·æ±‚参数
**请求头**:
```
Content-Type: application/json
```
**请求体**:
```json
{
  "releaseNo": "IQC202501270001"
}
```
**参数说明**:
| å‚数名 | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|--------|------|------|------|
| releaseNo | String | æ˜¯ | æ£€éªŒå•号,用于查询对应的钉钉消息内容 |
#### å“åº”结果
**成功响应** (HTTP 200):
```json
{
  "code": 200,
  "message": null,
  "successful": 0,
  "data": "接收成功"
}
```
**失败响应** (HTTP 200):
```json
{
  "code": 500,
  "message": "错误信息",
  "successful": 1,
  "data": "接收失败"
}
```
**响应字段说明**:
| å­—段名 | ç±»åž‹ | è¯´æ˜Ž |
|--------|------|------|
| code | Integer | å“åº”状态码,200表示成功,500表示失败 |
| message | String | é”™è¯¯ä¿¡æ¯ï¼ŒæˆåŠŸæ—¶ä¸ºnull |
| successful | Integer | æˆåŠŸæ ‡è¯†ï¼Œ0表示成功,1表示失败 |
| data | String | å“åº”数据,成功时为"接收成功",失败时为"接收失败" |
#### é”™è¯¯ç è¯´æ˜Ž
| é”™è¯¯ç  | è¯´æ˜Ž   |
|-----|------|
| 200 | è¯·æ±‚成功 |
| 500 | è¯·æ±‚失败 |
#### ä½¿ç”¨ç¤ºä¾‹
**cURL示例**:
```bash
curl -X POST http://localhost:9095/api/dingtalk/sendMessage \
  -H "Content-Type: application/json" \
  -d '{"releaseNo": "IQC202501270001"}'
```
**JavaScript示例**:
```javascript
fetch('http://localhost:9095/api/dingtalk/sendMessage', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    releaseNo: 'IQC202501270001'
  })
})
.then(response => response.json())
.then(data => {
  if (data.code === 200) {
    console.log('消息发送成功');
  } else {
    console.log('消息发送失败:', data.message);
  }
})
.catch(error => {
  console.error('请求失败:', error);
});
```
**Java示例**:
```java
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.*;
public class DingtalkApiClient {
    private static final String BASE_URL = "http://localhost:9095";
    private static final OkHttpClient client = new OkHttpClient();
    private static final ObjectMapper objectMapper = new ObjectMapper();
    public static void sendMessage(String releaseNo) throws Exception {
        // æž„建请求体
        String requestBody = objectMapper.writeValueAsString(
                Map.of("releaseNo", releaseNo)
        );
        // åˆ›å»ºè¯·æ±‚
        Request request = new Request.Builder()
                .url(BASE_URL + "/api/dingtalk/sendMessage")
                .post(RequestBody.create(
                        MediaType.parse("application/json"),
                        requestBody
                ))
                .build();
        // å‘送请求
        try (Response response = client.newCall(request).execute()) {
            String responseBody = response.body().string();
            System.out.println("响应: " + responseBody);
        }
    }
}
```
#### å¸¸è§é”™è¯¯åŠè§£å†³æ–¹æ¡ˆ
1. **检验单号为空**
    - é”™è¯¯ä¿¡æ¯: "检验单号不能为空"
    - è§£å†³æ–¹æ¡ˆ: ç¡®ä¿ä¼ å…¥çš„releaseNo参数不为null且不为空字符串
2. **数据不存在**
    - é”™è¯¯ä¿¡æ¯: "钉钉消息发送失败"
    - è§£å†³æ–¹æ¡ˆ: ç¡®ä¿æ£€éªŒå•号在`DINGTALK_MSG`表中有对应的记录
3. **钉钉API调用失败**
    - é”™è¯¯ä¿¡æ¯: "钉钉消息发送失败"
    - è§£å†³æ–¹æ¡ˆ: æ£€æŸ¥é’‰é’‰åº”用配置是否正确,确保有发送权限
4. **系统异常**
    - é”™è¯¯ä¿¡æ¯: å…·ä½“的异常信息
    - è§£å†³æ–¹æ¡ˆ: æ£€æŸ¥ç³»ç»Ÿæ—¥å¿—,联系技术支持
#### æ³¨æ„äº‹é¡¹
1. **数据准备**: ç¡®ä¿æ£€éªŒå•号在`DINGTALK_MSG`表中有对应的记录
2. **权限配置**: ç¡®ä¿é’‰é’‰åº”用配置正确且有发送工作通知的权限
3. **调用频率**: å»ºè®®æŽ¥å£è°ƒç”¨é¢‘率不超过每分钟1次,避免触发钉钉API限制
4. **用户配置**: ç¡®ä¿æŽ¥æ”¶æ¶ˆæ¯çš„用户在`DINGTALK_INFO`表中配置正确
5. **网络环境**: ç¡®ä¿æœåŠ¡å™¨èƒ½å¤Ÿæ­£å¸¸è®¿é—®é’‰é’‰å¼€æ”¾API
#### æ¶ˆæ¯æ ¼å¼
发送的钉钉消息格式如下:
```
供应商[xxx] æ¥æ–™æ—¥æœŸ[yyyy-MM-dd] é¡¹ç›®[xxx] æ–™å·[xxx]的不合格检验单被[xxx]审批为[xxx],请查收!
```
#### æ•°æ®åº“依赖
该接口依赖以下数据库表:
- `DINGTALK_MSG`: å­˜å‚¨é’‰é’‰æ¶ˆæ¯å†…容
- `DINGTALK_INFO`: å­˜å‚¨é’‰é’‰ç”¨æˆ·ä¿¡æ¯
#### ç‰ˆæœ¬åŽ†å²
| ç‰ˆæœ¬   | æ—¥æœŸ         | æ›´æ–°å†…容               |
|------|------------|--------------------|
| v1.0 | 2025-01-27 | åˆå§‹ç‰ˆæœ¬ï¼Œæ”¯æŒåŸºæœ¬çš„钉钉消息发送功能 |
## è”系支持
如有问题或建议,请联系技术支持团队。
PROJECT_SUMMARY.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,233 @@
# é¡¹ç›®å®Œæˆæ€»ç»“
## é¡¹ç›®æ¦‚è¿°
本次任务是为采购订单管理系统创建一个REST API接口,用于发送钉钉消息通知。接口使用`NumbericalDto`作为请求参数,
`ResultMessage`作为响应结果。
## å®Œæˆçš„工作
### 1. åˆ›å»ºController接口
**文件位置**: `src/main/java/com/gs/xky/controller/DingtalkController.java`
**主要功能**:
- æä¾›POST接口 `/api/dingtalk/sendMessage`
- æŽ¥æ”¶`NumbericalDto`参数,包含检验单号
- è¿”回`ResultMessage`响应结果
- åŒ…含完整的参数校验和异常处理
- è¯¦ç»†çš„æ—¥å¿—记录
**接口特性**:
- RESTful设计风格
- ç»Ÿä¸€çš„错误处理机制
- å®Œæ•´çš„参数验证
- è¯¦ç»†çš„æ—¥å¿—记录
### 2. åˆ›å»ºæµ‹è¯•ç±»
**文件位置**: `src/test/java/com/gs/xky/controller/DingtalkControllerTest.java`
**测试覆盖**:
- æˆåŠŸåœºæ™¯æµ‹è¯•
- å¤±è´¥åœºæ™¯æµ‹è¯•
- å‚数为空测试
- å‚数为null测试
### 3. æ›´æ–°é¡¹ç›®æ–‡æ¡£
**更新的文件**:
- `README.md`: æ·»åŠ äº†REST API接口说明
- `API_DOCUMENTATION.md`: åˆ›å»ºäº†è¯¦ç»†çš„API文档
**文档内容**:
- æŽ¥å£ä½¿ç”¨è¯´æ˜Ž
- è¯·æ±‚/响应格式
- é”™è¯¯å¤„理说明
- ä½¿ç”¨ç¤ºä¾‹
- æ³¨æ„äº‹é¡¹
### 4. åˆ›å»ºè¾…助脚本
**创建的文件**:
- `start_api.bat`: API服务启动脚本
- `test_api.bat`: API接口测试脚本
## é¡¹ç›®ç»“æž„
```
src/
├── main/
│   â””── java/
│       â””── com/
│           â””── gs/
│               â””── xky/
│                   â”œâ”€â”€ controller/
│                   â”‚   â””── DingtalkController.java          # æ–°åˆ›å»ºçš„Controller
│                   â”œâ”€â”€ service/
│                   â”‚   â”œâ”€â”€ DingtalkInfoService.java         # æœåŠ¡æŽ¥å£
│                   â”‚   â””── Impl/
│                   â”‚       â””── DingtalkInfoServiceImpl.java # æœåŠ¡å®žçŽ°
│                   â”œâ”€â”€ dto/
│                   â”‚   â””── NumbericalDto.java               # è¯·æ±‚参数DTO
│                   â””── config/
│                       â””── ResultMessage.java               # å“åº”结果类
├── test/
│   â””── java/
│       â””── com/
│           â””── gs/
│               â””── xky/
│                   â””── controller/
│                       â””── DingtalkControllerTest.java      # æ–°åˆ›å»ºçš„æµ‹è¯•ç±»
└── resources/
    â””── mapper/
        â””── DingtalkMsgMapper.xml                            # æ•°æ®è®¿é—®å±‚
文档文件:
├── README.md                    # é¡¹ç›®ä¸»æ–‡æ¡£ï¼ˆå·²æ›´æ–°ï¼‰
├── API_DOCUMENTATION.md         # API详细文档(新创建)
├── PROJECT_SUMMARY.md           # é¡¹ç›®æ€»ç»“(本文件)
├── start_api.bat               # å¯åŠ¨è„šæœ¬ï¼ˆæ–°åˆ›å»ºï¼‰
└── test_api.bat                # æµ‹è¯•脚本(新创建)
```
## æŽ¥å£è¯¦æƒ…
### è¯·æ±‚信息
- **URL**: `POST /api/dingtalk/sendMessage`
- **Content-Type**: `application/json`
- **参数**: `NumbericalDto` (包含releaseNo字段)
### å“åº”信息
- **格式**: `ResultMessage`
- **字段**:
    - `code`: çŠ¶æ€ç  (200成功, 500失败)
    - `message`: é”™è¯¯ä¿¡æ¯
    - `successful`: æˆåŠŸæ ‡è¯† (0成功, 1失败)
    - `data`: å“åº”数据
### ä½¿ç”¨ç¤ºä¾‹
**请求**:
```bash
curl -X POST http://localhost:9095/api/dingtalk/sendMessage \
  -H "Content-Type: application/json" \
  -d '{"releaseNo": "IQC202501270001"}'
```
**成功响应**:
```json
{
  "code": 200,
  "message": null,
  "successful": 0,
  "data": "接收成功"
}
```
**失败响应**:
```json
{
  "code": 500,
  "message": "检验单号不能为空",
  "successful": 1,
  "data": "接收失败"
}
```
## æŠ€æœ¯ç‰¹ç‚¹
### 1. ä»£ç è´¨é‡
- éµå¾ªSOLID原则
- ä½¿ç”¨è®¾è®¡æ¨¡å¼ï¼ˆä¾èµ–注入、模板方法)
- å®Œæ•´çš„异常处理机制
- è¯¦ç»†çš„æ—¥å¿—记录
### 2. å®‰å…¨æ€§
- å‚数验证
- å¼‚常捕获
- é”™è¯¯ä¿¡æ¯å¤„理
### 3. å¯ç»´æŠ¤æ€§
- æ¸…晰的代码结构
- å®Œæ•´çš„æ–‡æ¡£è¯´æ˜Ž
- å•元测试覆盖
- è¯¦ç»†çš„æ³¨é‡Š
### 4. ç”¨æˆ·ä½“验
- ç»Ÿä¸€çš„响应格式
- æ¸…晰的错误信息
- è¯¦ç»†çš„使用文档
- ä¾¿æ·çš„æµ‹è¯•脚本
## éƒ¨ç½²è¯´æ˜Ž
### çŽ¯å¢ƒè¦æ±‚
- Java 8+
- Oracle数据库
- é’‰é’‰åº”用配置
### å¯åŠ¨æ­¥éª¤
1. ç¡®ä¿æ•°æ®åº“连接正常
2. ç¡®ä¿é’‰é’‰åº”用配置正确
3. è¿è¡Œ `start_api.bat` å¯åŠ¨æœåŠ¡
4. ä½¿ç”¨ `test_api.bat` æµ‹è¯•接口
### é…ç½®è¯´æ˜Ž
- æœåŠ¡ç«¯å£: 9095
- æ•°æ®åº“配置: `application.yml`
- é’‰é’‰é…ç½®: `DataAcquisitionConfiguration.java`
## åŽç»­å»ºè®®
### 1. åŠŸèƒ½æ‰©å±•
- æ·»åŠ æŽ¥å£è®¿é—®æƒé™æŽ§åˆ¶
- æ”¯æŒæ‰¹é‡å‘送消息
- æ·»åŠ æ¶ˆæ¯å‘é€åŽ†å²è®°å½•
- æ”¯æŒæ¶ˆæ¯æ¨¡æ¿é…ç½®
### 2. æ€§èƒ½ä¼˜åŒ–
- æ·»åŠ æŽ¥å£ç¼“å­˜æœºåˆ¶
- å®žçŽ°å¼‚æ­¥æ¶ˆæ¯å‘é€
- æ·»åŠ æŽ¥å£é™æµæŽ§åˆ¶
- ä¼˜åŒ–数据库查询
### 3. ç›‘控告警
- æ·»åŠ æŽ¥å£è°ƒç”¨ç›‘æŽ§
- å®žçŽ°æ¶ˆæ¯å‘é€çŠ¶æ€è·Ÿè¸ª
- æ·»åŠ å¼‚å¸¸å‘Šè­¦æœºåˆ¶
- é›†æˆæ—¥å¿—分析系统
## æ€»ç»“
本次任务成功完成了以下目标:
1. âœ… åˆ›å»ºäº†ç¬¦åˆè¦æ±‚çš„REST API接口
2. âœ… ä½¿ç”¨äº†æŒ‡å®šçš„参数和返回值类型
3. âœ… å®žçŽ°äº†å®Œæ•´çš„é”™è¯¯å¤„ç†æœºåˆ¶
4. âœ… æä¾›äº†è¯¦ç»†çš„使用文档
5. âœ… åˆ›å»ºäº†æµ‹è¯•用例和辅助脚本
6. âœ… éµå¾ªäº†è‰¯å¥½çš„代码规范和设计原则
项目代码结构清晰,文档完整,具有良好的可维护性和扩展性,可以满足当前的需求并为未来的功能扩展提供良好的基础。
README.md
@@ -19,6 +19,7 @@
4. é€€è´§ç®¡ç†
5. ä¾›åº”商管理
6. ERP与SRM系统数据比对
7. é’‰é’‰æ¶ˆæ¯é€šçŸ¥
## æ ¸å¿ƒæ•°æ®å®žä½“
@@ -40,6 +41,98 @@
- API调用频率不低于2小时
- è¯·æ±‚时间范围不大于24小时
- æ•°æ®ç±»åž‹è½¬æ¢éœ€æ³¨æ„ï¼šERP系统(Long)与SRM系统(Integer)
## REST API接口
### é’‰é’‰æ¶ˆæ¯å‘送接口
#### æŽ¥å£ä¿¡æ¯
- **URL**: `POST /api/dingtalk/sendMessage`
- **功能**: å‘送钉钉消息通知
- **描述**: æ ¹æ®æ£€éªŒå•号发送不合格检验单的钉钉通知消息
#### è¯·æ±‚参数
```json
{
  "releaseNo": "检验单号"
}
```
**参数说明**:
- `releaseNo` (String, å¿…å¡«): æ£€éªŒå•号,用于查询对应的钉钉消息内容
#### å“åº”结果
```json
{
  "code": 200,
  "message": null,
  "successful": 0,
  "data": "接收成功"
}
```
**响应字段说明**:
- `code` (Integer): å“åº”状态码,200表示成功,500表示失败
- `message` (String): é”™è¯¯ä¿¡æ¯ï¼ŒæˆåŠŸæ—¶ä¸ºnull
- `successful` (Integer): æˆåŠŸæ ‡è¯†ï¼Œ0表示成功,1表示失败
- `data` (String): å“åº”数据,成功时为"接收成功",失败时为"接收失败"
#### ä½¿ç”¨ç¤ºä¾‹
**请求示例**:
```bash
curl -X POST http://localhost:9095/api/dingtalk/sendMessage \
  -H "Content-Type: application/json" \
  -d '{"releaseNo": "IQC202501270001"}'
```
**成功响应示例**:
```json
{
  "code": 200,
  "message": null,
  "successful": 0,
  "data": "接收成功"
}
```
**失败响应示例**:
```json
{
  "code": 500,
  "message": "检验单号不能为空",
  "successful": 1,
  "data": "接收失败"
}
```
#### é”™è¯¯å¤„理
接口会处理以下错误情况:
1. **参数为空**: æ£€éªŒå•号为空或null时返回错误信息
2. **数据不存在**: æ£€éªŒå•号对应的钉钉消息内容不存在时返回失败
3. **发送失败**: é’‰é’‰API调用失败时返回失败信息
4. **系统异常**: å…¶ä»–系统异常时返回异常信息
#### æ³¨æ„äº‹é¡¹
1. ç¡®ä¿æ£€éªŒå•号在`DINGTALK_MSG`表中有对应的记录
2. ç¡®ä¿é’‰é’‰åº”用配置正确且有发送权限
3. æŽ¥å£è°ƒç”¨é¢‘率建议不超过每分钟1次
4. å»ºè®®åœ¨ç”Ÿäº§çŽ¯å¢ƒä¸­æ·»åŠ æŽ¥å£è®¿é—®æƒé™æŽ§åˆ¶
#### è¯¦ç»†æ–‡æ¡£
更多详细的API使用说明、错误处理、最佳实践等内容,请参考 [API_DOCUMENTATION.md](./API_DOCUMENTATION.md) æ–‡ä»¶ã€‚
## æ•°æ®åº“表结构
@@ -330,4 +423,113 @@
异步线程2: æ‰¹æ¬¡5处理完成
```
这种方式确保了主线程不会被长时间阻塞,同时充分利用了系统资源进行并行处理。
这种方式确保了主线程不会被长时间阻塞,同时充分利用了系统资源进行并行处理。
## é’‰é’‰æ¶ˆæ¯é€šçŸ¥åŠŸèƒ½
系统集成了钉钉消息通知功能,用于在重要事件发生时向指定用户发送通知。主要包括以下几个部分:
### åŠŸèƒ½æ¦‚è¿°
1. **通知场景**:
    - é‡‡è´­è®¢å•数据比对完成后,通知相关人员查看结果
    - æ•°æ®å¼‚常时的预警通知
    - ç³»ç»Ÿé‡è¦æ“ä½œçš„确认通知
    - ä¸åˆæ ¼æ£€éªŒå•审批情况通知
2. **通知方式**:
    - ä¸ªäººå·¥ä½œé€šçŸ¥ï¼šç›´æŽ¥å‘送给指定用户
    - ç¾¤æœºå™¨äººé€šçŸ¥ï¼šå‘送到指定的钉钉群
### æ•°æ®ç»“æž„
钉钉用户信息存储在 `DINGTALK_INFO` è¡¨ä¸­ï¼Œè¡¨ç»“构如下:
| å­—段名              | ç±»åž‹      | è¯´æ˜Ž                 |
|------------------|---------|--------------------|
| id               | Long    | ä¸»é”®                 |
| sid              | Long    | èŒå·¥ID               |
| phone            | String  | ç”µè¯å·ç                |
| dingtalk_id      | String  | é’‰é’‰ç”¨æˆ·ID             |
| is_send_dingtalk | Integer | æ˜¯å¦å‘送钉钉通知(1:是, 0:否) |
不合格检验单信息存储在 `DINGTALK_MSG` è¡¨ä¸­ï¼Œè¡¨ç»“构如下:
| å­—段名           | ç±»åž‹     | è¯´æ˜Ž    |
|---------------|--------|-------|
| release_no    | String | æ£€éªŒå•号  |
| supp_name     | String | ä¾›åº”商名称 |
| create_date   | Date   | æ¥æ–™æ—¥æœŸ  |
| project_codes | String | é¡¹ç›®ä»£ç   |
| item_no       | String | æ–™å·    |
| fname         | String | å®¡æ ¸äºº   |
| fng_handle    | String | å¤„理方式  |
### æ¶ˆæ¯å‘送流程
1. **获取通知用户**:
    - ä»Ž `DINGTALK_INFO` è¡¨ä¸­ç­›é€‰ `is_send_dingtalk` ä¸º1的用户
    - å¦‚果用户的钉钉ID为空,则通过钉钉API根据手机号获取钉钉ID
    - æ›´æ–°æ•°æ®åº“中的钉钉ID
2. **发送通知**:
    - æ±‡æ€»æ‰€æœ‰æœ‰æ•ˆçš„钉钉用户ID
    - æ ¹æ®æ£€éªŒå•号查询 `DINGTALK_MSG` è¡¨èŽ·å–æ¶ˆæ¯å†…å®¹
    - è°ƒç”¨é’‰é’‰å¼€æ”¾API发送工作通知
### é…ç½®è¯´æ˜Ž
钉钉应用配置信息存储在 `DataAcquisitionConfiguration` ä¸­ï¼š
```java
// é’‰é’‰åº”用Key
public static final String TALK_APP_KEY = "your_app_key";
// é’‰é’‰åº”用Secret
public static final String TALK_APP_SECRET = "your_app_secret";
// é’‰é’‰è‡ªå®šä¹‰æœºå™¨äººToken
public static final String CUSTOM_ROBOT_TOKEN = "your_robot_token";
```
### ä½¿ç”¨æ–¹æ³•
要发送钉钉通知,可以调用 `DingtalkInfoService` çš„ `sendMessage` æ–¹æ³•:
```java
@Autowired
private DingtalkInfoService dingtalkInfoService;
// å‘送不合格检验单通知
boolean result = dingtalkInfoService.sendMessage("检验单号");
```
### å®šæ—¶ä»»åŠ¡
系统配置了定时任务自动发送不合格检验单通知:
```java
/**
 * å®šæ—¶å‘送不合格检验单钉钉通知
 * æ¯å°æ—¶æ£€æŸ¥ä¸€æ¬¡æ˜¯å¦æœ‰æ–°çš„不合格检验单
 */
@Scheduled(cron = "0 0 */1 * * ?")
public void sendInspectionNotification() {
    // ä»»åŠ¡å®žçŽ°...
}
```
### æ¶ˆæ¯æ ¼å¼
不合格检验单通知的格式如下:
```
供应商[xxx] æ¥æ–™æ—¥æœŸ[yyyy-MM-dd] é¡¹ç›®[xxx] æ–™å·[xxx]的不合格检验单被[xxx]审批为[xxx],请查收!
```
### æ³¨æ„äº‹é¡¹
1. é’‰é’‰æ¶ˆæ¯å‘送频率有限制,请勿过于频繁发送
2. ç¡®ä¿åº”用有发送工作通知的权限
3. ç”¨æˆ·æ‰‹æœºå·å¿…须与钉钉注册手机号一致
4. å»ºè®®å°†é‡è¦é€šçŸ¥åŒæ—¶é€šè¿‡å¤šç§æ¸ é“发送(如邮件、短信等)
5. æ£€éªŒå•通知在`DINGTALK_MSG`表中必须有对应的记录
pom.xml
@@ -67,6 +67,18 @@
            <version>4.9.3</version>
        </dependency>
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>dingtalk</artifactId>
            <version>2.0.14</version>
        </dependency>
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>alibaba-dingtalk-service-sdk</artifactId>
            <version>2.0.0</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
src/main/java/com/gs/xky/config/DataAcquisitionConfiguration.java
@@ -20,4 +20,24 @@
    public static final String APP_SECRET = "f13bd1bcb130f0090ed92dc021e5f4e1";
    public static final String ERP_CODE = "Z106";
    /**
     * åº”用的 AgentId
     */
    public static final Long AGENT_ID = 3303296035L;
    /**
     * åº”用的 AppKey
     */
    public static final String TALK_APP_KEY = "dingnpc4kma0t19nphhd";
    /**
     * åº”用的 AppSecret
     */
    public static final String TALK_APP_SECRET = "uMecvKIfErf9htigYIyjI3svHpXEEDWROy2v1cXw7V6EXIG09oQI5N5e-EMqBo4r";
    //群聊机器人相关
    public static final String CUSTOM_ROBOT_TOKEN = "c2849e46cb0d91b0721c377742938b8ac5ef57e3c9eeab918e2cd5dd9c3aad2a";
    public static final String SECRET = "SEC382027a5c81ea5152b71b687fb2c1ebf26acbde035355da6ab2fb37306454134";
}
src/main/java/com/gs/xky/config/ResultMessage.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,35 @@
package com.gs.xky.config;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ResultMessage {
    private static final int SUCCESS = 200;
    private static final int ERROR = 500;
    private int code;
    private String message;
    private int successful;
    private String data;
    public static ResultMessage ok() {
        return new ResultMessage(SUCCESS, null, 0, "接收成功");
    }
    public static ResultMessage error(Exception e) {
        return new ResultMessage(ERROR, e.getMessage(), 1, "接收失败");
    }
    public static ResultMessage error(String message) {
        return new ResultMessage(ERROR, message, 1, "接收失败");
    }
}
src/main/java/com/gs/xky/controller/DingtalkController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,61 @@
package com.gs.xky.controller;
import com.gs.xky.config.ResultMessage;
import com.gs.xky.dto.NumbericalDto;
import com.gs.xky.service.DingtalkInfoService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * é’‰é’‰æ¶ˆæ¯å‘送控制器
 *
 * @author 28567
 * @description æä¾›é’‰é’‰æ¶ˆæ¯å‘送相关的REST接口
 * @createDate 2025-01-27
 */
@RestController
@RequestMapping("/api/dingtalk")
@RequiredArgsConstructor
@Slf4j
public class DingtalkController {
    private final DingtalkInfoService dingtalkInfoService;
    /**
     * å‘送钉钉消息
     *
     * @param numbericalDto åŒ…含检验单号的请求参数
     * @return ResultMessage æ“ä½œç»“æžœ
     */
    @PostMapping("/sendMessage")
    public ResultMessage sendMessage(@RequestBody NumbericalDto numbericalDto) {
        try {
            log.info("开始发送钉钉消息,检验单号:{}", numbericalDto.getReleaseNo());
            // å‚数校验
            if (numbericalDto == null || numbericalDto.getReleaseNo() == null || numbericalDto.getReleaseNo().trim().isEmpty()) {
                log.warn("检验单号不能为空");
                return ResultMessage.error("检验单号不能为空");
            }
            // è°ƒç”¨æœåŠ¡å±‚å‘é€æ¶ˆæ¯
            boolean result = dingtalkInfoService.sendMessage(numbericalDto.getReleaseNo());
            if (result) {
                log.info("钉钉消息发送成功,检验单号:{}", numbericalDto.getReleaseNo());
                return ResultMessage.ok();
            } else {
                log.warn("钉钉消息发送失败,检验单号:{}", numbericalDto.getReleaseNo());
                return ResultMessage.error("钉钉消息发送失败");
            }
        } catch (Exception e) {
            log.error("发送钉钉消息时发生异常,检验单号:{}", numbericalDto != null ? numbericalDto.getReleaseNo() : "null", e);
            return ResultMessage.error(e);
        }
    }
}
src/main/java/com/gs/xky/dto/DingTalkMessage.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,21 @@
package com.gs.xky.dto;
import com.google.gson.annotations.SerializedName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DingTalkMessage {
    @SerializedName("errcode")
    private int errcode;
    @SerializedName("task_id")
    private Long taskId;
    @SerializedName("request_id")
    private String requestId;
}
src/main/java/com/gs/xky/dto/DingTalkResponse.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,23 @@
package com.gs.xky.dto;
import com.google.gson.annotations.SerializedName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DingTalkResponse {
    @SerializedName("errcode")
    private int errcode;
    @SerializedName("errmsg")
    private String errmsg;
    @SerializedName("result")
    private Result result;
}
src/main/java/com/gs/xky/dto/NumbericalDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,14 @@
package com.gs.xky.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class NumbericalDto {
    private String releaseNo;
}
src/main/java/com/gs/xky/dto/Result.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
package com.gs.xky.dto;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import java.util.List;
@Data
public class Result {
    // Getters and Setters
    @SerializedName("exclusive_account_userid_list")
    private List<String> exclusiveAccountUseridList;
    private String userid;
}
src/main/java/com/gs/xky/entity/DingtalkInfo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,39 @@
package com.gs.xky.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
/**
 * @TableName DINGTALK_INFO
 */
@TableName(value = "DINGTALK_INFO")
@Data
public class DingtalkInfo implements Serializable {
    @TableField(exist = false)
    private static final long serialVersionUID = 1L;
    /**
     * SEQ_DingTalk
     */
    @TableId
    private Long id;
    /**
     * èŒå·¥id
     */
    private Long sid;
    /**
     * ç”µè¯
     */
    private String phone;
    /**
     * é’‰é’‰id
     */
    private String dingtalkId;
    /**
     * é’‰é’‰é€šçŸ¥
     */
    private Integer isSendDingtalk;
}
src/main/java/com/gs/xky/entity/DingtalkMsg.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,46 @@
package com.gs.xky.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
 * @TableName DINGTALK_MSG
 */
@TableName(value = "DINGTALK_MSG")
@Data
public class DingtalkMsg implements Serializable {
    @TableField(exist = false)
    private static final long serialVersionUID = 1L;
    /**
     * æ£€éªŒå•号
     */
    private String releaseNo;
    /**
     * ä¾›åº”商
     */
    private String suppName;
    /**
     * æ¥æ–™æ—¥æœŸ
     */
    private Date createDate;
    /**
     * é¡¹ç›®
     */
    private String projectCodes;
    /**
     * æ–™å·
     */
    private String itemNo;
    /**
     * å®¡æ ¸äºº
     */
    private String fname;
    /**
     * å¤„理方式
     */
    private String fngHandle;
}
src/main/java/com/gs/xky/mapper/DingtalkInfoMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package com.gs.xky.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.gs.xky.entity.DingtalkInfo;
/**
 * @author 28567
 * @description é’ˆå¯¹è¡¨ã€DINGTALK_INFO】的数据库操作Mapper
 * @createDate 2025-06-20 16:12:48
 * @Entity com.gs.xky.entity.DingtalkInfo
 */
public interface DingtalkInfoMapper extends BaseMapper<DingtalkInfo> {
}
src/main/java/com/gs/xky/mapper/DingtalkMsgMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package com.gs.xky.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.gs.xky.entity.DingtalkMsg;
/**
 * @author 28567
 * @description é’ˆå¯¹è¡¨ã€DINGTALK_MSG】的数据库操作Mapper
 * @createDate 2025-06-21 10:07:15
 * @Entity com.gs.xky.entity.DingtalkMsg
 */
public interface DingtalkMsgMapper extends BaseMapper<DingtalkMsg> {
}
src/main/java/com/gs/xky/service/DingtalkInfoService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,14 @@
package com.gs.xky.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.gs.xky.entity.DingtalkInfo;
/**
 * @author 28567
 * @description é’ˆå¯¹è¡¨ã€DINGTALK_INFO】的数据库操作Service
 * @createDate 2025-06-20 16:12:48
 */
public interface DingtalkInfoService extends IService<DingtalkInfo> {
    boolean sendMessage(String releaseNo);
}
src/main/java/com/gs/xky/service/Impl/DingtalkInfoServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,155 @@
package com.gs.xky.service.Impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dingtalk.api.DefaultDingTalkClient;
import com.dingtalk.api.DingTalkClient;
import com.dingtalk.api.request.OapiMessageCorpconversationAsyncsendV2Request;
import com.dingtalk.api.response.OapiMessageCorpconversationAsyncsendV2Response;
import com.gs.xky.entity.DingtalkInfo;
import com.gs.xky.entity.DingtalkMsg;
import com.gs.xky.mapper.DingtalkInfoMapper;
import com.gs.xky.mapper.DingtalkMsgMapper;
import com.gs.xky.service.DingtalkInfoService;
import com.gs.xky.service.SimpleExample;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
 * @author 28567
 * @description é’ˆå¯¹è¡¨ã€DINGTALK_INFO】的数据库操作Service实现
 * @createDate 2025-06-20 16:12:48
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class DingtalkInfoServiceImpl extends ServiceImpl<DingtalkInfoMapper, DingtalkInfo>
        implements DingtalkInfoService {
    private final SimpleExample simpleExample;
    private final DingtalkMsgMapper dingtalkMsgMapper;
    @Override
    public boolean sendMessage(String releaseNo) {
        try {
            // æŸ¥è¯¢é’‰é’‰æ¶ˆæ¯å†…容
            LambdaQueryWrapper<DingtalkMsg> msgWrapper = new LambdaQueryWrapper<>();
            msgWrapper.eq(DingtalkMsg::getReleaseNo, releaseNo);
            DingtalkMsg dingtalkMsg = dingtalkMsgMapper.selectOne(msgWrapper);
            if (dingtalkMsg == null) {
                log.error("未找到检验单号为 {} çš„钉钉消息内容", releaseNo);
                return false;
            }
            // æ ¼å¼åŒ–日期
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
            String createDateStr = (dingtalkMsg.getCreateDate() != null) ?
                    dateFormat.format(dingtalkMsg.getCreateDate()) : "未知";
            // æž„建消息内容
            String message = String.format("供应商[%s] æ¥æ–™æ—¥æœŸ[%s] é¡¹ç›®[%s] æ–™å·[%s]的不合格检验单被[%s]审批为[%s],请查收!",
                    dingtalkMsg.getSuppName(), createDateStr, dingtalkMsg.getProjectCodes(),
                    dingtalkMsg.getItemNo(), dingtalkMsg.getFname(), dingtalkMsg.getFngHandle());
            // è°ƒç”¨getDingtalkUserIdList方法获取UserIdList
            List<String> userIdList = getDingtalkUserIdList();
            if (userIdList == null || userIdList.isEmpty()) {
                log.warn("没有需要发送钉钉消息的用户");
                return false;
            }
            // é€šè¿‡é’‰é’‰å‘送消息
            String userIdListStr = String.join(",", userIdList);
            OapiMessageCorpconversationAsyncsendV2Response rsp = sendMessage(userIdListStr, message);
            System.out.println(rsp.getBody());
            log.info("成功发送钉钉消息: {}", message);
            return true;
        } catch (Exception e) {
            log.error("发送钉钉消息失败", e);
            return false;
        }
    }
    private List<String> getDingtalkUserIdList() {
        try {
            LambdaQueryWrapper<DingtalkInfo> wrapper = new LambdaQueryWrapper<>();
            wrapper.ge(DingtalkInfo::getIsSendDingtalk, 1);
            List<DingtalkInfo> list = list(wrapper);
            if (list == null || list.isEmpty()) {
                return new ArrayList<>();
            }
            // ä½¿ç”¨stream流过滤出list中dingtalkId为空的数据
            List<DingtalkInfo> emptyDingtalkIdList = list.stream()
                    .filter(info -> !StringUtils.hasText(info.getDingtalkId()))
                    .collect(Collectors.toList());
            // å¦‚果存在为空的数据就通过钉钉的接口获取,为dingtalkId赋值,并且更新数据库
            if (!emptyDingtalkIdList.isEmpty()) {
                String accessToken = simpleExample.getAccessToken();
                for (DingtalkInfo info : emptyDingtalkIdList) {
                    if (StringUtils.hasText(info.getPhone())) {
                        try {
                            // é€šè¿‡æ‰‹æœºå·èŽ·å–é’‰é’‰ç”¨æˆ·ID
                            com.dingtalk.api.response.OapiV2UserGetbymobileResponse response =
                                    simpleExample.getOapiV2UserGetbymobileResponse(info.getPhone(), accessToken);
                            if (response != null && response.getResult() != null) {
                                info.setDingtalkId(response.getResult().getUserid());
                                // æ›´æ–°æ•°æ®åº“
                                updateById(info);
                            }
                        } catch (Exception e) {
                            log.error("获取钉钉用户ID失败,手机号:{}", info.getPhone(), e);
                        }
                    }
                }
            }
            // ä¸å­˜åœ¨ä¸ºç©ºçš„æ•°æ®æˆ–者处理完空数据后,返回所有有效的dingtalkId列表
            return list.stream()
                    .map(DingtalkInfo::getDingtalkId)
                    .filter(StringUtils::hasText)
                    .collect(Collectors.toList());
        } catch (Exception e) {
            log.error("获取钉钉用户列表失败", e);
            return new ArrayList<>();
        }
    }
    private OapiMessageCorpconversationAsyncsendV2Response sendMessage(String userIdListStr, String message) throws Exception {
        String accessToken = simpleExample.getAccessToken();
        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/message/corpconversation/asyncsend_v2");
        OapiMessageCorpconversationAsyncsendV2Request request = new OapiMessageCorpconversationAsyncsendV2Request();
        request.setAgentId(3917187842L);
        request.setUseridList(userIdListStr);
        request.setToAllUser(false);
        OapiMessageCorpconversationAsyncsendV2Request.Msg msg = new OapiMessageCorpconversationAsyncsendV2Request.Msg();
        msg.setMsgtype("text");
        msg.setText(new OapiMessageCorpconversationAsyncsendV2Request.Text());
        msg.getText().setContent(message);
        request.setMsg(msg);
        return client.execute(request, accessToken);
    }
}
src/main/java/com/gs/xky/service/SimpleExample.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,239 @@
package com.gs.xky.service;
import cn.hutool.core.util.StrUtil;
import com.aliyun.dingtalkoauth2_1_0.Client;
import com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenRequest;
import com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenResponse;
import com.aliyun.dingtalkrobot_1_0.models.BatchSendOTOHeaders;
import com.aliyun.dingtalkrobot_1_0.models.BatchSendOTORequest;
import com.aliyun.dingtalkrobot_1_0.models.BatchSendOTOResponse;
import com.aliyun.dingtalkrobot_1_0.models.BatchSendOTOResponseBody;
import com.aliyun.tea.TeaException;
import com.aliyun.teaopenapi.models.Config;
import com.aliyun.teautil.Common;
import com.aliyun.teautil.models.RuntimeOptions;
import com.dingtalk.api.DefaultDingTalkClient;
import com.dingtalk.api.DingTalkClient;
import com.dingtalk.api.request.OapiRobotSendRequest;
import com.dingtalk.api.request.OapiV2UserGetbymobileRequest;
import com.dingtalk.api.response.OapiRobotSendResponse;
import com.dingtalk.api.response.OapiV2UserGetbymobileResponse;
import com.google.gson.Gson;
import com.gs.xky.config.DataAcquisitionConfiguration;
import com.gs.xky.dto.DingTalkMessage;
import com.gs.xky.dto.DingTalkResponse;
import com.taobao.api.ApiException;
import lombok.RequiredArgsConstructor;
import org.apache.commons.codec.binary.Base64;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.List;
/**
 * å‘送个人消息
 *
 * @author tjx
 * @date 2024/11/17 16:18
 */
@Service
@RequiredArgsConstructor
@Transactional(rollbackFor = Exception.class)
public class SimpleExample {
    /**
     * ç¾¤é‡Œæœºå™¨äººå‘送消息
     *
     * @author tjx
     * @date 2024/11/18 08:54
     */
    public DingTalkResponse chatSendMessage(String message) throws Exception {
        try {
            Long timestamp = System.currentTimeMillis();
            System.out.println(timestamp);
            String secret = DataAcquisitionConfiguration.TALK_APP_SECRET;
            String stringToSign = timestamp + "\n" + secret;
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(new SecretKeySpec(secret.getBytes("UTF-8"), "HmacSHA256"));
            byte[] signData = mac.doFinal(stringToSign.getBytes("UTF-8"));
            String sign = URLEncoder.encode(new String(Base64.encodeBase64(signData)), "UTF-8");
            System.out.println(sign);
            //sign字段和timestamp字段必须拼接到请求URL上,否则会出现 310000 çš„错误信息
            DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/robot/send?sign=" + sign + "&timestamp=" + timestamp);
            OapiRobotSendRequest req = new OapiRobotSendRequest();
            /**
             * å‘送文本消息
             */
            //定义文本内容
            OapiRobotSendRequest.Text text = new OapiRobotSendRequest.Text();
            text.setContent(message);
            //定义 @ å¯¹è±¡
            OapiRobotSendRequest.At at = new OapiRobotSendRequest.At();
            at.setIsAtAll(true);
            //设置消息类型
            req.setMsgtype("text");
            req.setText(text);
            req.setAt(at);
            OapiRobotSendResponse rsp = client.execute(req, DataAcquisitionConfiguration.CUSTOM_ROBOT_TOKEN);
            Gson gson = new Gson();
            return gson.fromJson(rsp.getBody(), DingTalkResponse.class);
        } catch (ApiException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        } catch (InvalidKeyException e) {
            throw new RuntimeException(e);
        }
        return null;
    }
    /**
     * å‘送消息
     */
    public DingTalkMessage sendMessage(String userIdListStr, String message) throws Exception {
        if (!StrUtil.isNotEmpty(userIdListStr)) {
            return new DingTalkMessage(1, 0L, "null");
        }
        String[] strArray = userIdListStr.split(",");
        List<String> userIdList = Arrays.asList(strArray);
        String accessToken = getAccessToken();
        com.aliyun.dingtalkrobot_1_0.Client client1 = createClient1();
        BatchSendOTOHeaders batchSendOTOHeaders = new BatchSendOTOHeaders();
        batchSendOTOHeaders.xAcsDingtalkAccessToken = accessToken;
        BatchSendOTORequest batchSendOTORequest = new BatchSendOTORequest()
                .setRobotCode("ding7n8fldhylh2rt2l2")
                .setUserIds(userIdList)
                .setMsgKey("sampleText")
                .setMsgParam("{\"content\": \"" + message + "\"}");
        try {
            BatchSendOTOResponse batchSendOTOResponse = client1.batchSendOTOWithOptions(batchSendOTORequest, batchSendOTOHeaders, new RuntimeOptions());
            BatchSendOTOResponseBody body = batchSendOTOResponse.getBody();
            if (StrUtil.isNotEmpty(body.getProcessQueryKey())) {
                return new DingTalkMessage(0, 0L, "null");
            }
        } catch (TeaException err) {
            if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
                System.out.println(err.code);
                System.out.println(err.message);
                System.out.println(err.accessDeniedDetail.toString());
            }
        } catch (Exception _err) {
            TeaException err = new TeaException(_err.getMessage(), _err);
            if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
                System.out.println(err.code);
                System.out.println(err.message);
            }
        }
        return new DingTalkMessage(1, 0L, "null");
        //return gson.fromJson(rsp.getBody(), DingTalkMessage.class);
    }
    /**
     * èŽ·å– AccessToken
     *
     * @return AccessToken
     * @throws Exception
     */
    public String getAccessToken() throws Exception {
        Client client = createClient();
        GetAccessTokenRequest getAccessTokenRequest = new GetAccessTokenRequest()
                .setAppKey(DataAcquisitionConfiguration.TALK_APP_KEY)
                .setAppSecret(DataAcquisitionConfiguration.TALK_APP_SECRET);
        try {
            GetAccessTokenResponse accessToken = client.getAccessToken(getAccessTokenRequest);
            return accessToken.body.getAccessToken();
        } catch (TeaException err) {
            if (!Common.empty(err.code) && !Common.empty(err.message)) {
                // err ä¸­å«æœ‰ code å’Œ message å±žæ€§ï¼Œå¯å¸®åŠ©å¼€å‘å®šä½é—®é¢˜
                System.out.println(err.code);
                System.out.println(err.message);
            }
        } catch (Exception exception) {
            TeaException err = new TeaException(exception.getMessage(), exception);
            if (!Common.empty(err.code) && !Common.empty(err.message)) {
                // err ä¸­å«æœ‰ code å’Œ message å±žæ€§ï¼Œå¯å¸®åŠ©å¼€å‘å®šä½é—®é¢˜
                System.out.println(err.code);
                System.out.println(err.message);
            }
        }
        return null;
    }
    public static com.aliyun.dingtalkrobot_1_0.Client createClient1() throws Exception {
        Config config = new Config();
        config.protocol = "https";
        config.regionId = "central";
        return new com.aliyun.dingtalkrobot_1_0.Client(config);
    }
    /**
     * ä½¿ç”¨ Token åˆå§‹åŒ–账号Client
     *
     * @return Client
     * @throws Exception
     */
    public Client createClient() throws Exception {
        Config config = new Config();
        config.protocol = "https";
        config.regionId = "central";
        return new Client(config);
    }
    //    public OapiV2UserGetbymobileResponse getOapiV2UserGetbymobileResponse(SendDingtalk s, String accessToken) {
//        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/v2/user/getbymobile");
//        OapiV2UserGetbymobileRequest req = new OapiV2UserGetbymobileRequest();
//        req.setMobile(s.getTelephone());
//        req.setSupportExclusiveAccountSearch(true);
//        OapiV2UserGetbymobileResponse rsp = null;
//
//        try {
//            rsp = client.execute(req, accessToken);
//        } catch (ApiException e) {
//            throw new RuntimeException(e);
//        }
//        return rsp;
//    }
    public OapiV2UserGetbymobileResponse getOapiV2UserGetbymobileResponse(String mobile, String accessToken) {
        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/v2/user/getbymobile");
        OapiV2UserGetbymobileRequest req = new OapiV2UserGetbymobileRequest();
        req.setMobile(mobile);
        req.setSupportExclusiveAccountSearch(true);
        OapiV2UserGetbymobileResponse rsp = null;
        try {
            rsp = client.execute(req, accessToken);
        } catch (ApiException e) {
            throw new RuntimeException(e);
        }
        return rsp;
    }
}
src/main/resources/mapper/DingtalkInfoMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.gs.xky.mapper.DingtalkInfoMapper">
</mapper>
src/main/resources/mapper/DingtalkMsgMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.gs.xky.mapper.DingtalkMsgMapper">
    <resultMap id="BaseResultMap" type="com.gs.xky.entity.DingtalkMsg">
        <result property="releaseNo" column="RELEASE_NO"/>
        <result property="suppName" column="SUPP_NAME"/>
        <result property="createDate" column="CREATE_DATE"/>
        <result property="projectCodes" column="PROJECT_CODES"/>
        <result property="itemNo" column="ITEM_NO"/>
        <result property="fname" column="FNAME"/>
        <result property="fngHandle" column="FNG_HANDLE"/>
    </resultMap>
    <sql id="Base_Column_List">
        RELEASE_NO,SUPP_NAME,CREATE_DATE,PROJECT_CODES,ITEM_NO,FNAME,
        FNG_HANDLE
    </sql>
</mapper>
src/test/java/com/gs/xky/XkyApplicationTests.java
@@ -40,6 +40,8 @@
    private MesInvItemArnService invItemArnService;
    @Autowired
    private PurchaseService service;
    @Autowired
    private DingtalkInfoService dingtalkInfoService;
    @Test
    void contextLoads() throws IOException {
@@ -58,7 +60,7 @@
    @Test
    void cs() throws IOException {
        String str = "22250529195E;22250529EQHY;22250529HCM9;222505304W8Q;222505305XRK;22250530F6UC;22250530JQ31;22250531UF8T;222506013N09;222506015051;2225060152A2;2225060152EN;222506015AM7;222506015U07;2225060162K4;222506016C4C;222506017GEM;222506017YTU;222506019RWD;22250601A216;22250601C4C3;22250601GTT8;22250601GUGU;22250601H6Y7;22250601JN9X;22250601JVW5;22250601L6R5;22250601NANG;22250601NHK4;22250601NYAL;22250601Q669;22250601RGJX;22250601VCE2;22250601W6C6;22250601WFK1;22250601WH28;22250601XE2T;22250602M0MU;";
        String str = "2225060624DY;";
        String[] split = str.split(";");
@@ -157,8 +159,9 @@
    }
    @Test
    void cs3() throws IOException {
    void cs3() throws Exception {
        dingtalkInfoService.sendMessage("CGJY20250412166");
//        service.syncPurchaseOrderDetails();
    }
}
src/test/java/com/gs/xky/controller/DingtalkControllerTest.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,108 @@
package com.gs.xky.controller;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.gs.xky.dto.NumbericalDto;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
 * DingtalkController测试类
 *
 * @author 28567
 * @description æµ‹è¯•钉钉消息发送接口
 * @createDate 2025-01-27
 */
@WebMvcTest(DingtalkController.class)
public class DingtalkControllerTest {
    @Autowired
    private MockMvc mockMvc;
    @MockBean
    private com.gs.xky.service.DingtalkInfoService dingtalkInfoService;
    @Autowired
    private ObjectMapper objectMapper;
    @Test
    public void testSendMessage_Success() throws Exception {
        // å‡†å¤‡æµ‹è¯•数据
        NumbericalDto requestDto = new NumbericalDto();
        requestDto.setReleaseNo("IQC202501270001");
        // Mock服务层返回成功
        when(dingtalkInfoService.sendMessage(anyString())).thenReturn(true);
        // æ‰§è¡Œæµ‹è¯•
        mockMvc.perform(post("/api/dingtalk/sendMessage")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(requestDto)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(200))
                .andExpect(jsonPath("$.successful").value(0))
                .andExpect(jsonPath("$.data").value("接收成功"));
    }
    @Test
    public void testSendMessage_Failure() throws Exception {
        // å‡†å¤‡æµ‹è¯•数据
        NumbericalDto requestDto = new NumbericalDto();
        requestDto.setReleaseNo("IQC202501270001");
        // Mock服务层返回失败
        when(dingtalkInfoService.sendMessage(anyString())).thenReturn(false);
        // æ‰§è¡Œæµ‹è¯•
        mockMvc.perform(post("/api/dingtalk/sendMessage")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(requestDto)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(500))
                .andExpect(jsonPath("$.successful").value(1))
                .andExpect(jsonPath("$.data").value("接收失败"));
    }
    @Test
    public void testSendMessage_EmptyReleaseNo() throws Exception {
        // å‡†å¤‡æµ‹è¯•数据 - ç©ºçš„æ£€éªŒå•号
        NumbericalDto requestDto = new NumbericalDto();
        requestDto.setReleaseNo("");
        // æ‰§è¡Œæµ‹è¯•
        mockMvc.perform(post("/api/dingtalk/sendMessage")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(requestDto)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(500))
                .andExpect(jsonPath("$.message").value("检验单号不能为空"))
                .andExpect(jsonPath("$.successful").value(1))
                .andExpect(jsonPath("$.data").value("接收失败"));
    }
    @Test
    public void testSendMessage_NullReleaseNo() throws Exception {
        // å‡†å¤‡æµ‹è¯•数据 - null检验单号
        NumbericalDto requestDto = new NumbericalDto();
        requestDto.setReleaseNo(null);
        // æ‰§è¡Œæµ‹è¯•
        mockMvc.perform(post("/api/dingtalk/sendMessage")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(requestDto)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.code").value(500))
                .andExpect(jsonPath("$.message").value("检验单号不能为空"))
                .andExpect(jsonPath("$.successful").value(1))
                .andExpect(jsonPath("$.data").value("接收失败"));
    }
}