1 feat: 完善质量检测消息推送功能并添加测试
3 - 实现 MesQaDingtalkService.sendQaMsgSJ 方法,支持发送包含检验单号、物料编码、物料名称、车间、线体、工单号、检验结果等详细信息的钉钉消息
4 - 添加 MesQaDingtalkServiceTest 测试类,包含正常和异常情况的单元测试
已修改6个文件
已添加13个文件
614 ■■■■■ 文件已修改
README.md 124 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/dingtalk/dto/QaMsgDto.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/dingtalk/entity/MesQaDingtalk.java 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/dingtalk/entity/QaSj.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/dingtalk/entity/SendDingtalk.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/dingtalk/mapper/MesQaDingtalkMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/dingtalk/mapper/QaSjMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/dingtalk/service/MesQaDingtalkService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/dingtalk/service/QaSjService.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/dingtalk/service/SendDingtalkService.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/dingtalk/service/SimpleExample.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/dingtalk/service/impl/MesQaDingtalkServiceImpl.java 158 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/dingtalk/service/impl/QaSjServiceImpl.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/dingtalk/service/impl/SendDingtalkServiceImpl.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/dingtalk/task/ScheduledTasks.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application.yml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/MesQaDingtalkMapper.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/QaSjMapper.xml 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/test/java/com/gs/dingtalk/MesQaDingtalkServiceTest.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
README.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,124 @@
# é’‰é’‰æ¶ˆæ¯æŽ¨é€ç³»ç»Ÿ
## é¡¹ç›®æ¦‚è¿°
这是一个基于 Spring Boot çš„钉钉消息推送系统,名为 `dingtalk`。该系统通过钉钉开放平台 API å®žçŽ°äº†å®šæ—¶æ¶ˆæ¯æŽ¨é€ã€ç”¨æˆ·ç®¡ç†ã€ä»¥åŠè®¾å¤‡æ•°æ®æé†’ç­‰åŠŸèƒ½ã€‚
## æ ¸å¿ƒæŠ€æœ¯æ ˆ
- **框架**: Spring Boot 2.6.13
- **语言**: Java 1.8
- **数据库**: Oracle,使用 Druid è¿žæŽ¥æ± 
- **持久层**: MyBatis-Plus
- **钉钉集成**: é’‰é’‰å¼€æ”¾å¹³å° SDK
- **工具库**: Lombok, Gson, OkHttp3, Fastjson, Hutool, Apache POI
- **包管理**: Maven
## é¡¹ç›®æž¶æž„
```
src/
├── main/
│   â”œâ”€â”€ java/com/gs/dingtalk/
│   â”‚   â”œâ”€â”€ DingtalkApplication.java          # Spring Boot å¯åŠ¨ç±»
│   â”‚   â”œâ”€â”€ config/
│   â”‚   â”‚   â”œâ”€â”€ DataAcquisitionConfiguration.java # é’‰é’‰åº”用配置(AppKey、AppSecret等)
│   â”‚   â”‚   â”œâ”€â”€ ResultMessage.java            # ç»Ÿä¸€å“åº”消息格式
│   â”‚   â”‚   â””── URLEncoder.java               # URL编码工具类
│   â”‚   â”œâ”€â”€ controller/
│   â”‚   â”‚   â””── KMController.java             # æ¶ˆæ¯å‘送控制层
│   â”‚   â”œâ”€â”€ dto/
│   â”‚   â”‚   â”œâ”€â”€ ApiResponseCode.java          # API响应码
│   â”‚   â”‚   â”œâ”€â”€ DingTalkMessage.java          # é’‰é’‰æ¶ˆæ¯ DTO
│   â”‚   â”‚   â”œâ”€â”€ DingTalkResponse.java         # é’‰é’‰å“åº” DTO
│   â”‚   â”‚   â””── Result.java                   # ç»Ÿä¸€è¿”回结果
│   â”‚   â”œâ”€â”€ entity/
│   â”‚   â”‚   â”œâ”€â”€ MesQaDingtalk.java            # è´¨é‡æ£€æµ‹é’‰é’‰æ¶ˆæ¯å®žä½“
│   â”‚   â”‚   â”œâ”€â”€ SendDingtalk.java             # é’‰é’‰ç”¨æˆ·å®žä½“
│   â”‚   â”‚   â””── SendMessage.java              # æ¶ˆæ¯å‘送实体
│   â”‚   â”œâ”€â”€ mapper/
│   â”‚   â”‚   â”œâ”€â”€ MesQaDingtalkMapper.java      # è´¨é‡æ£€æµ‹é’‰é’‰æ¶ˆæ¯æ•°æ®è®¿é—®å±‚
│   â”‚   â”‚   â”œâ”€â”€ SendDingtalkMapper.java       # é’‰é’‰ç”¨æˆ·æ•°æ®è®¿é—®å±‚
│   â”‚   â”‚   â””── SendMessageMapper.java        # æ¶ˆæ¯å‘送数据访问层
│   â”‚   â”œâ”€â”€ service/
│   â”‚   â”‚   â”œâ”€â”€ MesQaDingtalkService.java     # è´¨é‡æ£€æµ‹é’‰é’‰æœåŠ¡æŽ¥å£
│   â”‚   â”‚   â”œâ”€â”€ SendDingtalkService.java      # é’‰é’‰æœåŠ¡æŽ¥å£
│   â”‚   â”‚   â”œâ”€â”€ SendMessageService.java       # æ¶ˆæ¯æœåŠ¡æŽ¥å£
│   â”‚   â”‚   â”œâ”€â”€ SimpleExample.java            # é’‰é’‰æ¶ˆæ¯å‘送实现
│   â”‚   â”‚   â””── impl/
│   â”‚   â”‚       â”œâ”€â”€ MesQaDingtalkServiceImpl.java # è´¨é‡æ£€æµ‹é’‰é’‰æœåŠ¡å®žçŽ°
│   â”‚   â”‚       â”œâ”€â”€ SendDingtalkServiceImpl.java # é’‰é’‰æœåŠ¡å®žçŽ°
│   â”‚   â”‚       â””── SendMessageServiceImpl.java  # æ¶ˆæ¯æœåŠ¡å®žçŽ°
│   â”‚   â””── task/
│   â”‚       â””── ScheduledTasks.java           # å®šæ—¶ä»»åŠ¡
│   â””── resources/
│       â”œâ”€â”€ application.yml                   # åº”用配置文件
│       â””── mapper/                           # MyBatis XML æ˜ å°„文件
└── test/
    â””── java/com/gs/dingtalk/DeviceReceivingApplicationTests.java # æµ‹è¯•ç±»
```
## æ•°æ®åº“配置
- **数据库**: Oracle
- **URL**: jdbc:oracle:thin:@172.16.0.219:1521/orcl
- **用户名**: yc_dev
- **密码**: ycdev
- **连接池**: Druid,初始连接数5,最大连接数20
## é’‰é’‰é…ç½®
在 `config/DataAcquisitionConfiguration.java` ä¸­é…ç½®ï¼š
- **AppKey**: é’‰é’‰åº”用的 AppKey
- **AppSecret**: é’‰é’‰åº”用的 AppSecret
- **CorpId**: é’‰é’‰ä¼ä¸š CorpId
- **机器人Token**: é’‰é’‰ç¾¤èŠæœºå™¨äºº Token
- **机器人Secret**: é’‰é’‰ç¾¤èŠæœºå™¨äºº Secret
## ä¸»è¦åŠŸèƒ½æ¨¡å—
### 1. ç”¨æˆ·ç®¡ç†
- `getDingTalkUserId()`: é€šè¿‡æ‰‹æœºå·èŽ·å–é’‰é’‰ç”¨æˆ·ID并保存到数据库
- ä½¿ç”¨é’‰é’‰ `OapiV2UserGetbymobileResponse` API å®žçް
### 2. æ¶ˆæ¯æŽ¨é€
- `chatSendMessage()`: å‘钉钉群聊机器人发送消息
- `sendDingTalkFiveMinute()`, `sendDingTalkFifteenMinute()`, `sendDingTalkthirtyMinute()`: å®šæ—¶å‘特定用户发送消息
### 3. å®šæ—¶ä»»åŠ¡
- `ScheduledTasks.java`: å®šä¹‰äº†ä¸‰ä¸ªå®šæ—¶ä»»åŠ¡
  - æ¯2分钟执行一次5分钟提醒检查
  - æ¯3分钟执行一次15分钟提醒检查
  - æ¯4分钟执行一次30分钟提醒检查
