已添加27个文件
已修改6个文件
已删除1个文件
2292 ■■■■■ 文件已修改
pom.xml 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/dingtalk/config/DataAcquisitionConfiguration.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/dingtalk/controller/DingtalkController.java 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/dingtalk/dto/NumbericalDto.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/dingtalk/entity/DingtalkInfo.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/dingtalk/entity/DingtalkMsg.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/dingtalk/entity/MesStaff.java 125 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/dingtalk/entity/QwStaff.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/dingtalk/entity/VwCjScSjTsBb.java 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/dingtalk/entity/VwCjScSjTsBbMonth.java 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/dingtalk/mapper/DingtalkInfoMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/dingtalk/mapper/DingtalkMsgMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/dingtalk/mapper/MesStaffMapper.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/dingtalk/mapper/QwStaffMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/dingtalk/mapper/VwCjScSjTsBbMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/dingtalk/mapper/VwCjScSjTsBbMonthMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/dingtalk/service/DingtalkInfoService.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/dingtalk/service/SimpleExample.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/dingtalk/service/VwCjScSjTsBbMonthService.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/dingtalk/service/VwCjScSjTsBbService.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/dingtalk/service/WorkWXService.java 403 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/dingtalk/service/impl/DingtalkInfoServiceImpl.java 552 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/dingtalk/service/impl/VwCjScSjTsBbMonthServiceImpl.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/dingtalk/service/impl/VwCjScSjTsBbServiceImpl.java 193 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/gs/dingtalk/task/ScheduledTasks.java 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application.yml 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/DingtalkInfoMapper.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/DingtalkMsgMapper.xml 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/MesStaffMapper.xml 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/QwStaffMapper.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/VwCjScSjTsBbMapper.xml 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/VwCjScSjTsBbMonthMapper.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/test/java/com/gs/dingtalk/DeviceReceivingApplicationTests.java 219 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/test/java/com/gs/dingtalk/MesQaDingtalkServiceTest.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml
@@ -102,10 +102,11 @@
            <version>5.8.18</version>
        </dependency>
        <!-- Apache POI for Excel (required by Hutool) -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>4.1.2</version> <!-- æˆ–更高版本 -->
            <version>5.2.3</version>
        </dependency>
    </dependencies>
src/main/java/com/gs/dingtalk/config/DataAcquisitionConfiguration.java
@@ -6,21 +6,26 @@
    /**
     * åº”用的 AgentId
     */
    public static final Long AGENT_ID = 3303296035L;
    public static final Long AGENT_ID = 4104598880L;
    /**
     * åº”用的 AppKey
     */
    public static final String APP_KEY = "dingyfqkfjecy4cjfyxa";
    public static final String TALK_APP_KEY = "dinggglb3pttl1x0gn0c";
    /**
     * åº”用的 AppSecret
     */
    public static final String APP_SECRET = "nCwmyBw8K-EqAvkuhrhhqFonbLp455awtMa4D4Q-VRaY8U2EDEVsnYSYYfPvjiAX";
    public static final String CORP_ID = "ding1dd72cd1d6adf70aa1320dcb25e91351";
    public static final String TALK_APP_SECRET = "Zc7r0Mb7bPsC_xy7ryrWoEnE5OzHEXibUMPDCA2LBusJ9pYzIolRk_OdZuLvNExf";
    //群聊机器人相关
    public static final String CUSTOM_ROBOT_TOKEN = "c2849e46cb0d91b0721c377742938b8ac5ef57e3c9eeab918e2cd5dd9c3aad2a";
    public static final String SECRET = "SEC382027a5c81ea5152b71b687fb2c1ebf26acbde035355da6ab2fb37306454134";
    //企业微信
    public static final String CORPID = "wwabe21b935901a7d8";
    public static final String CORPSECRET = "Z-7fNbZjrd80ypz69U14j8FMxI_fpUxcT6PksxlvKaY";
    public static final String TXL_CORPSECRET = "T64bdcV7fo0hvW10W3NJYmGUmlBYxYMfiW6EiUJ9VPM";
}
src/main/java/com/gs/dingtalk/controller/DingtalkController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,62 @@
package com.gs.dingtalk.controller;
import com.gs.dingtalk.config.ResultMessage;
import com.gs.dingtalk.dto.NumbericalDto;
import com.gs.dingtalk.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/dingtalk/dto/NumbericalDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,14 @@
package com.gs.dingtalk.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class NumbericalDto {
    private String releaseNo;
}
src/main/java/com/gs/dingtalk/entity/DingtalkInfo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,43 @@
package com.gs.dingtalk.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;
    /**
     * å›ºå®šæŽ¨é€ï¼Œ1表示是固定推送的
     */
    private Integer isHead;
}
src/main/java/com/gs/dingtalk/entity/DingtalkMsg.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,54 @@
package com.gs.dingtalk.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;
    /**
     * é‡‡è´­äººå‘˜
     */
    private String employeeName;
    /**
     * å®¡æ ¸äºº
     */
    private String modify1By;
}
src/main/java/com/gs/dingtalk/entity/MesStaff.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,125 @@
package com.gs.dingtalk.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
 * å‘˜å·¥ä¿¡æ¯è¡¨
 *
 * @TableName MES_STAFF
 */
@TableName(value = "MES_STAFF")
@Data
@KeySequence(value = "SEQ_MES_STAFF", dbType = DbType.ORACLE)
public class MesStaff implements Serializable {
    @TableField(exist = false)
    private static final long serialVersionUID = 1L;
    /**
     * ID
     */
    @TableId
    private Long id;
    /**
     * å‘˜å·¥ç¼–码
     */
    private String staffNo;
    /**
     * å‘˜å·¥å§“名
     */
    private String staffName;
    /**
     * éƒ¨é—¨ç¼–码
     */
    private String departmentNo;
    /**
     * éƒ¨é—¨åç§°
     */
    private String departmentName;
    /**
     * å²—位编码
     */
    private String positionCode;
    /**
     * å²—位名称
     */
    private String positionName;
    /**
     * æ‰‹æœºå·
     */
    private String phoneNumber;
    /**
     * ä»»å²—开始日期
     */
    private Date startDate;
    /**
     * å¤‡æ³¨
     */
    private String remark;
    /**
     * åˆ›å»ºç»„织
     */
    private Long createOrg;
    /**
     * ä½¿ç”¨ç»„织
     */
    private String useOrg;
    /**
     * è‡ªå®šä¹‰å­—段3
     */
    private String remark3;
    /**
     * è‡ªå®šä¹‰å­—段4
     */
    private String remark4;
    /**
     * è‡ªå®šä¹‰å­—段5
     */
    private String remark5;
    /**
     * åˆ›å»ºäºº
     */
    private String createBy;
    /**
     * åˆ›å»ºæ—¶é—´
     */
    private Date createDate;
    /**
     * æ›´æ–°äºº
     */
    private String lastupdateBy;
    /**
     * æ›´æ–°æ—¶é—´
     */
    private Date lastupdateDate;
    /**
     * å¤‡æ³¨
     */
    private String memo;
    /**
     * ç”Ÿäº§çº¿
     */
    private String lineNo;
    /**
     * ç”Ÿäº§å‘˜å·¥æ ‡è¯†
     */
    private Long workMk;
    /**
     * æ˜¯å¦ç¦ç”¨
     */
    private String fforbidStatus;
    /**
     * ä¸šåŠ¡å‘˜ç±»åž‹
     */
    private String operatorType;
    /**
     * ç¦»èŒæ—¶é—´
     */
    private Date separationTime;
    /**
     * æ˜¯å¦æ˜¯å…³é”®å²—位 0:否,1:是
     */
    private String keyPosts;
}
src/main/java/com/gs/dingtalk/entity/QwStaff.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,47 @@
package com.gs.dingtalk.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serializable;
/**
 *
 * @TableName QW_STAFF
 */
@TableName(value = "QW_STAFF")
@Data
@KeySequence(value = "SEQ_QW_STAFF", dbType = DbType.ORACLE)
public class QwStaff implements Serializable {
    @TableField(exist = false)
    private static final long serialVersionUID = 1L;
    /**
     *
     */
    @TableId
    private Long id;
    /**
     *
     */
    private String name;
    /**
     * ä¼å¾®çš„userid
     */
    private String account;
    /**
     *
     */
    private String position;
    /**
     *
     */
    private String dept;
    /**
     *
     */
    private String sex;
    /**
     *
     */
    private String phone;
}
src/main/java/com/gs/dingtalk/entity/VwCjScSjTsBb.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,83 @@
package com.gs.dingtalk.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
/**
 *
 * @TableName VW_CJ_SC_SJ_TS_BB
 */
