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;
+ }
+}