### 4. è´¨é‡æ£€æµ‹
- `MesQaDingtalkServiceImpl`: å¤„理质量检测相关的钉钉消息推送
### 5. æƒé™æŽ§åˆ¶
- é€šè¿‡ `SendDingtalk` å®žä½“中的 `purview` å­—段控制用户消息接收权限
- æ ¹æ® `procNo`(工序编号)匹配用户权限
## æž„建与运行
### æž„建
```bash
mvn clean package
```
### è¿è¡Œ
```bash
java -jar dingtalk.jar
```
或在 IDE ä¸­è¿è¡Œ `DingtalkApplication.java` çš„ `main` æ–¹æ³•
### ç«¯å£
- æœåŠ¡å™¨ç«¯å£: 9096
## å¼€å‘约定
- ä½¿ç”¨ Lombok æ³¨è§£ç®€åŒ–代码
- ä½¿ç”¨ MyBatis-Plus è¿›è¡Œæ•°æ®åº“操作
- ä½¿ç”¨ç»Ÿä¸€çš„响应格式 `ResultMessage`
- é…ç½®æ–‡ä»¶ä½¿ç”¨ YAML æ ¼å¼
- å®šæ—¶ä»»åŠ¡ä½¿ç”¨ Spring çš„ `@Scheduled` æ³¨è§£
src/main/java/com/gs/dingtalk/dto/QaMsgDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,13 @@
package com.gs.dingtalk.dto;
import lombok.Data;
@Data
public class QaMsgDto {
    private String lineName;
    private String workshopName;
    private String qaType;
    private String billNo;
}
src/main/java/com/gs/dingtalk/entity/MesQaDingtalk.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,65 @@
package com.gs.dingtalk.entity;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import lombok.Data;
/**
 *
 * @TableName MES_QA_DINGTALK
 */