@TableName(value ="VW_CJ_SC_SJ_TS_BB")
@Data
public class VwCjScSjTsBb implements Serializable {
    /**
     * ç‰©æ–™åç§°
     */
    private String itemName;
    /**
     * ç‰©æ–™ç¼–码
     */
    private String itemNo;
    /**
     * è½¦é—´åç§°
     */
    private String departmentname;
    /**
     * è½¦é—´ç¼–码
     */
    private String daa001;
    /**
     * å·¥å•号
     */
    private String lineName;
    /**
     * çº¿ä½“名称
     */
    private String daa008;
    /**
     * é¢„计开工
     */
    private String yjkg;
    /**
     * å®žé™…开工
     */
    private String sjkg;
    /**
     *
     */
    private String sq;
    /**
     * å…¥åº“
     */
    private String rk;
    /**
     * ç”³è¯·æœªå®Œå·¥æ•°
     */
    private String sqwwg;
    /**
     * å…¥åº“未完工
     */
    private String rkwwg;
    /**
     * ç”³è¯·æœªå…¥åº“
     */
    private String sqwrk;
    @TableField(exist = false)
    private static final long serialVersionUID = 1L;
}
src/main/java/com/gs/dingtalk/entity/VwCjScSjTsBbMonth.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,83 @@
package com.gs.dingtalk.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
/**
 *
 * @TableName VW_CJ_SC_SJ_TS_BB_MONTH
 */
@TableName(value ="VW_CJ_SC_SJ_TS_BB_MONTH")
@Data
public class VwCjScSjTsBbMonth implements Serializable {
    /**
     *
     */
    private String itemName;
    /**
     *
     */
    private String itemNo;
    /**
     *
     */
    private String departmentname;
    /**
     *
     */
    private String daa001;
    /**
     *
     */
    private String lineName;
    /**
     *
     */
    private String daa008;
    /**
     *
     */
    private String yjkg;
    /**
     *
     */
    private String sjkg;
    /**
     *
     */
    private String sq;
    /**
     *
     */
    private String rk;
    /**
     *
     */
    private String sqwwg;
    /**
     *
     */
    private String rkwwg;
    /**
     *
     */
    private String sqwrk;
    @TableField(exist = false)
    private static final long serialVersionUID = 1L;
}
src/main/java/com/gs/dingtalk/mapper/DingtalkInfoMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package com.gs.dingtalk.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.gs.dingtalk.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/dingtalk/mapper/DingtalkMsgMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package com.gs.dingtalk.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.gs.dingtalk.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/dingtalk/mapper/MesStaffMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,23 @@
package com.gs.dingtalk.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.gs.dingtalk.entity.MesStaff;
/**
 * @author 28567
 * @description é’ˆå¯¹è¡¨ã€MES_STAFF(员工信息表)】的数据库操作Mapper
 * @createDate 2025-02-17 20:59:36
 * @Entity com.gs.xky.entity.MesStaff
 */
