package com.gs.xky.service.Impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.dingtalk.api.DefaultDingTalkClient; import com.dingtalk.api.DingTalkClient; import com.dingtalk.api.request.OapiMessageCorpconversationAsyncsendV2Request; import com.dingtalk.api.response.OapiMessageCorpconversationAsyncsendV2Response; import com.gs.xky.entity.DingtalkInfo; import com.gs.xky.entity.DingtalkMsg; import com.gs.xky.entity.MesStaff; import com.gs.xky.mapper.DingtalkInfoMapper; import com.gs.xky.mapper.DingtalkMsgMapper; import com.gs.xky.mapper.MesStaffMapper; import com.gs.xky.service.DingtalkInfoService; import com.gs.xky.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 implements DingtalkInfoService { private final SimpleExample simpleExample; private final DingtalkMsgMapper dingtalkMsgMapper; private final MesStaffMapper mesStaffMapper; @Override public boolean sendMessage(String releaseNo) { try { // 查询钉钉消息内容 LambdaQueryWrapper 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 sidList = new ArrayList<>(); // 1. 固定推送人员(isSendDingtalk=1) List fixedList = baseMapper.selectList( new LambdaQueryWrapper().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().eq(MesStaff::getStaffName, dingtalkMsg.getEmployeeName()), false ); if (buyer != null) { DingtalkInfo buyerInfo = baseMapper.selectOne( new LambdaQueryWrapper().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().eq(MesStaff::getStaffNo, dingtalkMsg.getModify1By()) ); if (auditor != null) { DingtalkInfo auditorInfo = baseMapper.selectOne( new LambdaQueryWrapper().eq(DingtalkInfo::getSid, auditor.getId()) ); if (auditorInfo != null) sidList.add(auditorInfo.getSid()); } } // 4. 根据收集的sid获取dingtalkId (复用getDingtalkUserIdList的关键逻辑) List 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 fixedList = baseMapper.selectList( new LambdaQueryWrapper().eq(DingtalkInfo::getIsHead, 1) ); List sidList = fixedList.stream() .map(DingtalkInfo::getSid) .collect(Collectors.toList()); List 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 fixedList = baseMapper.selectList( new LambdaQueryWrapper().eq(DingtalkInfo::getIsHead, 1) ); if (fixedList == null || fixedList.isEmpty()) { log.warn("没有需要发送文件的用户(isHead=1)"); return false; } List sidList = fixedList.stream() .map(DingtalkInfo::getSid) .collect(Collectors.toList()); List userIdList = getDingtalkUserIdListBySids(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 getDingtalkUserIdListBySids(List sidList) { try { if (sidList == null || sidList.isEmpty()) { return new ArrayList<>(); } // 去重 sidList = sidList.stream().distinct().collect(Collectors.toList()); // 根据sid查询DingtalkInfo List list = baseMapper.selectList( new LambdaQueryWrapper().in(DingtalkInfo::getSid, sidList) ); if (list == null || list.isEmpty()) { return new ArrayList<>(); } // 使用stream流过滤出list中dingtalkId为空的数据 List 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 getDingtalkUserIdList() { try { LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.ge(DingtalkInfo::getIsSendDingtalk, 1); List list = list(wrapper); if (list == null || list.isEmpty()) { return new ArrayList<>(); } // 使用stream流过滤出list中dingtalkId为空的数据 List 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<>(); } } }