@TableName(value ="MES_QA_DINGTALK")
@Data
@KeySequence(value = "SEQ_SEND", dbType = DbType.ORACLE)
public class MesQaDingtalk implements Serializable {
    /**
     *
     */
    private Long id;
    /**
     * çº¿ä½“编码
     */
    private String lineNo;
    /**
     * çº¿ä½“名称
     */
    private String lineName;
    /**
     * è½¦é—´id
     */
    private Long departmentid;
    /**
     * è½¦é—´åç§°
     */
    private String departmentname;
    /**
     * æ£€éªŒç±»åž‹
     */
    private String qaType;
    /**
     * ç”¨æˆ·çš„钉钉id
     */
    private String userId;
    /**
     * ç”¨æˆ·ä¸­æ–‡
     */
    private String userName;
    /**
     * ç”µè¯
     */
    private String telephone;
    @TableField(exist = false)
    private static final long serialVersionUID = 1L;
}
src/main/java/com/gs/dingtalk/entity/QaSj.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,57 @@
package com.gs.dingtalk.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import lombok.Data;
/**
 *
 * @TableName QA_SJ
 */
@TableName(value ="QA_SJ")
@Data
public class QaSj implements Serializable {
    /**
     * æ£€éªŒå•号
     */
    private String billNo;
    /**
     * ç‰©æ–™ç¼–码
     */
    private String itemNo;
    /**
     * ç‰©æ–™åç§°
     */
    private String itemName;
    /**
     * çº¿ä½“
     */
    private String lineName;
    /**
     * è½¦é—´
     */
    private String workshopName;
    /**
     * æäº¤æ ‡è¯†
     */
    private Long fsubmit;
    /**
     * å·¥å•号
     */
    private String daa001;
    /**
     * æ£€éªŒç»“æžœ
     */
    private String jyjg;
    @TableField(exist = false)
    private static final long serialVersionUID = 1L;
}
src/main/java/com/gs/dingtalk/entity/SendDingtalk.java
@@ -41,6 +41,12 @@
     */
    private Integer fiveMinuteReminder;
    //Fifteen_MINUTE_REMINDER
    /**
     * åäº”分钟该提醒的人  0表示不提醒  1 è¡¨ç¤ºæé†’
     */
    private Integer FifteenMinuteReminder;
    /**
     * ä¸‰ååˆ†é’Ÿè¯¥æé†’的人 0表示不提醒  1 è¡¨ç¤ºæé†’
     */