public interface MesStaffMapper extends BaseMapper<MesStaff> {
    long getNextVal();
    int updateStaff();
    int deleteStaff();
}
src/main/java/com/gs/dingtalk/mapper/QwStaffMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package com.gs.dingtalk.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.gs.dingtalk.entity.QwStaff;
/**
* @author Administrator
* @description é’ˆå¯¹è¡¨ã€QW_STAFF】的数据库操作Mapper
* @createDate 2025-11-26 10:51:47
* @Entity com.gs.xky.entity.QwStaff
*/
public interface QwStaffMapper extends BaseMapper<QwStaff> {
}
src/main/java/com/gs/dingtalk/mapper/VwCjScSjTsBbMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package com.gs.dingtalk.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.gs.dingtalk.entity.VwCjScSjTsBb;
/**
* @author Administrator
* @description é’ˆå¯¹è¡¨ã€VW_CJ_SC_SJ_TS_BB】的数据库操作Mapper
* @createDate 2025-11-12 19:44:46
* @Entity generator.domain.VwCjScSjTsBb
*/
public interface VwCjScSjTsBbMapper extends BaseMapper<VwCjScSjTsBb> {
}
src/main/java/com/gs/dingtalk/mapper/VwCjScSjTsBbMonthMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package com.gs.dingtalk.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.gs.dingtalk.entity.VwCjScSjTsBbMonth;
/**
* @author Administrator
* @description é’ˆå¯¹è¡¨ã€VW_CJ_SC_SJ_TS_BB_MONTH】的数据库操作Mapper
* @createDate 2025-11-12 19:45:00
* @Entity com.gs.xky.entity.VwCjScSjTsBbMonth
*/
public interface VwCjScSjTsBbMonthMapper extends BaseMapper<VwCjScSjTsBbMonth> {
}
src/main/java/com/gs/dingtalk/service/DingtalkInfoService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
package com.gs.dingtalk.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.gs.dingtalk.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);
    boolean sendActionCardMessage() throws Exception;
    /**
     * å‘送文件消息
     *
     * @param filePath æœ¬åœ°æ–‡ä»¶è·¯å¾„
     * @return æ˜¯å¦å‘送成功
     * @throws Exception å¼‚常
     */
    boolean sendFileMessage(String filePath) throws Exception;
}
src/main/java/com/gs/dingtalk/service/SimpleExample.java
@@ -61,7 +61,7 @@
        try {
            Long timestamp = System.currentTimeMillis();
            System.out.println(timestamp);
            String secret = DataAcquisitionConfiguration.SECRET;
            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"));
@@ -164,8 +164,8 @@
    public String getAccessToken() throws Exception {
        Client client = createClient();
        GetAccessTokenRequest getAccessTokenRequest = new GetAccessTokenRequest()
                .setAppKey(DataAcquisitionConfiguration.APP_KEY)
                .setAppSecret(DataAcquisitionConfiguration.APP_SECRET);
                .setAppKey(DataAcquisitionConfiguration.TALK_APP_KEY)
                .setAppSecret(DataAcquisitionConfiguration.TALK_APP_SECRET);
        try {
            GetAccessTokenResponse accessToken = client.getAccessToken(getAccessTokenRequest);
            return accessToken.body.getAccessToken();
@@ -208,7 +208,7 @@
    }
    public OapiV2UserGetbymobileResponse getOapiV2UserGetbymobileResponse(SendDingtalk s, String accessToken) {
        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());
src/main/java/com/gs/dingtalk/service/VwCjScSjTsBbMonthService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,13 @@
package com.gs.dingtalk.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.gs.dingtalk.entity.VwCjScSjTsBbMonth;
/**
 * @author Administrator
 * @description é’ˆå¯¹è¡¨ã€VW_CJ_SC_SJ_TS_BB_MONTH】的数据库操作Service
 * @createDate 2025-11-12 19:45:00
 */
public interface VwCjScSjTsBbMonthService extends IService<VwCjScSjTsBbMonth> {
}
src/main/java/com/gs/dingtalk/service/VwCjScSjTsBbService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,19 @@
package com.gs.dingtalk.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.gs.dingtalk.entity.VwCjScSjTsBb;
/**
 * @author Administrator
 * @description é’ˆå¯¹è¡¨ã€VW_CJ_SC_SJ_TS_BB】的数据库操作Service
 * @createDate 2025-11-12 19:44:46
 */
public interface VwCjScSjTsBbService extends IService<VwCjScSjTsBb> {
    /**
     * å¯¼å‡ºæ•°æ®åˆ°Excel并发送钉钉消息
     *
     * @return æ˜¯å¦æˆåŠŸ
     */
    boolean exportAndSendToDingtalk() throws Exception;
}
src/main/java/com/gs/dingtalk/service/WorkWXService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,403 @@
package com.gs.dingtalk.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.gs.dingtalk.config.DataAcquisitionConfiguration;
import com.gs.dingtalk.entity.QwStaff;
import com.gs.dingtalk.mapper.QwStaffMapper;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import okhttp3.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class WorkWXService {
    private static final Logger log = LoggerFactory.getLogger(WorkWXService.class);
    private final OkHttpClient client = new OkHttpClient.Builder()
            .connectTimeout(90, TimeUnit.SECONDS)
            .readTimeout(90, TimeUnit.SECONDS)
            .build();
    private final ObjectMapper objectMapper = new ObjectMapper();
    private final QwStaffMapper qwStaffMapper;
    public String getAccessToken() throws IOException {
        String url = String.format("https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s",
                DataAcquisitionConfiguration.CORPID,
                DataAcquisitionConfiguration.CORPSECRET);
        Request request = new Request.Builder()
                .url(url)
                .get()
                .build();
        try (Response response = client.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                log.error("获取企业微信access_token失败,HTTP状态码: {}", response.code());
                throw new IOException("获取access_token失败: " + response.message());
            }
            String responseBody = response.body().string();
            WorkWXTokenResponse tokenResponse = objectMapper.readValue(responseBody, WorkWXTokenResponse.class);
            if (tokenResponse.getErrcode() != 0) {
                log.error("获取企业微信access_token失败,错误码: {}, é”™è¯¯ä¿¡æ¯: {}",
                        tokenResponse.getErrcode(), tokenResponse.getErrmsg());
                throw new IOException("获取access_token失败: " + tokenResponse.getErrmsg());
            }
            log.info("成功获取企业微信access_token,有效期: {}秒", tokenResponse.getExpiresIn());
            log.info("access_token : {}", tokenResponse.getAccessToken());
            return tokenResponse.getAccessToken();
        }
    }
    public String getContactAccessToken() throws IOException {
        String url = String.format("https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s",
                DataAcquisitionConfiguration.CORPID,
                DataAcquisitionConfiguration.TXL_CORPSECRET);
        Request request = new Request.Builder()
                .url(url)
                .get()
                .build();
        try (Response response = client.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                log.error("获取企业微信通讯录access_token失败,HTTP状态码: {}", response.code());
                throw new IOException("获取通讯录access_token失败: " + response.message());
            }
            String responseBody = response.body().string();
            WorkWXTokenResponse tokenResponse = objectMapper.readValue(responseBody, WorkWXTokenResponse.class);
            if (tokenResponse.getErrcode() != 0) {
                log.error("获取企业微信通讯录access_token失败,错误码: {}, é”™è¯¯ä¿¡æ¯: {}",
                        tokenResponse.getErrcode(), tokenResponse.getErrmsg());
                throw new IOException("获取通讯录access_token失败: " + tokenResponse.getErrmsg());
            }
            log.info("成功获取企业微信通讯录access_token,有效期: {}秒", tokenResponse.getExpiresIn());
            return tokenResponse.getAccessToken();
        }
    }
    public List<WorkWXUser> getUserList() throws IOException {
        String accessToken = getContactAccessToken();
        String url = String.format(
                "https://qyapi.weixin.qq.com/cgi-bin/user/list_id?access_token=%s",
                accessToken);
        List<WorkWXUser> allUsers = new ArrayList<>();
        String cursor = null;
        do {
            Map<String, Object> requestBody = new HashMap<>();
            requestBody.put("limit", 10000);
            if (cursor != null) {
                requestBody.put("cursor", cursor);
            }
            MediaType mediaType = MediaType.parse("application/json; charset=UTF-8");
            String jsonBody = objectMapper.writeValueAsString(requestBody);
            RequestBody body = RequestBody.create(mediaType, jsonBody);
            Request request = new Request.Builder()
                    .url(url)
                    .post(body)
                    .addHeader("Content-Type", "application/json; charset=UTF-8")
                    .build();
            try (Response response = client.newCall(request).execute()) {
                if (!response.isSuccessful()) {
                    log.error("获取企业微信用户列表失败,HTTP状态码: {}", response.code());
                    throw new IOException("获取用户列表失败: " + response.message());
                }
                String responseBody = response.body().string();
                log.info("获取用户列表响应: {}", responseBody);
                WorkWXUserListIdResponse userListResponse = objectMapper.readValue(responseBody, WorkWXUserListIdResponse.class);
                if (userListResponse.getErrcode() != 0) {
                    log.error("获取企业微信用户列表失败,错误码: {}, é”™è¯¯ä¿¡æ¯: {}",
                            userListResponse.getErrcode(), userListResponse.getErrmsg());
                    throw new IOException("获取用户列表失败: " + userListResponse.getErrmsg());
                }
                if (userListResponse.getDeptUser() != null && !userListResponse.getDeptUser().isEmpty()) {
                    allUsers.addAll(userListResponse.getDeptUser());
                }
                cursor = userListResponse.getNextCursor();
            } catch (IOException e) {
                log.error("解析用户列表响应失败", e);
                throw e;
            }
        } while (cursor != null && !cursor.isEmpty());
        log.info("成功获取企业微信用户列表,用户数量: {}", allUsers.size());
        return allUsers;
    }
    public List<CheckinData> getCheckinDataByQwStaff(long startTime, long endTime) throws IOException {
        List<QwStaff> qwStaffList = qwStaffMapper.selectList(new LambdaQueryWrapper<QwStaff>());
        if (qwStaffList == null || qwStaffList.isEmpty()) {
            log.warn("QW_STAFF表中没有数据");
            return new ArrayList<>();
        }
        List<String> useridList = qwStaffList.stream()
                .map(QwStaff::getAccount)
                .filter(account -> account != null && !account.isEmpty())
                .collect(Collectors.toList());
        if (useridList.isEmpty()) {
            log.warn("QW_STAFF表中没有有效的account数据");
            return new ArrayList<>();
        }
        log.info("从QW_STAFF表获取到 {} ä¸ªç”¨æˆ·account", useridList.size());
        return getCheckinData(startTime, endTime, useridList);
    }
    public WorkWXUserDetail getUserDetail(String userid) throws IOException {
        String accessToken = getContactAccessToken();
        String url = String.format("https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token=%s&userid=%s",
                accessToken, userid);
        Request request = new Request.Builder()
                .url(url)
                .get()
                .build();
        try (Response response = client.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                log.error("获取用户详情失败,HTTP状态码: {}", response.code());
                throw new IOException("获取用户详情失败: " + response.message());
            }
            String responseBody = response.body().string();
            log.info("获取用户详情响应: {}", responseBody);
            WorkWXUserDetailResponse userDetailResponse = objectMapper.readValue(responseBody, WorkWXUserDetailResponse.class);
            if (userDetailResponse.getErrcode() != 0) {
                log.error("获取用户详情失败,错误码: {}, é”™è¯¯ä¿¡æ¯: {}",
                        userDetailResponse.getErrcode(), userDetailResponse.getErrmsg());
                throw new IOException("获取用户详情失败: " + userDetailResponse.getErrmsg());
            }
            log.info("成功获取用户 {} çš„详情", userid);
            return userDetailResponse;
        }
    }
    public int syncUsersToQwStaff() throws IOException {
        List<WorkWXUser> userList = getUserList();
        if (userList == null || userList.isEmpty()) {
            log.warn("获取到的用户列表为空");
            return 0;
        }
        int insertCount = 0;
        for (WorkWXUser user : userList) {
            if (user.getUserid() == null || user.getUserid().isEmpty()) {
                continue;
            }
            qwStaffMapper.delete(
                new LambdaQueryWrapper<QwStaff>().eq(QwStaff::getAccount, user.getUserid())
            );
            QwStaff qwStaff = new QwStaff();
            qwStaff.setName(user.getName());
            qwStaff.setAccount(user.getUserid());
            qwStaff.setDept(user.getDepartment() != null ? user.getDepartment().toString() : null);
            qwStaffMapper.insert(qwStaff);
            insertCount++;
        }
        log.info("同步用户到QW_STAFF表完成,同步用户数: {}", insertCount);
        return insertCount;
    }
    public List<CheckinData> getCheckinData(long startTime, long endTime, List<String> useridList) throws IOException {
        String accessToken = getAccessToken();
        String url = String.format("https://qyapi.weixin.qq.com/cgi-bin/checkin/getcheckindata?access_token=%s", accessToken);
        List<CheckinData> allCheckinData = new ArrayList<>();
        int batchSize = 100;
        int totalUsers = useridList.size();
        int batchCount = (totalUsers + batchSize - 1) / batchSize;
        log.info("开始获取打卡数据,总用户数: {}, åˆ†æ‰¹æ•°: {}, æ—¶é—´èŒƒå›´: {} - {}", totalUsers, batchCount, startTime, endTime);
        for (int i = 0; i < batchCount; i++) {
            int fromIndex = i * batchSize;
            int toIndex = Math.min((i + 1) * batchSize, totalUsers);
            List<String> batchUserList = useridList.subList(fromIndex, toIndex);
            log.info("正在获取第 {}/{} æ‰¹æ‰“卡数据,用户数: {}", i + 1, batchCount, batchUserList.size());
            Map<String, Object> requestBody = new HashMap<>();
            requestBody.put("opencheckindatatype", 3);
            requestBody.put("starttime", startTime);
            requestBody.put("endtime", endTime);
            requestBody.put("useridlist", batchUserList);
            MediaType mediaType = MediaType.parse("application/json; charset=UTF-8");
            String jsonBody = objectMapper.writeValueAsString(requestBody);
            RequestBody body = RequestBody.create(mediaType, jsonBody);
            Request request = new Request.Builder()
                    .url(url)
                    .post(body)
                    .addHeader("Content-Type", "application/json; charset=UTF-8")
                    .build();
            try (Response response = client.newCall(request).execute()) {
                if (!response.isSuccessful()) {
                    log.error("获取打卡数据失败,HTTP状态码: {}", response.code());
                    throw new IOException("获取打卡数据失败: " + response.message());
                }
                String responseBody = response.body().string();
                WorkWXCheckinResponse checkinResponse = objectMapper.readValue(responseBody, WorkWXCheckinResponse.class);
                if (checkinResponse.getErrcode() != 0) {
                    log.error("获取打卡数据失败,错误码: {}, é”™è¯¯ä¿¡æ¯: {}",
                            checkinResponse.getErrcode(), checkinResponse.getErrmsg());
                    throw new IOException("获取打卡数据失败: " + checkinResponse.getErrmsg());
                }
                if (checkinResponse.getCheckindata() != null) {
                    allCheckinData.addAll(checkinResponse.getCheckindata());
                    log.info("第 {}/{} æ‰¹èŽ·å–åˆ°æ‰“å¡è®°å½•æ•°: {}", i + 1, batchCount, checkinResponse.getCheckindata().size());
                }
            }
            if (i < batchCount - 1) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    log.warn("批次间等待被中断");
                }
            }
        }
        log.info("打卡数据获取完成,总记录数: {}", allCheckinData.size());
        return allCheckinData;
    }
    @Data
    private static class WorkWXTokenResponse {
        private Integer errcode;
        private String errmsg;
        @JsonProperty("access_token")
        private String accessToken;
        @JsonProperty("expires_in")
        private Integer expiresIn;
    }
    @Data
    private static class WorkWXUserIdResponse {
        private Integer errcode;
        private String errmsg;
        private String userid;
    }
    @Data
    private static class WorkWXUserListIdResponse {
        private Integer errcode;
        private String errmsg;
        @JsonProperty("next_cursor")
        private String nextCursor;
        @JsonProperty("dept_user")
        private List<WorkWXUser> deptUser;
    }
    @Data
    public static class WorkWXUser {
        private String userid;
        private Integer department;
        private String name;
        @JsonProperty("open_userid")
        private String openUserid;
    }
    @Data
    public static class WorkWXUserDetail {
        private Integer errcode;
        private String errmsg;
        private String userid;
        private String name;
        private String mobile;
        private String position;
    }
    @Data
    private static class WorkWXUserDetailResponse extends WorkWXUserDetail {
    }
    @Data
    private static class WorkWXCheckinResponse {
        private Integer errcode;
        private String errmsg;
        private List<CheckinData> checkindata;
    }
    @Data
    public static class CheckinData {
        private String userid;
        private String groupname;
        @JsonProperty("checkin_type")
        private String checkinType;
        @JsonProperty("exception_type")
        private String exceptionType;
        @JsonProperty("checkin_time")
        private Long checkinTime;
        @JsonProperty("location_title")
        private String locationTitle;
        @JsonProperty("location_detail")
        private String locationDetail;
        private String wifiname;
        private String notes;
        private String wifimac;
        private List<String> mediaids;
        private Double lat;
        private Double lng;
        @JsonProperty("sch_checkin_time")
        private Long schCheckinTime;
        private Integer groupid;
        @JsonProperty("schedule_id")
        private Integer scheduleId;
        @JsonProperty("timeline_id")
        private Integer timelineId;
        private String deviceid;
    }
}
src/main/java/com/gs/dingtalk/service/impl/DingtalkInfoServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,552 @@
package com.gs.dingtalk.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.dingtalk.entity.DingtalkInfo;
import com.gs.dingtalk.entity.DingtalkMsg;
import com.gs.dingtalk.entity.MesStaff;
import com.gs.dingtalk.mapper.DingtalkInfoMapper;
import com.gs.dingtalk.mapper.DingtalkMsgMapper;
import com.gs.dingtalk.mapper.MesStaffMapper;
import com.gs.dingtalk.service.DingtalkInfoService;
import com.gs.dingtalk.service.SimpleExample;
import com.taobao.api.FileItem;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
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;
    private final MesStaffMapper mesStaffMapper;
    @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());
            String message = String.format(
                    "供应商[%s] æ¥æ–™æ—¥æœŸ[%s] é¡¹ç›®[%s] æ–™å·[%s]的不合格检验单被[%s]审批为[%s],请查收!",
                    Optional.ofNullable(dingtalkMsg.getSuppName()).orElse(" æœªçŸ¥ä¾›åº”商"),
                    createDateStr,
                    Optional.ofNullable(dingtalkMsg.getProjectCodes()).orElse(" æœªçŸ¥é¡¹ç›®"),
                    Optional.ofNullable(dingtalkMsg.getItemNo()).orElse(" æœªçŸ¥æ–™å·"),
                    Optional.ofNullable(dingtalkMsg.getFname()).orElse(" æœªçŸ¥å®¡æ‰¹äºº"),
                    Optional.ofNullable(dingtalkMsg.getFngHandle()).orElse(" æœªçŸ¥å®¡æ‰¹ç»“æžœ"));
            // æ”¶é›†éœ€è¦æŽ¨é€çš„人员sid
            List<Long> sidList = new ArrayList<>();
            // 1. å›ºå®šæŽ¨é€äººå‘˜ï¼ˆisSendDingtalk=1)
            List<DingtalkInfo> fixedList = baseMapper.selectList(
                    new LambdaQueryWrapper<DingtalkInfo>().eq(DingtalkInfo::getIsSendDingtalk, 1)
            );
            for (DingtalkInfo info : fixedList) {
                sidList.add(info.getSid());
            }
            // 2. é‡‡è´­äººå‘˜ï¼ˆemployeeName)
            if (StringUtils.hasText(dingtalkMsg.getEmployeeName())) {
                MesStaff buyer = mesStaffMapper.selectOne(
                        new LambdaQueryWrapper<MesStaff>().eq(MesStaff::getStaffName, dingtalkMsg.getEmployeeName()), false
                );
                if (buyer != null) {
                    DingtalkInfo buyerInfo = baseMapper.selectOne(
                            new LambdaQueryWrapper<DingtalkInfo>().eq(DingtalkInfo::getSid, buyer.getId()), false
                    );
                    if (buyerInfo != null) sidList.add(buyerInfo.getSid());
                }
            }
            // 3. å®¡æ ¸äººï¼ˆmodify1By)
            if (StringUtils.hasText(dingtalkMsg.getModify1By())) {
                MesStaff auditor = mesStaffMapper.selectOne(
                        new LambdaQueryWrapper<MesStaff>().eq(MesStaff::getStaffNo, dingtalkMsg.getModify1By())
                );
                if (auditor != null) {
                    DingtalkInfo auditorInfo = baseMapper.selectOne(
                            new LambdaQueryWrapper<DingtalkInfo>().eq(DingtalkInfo::getSid, auditor.getId())
                    );
                    if (auditorInfo != null) sidList.add(auditorInfo.getSid());
                }
            }
            // 4. æ ¹æ®æ”¶é›†çš„sid获取dingtalkId (复用getDingtalkUserIdList的关键逻辑)
            List<String> userIdList = getDingtalkUserIdListBySids(sidList);
            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;
        }
    }
    @Override
    public boolean sendActionCardMessage() {
        try {
            // 1. èŽ·å–éœ€è¦æŽ¨é€çš„ç”¨æˆ·åˆ—è¡¨ï¼ˆå¯ä»¥æ˜¯å›ºå®šæŽ¨é€ç”¨æˆ·ï¼‰
            List<DingtalkInfo> fixedList = baseMapper.selectList(
                    new LambdaQueryWrapper<DingtalkInfo>().eq(DingtalkInfo::getIsHead, 1)
            );
            List<Long> sidList = fixedList.stream()
                    .map(DingtalkInfo::getSid)
                    .collect(Collectors.toList());
            List<String> userIdList = getDingtalkUserIdListBySids(sidList);
            if (userIdList == null || userIdList.isEmpty()) {
                log.warn("没有需要发送钉钉消息的用户");
                return false;
            }
            String userIdListStr = String.join(",", userIdList);
            // 2. æž„建消息内容
            String title = "生产数据看板";
            String markdown = "请点击下方按钮查看详细BI报表";
            String singleTitle = "查看报表";
            String singleUrl = "http://192.168.1.22:8081/design?fid=rpte6045ab079b211f0824bd3cfd50c6b93&fserid=4b198960bedd11f09f6f792bfe147b64&fsharetype=3";
            // 3. å‘送消息
            OapiMessageCorpconversationAsyncsendV2Response rsp =
                    sendActionCardMessage(userIdListStr, title, markdown, singleTitle, singleUrl);
            log.info("成功发送ActionCard消息: {}", rsp.getBody());
            return true;
        } catch (Exception e) {
            log.error("发送ActionCard消息失败", e);
            return false;
        }
    }
    @Override
    public boolean sendFileMessage(String filePath) throws Exception {
        try {
            // 1. æ£€æŸ¥æ–‡ä»¶æ˜¯å¦å­˜åœ¨
            File file = new File(filePath);
            if (!file.exists()) {
                log.error("文件不存在: {}", filePath);
                return false;
            }
            // 2. èŽ·å–éœ€è¦æŽ¨é€çš„ç”¨æˆ·åˆ—è¡¨ï¼ˆå¯æ ¹æ®å®žé™…éœ€æ±‚è°ƒæ•´ï¼Œè¿™é‡Œä½¿ç”¨isHead=1的用户)
            List<DingtalkInfo> fixedList = baseMapper.selectList(
                    new LambdaQueryWrapper<DingtalkInfo>().eq(DingtalkInfo::getIsHead, 1)
            );
            if (fixedList == null || fixedList.isEmpty()) {
                log.warn("没有需要发送文件的用户(isHead=1)");
                return false;
            }
            List<String> sidList = fixedList.stream()
                    .map(DingtalkInfo::getPhone)
                    .collect(Collectors.toList());
            List<String> userIdList = getDingtalkUserIdListByPhones(sidList);
            if (userIdList == null || userIdList.isEmpty()) {
                log.warn("没有有效的钉钉用户ID");
                return false;
            }
            String userIdListStr = String.join(",", userIdList);
            // 3. ä¸Šä¼ æ–‡ä»¶åˆ°é’‰é’‰æœåС噍
            log.info("开始上传文件: {}", filePath);
            String mediaId = uploadMedia(filePath, "file");
            // 4. å‘送文件消息
            log.info("开始发送文件消息,mediaId: {}", mediaId);
            OapiMessageCorpconversationAsyncsendV2Response response = sendFileMessageByMediaId(userIdListStr, mediaId);
            log.info("文件消息发送响应: {}", response.getBody());
            return response.getErrcode() == 0;
        } catch (Exception e) {
            log.error("发送文件消息失败", e);
            throw e;
        }
    }
    /**
     * æ ¹æ®æŒ‡å®šçš„sid列表获取钉钉用户ID列表
     *
     * @param sidList sid列表
     * @return é’‰é’‰ç”¨æˆ·ID列表
     */
    private List<String> getDingtalkUserIdListBySids(List<Long> sidList) {
        try {
            if (sidList == null || sidList.isEmpty()) {
                return new ArrayList<>();
            }
            // åŽ»é‡
            sidList = sidList.stream().distinct().collect(Collectors.toList());
            // æ ¹æ®sid查询DingtalkInfo
            List<DingtalkInfo> list = baseMapper.selectList(
                    new LambdaQueryWrapper<DingtalkInfo>().in(DingtalkInfo::getSid, sidList)
            );
            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)
                    .distinct()
                    .collect(Collectors.toList());
        } catch (Exception e) {
            log.error("获取钉钉用户列表失败", e);
            return new ArrayList<>();
        }
    }
    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<DingtalkInfo> list = baseMapper.selectList(
                    new LambdaQueryWrapper<DingtalkInfo>().in(DingtalkInfo::getPhone, phoneList)
            );
            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)
                    .distinct()
                    .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(4104598880L);
        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);
    }
    /**
     * å‘送link消息(在钉钉内置浏览器中打开)
     *
     * @param userIdListStr ç”¨æˆ·ID列表,逗号分隔
     * @param title         æ¶ˆæ¯æ ‡é¢˜
     * @param text          æ¶ˆæ¯å†…容
     * @param messageUrl    ç‚¹å‡»æ¶ˆæ¯åŽè·³è½¬çš„URL
     * @param picUrl        å›¾ç‰‡URL(可选)
     * @return å“åº”结果
     * @throws Exception å¼‚常
     */
    private OapiMessageCorpconversationAsyncsendV2Response sendLinkMessage(String userIdListStr, String title, String text, String messageUrl, String picUrl) 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(4104598880L);
        request.setUseridList(userIdListStr);
        request.setToAllUser(false);
        OapiMessageCorpconversationAsyncsendV2Request.Msg msg = new OapiMessageCorpconversationAsyncsendV2Request.Msg();
        msg.setMsgtype("link");
        msg.setLink(new OapiMessageCorpconversationAsyncsendV2Request.Link());
        msg.getLink().setTitle(title);
        msg.getLink().setText(text);
        msg.getLink().setMessageUrl(messageUrl);
        if (StringUtils.hasText(picUrl)) {
            msg.getLink().setPicUrl(picUrl);
        }
        request.setMsg(msg);
        return client.execute(request, accessToken);
    }
    /**
     * å‘送ActionCard消息(在外部浏览器中打开链接,适合BI等外部系统)
     *
     * @param userIdListStr ç”¨æˆ·ID列表,逗号分隔
     * @param title         æ¶ˆæ¯æ ‡é¢˜
     * @param markdown      æ¶ˆæ¯å†…容(支持Markdown格式)
     * @param singleTitle   æŒ‰é’®æ–‡å­—,例如:"查看详情"
     * @param singleUrl     ç‚¹å‡»æŒ‰é’®åŽè·³è½¬çš„URL(外部链接)
     * @return å“åº”结果
     * @throws Exception å¼‚常
     */
    private OapiMessageCorpconversationAsyncsendV2Response sendActionCardMessage(String userIdListStr, String title, String markdown, String singleTitle, String singleUrl) 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(4104598880L);
        request.setUseridList(userIdListStr);
        request.setToAllUser(false);
        OapiMessageCorpconversationAsyncsendV2Request.Msg msg = new OapiMessageCorpconversationAsyncsendV2Request.Msg();
        msg.setMsgtype("action_card");
        msg.setActionCard(new OapiMessageCorpconversationAsyncsendV2Request.ActionCard());
        msg.getActionCard().setTitle(title);
        msg.getActionCard().setMarkdown(markdown);
        msg.getActionCard().setSingleTitle(singleTitle);
        msg.getActionCard().setSingleUrl(singleUrl);
        request.setMsg(msg);
        return client.execute(request, accessToken);
    }
    /**
     * ä¸Šä¼ æ–‡ä»¶åˆ°é’‰é’‰æœåŠ¡å™¨ï¼ŒèŽ·å–media_id
     *
     * @param filePath æœ¬åœ°æ–‡ä»¶è·¯å¾„
     * @param fileType æ–‡ä»¶ç±»åž‹ï¼šfile(普通文件), voice(语音文件), video(视频文件), image(图片文件)
     * @return media_id
     * @throws Exception å¼‚常
     */
    private String uploadMedia(String filePath, String fileType) throws Exception {
        String accessToken = simpleExample.getAccessToken();
        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/media/upload");
        com.dingtalk.api.request.OapiMediaUploadRequest request = new com.dingtalk.api.request.OapiMediaUploadRequest();
        request.setType(fileType);
        // ä½¿ç”¨ FileItem åŒ…装文件
        File file = new File(filePath);
        FileItem fileItem = new FileItem(file);
        request.setMedia(fileItem);
        com.dingtalk.api.response.OapiMediaUploadResponse response = client.execute(request, accessToken);
        if (response.getErrcode() == 0) {
            log.info("文件上传成功,media_id: {}", response.getMediaId());
            return response.getMediaId();
        } else {
            log.error("文件上传失败,错误码: {}, é”™è¯¯ä¿¡æ¯: {}", response.getErrcode(), response.getErrmsg());
            throw new Exception("文件上传失败: " + response.getErrmsg());
        }
    }
    /**
     * é€šè¿‡media_id发送文件消息
     *
     * @param userIdListStr ç”¨æˆ·ID列表,逗号分隔
     * @param mediaId       æ–‡ä»¶çš„media_id(通过uploadMedia方法获取)
     * @return å“åº”结果
     * @throws Exception å¼‚常
     */
    private OapiMessageCorpconversationAsyncsendV2Response sendFileMessageByMediaId(String userIdListStr, String mediaId) 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(4104598880L);
        request.setUseridList(userIdListStr);
        request.setToAllUser(false);
        OapiMessageCorpconversationAsyncsendV2Request.Msg msg = new OapiMessageCorpconversationAsyncsendV2Request.Msg();
        msg.setMsgtype("file");
        msg.setFile(new OapiMessageCorpconversationAsyncsendV2Request.File());
        msg.getFile().setMediaId(mediaId);
        request.setMsg(msg);
        return client.execute(request, accessToken);
    }
    /**
     * èŽ·å–isSendDingtalk=1的用户的钉钉用户ID列表(保留原有方法,向后兼容)
     */
    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)
                    .distinct()
                    .collect(Collectors.toList());
        } catch (Exception e) {
            log.error("获取钉钉用户列表失败", e);
            return new ArrayList<>();
        }
    }
}
src/main/java/com/gs/dingtalk/service/impl/VwCjScSjTsBbMonthServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,22 @@
package com.gs.dingtalk.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gs.dingtalk.entity.VwCjScSjTsBbMonth;
import com.gs.dingtalk.mapper.VwCjScSjTsBbMonthMapper;
import com.gs.dingtalk.service.VwCjScSjTsBbMonthService;
import org.springframework.stereotype.Service;
/**
 * @author Administrator
 * @description é’ˆå¯¹è¡¨ã€VW_CJ_SC_SJ_TS_BB_MONTH】的数据库操作Service实现
 * @createDate 2025-11-12 19:45:00
 */
