diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index da14153..a1afa3b 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -76,9 +76,9 @@ spring: servlet: multipart: # 单个文件大小 - max-file-size: 10MB + max-file-size: 50MB # 设置总上传的文件大小 - max-request-size: 20MB + max-request-size: 50MB # 服务模块 devtools: restart: @@ -149,12 +149,12 @@ security: - /system/proPlan/list2 - /system/mrp/** - /system/orderPro/** + - /system/cost/** # MyBatisPlus配置 # https://baomidou.com/config/ mybatis-plus: - mapperPath: - # 不支持多包, 如有需要可在注解配置 或 提升扫包等级 + mapperPath: # 不支持多包, 如有需要可在注解配置 或 提升扫包等级 # 例如 com.**.**.mapper mapperPackage: com.ruoyi.**.mapper # 对应的 XML 文件位置 diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java index 9fcdde1..2b66c0e 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java @@ -88,5 +88,79 @@ public interface Constants { public static final String ETP_URL = "/1.1.9 ETP-P系列减速器标准图纸/"; public static final String ETH_URL = "/1.1.10 ETH-H系列减速器标准图纸/"; + + /** + * 企业ID + */ + String CORPORATE_ID = "ww3cd1a87df502cf3a"; + /** + * 应用Id + */ + String APPLICATION_ID = "cyGYk5QxLTn-k3_QG_nO6EANAigqBWhdDaqy_3sF6Y0"; + String AgentId = "1000006"; + + /** + * 苏硕 + */ + String Y = "y"; + /** + * 苏硕 + */ + String SuShuo = "SuShuo"; + /** + * 闫建楼 + */ + String YanJianLou = "YanJianLou"; + /** + * 何春玲 + */ + String HeChunLing = "HeChunLing"; + /** + * 曹盼 + */ + String 曹盼 = "-"; + /** + * 孙灵海 + */ + String YeZhen = "YeZhen"; + /** + * 师敏玲 + */ + String PingAnXiLe = "PingAnXiLe"; + /** + * 李祎泽 + */ + String ronin = "ronin"; + /** + * 吴海静 + */ + String JingJing = "JingJing"; + /** + * 李祎晗 + */ + String LiYiHan = "LiYiHan"; + /** + * 陈志学 + */ + String 陈志学 = "-"; + /** + * 马天宇 + */ + String MaTianYu = "MaTianYu"; + /** + * 康建新 + */ + String JianXin = "JianXin"; + /** + * 刘冉 + */ + String ChengChengChengChengCheng = "ChengChengChengChengCheng"; + /** + * 张敏 + */ + String lingyan = "lingyan"; + String WangXuDe = "WangXuDe"; + + } diff --git a/ruoyi-system/pom.xml b/ruoyi-system/pom.xml index fc2d270..19a7e60 100644 --- a/ruoyi-system/pom.xml +++ b/ruoyi-system/pom.xml @@ -6,7 +6,7 @@ ruoyi-vue-plus com.ruoyi 4.7.0 - org.apache.maven.pluginsmaven-compiler-plugin88 + org.apache.maven.pluginsmaven-compiler-plugin1.81.8 4.0.0 ruoyi-system diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/controller/KingdeeWorkCenterDataController.java b/ruoyi-system/src/main/java/com/ruoyi/system/controller/KingdeeWorkCenterDataController.java index 7e529b9..13002b2 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/controller/KingdeeWorkCenterDataController.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/controller/KingdeeWorkCenterDataController.java @@ -1,6 +1,7 @@ package com.ruoyi.system.controller; import java.io.File; +import java.io.UnsupportedEncodingException; import java.time.LocalDate; import java.time.temporal.ChronoUnit; import java.time.format.DateTimeFormatter; @@ -30,6 +31,7 @@ import com.ruoyi.system.mapper.WlStockDataMapper; import com.ruoyi.system.runner.JdUtil; import com.ruoyi.system.service.IProcessRouteService; import com.ruoyi.system.service.ISafetyStockService; +import com.ruoyi.system.service.IWeComService; import com.xxl.job.core.handler.annotation.XxlJob; import lombok.RequiredArgsConstructor; @@ -58,9 +60,12 @@ import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.system.domain.vo.KingdeeWorkCenterDataVo; import com.ruoyi.system.domain.bo.KingdeeWorkCenterDataBo; import com.ruoyi.system.service.IKingdeeWorkCenterDataService; +import java.util.stream.Collectors; import com.ruoyi.common.core.page.TableDataInfo; import org.springframework.util.StringUtils; +import static com.ruoyi.common.constant.Constants.*; + /** * 金蝶工段数据 * @@ -79,8 +84,12 @@ public class KingdeeWorkCenterDataController extends BaseController { private static final Logger log = LoggerFactory.getLogger(KingdeeWorkCenterDataController.class); private final WlStockDataMapper baseMapper; private final ISafetyStockService iSafetyStockService; + private final IWeComService iWeComService; @Autowired IProcessRouteService iProcessRouteService; + private static final String QUALITY_KANBAN_URL = "http://192.168.5.8/K3Cloud/DataBoard/QualityKanBan/index.html#/"; + + /** * 查询金蝶工段数据列表 */ @@ -1661,5 +1670,130 @@ public class KingdeeWorkCenterDataController extends BaseController { return R.fail("发送工段数据失败:" + e.getMessage()); } } + private String buildTextCardDescription(ProcessInspectionDTO pd) { + // 日期 + String dateStr = formatDate(pd.getFSubmitInspectTime()); + if (dateStr == null || dateStr.isEmpty()) { + dateStr = "今日"; + } + + // 基础信息 + String moOrderNo = pd.getMoBillNo() == null ? "-" : pd.getMoBillNo(); + String productionNo = pd.getMoOrderNo() == null ? "-" : pd.getMoOrderNo(); // 生产令号 + String materialName = pd.getFMaterialName() == null ? "-" : pd.getFMaterialName(); + String center = pd.getFWorkCenterName() == null ? "-" : pd.getFWorkCenterName(); + String process = pd.getFProcessName() == null ? "-" : pd.getFProcessName(); + + // 检验统计 + int inspected = pd.getFFinishInspectQty(); + int pending = pd.getFWaitInspectQty(); + int qualified = pd.getFQuaQty(); + int scrap = pd.getFFailQty(); + int rework = pd.getFReworkQty(); + String status = pd.getFInspectStatus() == null ? "未完成" : pd.getFInspectStatus(); + + StringBuilder sb = new StringBuilder(); + sb.append("
交检时间:").append(dateStr).append("
"); + sb.append("
生产订单编号:").append(moOrderNo).append("
"); + sb.append("
生产令号:").append(productionNo).append("
"); + sb.append("
产品名称:").append(materialName).append("
"); + sb.append("
工段 / 工序:").append(center).append(" / ").append(process).append("
"); + sb.append("
已检:").append(inspected) + .append(" 待检:").append(pending) + .append(" 合格:").append(qualified) + .append("
"); + sb.append("
废品:").append(scrap) + .append(" 返修:").append(rework) + .append("
"); + sb.append("
质检状态:").append(status).append("
"); + + return sb.toString(); + } + + + /** + * 应用消息质检数据 + */ + @XxlJob("getApplicationMessage") + @Log(title = "应用消息质检数据", businessType = BusinessType.DELETE) + @PostMapping("/getApplicationMessage") + public R> getApplicationMessage() throws UnsupportedEncodingException { + List allList = JdUtil.getUninspectedData(); + if (allList.isEmpty()) { + log.info("没有获取到质检数据"); + return R.ok(Collections.emptyList()); + } + + /*LocalDate today = LocalDate.now(); + List todayList = allList.stream().filter(pd -> { + LocalDate submit = parseDate(pd.getFSubmitInspectTime()); + LocalDate finish = parseDate(pd.getFFinishInspectTime()); + return (submit != null && submit.isEqual(today)) || (finish != null && finish.isEqual(today)); + }).collect(Collectors.toList()); +*/ + /* if (todayList.isEmpty()) { + log.info("今日没有质检数据"); + return R.ok(Collections.emptyList()); + }*/ + + String accessToken = iWeComService.getAccessToken(CORPORATE_ID, APPLICATION_ID); + + // 工段/工序 -> 接收人映射 + Map workCenterMap = new HashMap<>(); + /*workCenterMap.put("铆焊工段", YanJianLou); + workCenterMap.put("机二工段", "y"); + workCenterMap.put("机三工段", YeZhen + "|" + PingAnXiLe); + workCenterMap.put("装一工段", ronin + "|" + JingJing); + workCenterMap.put("装二工段", LiYiHan + "|" + MaTianYu);*/ + + + workCenterMap.put("铆焊工段", ChengChengChengChengCheng); + workCenterMap.put("机二工段", "WangXuDe"); + workCenterMap.put("机三工段", ChengChengChengChengCheng + "|" + WangXuDe); + workCenterMap.put("装一工段", ChengChengChengChengCheng + "|" + WangXuDe); + workCenterMap.put("装二工段", ChengChengChengChengCheng + "|" + WangXuDe); + // 特殊:机一工段下料 + workCenterMap.put("机一工段", Y); + + for (ProcessInspectionDTO pd : allList) { + try { + String description = buildTextCardDescription(pd); + String title = "质检通知"; + String workCenter = pd.getFWorkCenterName() == null ? "" : pd.getFWorkCenterName(); + String process = pd.getFProcessName() == null ? "" : pd.getFProcessName(); + + String key = workCenter; + // 机一工段下料特殊判断 + if ("机一工段".equals(workCenter) && process.contains("下料")) { + key = "机一工段下料"; + } + + String receivers = workCenterMap.get(key); + if (receivers != null && !receivers.isEmpty()) { + iWeComService.sendTextCardMessage( + accessToken, + AgentId, + receivers, + "", + "", + workCenter + title, + description, + QUALITY_KANBAN_URL, + "查看详情", + 0, + 0, + 1800 + ); + } else { + log.warn("未找到接收人映射:工段={} 工序={}", workCenter, process); + } + } catch (Exception e) { + log.error("发送质检消息异常:{}", pd, e); + } + } + return R.ok(null); + } + + } diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/controller/WeComController.java b/ruoyi-system/src/main/java/com/ruoyi/system/controller/WeComController.java new file mode 100644 index 0000000..5006b13 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/controller/WeComController.java @@ -0,0 +1,62 @@ +package com.ruoyi.system.controller; + +import com.ruoyi.common.core.domain.R; +import com.ruoyi.system.service.IWeComService; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.io.UnsupportedEncodingException; + +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/system/wecom") +public class WeComController { + + private final IWeComService weComService; + + @PostMapping("/getToken") + public R getToken(@RequestParam String corpId, @RequestParam String corpSecret) throws UnsupportedEncodingException { + String token = weComService.getAccessToken(corpId, corpSecret); + return R.ok(token); + } + + @PostMapping("/sendText") + public R sendText(@RequestParam String accessToken, + @RequestParam String agentId, + @RequestParam(required = false) String toUser, + @RequestParam(required = false) String toParty, + @RequestParam(required = false) String toTag, + @RequestParam String content, + @RequestParam(required = false) Integer safe, + @RequestParam(required = false, name = "enable_id_trans") Integer enableIdTrans, + @RequestParam(required = false, name = "enable_duplicate_check") Integer enableDuplicateCheck, + @RequestParam(required = false, name = "duplicate_check_interval") Integer duplicateCheckInterval) { + String result = weComService.sendTextMessage(accessToken, agentId, toUser, toParty, toTag, content, safe, enableIdTrans, enableDuplicateCheck, duplicateCheckInterval); + return R.ok(result); + } + + @PostMapping("/sendMarkdown") + public R sendMarkdown(@RequestParam String accessToken, + @RequestParam String agentId, + @RequestParam(required = false) String toUser, + @RequestParam(required = false) String toParty, + @RequestParam(required = false) String toTag, + @RequestParam String content, + @RequestParam(required = false) Integer safe, + @RequestParam(required = false, name = "enable_id_trans") Integer enableIdTrans, + @RequestParam(required = false, name = "enable_duplicate_check") Integer enableDuplicateCheck, + @RequestParam(required = false, name = "duplicate_check_interval") Integer duplicateCheckInterval) { + String result = weComService.sendMarkdownMessage(accessToken, agentId, toUser, toParty, toTag, content, safe, enableIdTrans, enableDuplicateCheck, duplicateCheckInterval); + return R.ok(result); + } + + @PostMapping("/sendTextCard") + public R sendTextCard(@RequestParam String accessToken, @RequestParam String agentId, @RequestParam(required = false) String toUser, @RequestParam(required = false) String toParty, @RequestParam(required = false) String toTag, @RequestParam String title, + @RequestParam String description, @RequestParam String url, @RequestParam(required = false) String btntxt, @RequestParam(required = false, name = "enable_id_trans") Integer enableIdTrans, @RequestParam(required = false, name = "enable_duplicate_check") Integer enableDuplicateCheck, @RequestParam(required = false, name = "duplicate_check_interval") Integer duplicateCheckInterval) { + String result = weComService.sendTextCardMessage(accessToken, agentId, toUser, toParty, toTag, + title, description, url, btntxt, enableIdTrans, enableDuplicateCheck, duplicateCheckInterval); + return R.ok(result); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/runner/PDFGenerator.java b/ruoyi-system/src/main/java/com/ruoyi/system/runner/PDFGenerator.java index e33a6d7..02bd99b 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/runner/PDFGenerator.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/runner/PDFGenerator.java @@ -91,7 +91,7 @@ public class PDFGenerator { return LosslessFactory.createFromImage(document, bufferedImage); } - public static String writeToPdf(List combinedVoList, String rooteProdet) { + public static String writeToPdf(List combinedVoList, String rooteProdet) { String fontPath = "C:\\Users\\Administrator\\Desktop\\arial unicode ms.ttf"; List pdfPaths = new ArrayList<>(); String directoryPath = "D:\\上传BOM\\" + rooteProdet; diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/IWeComService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/IWeComService.java new file mode 100644 index 0000000..b10f993 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/IWeComService.java @@ -0,0 +1,20 @@ +package com.ruoyi.system.service; + +import java.io.UnsupportedEncodingException; + +public interface IWeComService { + String getAccessToken(String corpId, String corpSecret) throws UnsupportedEncodingException; + + String sendTextMessage(String accessToken, String agentId, String toUser, String toParty, String toTag, String content, + Integer safe, Integer enableIdTrans, Integer enableDuplicateCheck, Integer duplicateCheckInterval + ); + + String sendMarkdownMessage(String accessToken, String agentId, String toUser, String toParty, String toTag, String content, + Integer safe, Integer enableIdTrans, Integer enableDuplicateCheck, Integer duplicateCheckInterval + ); + + String sendTextCardMessage(String accessToken, String agentId, String toUser, String toParty, String toTag, + String title, String description, String url, String btntxt, + Integer enableIdTrans, Integer enableDuplicateCheck, Integer duplicateCheckInterval + ); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/WeComServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/WeComServiceImpl.java new file mode 100644 index 0000000..5f11b83 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/WeComServiceImpl.java @@ -0,0 +1,234 @@ +package com.ruoyi.system.service.impl; + +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.system.service.IWeComService; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Service +public class WeComServiceImpl implements IWeComService { + + private final RestTemplate restTemplate = new RestTemplate(); + + private static class TokenEntry { + final String token; + final long expireAtMillis; + TokenEntry(String token, long expireAtMillis) { + this.token = token; + this.expireAtMillis = expireAtMillis; + } + } + + private final Map tokenCache = new ConcurrentHashMap<>(); + + /** + * 获取企业微信 access_token。 + * @param corpId 企业 ID(corpid) + * @param corpSecret 企业密钥(corpsecret) + * @return access_token 字符串 + */ + @Override + public String getAccessToken(String corpId, String corpSecret) throws UnsupportedEncodingException { + String cacheKey = corpId + "|" + corpSecret; + TokenEntry cached = tokenCache.get(cacheKey); + long now = System.currentTimeMillis(); + if (cached != null && cached.expireAtMillis > now) { + return cached.token; + } + + String url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=" + URLEncoder.encode(corpId, "UTF-8") + "&corpsecret=" + URLEncoder.encode(corpSecret, "UTF-8"); + ResponseEntity resp = restTemplate.getForEntity(url, String.class); + String body = resp.getBody(); + if (body == null) { + throw new ServiceException("企业微信 gettoken 接口响应为空"); + } + JSONObject json = JSONUtil.parseObj(body); + int errcode = json.getInt("errcode", -1); + if (errcode != 0) { + String errmsg = json.getStr("errmsg"); + throw new ServiceException("企业微信 gettoken 调用失败,错误码: " + errcode + ",错误信息: " + errmsg); + } + String token = json.getStr("access_token"); + int expiresIn = json.getInt("expires_in", 7200); + long expireAt = now + Math.max(0, (expiresIn - 300)) * 1000L; // 缓冲 5 分钟 + tokenCache.put(cacheKey, new TokenEntry(token, expireAt)); + return token; + } + + /** + * 发送企业微信文本消息。 + * + * @param accessToken 已获取的 access_token + * @param agentId 应用的 AgentId(数字) + * @param toUser 目标用户,多个用 | 分隔,可为空 + * @param toParty 目标部门,多个用 | 分隔,可为空 + * @param toTag 目标标签,多个用 | 分隔,可为空 + * @param content 文本内容 + * @param safe 保密消息标志(0 或 1) + * @param enableIdTrans 是否开启 id 转译 + * @param enableDuplicateCheck 是否开启重复消息检查 + * @param duplicateCheckInterval 重复消息检查间隔 + * @return 企业微信原始响应 JSON 字符串 + */ + @Override + public String sendTextMessage(String accessToken, + String agentId, + String toUser, + String toParty, + String toTag, + String content, + Integer safe, + Integer enableIdTrans, + Integer enableDuplicateCheck, + Integer duplicateCheckInterval) { + String url = "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=" + accessToken; + + Map payload = new HashMap<>(); + if (toUser != null && !toUser.isEmpty()) payload.put("touser", toUser); + if (toParty != null && !toParty.isEmpty()) payload.put("toparty", toParty); + if (toTag != null && !toTag.isEmpty()) payload.put("totag", toTag); + payload.put("msgtype", "text"); + try { + payload.put("agentid", Integer.parseInt(agentId)); + } catch (NumberFormatException e) { + throw new ServiceException("agentId 必须为数字"); + } + + Map text = new HashMap<>(); + text.put("content", content); + payload.put("text", text); + if (safe != null) payload.put("safe", safe); + if (enableIdTrans != null) payload.put("enable_id_trans", enableIdTrans); + if (enableDuplicateCheck != null) payload.put("enable_duplicate_check", enableDuplicateCheck); + if (duplicateCheckInterval != null) payload.put("duplicate_check_interval", duplicateCheckInterval); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + HttpEntity entity = new HttpEntity<>(JSONUtil.toJsonStr(payload), headers); + ResponseEntity resp = restTemplate.postForEntity(url, entity, String.class); + String body = resp.getBody(); + if (body == null) { + throw new ServiceException("企业微信 message/send 接口响应为空"); + } + return body; + } + + + + /** + * 发送企业微信 Markdown 消息。 + * + * @param accessToken 已获取的 access_token + * @param agentId 应用的 AgentId(数字) + * @param toUser 目标用户,多个用 | 分隔,可为空 + * @param toParty 目标部门,多个用 | 分隔,可为空 + * @param toTag 目标标签,多个用 | 分隔,可为空 + * @param content Markdown 内容 + * @param safe 保密消息标志(0 或 1) + * @param enableIdTrans 是否开启 id 转译 + * @param enableDuplicateCheck 是否开启重复消息检查 + * @param duplicateCheckInterval 重复消息检查间隔 + * @return 企业微信原始响应 JSON 字符串 + */ + @Override + public String sendMarkdownMessage(String accessToken, String agentId, String toUser, String toParty, String toTag, String content,Integer safe, Integer enableIdTrans, + Integer enableDuplicateCheck, Integer duplicateCheckInterval) { + String url = "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=" + accessToken; + + Map payload = new HashMap<>(); + if (toUser != null && !toUser.isEmpty()) payload.put("touser", toUser); + if (toParty != null && !toParty.isEmpty()) payload.put("toparty", toParty); + if (toTag != null && !toTag.isEmpty()) payload.put("totag", toTag); + payload.put("msgtype", "markdown"); + try { + payload.put("agentid", Integer.parseInt(agentId)); + } catch (NumberFormatException e) { + throw new ServiceException("agentId 必须为数字"); + } + + Map markdown = new HashMap<>(); + markdown.put("content", content); + payload.put("markdown", markdown); + if (safe != null) payload.put("safe", safe); + if (enableIdTrans != null) payload.put("enable_id_trans", enableIdTrans); + if (enableDuplicateCheck != null) payload.put("enable_duplicate_check", enableDuplicateCheck); + if (duplicateCheckInterval != null) payload.put("duplicate_check_interval", duplicateCheckInterval); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + HttpEntity entity = new HttpEntity<>(JSONUtil.toJsonStr(payload), headers); + ResponseEntity resp = restTemplate.postForEntity(url, entity, String.class); + String body = resp.getBody(); + if (body == null) { + throw new ServiceException("企业微信 message/send 接口响应为空"); + } + return body; + } + + /** + * 发送企业微信文本卡片消息(textcard)。 + * + * @param accessToken 已获取的 access_token + * @param agentId 应用的 AgentId(数字) + * @param toUser 目标用户,多个用 | 分隔,可为空 + * @param toParty 目标部门,多个用 | 分隔,可为空 + * @param toTag 目标标签,多个用 | 分隔,可为空 + * @param title 卡片标题 + * @param description 卡片描述(支持少量 HTML:gray/normal/highlight 等) + * @param url 点击卡片跳转链接 + * @param btntxt 按钮文案(可选,默认“详情/更多”) + * @param enableIdTrans 是否开启 id 转译(可选) + * @param enableDuplicateCheck 是否开启重复消息检查(可选) + * @param duplicateCheckInterval 重复消息检查时间间隔(秒,可选) + * @return 企业微信原始响应 JSON 字符串 + */ + @Override + public String sendTextCardMessage(String accessToken, String agentId, String toUser, String toParty, String toTag, String title, String description, String url, + String btntxt, Integer enableIdTrans, Integer enableDuplicateCheck, Integer duplicateCheckInterval) { + String api = "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=" + accessToken; + + Map payload = new HashMap<>(); + if (toUser != null && !toUser.isEmpty()) payload.put("touser", toUser); + if (toParty != null && !toParty.isEmpty()) payload.put("toparty", toParty); + if (toTag != null && !toTag.isEmpty()) payload.put("totag", toTag); + payload.put("msgtype", "textcard"); + try { + payload.put("agentid", Integer.parseInt(agentId)); + } catch (NumberFormatException e) { + throw new ServiceException("agentId 必须为数字"); + } + + Map textcard = new HashMap<>(); + textcard.put("title", title); + textcard.put("description", description); + textcard.put("url", url); + if (btntxt != null && !btntxt.isEmpty()) textcard.put("btntxt", btntxt); + payload.put("textcard", textcard); + + if (enableIdTrans != null) payload.put("enable_id_trans", enableIdTrans); + if (enableDuplicateCheck != null) payload.put("enable_duplicate_check", enableDuplicateCheck); + if (duplicateCheckInterval != null) payload.put("duplicate_check_interval", duplicateCheckInterval); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + HttpEntity entity = new HttpEntity<>(JSONUtil.toJsonStr(payload), headers); + ResponseEntity resp = restTemplate.postForEntity(api, entity, String.class); + String body = resp.getBody(); + if (body == null) { + throw new ServiceException("企业微信 message/send 接口响应为空"); + } + return body; + } +}