src/main/java/com/gs/dingtalk/mapper/MesQaDingtalkMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package com.gs.dingtalk.mapper;
import com.gs.dingtalk.entity.MesQaDingtalk;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author Administrator
* @description é’ˆå¯¹è¡¨ã€MES_QA_DINGTALK】的数据库操作Mapper
* @createDate 2025-12-03 18:53:07
* @Entity com.gs.dingtalk.entity.MesQaDingtalk
*/
public interface MesQaDingtalkMapper extends BaseMapper<MesQaDingtalk> {
}
src/main/java/com/gs/dingtalk/mapper/QaSjMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package com.gs.dingtalk.mapper;
import com.gs.dingtalk.entity.QaSj;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author Administrator
* @description é’ˆå¯¹è¡¨ã€QA_SJ】的数据库操作Mapper
* @createDate 2025-12-03 19:48:50
* @Entity com.gs.dingtalk.entity.QaSj
*/
public interface QaSjMapper extends BaseMapper<QaSj> {
}
src/main/java/com/gs/dingtalk/service/MesQaDingtalkService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
package com.gs.dingtalk.service;
import com.gs.dingtalk.dto.QaMsgDto;
import com.gs.dingtalk.entity.MesQaDingtalk;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* @author Administrator
* @description é’ˆå¯¹è¡¨ã€MES_QA_DINGTALK】的数据库操作Service
* @createDate 2025-12-03 18:53:07
*/
public interface MesQaDingtalkService extends IService<MesQaDingtalk> {
    void sendQaMsgSJ(QaMsgDto dto);
}
src/main/java/com/gs/dingtalk/service/QaSjService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,13 @@
package com.gs.dingtalk.service;
import com.gs.dingtalk.entity.QaSj;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* @author Administrator
* @description é’ˆå¯¹è¡¨ã€QA_SJ】的数据库操作Service
* @createDate 2025-12-03 19:48:50
*/
public interface QaSjService extends IService<QaSj> {
}
src/main/java/com/gs/dingtalk/service/SendDingtalkService.java
@@ -10,6 +10,8 @@
    void sendDingTalkFiveMinute() throws Exception;
    void sendDingTalkFifteenMinute() throws Exception;
    void sendDingTalkthirtyMinute() throws Exception;
    void chatSendMessage() throws Exception;
src/main/java/com/gs/dingtalk/service/SimpleExample.java
@@ -222,4 +222,19 @@
        }
        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/java/com/gs/dingtalk/service/impl/MesQaDingtalkServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,158 @@
package com.gs.dingtalk.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gs.dingtalk.dto.DingTalkMessage;
import com.gs.dingtalk.dto.QaMsgDto;
import com.gs.dingtalk.entity.MesQaDingtalk;
import com.gs.dingtalk.entity.QaSj;
import com.gs.dingtalk.service.MesQaDingtalkService;
import com.gs.dingtalk.mapper.MesQaDingtalkMapper;
import com.gs.dingtalk.service.QaSjService;
import com.gs.dingtalk.service.SimpleExample;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
 * @author Administrator
 * @description é’ˆå¯¹è¡¨ã€MES_QA_DINGTALK】的数据库操作Service实现
 * @createDate 2025-12-03 18:53:07
 */