@Service
public class VwCjScSjTsBbMonthServiceImpl extends ServiceImpl<VwCjScSjTsBbMonthMapper, VwCjScSjTsBbMonth>
        implements VwCjScSjTsBbMonthService {
}
src/main/java/com/gs/dingtalk/service/impl/VwCjScSjTsBbServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,193 @@
package com.gs.dingtalk.service.impl;
import cn.hutool.core.io.FileUtil;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gs.dingtalk.entity.VwCjScSjTsBb;
import com.gs.dingtalk.entity.VwCjScSjTsBbMonth;
import com.gs.dingtalk.mapper.VwCjScSjTsBbMapper;
import com.gs.dingtalk.service.DingtalkInfoService;
import com.gs.dingtalk.service.VwCjScSjTsBbMonthService;
import com.gs.dingtalk.service.VwCjScSjTsBbService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.List;
/**
 * @author Administrator
 * @description é’ˆå¯¹è¡¨ã€VW_CJ_SC_SJ_TS_BB】的数据库操作Service实现
 * @createDate 2025-11-12 16:42:06
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class VwCjScSjTsBbServiceImpl extends ServiceImpl<VwCjScSjTsBbMapper, VwCjScSjTsBb>
        implements VwCjScSjTsBbService {
    private final DingtalkInfoService dingtalkInfoService;
    private final VwCjScSjTsBbMonthService vwCjScSjTsBbMonthService;
    @Override
    public boolean exportAndSendToDingtalk() throws Exception {
        String exportFilePath = null;
        try {
            // 2. å‡†å¤‡å¯¼å‡ºæ–‡ä»¶è·¯å¾„
            String timestamp = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
            String fileName = "生产数据报表_" + timestamp + ".xlsx";
            exportFilePath = "D:\\BIFile\\" + fileName;
            // ç¡®ä¿ç›®å½•存在
            FileUtil.mkdir("D:\\BIFile");
            // 3. å¯¼å‡ºåˆ°Excel
            log.info("开始导出Excel文件: {}", exportFilePath);
            exportToExcel(exportFilePath);
            log.info("Excel文件导出成功");
            // 4. å‘送钉钉消息
            log.info("开始发送钉钉文件消息...");
            boolean sendResult = dingtalkInfoService.sendFileMessage(exportFilePath);
            if (sendResult) {
                log.info("钉钉文件消息发送成功");
            } else {
                log.warn("钉钉文件消息发送失败");
            }
            return sendResult;
        } catch (Exception e) {
            log.error("导出并发送失败", e);
            throw e;
        } finally {
            // å¯é€‰ï¼šå‘送后删除临时文件
            // if (exportFilePath != null && FileUtil.exist(exportFilePath)) {
            //     FileUtil.del(exportFilePath);
            //     log.info("临时文件已删除: {}", exportFilePath);
            // }
        }
    }
    /**
     * å¯¼å‡ºæ•°æ®åˆ°Excel(两个sheet页)
     *
     * @param filePath æ–‡ä»¶è·¯å¾„
     */
    private void exportToExcel(String filePath) {
        // è®¡ç®—昨天的日期
        LocalDate yesterday = LocalDate.now().minusDays(1);
        String yesterdayStr = yesterday.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        // è®¡ç®—本月的起止日期
        LocalDate today = LocalDate.now();
        LocalDate firstDayOfMonth = today.withDayOfMonth(1);
        LocalDate lastDayOfMonth = today.withDayOfMonth(today.lengthOfMonth());
        String firstDayStr = firstDayOfMonth.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        String lastDayStr = lastDayOfMonth.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        log.info("昨天日期: {}", yesterdayStr);
        log.info("本月范围: {} è‡³ {}", firstDayStr, lastDayStr);
        // è¿‡æ»¤æ•°æ®ï¼šé¢„计开工在昨天的数据
        List<VwCjScSjTsBb> yesterdayData = list();
        // è¿‡æ»¤æ•°æ®ï¼šé¢„计开工在本月的数据
        List<VwCjScSjTsBbMonth> thisMonthData = vwCjScSjTsBbMonthService.list();
        log.info("昨天数据: {} æ¡", yesterdayData.size());
        log.info("本月数据: {} æ¡", thisMonthData.size());
        // åˆ›å»ºExcel写入器(第一个sheet)
        ExcelWriter writer = ExcelUtil.getWriter(filePath, "昨天入库工单申请");
        // å†™å…¥ç¬¬ä¸€ä¸ªsheet:昨天预计开工的数据
        writeSheetData(writer, "昨天入库工单申请数据(" + yesterdayStr + ")", yesterdayData);
        // åˆ›å»ºç¬¬äºŒä¸ªsheet
        writer.setSheet("本月入库工单申请");
        // å†™å…¥ç¬¬äºŒä¸ªsheet:本月预计开工的数据
        writeSheetData(writer, thisMonthData, "本月入库工单申请数据(" + firstDayStr + " è‡³ " + lastDayStr + ")");
        // å…³é—­writer,释放内存
        writer.close();
    }
    /**
     * å†™å…¥å•个sheet的数据
     *
     * @param writer   Excel写入器
     * @param dataList æ•°æ®åˆ—表
     * @param title    æ ‡é¢˜
     */
    private void writeSheetData(ExcelWriter writer, String title, List<VwCjScSjTsBb> dataList) {
        // æ¸…空之前的别名设置
        writer.clearHeaderAlias();
        // è®¾ç½®è¡¨å¤´åˆ«åï¼ˆä¸­æ–‡åˆ—名)
        writer.addHeaderAlias("departmentname", "车间");
        writer.addHeaderAlias("lineName", "线体名称");
        writer.addHeaderAlias("itemNo", "产品编码");
        writer.addHeaderAlias("itemName", "产品名称");
        writer.addHeaderAlias("daa001", "工单号");
        writer.addHeaderAlias("daa008", "工单数");
        writer.addHeaderAlias("sq", "申请数");
        writer.addHeaderAlias("rk", "入库数");
        writer.addHeaderAlias("sqwwg", "申请未完工数");
        writer.addHeaderAlias("rkwwg", "入库未完工");
        writer.addHeaderAlias("sqwrk", "申请未入库");
        writer.addHeaderAlias("yjkg", "预计开工");
        writer.addHeaderAlias("sjkg", "实际开工");
        // åˆå¹¶å•元格作为标题行
        writer.merge(14, title);
        // å†™å…¥æ•°æ®ï¼Œé»˜è®¤ä¼šä½¿ç”¨åˆ«åä½œä¸ºè¡¨å¤´
        writer.write(dataList, true);
        // è®¾ç½®åˆ—宽自适应
        writer.autoSizeColumnAll();
    }
    private void writeSheetData(ExcelWriter writer, List<VwCjScSjTsBbMonth> dataList, String title) {
        // æ¸…空之前的别名设置
        writer.clearHeaderAlias();
        // è®¾ç½®è¡¨å¤´åˆ«åï¼ˆä¸­æ–‡åˆ—名)
        writer.addHeaderAlias("departmentname", "车间");
        writer.addHeaderAlias("lineName", "线体名称");
        writer.addHeaderAlias("itemNo", "产品编码");
        writer.addHeaderAlias("itemName", "产品名称");
        writer.addHeaderAlias("daa001", "工单号");
        writer.addHeaderAlias("daa008", "工单数");
        writer.addHeaderAlias("sq", "申请数");
        writer.addHeaderAlias("rk", "入库数");
        writer.addHeaderAlias("sqwwg", "申请未完工数");
        writer.addHeaderAlias("rkwwg", "入库未完工");
        writer.addHeaderAlias("sqwrk", "申请未入库");
        writer.addHeaderAlias("yjkg", "预计开工");
        writer.addHeaderAlias("sjkg", "实际开工");
        // åˆå¹¶å•元格作为标题行
        writer.merge(14, title);
        // å†™å…¥æ•°æ®ï¼Œé»˜è®¤ä¼šä½¿ç”¨åˆ«åä½œä¸ºè¡¨å¤´
        writer.write(dataList, true);
        // è®¾ç½®åˆ—宽自适应
        writer.autoSizeColumnAll();
    }
}
src/main/java/com/gs/dingtalk/task/ScheduledTasks.java
@@ -2,54 +2,90 @@
import com.gs.dingtalk.service.SendDingtalkService;
import com.gs.dingtalk.service.VwCjScSjTsBbService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.concurrent.atomic.AtomicBoolean;
@Component
@RequiredArgsConstructor
@Slf4j
public class ScheduledTasks {
    private final SendDingtalkService sendDingtalkService;
//    private final SendDingtalkService sendDingtalkService;
    private final VwCjScSjTsBbService vwCjScSjTsBbService;
    private final AtomicBoolean exportTaskRunning = new AtomicBoolean(false);
//    /**
//     * æ¯ä¸¤åˆ†é’Ÿæ‰§è¡Œä¸€æ¬¡
//     * èŽ·å–è®¾å¤‡æœ€è¿‘çš„ä¸€æ¡è®°å½•
//     *
//     * @return void
//     * @author tjx
//     * @description TODO
//     * @date 2024/9/27 21:48
//     */
//    @Scheduled(cron = "0 0/2 * * * ?")
//    public void getDeviceRealTimeData() {
//        try {
//            sendDingtalkService.sendDingTalkFiveMinute();
//            log.info("定时任务 getDeviceRealTimeData æ‰§è¡ŒæˆåŠŸ");
//        } catch (Exception e) {
//            log.error("定时任务 getDeviceRealTimeData æ‰§è¡Œå¤±è´¥: ", e);
//        }
//    }
//
//    @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();
//            log.info("定时任务 sendDingTalkThirtyMinute æ‰§è¡ŒæˆåŠŸ");
//        } catch (Exception e) {
//            log.error("定时任务 sendDingTalkThirtyMinute æ‰§è¡Œå¤±è´¥: ", e);
//        }
//    }
    /**
     * æ¯ä¸¤åˆ†é’Ÿæ‰§è¡Œä¸€æ¬¡
     * èŽ·å–è®¾å¤‡æœ€è¿‘çš„ä¸€æ¡è®°å½•
     *
     * @return void
     * @author tjx
     * @description TODO
     * @date 2024/9/27 21:48
     * æ¯å¤©ä¸Šåˆ9点执行
     * å¯¼å‡ºç”Ÿäº§æ•°æ®å¹¶å‘送钉钉消息
     */
    @Scheduled(cron = "0 0/2 * * * ?")
    public void getDeviceRealTimeData() {
        try {
            sendDingtalkService.sendDingTalkFiveMinute();
            log.info("定时任务 getDeviceRealTimeData æ‰§è¡ŒæˆåŠŸ");
        } catch (Exception e) {
            log.error("定时任务 getDeviceRealTimeData æ‰§è¡Œå¤±è´¥: ", e);
    @Async("taskExecutor")
    @Scheduled(cron = "0 0 12,16 * * ?")
    public void exportAndSendProductionDataTask() {
        if (!exportTaskRunning.compareAndSet(false, true)) {
            log.warn("生产数据导出任务正在执行中,跳过本次执行");
            return;
        }
    }
    @Scheduled(cron = "0 0/3 * * * ?")
    public void sendDingTalkFifteenMinute() {
        try {
            sendDingtalkService.sendDingTalkFifteenMinute();
            log.info("定时任务 sendDingTalkFifteenMinute æ‰§è¡ŒæˆåŠŸ");
        } catch (Exception e) {
            log.error("定时任务 sendDingTalkFifteenMinute æ‰§è¡Œå¤±è´¥: ", e);
        }
    }
            log.info("开始执行生产数据导出并发送钉钉任务");
            boolean result = vwCjScSjTsBbService.exportAndSendToDingtalk();
    @Scheduled(cron = "0 0/4 * * * ?")
    public void sendDingTalkThirtyMinute() {
        try {
            sendDingtalkService.sendDingTalkthirtyMinute();
            log.info("定时任务 sendDingTalkThirtyMinute æ‰§è¡ŒæˆåŠŸ");
            if (result) {
                log.info("生产数据导出并发送钉钉任务执行成功");
            } else {
                log.error("生产数据导出并发送钉钉任务执行失败");
            }
        } catch (Exception e) {
            log.error("定时任务 sendDingTalkThirtyMinute æ‰§è¡Œå¤±è´¥: ", e);
            log.error("生产数据导出并发送钉钉任务执行异常", e);
        } finally {
            exportTaskRunning.set(false);
        }
    }
}
src/main/resources/application.yml
@@ -5,9 +5,9 @@
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: oracle.jdbc.OracleDriver
    url: jdbc:oracle:thin:@192.168.0.100:1521/orcl
    username: yc_dev
    password: ycdev
    url: jdbc:oracle:thin:@192.168.1.22:1521/orcl
    username: hm_prd
    password: hmprd
    druid:
      initialSize: 5
      minIdle: 5
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.dingtalk.mapper.DingtalkInfoMapper">
</mapper>
src/main/resources/mapper/DingtalkMsgMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,8 @@
<?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.DingtalkMsgMapper">
</mapper>
src/main/resources/mapper/MesStaffMapper.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.MesStaffMapper">
    <update id="updateStaff">
        update SYS_USER a
        set SID = (select ID from MES_STAFF where STAFF_NO = a.FCODE and STAFF_NAME = a.FNAME)
        where 1 = 1
    </update>
    <delete id="deleteStaff">
        delete
        from MES_STAFF
        where 1 = 1
    </delete>
    <select id="getNextVal" resultType="java.lang.Long">
        select SEQ_MES_STAFF.NextVal
        from dual
    </select>
</mapper>
src/main/resources/mapper/QwStaffMapper.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.QwStaffMapper">
</mapper>
src/main/resources/mapper/VwCjScSjTsBbMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,8 @@
<?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.VwCjScSjTsBbMapper">
</mapper>
src/main/resources/mapper/VwCjScSjTsBbMonthMapper.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.VwCjScSjTsBbMonthMapper">
</mapper>
src/test/java/com/gs/dingtalk/DeviceReceivingApplicationTests.java
@@ -1,63 +1,230 @@
package com.gs.dingtalk;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.gs.dingtalk.config.URLEncoder;
import com.gs.dingtalk.entity.QwStaff;
import com.gs.dingtalk.mapper.QwStaffMapper;
import com.gs.dingtalk.service.SendDingtalkService;
import com.gs.dingtalk.service.SimpleExample;
import com.gs.dingtalk.service.VwCjScSjTsBbService;
import com.gs.dingtalk.service.WorkWXService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
@SpringBootTest
class DeviceReceivingApplicationTests {
    @Autowired
    private SimpleExample simpleExample;
    private VwCjScSjTsBbService vwCjScSjTsBbService;
    @Autowired
    private SendDingtalkService sendDingtalkService;
    private WorkWXService workWXService;
    @Autowired
    private QwStaffMapper qwStaffMapper;
    /**
     * æµ‹è¯•导出生产数据并发送钉钉消息
     * åŠŸèƒ½ï¼šæŸ¥è¯¢VW_CJ_SC_SJ_TS_BB表数据 -> å¯¼å‡ºExcel -> å‘送钉钉文件消息
     */
    @Test
    void contextLoads() {
        //FFFFFFFE
        long decimal = Long.parseLong("FFFFFFFE", 16);  // å°†16进制字符串转换为long类型的10进制数
        BigDecimal a = new BigDecimal(String.valueOf((int) decimal));
        BigDecimal b = new BigDecimal("1000");
        BigDecimal c = a.divide(b, 3, RoundingMode.HALF_UP);
        System.out.println(c);
    void testExportAndSendProductionData() throws Exception {
        System.out.println("=== å¼€å§‹æµ‹è¯•导出生产数据并发送钉钉 ===");
        try {
            boolean result = vwCjScSjTsBbService.exportAndSendToDingtalk();
            if (result) {
                System.out.println("✓ ç”Ÿäº§æ•°æ®å¯¼å‡ºå¹¶å‘送成功");
                System.out.println("  - æ•°æ®å·²ä»Ž VW_CJ_SC_SJ_TS_BB è¡¨æŸ¥è¯¢");
                System.out.println("  - Excel æ–‡ä»¶å·²ç”Ÿæˆå¹¶ä¿å­˜åˆ° D:\\BIFile\\");
                System.out.println("  - é’‰é’‰æ–‡ä»¶æ¶ˆæ¯å·²å‘送");
            } else {
                System.out.println("✗ ç”Ÿäº§æ•°æ®å¯¼å‡ºæˆ–发送失败");
            }
        } catch (Exception e) {
            System.out.println("✗ å¯¼å‡ºå¹¶å‘送时发生异常: " + e.getMessage());
            e.printStackTrace();
        }
        System.out.println("=== æµ‹è¯•结束 ===");
    }
    @Test
    void cont() {
//        long resultCode = Long.parseLong("00000001");
//        int result = (int) resultCode;
//        System.out.println(result);
    void testGetCheckinDataByQwStaff() {
        System.out.println("=== å¼€å§‹æµ‹è¯•通过QW_STAFF表获取打卡数据 ===");
        String encode = URLEncoder.encode("");
        System.out.println(encode);
        // ä¼ä¸šå¾®ä¿¡æ‰“卡接口限制:
        // 1. èŽ·å–è®°å½•æ—¶é—´è·¨åº¦ä¸è¶…è¿‡30天
        // 2. ç”¨æˆ·åˆ—表不超过100个。若用户超过100个,请分批获取(已在Service层实现自动分批)
        // 3. æŽ¥å£è¿”回最多3000条打卡数据
        // 4. æ ‡å‡†æ‰“卡时间只对于固定排班和自定义排班两种类型有效
        // 5. æŽ¥å£è°ƒç”¨é¢‘率限制为600次/分钟(已在Service层实现批次间延迟)
        try {
            long currentTime = System.currentTimeMillis() / 1000;
            long oneDaySeconds = 86400;
            long thirtyDaysSeconds = 30 * oneDaySeconds;
            long endTime = (currentTime / oneDaySeconds) * oneDaySeconds - 1;
            long startTime = endTime - oneDaySeconds + 1;
            // éªŒè¯æ—¶é—´è·¨åº¦ä¸è¶…过30天
            long timeSpan = endTime - startTime;
            if (timeSpan > thirtyDaysSeconds) {
                System.out.println("✗ æ—¶é—´è·¨åº¦è¶…过30天限制: " + (timeSpan / oneDaySeconds) + "天");
                return;
            }
            System.out.println("  - å¼€å§‹æ—¶é—´: " + new java.util.Date(startTime * 1000));
            System.out.println("  - ç»“束时间: " + new java.util.Date(endTime * 1000));
            System.out.println("  - æ—¶é—´è·¨åº¦: " + (timeSpan / oneDaySeconds) + "天 (限制: â‰¤30天)");
            // èŽ·å–ç”¨æˆ·æ€»æ•°
            long totalUsers = qwStaffMapper.selectCount(new LambdaQueryWrapper<QwStaff>()
                    .isNotNull(QwStaff::getAccount)
                    .ne(QwStaff::getAccount, ""));
            System.out.println("  - QW_STAFF表用户总数: " + totalUsers);
            if (totalUsers > 100) {
                int batchCount = (int) ((totalUsers + 99) / 100);
                System.out.println("  - å°†è‡ªåŠ¨åˆ†æ‰¹å¤„ç†: " + batchCount + "批 (每批≤100用户)");
            }
            List<WorkWXService.CheckinData> checkinDataList = workWXService.getCheckinDataByQwStaff(startTime, endTime);
            if (checkinDataList != null && !checkinDataList.isEmpty()) {
                System.out.println("✓ æˆåŠŸèŽ·å–æ‰“å¡æ•°æ®");
                System.out.println("  - æ‰“卡记录总数: " + checkinDataList.size());
                System.out.println("  - å‰5条数据:");
                checkinDataList.stream().limit(5).forEach(data -> {
                    System.out.println("    * userid: " + data.getUserid() +
                            ", æ‰“卡时间: " + new java.util.Date(data.getCheckinTime() * 1000) +
                            ", æ‰“卡类型: " + data.getCheckinType() +
                            ", å¼‚常类型: " + data.getExceptionType() +
                            ", åœ°ç‚¹: " + data.getLocationDetail());
                });
            } else {
                System.out.println("✗ èŽ·å–çš„æ‰“å¡æ•°æ®ä¸ºç©ºï¼ˆå¯èƒ½QW_STAFF表无数据或时间范围内无打卡记录)");
            }
        } catch (IOException e) {
            System.out.println("✗ èŽ·å–æ‰“å¡æ•°æ®å¤±è´¥: " + e.getMessage());
            e.printStackTrace();
        }
        System.out.println("=== æµ‹è¯•结束 ===");
    }
    @Test
    void getPhone() throws Exception {
        sendDingtalkService.getDingTalkUserId();
    void testGetCheckinDataById() {
        System.out.println("=== å¼€å§‹æµ‹è¯•通过QW_STAFF表获取打卡数据 ===");
        QwStaff qwStaff = qwStaffMapper.selectById(3);
        if (qwStaff == null || qwStaff.getAccount() == null || qwStaff.getAccount().isEmpty()) {
            System.out.println("✗ æœªæ‰¾åˆ°ID为3的员工或员工account为空");
            return;
        }
        System.out.println("  - å‘˜å·¥å§“名: " + qwStaff.getName());
        System.out.println("  - å‘˜å·¥è´¦å·: " + qwStaff.getAccount());
        try {
            long currentTime = System.currentTimeMillis() / 1000;
            long oneDaySeconds = 86400;
            long thirtyDaysSeconds = 30 * oneDaySeconds;
            // æ˜¨å¤©ç»“束时间(23:59:59)
            long yesterdayEnd = ((currentTime / oneDaySeconds) - 1) * oneDaySeconds + oneDaySeconds - 1;
            // 20天前开始时间(00:00:00)
            long twentyDaysAgoStart = yesterdayEnd - 20 * oneDaySeconds + 1;
            long endTime = yesterdayEnd;
            long startTime = twentyDaysAgoStart;
            // éªŒè¯æ—¶é—´è·¨åº¦ä¸è¶…过30天
            long timeSpan = endTime - startTime;
            if (timeSpan > thirtyDaysSeconds) {
                System.out.println("✗ æ—¶é—´è·¨åº¦è¶…过30天限制: " + (timeSpan / oneDaySeconds) + "天");
                return;
            }
            System.out.println("  - å¼€å§‹æ—¶é—´: " + new java.util.Date(startTime * 1000) + " (20天前)");
            System.out.println("  - ç»“束时间: " + new java.util.Date(endTime * 1000) + " (昨天)");
            System.out.println("  - æ—¶é—´è·¨åº¦: " + (timeSpan / oneDaySeconds) + "天");
            List<String> useridList = new java.util.ArrayList<>();
            useridList.add(qwStaff.getAccount());
            List<WorkWXService.CheckinData> checkinDataList = workWXService.getCheckinData(startTime, endTime, useridList);
            if (checkinDataList != null && !checkinDataList.isEmpty()) {
                System.out.println("✓ æˆåŠŸèŽ·å–æ‰“å¡æ•°æ®");
                System.out.println("  - æ‰“卡记录总数: " + checkinDataList.size());
                checkinDataList.forEach(data -> {
                    System.out.println("    * æ‰“卡时间: " + new java.util.Date(data.getCheckinTime() * 1000) +
                            ", æ‰“卡类型: " + data.getCheckinType() +
                            ", å¼‚常类型: " + data.getExceptionType() +
                            ", åœ°ç‚¹: " + data.getLocationDetail());
                });
            } else {
                System.out.println("✗ è¯¥å‘˜å·¥åœ¨æ—¶é—´èŒƒå›´å†…无打卡记录");
            }
        } catch (IOException e) {
            System.out.println("✗ èŽ·å–æ‰“å¡æ•°æ®å¤±è´¥: " + e.getMessage());
            e.printStackTrace();
        }
        System.out.println("=== æµ‹è¯•结束 ===");
    }
    @Test
    void sendDingTalk() throws Exception {
        sendDingtalkService.sendDingTalkFiveMinute();
    void testGetWorkWXUserList() {
        System.out.println("=== å¼€å§‹æµ‹è¯•获取企业微信用户列表 ===");
        try {
            List<WorkWXService.WorkWXUser> userList = workWXService.getUserList();
            if (userList != null && !userList.isEmpty()) {
                System.out.println("✓ æˆåŠŸèŽ·å–ä¼ä¸šå¾®ä¿¡ç”¨æˆ·åˆ—è¡¨");
                System.out.println("  - ç”¨æˆ·æ€»æ•°: " + userList.size());
                System.out.println("  - å‰10条数据:");
                userList.stream().limit(10).forEach(user -> {
                    System.out.println("    * userid: " + user.getUserid() +
                            ", å§“名: " + user.getName() +
                            ", éƒ¨é—¨: " + user.getDepartment());
                });
            } else {
                System.out.println("✗ èŽ·å–çš„ç”¨æˆ·åˆ—è¡¨ä¸ºç©º");
            }
        } catch (IOException e) {
            System.out.println("✗ èŽ·å–ç”¨æˆ·åˆ—è¡¨å¤±è´¥: " + e.getMessage());
            e.printStackTrace();
        }
        System.out.println("=== æµ‹è¯•结束 ===");
    }
    @Test
    void sendDingTalkthirtyMinute() throws Exception {
        sendDingtalkService.sendDingTalkthirtyMinute();
    void testSyncUsersToQwStaff() {
        System.out.println("=== å¼€å§‹æµ‹è¯•同步企业微信用户到QW_STAFF表 ===");
        try {
            int insertCount = workWXService.syncUsersToQwStaff();
            System.out.println("✓ åŒæ­¥å®Œæˆ");
            System.out.println("  - æ–°å¢žç”¨æˆ·æ•°: " + insertCount);
        } catch (IOException e) {
            System.out.println("✗ åŒæ­¥ç”¨æˆ·å¤±è´¥: " + e.getMessage());
            e.printStackTrace();
        }
        System.out.println("=== æµ‹è¯•结束 ===");
    }
    //chatSendMessage
    @Test
    void chatSendMessage() throws Exception {
        sendDingtalkService.chatSendMessage();
    }
}
src/test/java/com/gs/dingtalk/MesQaDingtalkServiceTest.java
ÎļþÒÑɾ³ý