From e31b7dbb28e3a2bce4cebd8d661479378889b19c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=BF=97=E6=B5=A9?= <8583167+chen-zh@user.noreply.gitee.com> Date: Sat, 25 Jul 2026 02:52:19 +0000 Subject: [PATCH] =?UTF-8?q?!93=20czh-20260725-getDistinctHandlerNames?= =?UTF-8?q?=E5=90=8C=E6=97=B6=E8=BF=94=E5=9B=9E=E5=B7=A5=E5=8F=B7+?= =?UTF-8?q?=E5=A7=93=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * czh-20260725-getDistinctHandlerNames同时返回工号+姓名 * czh-20260725-移除linkOpenMode和dataFormat默认值 * czh-20260725-移除接收人上限限制 * czh-20260725-IM模块code-review补充修复 * czh-20260725-code-review问题修复 --- .../modules/ksim/client/KsimApiClient.java | 7 +- .../ksim/controller/KsimTestController.java | 31 +--- .../modules/ksim/dto/KsimMsgSendDTO.java | 30 ++++ .../service/impl/KsimMessageServiceImpl.java | 38 ++--- .../impl/TaskApprovalOpinionServiceImpl.java | 150 ++++++++++-------- 5 files changed, 143 insertions(+), 113 deletions(-) diff --git a/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/client/KsimApiClient.java b/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/client/KsimApiClient.java index 0f07f05..bb0528c 100644 --- a/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/client/KsimApiClient.java +++ b/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/client/KsimApiClient.java @@ -58,12 +58,13 @@ public class KsimApiClient { HttpHeaders headers = jsonHeaders(); headers.add("imOpenApiToken", token); - String bodyJson = JSON.toJSONString(dto); + // 直接 POJO→JSONObject,避免 DTO→String→JSONObject 三重序列化 + JSONObject body = (JSONObject) JSON.toJSON(dto); String url = ksimProperties.getHost() + "/api-open/v1/msg/sendBatch"; - log.info("【即时通推送-消息发送】请求体: {}", bodyJson); + log.debug("【即时通推送-消息发送】请求体: {}", body.toJSONString()); ResponseEntity resp = RestUtil.request(url, HttpMethod.POST, headers, null, - JSON.parseObject(bodyJson), JSONObject.class); + body, JSONObject.class); JSONObject respBody = resp.getBody(); int code = respBody != null ? respBody.getIntValue("code") : -1; diff --git a/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/controller/KsimTestController.java b/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/controller/KsimTestController.java index 95c4cb8..d73a871 100644 --- a/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/controller/KsimTestController.java +++ b/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/controller/KsimTestController.java @@ -19,7 +19,6 @@ import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.stream.Collectors; @Slf4j @@ -28,8 +27,6 @@ import java.util.stream.Collectors; @RequestMapping("/ksim/test") public class KsimTestController { - private static final Set ALLOWED_LINK_OPEN_MODES = Set.of("Exe", "Web", "None"); - @PostMapping("/sendBatch") public Result> sendBatch(@RequestBody Map params) { Map result = new LinkedHashMap<>(); @@ -47,8 +44,8 @@ public class KsimTestController { String content = (String) params.getOrDefault("content", ""); String linkUrl = (String) params.getOrDefault("linkUrl", ""); String linkTitle = (String) params.getOrDefault("linkTitle", ""); - String linkOpenMode = (String) params.getOrDefault("linkOpenMode", "Exe"); - String dataFormat = (String) params.getOrDefault("dataFormat", "JsonListView"); + String linkOpenMode = (String) params.getOrDefault("linkOpenMode", ""); + String dataFormat = (String) params.getOrDefault("dataFormat", ""); String data = (String) params.getOrDefault("data", ""); // --- Step 1: 创建 Token --- @@ -82,25 +79,11 @@ public class KsimTestController { dto.setToUserValueList(toUserValueList); dto.setTitle(title); dto.setContent(content); - // 可选字段:传了才设置,不传则不发给 ksim - if (data != null && !data.isEmpty()) { - dto.setData(data); - } - if (linkOpenMode != null && !linkOpenMode.isEmpty()) { - if (!ALLOWED_LINK_OPEN_MODES.contains(linkOpenMode)) { - log.warn("【即时通测试-sendBatch】非法的 linkOpenMode: {}, 已跳过", linkOpenMode); - } else { - dto.setLinkOpenMode(linkOpenMode); - } - } - if (dataFormat != null && !dataFormat.isEmpty()) { - dto.setDataFormat(dataFormat); - } - if (linkTitle != null && !linkTitle.isEmpty()) { - dto.setLinkTitle(linkTitle); - } - if (linkUrl != null && !linkUrl.isEmpty()) { - dto.setLinkUrl(linkUrl); + // 可选字段统一通过 setOptionalFields 设置 + try { + dto.setOptionalFields(data, linkOpenMode, dataFormat, linkTitle, linkUrl); + } catch (IllegalArgumentException e) { + log.warn("【即时通测试-sendBatch】可选字段校验失败: {}", e.getMessage()); } String msgUrl = host + "/api-open/v1/msg/sendBatch"; diff --git a/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/dto/KsimMsgSendDTO.java b/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/dto/KsimMsgSendDTO.java index 995ccab..a5bf238 100644 --- a/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/dto/KsimMsgSendDTO.java +++ b/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/dto/KsimMsgSendDTO.java @@ -1,12 +1,15 @@ package org.jeecg.modules.ksim.dto; import java.util.List; +import java.util.Set; /** * 对应即时通文档 3.4.3 ReqMsgSendDTO */ public class KsimMsgSendDTO { + public static final Set ALLOWED_LINK_OPEN_MODES = Set.of("Exe", "Web", "None"); + private String title; private String content; private String toUserKeyType; @@ -17,6 +20,33 @@ public class KsimMsgSendDTO { private String linkTitle; private String linkUrl; + /** + * 设置可选字段。仅当值非空时才设置,避免向 ksim 发送 null 字段。 + * linkOpenMode 会校验白名单。 + */ + public void setOptionalFields(String data, String linkOpenMode, String dataFormat, + String linkTitle, String linkUrl) { + if (data != null && !data.isEmpty()) { + this.data = data; + } + if (linkOpenMode != null && !linkOpenMode.isEmpty()) { + if (!ALLOWED_LINK_OPEN_MODES.contains(linkOpenMode)) { + throw new IllegalArgumentException("非法的 linkOpenMode: " + linkOpenMode + + ",允许的值: " + ALLOWED_LINK_OPEN_MODES); + } + this.linkOpenMode = linkOpenMode; + } + if (dataFormat != null && !dataFormat.isEmpty()) { + this.dataFormat = dataFormat; + } + if (linkTitle != null && !linkTitle.isEmpty()) { + this.linkTitle = linkTitle; + } + if (linkUrl != null && !linkUrl.isEmpty()) { + this.linkUrl = linkUrl; + } + } + public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getContent() { return content; } diff --git a/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/service/impl/KsimMessageServiceImpl.java b/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/service/impl/KsimMessageServiceImpl.java index 7fe3d6c..bf620bd 100644 --- a/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/service/impl/KsimMessageServiceImpl.java +++ b/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/service/impl/KsimMessageServiceImpl.java @@ -3,6 +3,7 @@ package org.jeecg.modules.ksim.service.impl; import com.alibaba.fastjson2.JSONObject; import lombok.extern.slf4j.Slf4j; import org.jeecg.common.api.vo.Result; +import org.jeecg.common.util.oConvertUtils; import org.jeecg.modules.ksim.client.KsimApiClient; import org.jeecg.modules.ksim.dto.KsimApiResult; import org.jeecg.modules.ksim.dto.KsimMsgSendDTO; @@ -12,14 +13,11 @@ import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.List; -import java.util.Set; @Slf4j @Service public class KsimMessageServiceImpl implements IKsimMessageService { - private static final Set ALLOWED_LINK_OPEN_MODES = Set.of("Exe", "Web", "None"); - @Resource private KsimTokenManager tokenManager; @@ -31,6 +29,16 @@ public class KsimMessageServiceImpl implements IKsimMessageService { String title, String content, String linkUrl, String linkTitle, String data, String linkOpenMode, String dataFormat) { + if (toUserValueList == null || toUserValueList.isEmpty()) { + log.warn("【即时通推送-sendBatch】toUserValueList为空,跳过发送"); + return Result.error("接收人列表为空"); + } + if (oConvertUtils.isEmpty(title)) { + log.warn("【即时通推送-sendBatch】title为空"); + } + if (oConvertUtils.isEmpty(content)) { + log.warn("【即时通推送-sendBatch】content为空"); + } String token = tokenManager.getToken(); KsimMsgSendDTO dto = new KsimMsgSendDTO(); @@ -38,25 +46,11 @@ public class KsimMessageServiceImpl implements IKsimMessageService { dto.setToUserValueList(toUserValueList); dto.setTitle(title); dto.setContent(content); - // 以下可选字段:传了才设置,不传则不发给 ksim - if (data != null && !data.isEmpty()) { - dto.setData(data); - } - if (linkOpenMode != null && !linkOpenMode.isEmpty()) { - if (!ALLOWED_LINK_OPEN_MODES.contains(linkOpenMode)) { - log.warn("【即时通推送-sendBatch】非法的 linkOpenMode: {}, 已跳过", linkOpenMode); - } else { - dto.setLinkOpenMode(linkOpenMode); - } - } - if (dataFormat != null && !dataFormat.isEmpty()) { - dto.setDataFormat(dataFormat); - } - if (linkTitle != null && !linkTitle.isEmpty()) { - dto.setLinkTitle(linkTitle); - } - if (linkUrl != null && !linkUrl.isEmpty()) { - dto.setLinkUrl(linkUrl); + // 可选字段统一通过 setOptionalFields 设置,避免与测试控制器重复 + try { + dto.setOptionalFields(data, linkOpenMode, dataFormat, linkTitle, linkUrl); + } catch (IllegalArgumentException e) { + log.warn("【即时通推送-sendBatch】可选字段校验失败: {}", e.getMessage()); } KsimApiResult apiResult = apiClient.sendMsg(token, dto); diff --git a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/taskapprovalopinion/service/impl/TaskApprovalOpinionServiceImpl.java b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/taskapprovalopinion/service/impl/TaskApprovalOpinionServiceImpl.java index fba7599..a4779ce 100644 --- a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/taskapprovalopinion/service/impl/TaskApprovalOpinionServiceImpl.java +++ b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/taskapprovalopinion/service/impl/TaskApprovalOpinionServiceImpl.java @@ -21,16 +21,14 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; +import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; -import java.util.LinkedHashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; /** * 审批意见快照 Service 实现。 @@ -54,7 +52,6 @@ public class TaskApprovalOpinionServiceImpl extends ServiceImpl saveSupplementOpinion(SupplementApprovalOpinionDTO supplementDTO) { if (supplementDTO == null || oConvertUtils.isEmpty(supplementDTO.getProcessInstId())) { throw new JeecgBootException("流程实例ID不能为空"); @@ -93,41 +90,61 @@ public class TaskApprovalOpinionServiceImpl extends ServiceImpl doPushNotification(TaskApprovalOpinion opinion, ApprovalDeptInfo deptInfo, + LoginUser loginUser, String opinionText, String frontendBizTitle) { Map pushResult = new HashMap<>(); + // 确保所有代码路径返回一致的 key 集合 + pushResult.put("pushed", false); + pushResult.put("toUsers", Collections.emptyList()); + pushResult.put("content", ""); + pushResult.put("title", ""); + pushResult.put("error", ""); + try { - if (isSuoLeader(loginUser, deptInfo.id)) { - String bizTitle = oConvertUtils.isNotEmpty(supplementDTO.getBizTitle()) - ? supplementDTO.getBizTitle() - : getBizTitle(opinion.getTaskTaskId(), opinion.getProcessInstId()); - String content = "所领导" + (oConvertUtils.isNotEmpty(loginUser.getRealname()) ? loginUser.getRealname() : loginUser.getUsername()) - + "增补意见:" + opinionText; - List handlerNames = getDistinctHandlerNames(opinion.getProcessInstId(), loginUser.getUsername(), loginUser.getRealname()); - pushResult.put("pushed", !handlerNames.isEmpty()); - pushResult.put("toUsers", handlerNames); - pushResult.put("content", content); - pushResult.put("title", bizTitle); - if (!handlerNames.isEmpty()) { - Result sendResult = ksimMessageService.sendBatch("Account", handlerNames, bizTitle, content, - null, null, null, null, null); - if (sendResult != null && sendResult.isSuccess()) { - log.info("【增补意见-即时通推送】已发送, processInstId: {}, toUserCount: {}, toUsers: {}, title: {}, content: {}", - opinion.getProcessInstId(), handlerNames.size(), handlerNames, bizTitle, content); - } else { - log.warn("【增补意见-即时通推送】发送失败, reason: {}", - sendResult != null ? sendResult.getMessage() : "null"); - pushResult.put("pushed", false); - } + if (!isSuoLeader(deptInfo)) { + return pushResult; + } + String bizTitle = oConvertUtils.isNotEmpty(frontendBizTitle) + ? frontendBizTitle + : getBizTitle(opinion.getTaskTaskId(), opinion.getProcessInstId()); + String content = "所领导" + (oConvertUtils.isNotEmpty(loginUser.getRealname()) ? loginUser.getRealname() : loginUser.getUsername()) + + "增补意见:" + opinionText; + LinkedHashMap handlerMap = getDistinctHandlerNames(opinion.getProcessInstId(), loginUser.getUsername(), loginUser.getRealname()); + List handlerIds = new ArrayList<>(handlerMap.keySet()); + List handlerNames = new ArrayList<>(handlerMap.values()); + pushResult.put("pushed", !handlerIds.isEmpty()); + pushResult.put("toUsers", handlerNames); + pushResult.put("content", content); + pushResult.put("title", bizTitle); + if (!handlerIds.isEmpty()) { + Result sendResult = ksimMessageService.sendBatch("Account", handlerIds, bizTitle, content, + null, null, null, null, null); + if (sendResult != null && sendResult.isSuccess()) { + log.info("【增补意见-即时通推送】已发送, processInstId: {}, toUserCount: {}, toUsers: {}, title: {}, content: {}", + opinion.getProcessInstId(), handlerIds.size(), handlerNames, bizTitle, content); + } else { + String reason = sendResult != null ? sendResult.getMessage() : "null"; + log.warn("【增补意见-即时通推送】发送失败, reason: {}", reason); + pushResult.put("pushed", false); + pushResult.put("error", reason); } - } else { - pushResult.put("pushed", false); } } catch (Exception e) { - log.warn("【增补意见-即时通推送】失败,不影响增补意见保存, error: {}", e.getMessage()); + String errMsg = e.getMessage() != null ? e.getMessage() : e.getClass().getSimpleName(); + log.warn("【增补意见-即时通推送】失败,不影响增补意见保存, error: {}", errMsg); pushResult.put("pushed", false); - pushResult.put("error", e.getMessage()); + pushResult.put("error", errMsg); } return pushResult; } @@ -144,6 +161,7 @@ public class TaskApprovalOpinionServiceImpl extends ServiceImpl> rows = jdbcTemplate.queryForList( + "SELECT depart_type FROM sys_depart WHERE id = ?", deptId); + if (!rows.isEmpty()) { + departType = getStringValue(rows.get(0), "depart_type"); + } + } catch (Exception e) { + log.warn("【增补意见-审批意见】查询部门类型失败, deptId={}", deptId, e); + } + } + return new ApprovalDeptInfo(deptId, deptName, deptCode, departType); } private String resolveDeptName(String username) { @@ -176,7 +211,7 @@ public class TaskApprovalOpinionServiceImpl extends ServiceImpl> rows = jdbcTemplate.queryForList( - "select depart_type from sys_depart where id = ?", deptId); - if (!rows.isEmpty()) { - Object val = rows.get(0).get("depart_type"); - return val != null && "1".equals(String.valueOf(val)); - } - } catch (Exception e) { - log.debug("查询部门类型失败, deptId={}", deptId, e); - } - return false; + private boolean isSuoLeader(ApprovalDeptInfo deptInfo) { + return "1".equals(deptInfo.departType); } private String getBizTitle(String taskTaskId, String processInstId) { @@ -287,15 +306,16 @@ public class TaskApprovalOpinionServiceImpl extends ServiceImpl getDistinctHandlerNames(String processInstId, String excludeUserId, String excludeRealName) { + /** userId → userName 映射,保持插入顺序 */ + private LinkedHashMap getDistinctHandlerNames(String processInstId, String excludeUserId, String excludeRealName) { if (jdbcTemplate == null || oConvertUtils.isEmpty(processInstId)) { - return Collections.emptyList(); + return new LinkedHashMap<>(); } try { List> rows = jdbcTemplate.queryForList( - "select distinct op_user_id, op_user_name from task_approval_opinion where process_inst_id = ?", + "SELECT DISTINCT op_user_id, op_user_name FROM task_approval_opinion WHERE process_inst_id = ?", processInstId); - Set userIds = new LinkedHashSet<>(); + LinkedHashMap handlerMap = new LinkedHashMap<>(); for (Map row : rows) { Object idVal = row.get("op_user_id"); Object nameVal = row.get("op_user_name"); @@ -318,14 +338,14 @@ public class TaskApprovalOpinionServiceImpl extends ServiceImpl(); } } @@ -333,11 +353,13 @@ public class TaskApprovalOpinionServiceImpl extends ServiceImpl