@Service
@RequiredArgsConstructor
@Transactional(rollbackFor = Exception.class)
public class MesQaDingtalkServiceImpl extends ServiceImpl<MesQaDingtalkMapper, MesQaDingtalk>
        implements MesQaDingtalkService {
    private final SimpleExample simpleExample;
    private final QaSjService qaSjService;
    @Override
    public void sendQaMsgSJ(QaMsgDto dto) {
        LambdaQueryWrapper<MesQaDingtalk> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(MesQaDingtalk::getLineName, dto.getLineName())
                .eq(MesQaDingtalk::getDepartmentname, dto.getWorkshopName())
                .eq(MesQaDingtalk::getQaType, dto.getQaType());
        //获取到用户id,当用户没有id时重新获取并更新MesQaDingtalk表
        List<MesQaDingtalk> list = list(wrapper);
        if (list.isEmpty()) {
            // å¦‚果没有找到对应的用户,直接返回
            return;
        }
        LambdaQueryWrapper<QaSj> qaSjQueryWrapper = new LambdaQueryWrapper<>();
        qaSjQueryWrapper.eq(QaSj::getBillNo, dto.getBillNo());
        QaSj qaSj = qaSjService.getOne(qaSjQueryWrapper, false);
        if (qaSj == null) {
            // å¦‚果没有找到对应的检验信息,直接返回
            return;
        }
        String message = String.format("首件[检验单号:%s, ç‰©æ–™ç¼–码:%s, ç‰©æ–™åç§°:%s, è½¦é—´:%s, çº¿ä½“:%s, å·¥å•号:%s, æ£€éªŒç»“æžœ:%s],请进行后续操作",
                qaSj.getBillNo(), qaSj.getItemNo(), qaSj.getItemName(), qaSj.getWorkshopName(),
                qaSj.getLineName(), qaSj.getDaa001(), qaSj.getJyjg());
        // æŠŠList<MesQaDingtalk>中的userId使用,拼接起来变成userIdList这个变量
        List<String> sidList = list.stream()
                .map(MesQaDingtalk::getTelephone)
                .collect(Collectors.toList());
        List<String> userIdList = getDingtalkUserIdListByPhones(sidList);
        if (userIdList == null || userIdList.isEmpty()) {
            log.warn("没有有效的钉钉用户ID");
            return;
        }
        String userIdListStr = String.join(",", userIdList);
        DingTalkMessage dingTalkMessage = null;
        try {
            dingTalkMessage = simpleExample.sendMessage(userIdListStr, message);
        } catch (Exception e) {
            throw new RuntimeException("发送钉钉消息失败", e);
        }
        if (dingTalkMessage != null && dingTalkMessage.getErrcode() == 0) {
            System.out.println("发送成功");
        } else {
            System.out.println("发送失败");
        }
    }
    private List<String> getDingtalkUserIdListByPhones(List<String> phoneList) {
        try {
            if (phoneList == null || phoneList.isEmpty()) {
                return new ArrayList<>();
            }
            // åŽ»é‡
            phoneList = phoneList.stream().distinct().collect(Collectors.toList());
            // æ ¹æ®sid查询DingtalkInfo
            List<MesQaDingtalk> list = baseMapper.selectList(
                    new LambdaQueryWrapper<MesQaDingtalk>().in(MesQaDingtalk::getTelephone, phoneList)
            );
            if (list == null || list.isEmpty()) {
                return new ArrayList<>();
            }
            // ä½¿ç”¨stream流过滤出list中dingtalkId为空的数据
            List<MesQaDingtalk> emptyDingtalkIdList = list.stream()
                    .filter(info -> !StringUtils.hasText(info.getUserId()))
                    .collect(Collectors.toList());
            // å¦‚果存在为空的数据就通过钉钉的接口获取,为dingtalkId赋值,并且更新数据库
            if (!emptyDingtalkIdList.isEmpty()) {
                String accessToken = simpleExample.getAccessToken();
                for (MesQaDingtalk info : emptyDingtalkIdList) {
                    if (StringUtils.hasText(info.getTelephone())) {
                        try {
                            // é€šè¿‡æ‰‹æœºå·èŽ·å–é’‰é’‰ç”¨æˆ·ID
                            com.dingtalk.api.response.OapiV2UserGetbymobileResponse response =
                                    simpleExample.getOapiV2UserGetbymobileResponse(info.getTelephone(), accessToken);
                            if (response != null && response.getResult() != null) {
                                info.setUserId(response.getResult().getUserid());
                                // æ›´æ–°æ•°æ®åº“
                                updateById(info);
                            }
                        } catch (Exception e) {
                            log.error("获取钉钉用户ID失败,手机号:" + info.getTelephone());
                        }
                    }
                }
            }
            // ä¸å­˜åœ¨ä¸ºç©ºçš„æ•°æ®æˆ–者处理完空数据后,返回所有有效的dingtalkId列表
            return list.stream()
                    .map(MesQaDingtalk::getUserId)
                    .filter(StringUtils::hasText)
                    .distinct()
                    .collect(Collectors.toList());
        } catch (Exception e) {
            log.error("获取钉钉用户列表失败", e);
            return new ArrayList<>();
        }
    }
}
src/main/java/com/gs/dingtalk/service/impl/QaSjServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,22 @@
package com.gs.dingtalk.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gs.dingtalk.entity.QaSj;
import com.gs.dingtalk.service.QaSjService;
import com.gs.dingtalk.mapper.QaSjMapper;
import org.springframework.stereotype.Service;
/**
* @author Administrator
* @description é’ˆå¯¹è¡¨ã€QA_SJ】的数据库操作Service实现
* @createDate 2025-12-03 19:48:50
*/
@Service
public class QaSjServiceImpl extends ServiceImpl<QaSjMapper, QaSj>
    implements QaSjService{
}
src/main/java/com/gs/dingtalk/service/impl/SendDingtalkServiceImpl.java
@@ -83,6 +83,16 @@
    }
    @Override
    public void sendDingTalkFifteenMinute() throws Exception {
        LambdaQueryWrapper<SendDingtalk> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SendDingtalk::getFifteenMinuteReminder, 1);
        List<SendDingtalk> list = list(wrapper);
        getMessage(list, 15);
    }
    @Override
    public void sendDingTalkthirtyMinute() throws Exception {
        LambdaQueryWrapper<SendDingtalk> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SendDingtalk::getThirtyMinuteReminder, 1);
src/main/java/com/gs/dingtalk/task/ScheduledTasks.java
@@ -34,6 +34,16 @@
    }
    @Scheduled(cron = "0 0/3 * * * ?")
    public void sendDingTalkFifteenMinute() {
        try {
            sendDingtalkService.sendDingTalkFifteenMinute();
            log.info("定时任务 sendDingTalkFifteenMinute æ‰§è¡ŒæˆåŠŸ");
        } catch (Exception e) {
            log.error("定时任务 sendDingTalkFifteenMinute æ‰§è¡Œå¤±è´¥: ", e);
        }
    }
    @Scheduled(cron = "0 0/4 * * * ?")
    public void sendDingTalkThirtyMinute() {
        try {
            sendDingtalkService.sendDingTalkthirtyMinute();
src/main/resources/application.yml
@@ -5,7 +5,7 @@
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: oracle.jdbc.OracleDriver
    url: jdbc:oracle:thin:@192.168.0.100:1521/orcl
    url: jdbc:oracle:thin:@172.16.0.219:1521/orcl
    username: yc_dev
    password: ycdev
    druid:
src/main/resources/mapper/MesQaDingtalkMapper.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.dingtalk.mapper.MesQaDingtalkMapper">
</mapper>
src/main/resources/mapper/QaSjMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,22 @@
<?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.dingtalk.mapper.QaSjMapper">
    <resultMap id="BaseResultMap" type="com.gs.dingtalk.entity.QaSj">
            <result property="billNo" column="BILL_NO" />
            <result property="itemNo" column="ITEM_NO" />
            <result property="itemName" column="ITEM_NAME" />
            <result property="lineName" column="LINE_NAME" />
            <result property="workshopName" column="WORKSHOP_NAME" />
            <result property="fsubmit" column="FSUBMIT" />
            <result property="daa001" column="DAA001" />
            <result property="jyjg" column="JYJG" />
    </resultMap>
    <sql id="Base_Column_List">
        BILL_NO,ITEM_NO,ITEM_NAME,LINE_NAME,WORKSHOP_NAME,FSUBMIT,
        DAA001,JYJG
    </sql>
</mapper>
src/test/java/com/gs/dingtalk/MesQaDingtalkServiceTest.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,36 @@
package com.gs.dingtalk;
import com.gs.dingtalk.dto.QaMsgDto;
import com.gs.dingtalk.entity.MesQaDingtalk;
import com.gs.dingtalk.entity.QaSj;
import com.gs.dingtalk.service.MesQaDingtalkService;
import com.gs.dingtalk.service.QaSjService;
import com.gs.dingtalk.service.SimpleExample;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
@SpringBootTest
class MesQaDingtalkServiceTest {
    @Autowired
    private MesQaDingtalkService mesQaDingtalkService;
    @Test
    void testSendQaMsgSJ() throws Exception {
        // å‡†å¤‡æµ‹è¯•数据
        QaMsgDto dto = new QaMsgDto();
        dto.setLineName("注塑3号机");
        dto.setWorkshopName("注塑车间");
        dto.setQaType("首件首检完成");
        dto.setBillNo("SJ202509150022");
        mesQaDingtalkService.sendQaMsgSJ(dto);
    }
}