From 8b747dccda1deaa4c5d8fb0d3e82dc9254c070a2 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: Thu, 23 Jul 2026 07:08:12 +0000 Subject: [PATCH] =?UTF-8?q?!88=20czh-20260723-=E5=8F=AF=E9=80=89=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=E4=BC=A0=E4=BA=86=E6=89=8D=E8=AE=BE=E5=80=BC=E4=B8=8D?= =?UTF-8?q?=E4=BC=A0=E4=B8=8D=E5=8F=91ksim?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * czh-20260723-可选字段传了才设值不传不发ksim * czh-20260723-测试接口字段名toUserValues统一改为toUserValueList * czh-20260723-IM模块重构为sendBatch批量发送-移除定时刷新改用RedisTTL过期 * czh-20260723-IM模块linkOpenMode增加白名单校验Exe-Web-None --- .../modules/ksim/client/KsimApiClient.java | 4 +- .../ksim/config/KsimScheduleConfig.java | 9 ---- .../ksim/controller/KsimTestController.java | 51 +++++++++++++++---- .../modules/ksim/dto/KsimMsgSendDTO.java | 14 ++--- .../ksim/service/IKsimMessageService.java | 10 ++-- .../service/impl/KsimMessageServiceImpl.java | 46 +++++++++++------ .../modules/ksim/token/KsimTokenManager.java | 38 ++++---------- 7 files changed, 97 insertions(+), 75 deletions(-) delete mode 100644 jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/config/KsimScheduleConfig.java 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 dd1c08c..1188172 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 @@ -66,8 +66,8 @@ public class KsimApiClient { JSONObject respBody = resp.getBody(); int code = respBody != null ? respBody.getIntValue("code") : -1; - log.info("【即时通推送-消息发送】请求完成, toUser: {}, title: {}, code: {}", - dto.getToUserValue(), dto.getTitle(), code); + log.info("【即时通推送-消息发送】请求完成, toUserCount: {}, title: {}, code: {}", + dto.getToUserValueList() != null ? dto.getToUserValueList().size() : 0, dto.getTitle(), code); return respBody != null ? respBody.toJavaObject(KsimApiResult.class) : null; } diff --git a/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/config/KsimScheduleConfig.java b/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/config/KsimScheduleConfig.java deleted file mode 100644 index 9cb70e4..0000000 --- a/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/config/KsimScheduleConfig.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.jeecg.modules.ksim.config; - -import org.springframework.context.annotation.Configuration; -import org.springframework.scheduling.annotation.EnableScheduling; - -@Configuration -@EnableScheduling -public class KsimScheduleConfig { -} 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 22970b1..926fd85 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 @@ -15,8 +15,12 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +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 @RestController @@ -24,8 +28,10 @@ import java.util.Map; @RequestMapping("/ksim/test") public class KsimTestController { - @PostMapping("/send") - public Result> send(@RequestBody Map params) { + 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<>(); Map requestInfo = new LinkedHashMap<>(); result.put("request", requestInfo); @@ -35,14 +41,15 @@ public class KsimTestController { String appId = (String) params.getOrDefault("appId", ""); String appSecret = (String) params.getOrDefault("appSecret", ""); String toUserKeyType = (String) params.getOrDefault("toUserKeyType", ""); - String toUserValue = (String) params.getOrDefault("toUserValue", ""); + String toUserValueListStr = (String) params.getOrDefault("toUserValueList", ""); + List toUserValueList = parseList(toUserValueListStr); String title = (String) params.getOrDefault("title", ""); 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"); - Object data = params.get("data"); + String data = (String) params.getOrDefault("data", ""); // --- Step 1: 创建 Token --- JSONObject tokenBody = new JSONObject(); @@ -72,12 +79,23 @@ public class KsimTestController { // --- Step 2: 发送消息 --- KsimMsgSendDTO dto = new KsimMsgSendDTO(); dto.setToUserKeyType(toUserKeyType); - dto.setToUserValue(toUserValue); + dto.setToUserValueList(toUserValueList); dto.setTitle(title); dto.setContent(content); - dto.setData(data != null ? data : new JSONObject()); - dto.setLinkOpenMode(linkOpenMode != null && !linkOpenMode.isEmpty() ? linkOpenMode : "Exe"); - dto.setDataFormat(dataFormat != null && !dataFormat.isEmpty() ? dataFormat : "JsonListView"); + // 可选字段:传了才设置,不传则不发给 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); } @@ -98,7 +116,7 @@ public class KsimTestController { JSONObject msgRespBody = msgResp.getBody(); result.put("msgResponse", msgRespBody); - log.info("【即时通测试-消息发送】完成, code: {}", + log.info("【即时通测试-sendBatch】完成, code: {}", msgRespBody != null ? msgRespBody.getIntValue("code") : -1); if (msgRespBody != null) { @@ -114,7 +132,7 @@ public class KsimTestController { } } catch (Exception e) { - log.error("【即时通测试-发送】异常, error: {}", e.getMessage(), e); + log.error("【即时通测试-sendBatch】异常, error: {}", e.getMessage(), e); result.put("success", false); result.put("errorMessage", e.getMessage()); } @@ -122,6 +140,19 @@ public class KsimTestController { return Result.OK(result); } + /** + * 逗号分隔字符串 → List + */ + private List parseList(String str) { + if (str == null || str.trim().isEmpty()) { + return Arrays.asList(); + } + return Arrays.stream(str.split(",")) + .map(String::trim) + .filter(s -> !s.isEmpty()) + .collect(Collectors.toList()); + } + private HttpHeaders jsonHeaders() { return RestUtil.getHeaderApplicationJson(); } 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 e995553..995ccab 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,5 +1,7 @@ package org.jeecg.modules.ksim.dto; +import java.util.List; + /** * 对应即时通文档 3.4.3 ReqMsgSendDTO */ @@ -8,8 +10,8 @@ public class KsimMsgSendDTO { private String title; private String content; private String toUserKeyType; - private String toUserValue; - private Object data; + private List toUserValueList; + private String data; private String dataFormat; private String linkOpenMode; private String linkTitle; @@ -21,10 +23,10 @@ public class KsimMsgSendDTO { public void setContent(String content) { this.content = content; } public String getToUserKeyType() { return toUserKeyType; } public void setToUserKeyType(String toUserKeyType) { this.toUserKeyType = toUserKeyType; } - public String getToUserValue() { return toUserValue; } - public void setToUserValue(String toUserValue) { this.toUserValue = toUserValue; } - public Object getData() { return data; } - public void setData(Object data) { this.data = data; } + public List getToUserValueList() { return toUserValueList; } + public void setToUserValueList(List toUserValueList) { this.toUserValueList = toUserValueList; } + public String getData() { return data; } + public void setData(String data) { this.data = data; } public String getDataFormat() { return dataFormat; } public void setDataFormat(String dataFormat) { this.dataFormat = dataFormat; } public String getLinkOpenMode() { return linkOpenMode; } diff --git a/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/service/IKsimMessageService.java b/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/service/IKsimMessageService.java index 1627e56..2aaaf01 100644 --- a/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/service/IKsimMessageService.java +++ b/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/service/IKsimMessageService.java @@ -2,13 +2,15 @@ package org.jeecg.modules.ksim.service; import org.jeecg.common.api.vo.Result; +import java.util.List; + /** * 即时通消息推送服务(对外唯一入口) */ public interface IKsimMessageService { - Result sendMessage(String toUserKeyType, String toUserValue, - String title, String content, - String linkUrl, String linkTitle, - Object data, String linkOpenMode, String dataFormat); + Result sendBatch(String toUserKeyType, List toUserValueList, + String title, String content, + String linkUrl, String linkTitle, + String data, String linkOpenMode, String dataFormat); } 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 d395c77..06065da 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 @@ -11,10 +11,15 @@ import org.jeecg.modules.ksim.token.KsimTokenManager; 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; @@ -22,23 +27,31 @@ public class KsimMessageServiceImpl implements IKsimMessageService { private KsimApiClient apiClient; @Override - public Result sendMessage(String toUserKeyType, String toUserValue, - String title, String content, - String linkUrl, String linkTitle, - Object data, String linkOpenMode, String dataFormat) { + public Result sendBatch(String toUserKeyType, List toUserValueList, + String title, String content, + String linkUrl, String linkTitle, + String data, String linkOpenMode, String dataFormat) { String token = tokenManager.getToken(); KsimMsgSendDTO dto = new KsimMsgSendDTO(); dto.setToUserKeyType(toUserKeyType); - dto.setToUserValue(toUserValue); + dto.setToUserValueList(toUserValueList); dto.setTitle(title); dto.setContent(content); - // data 默认 {} - dto.setData(data != null ? data : new JSONObject()); - // linkOpenMode 默认 "Exe" - dto.setLinkOpenMode(linkOpenMode != null && !linkOpenMode.isEmpty() ? linkOpenMode : "Exe"); - // dataFormat 默认 "JsonListView" - dto.setDataFormat(dataFormat != null && !dataFormat.isEmpty() ? dataFormat : "JsonListView"); + // 以下可选字段:传了才设置,不传则不发给 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); } @@ -48,18 +61,21 @@ public class KsimMessageServiceImpl implements IKsimMessageService { KsimApiResult apiResult = apiClient.sendMsg(token, dto); if (apiResult == null) { - log.error("【即时通推送-消息发送】响应为空, toUser: {}, title: {}", toUserValue, title); + log.error("【即时通推送-sendBatch】响应为空, toUserCount: {}, title: {}", + toUserValueList != null ? toUserValueList.size() : 0, title); return Result.error("即时通响应为空"); } if (!apiResult.isSuccess()) { - log.error("【即时通推送-消息发送】发送失败, code: {}, message: {}, toUser: {}, title: {}", - apiResult.getCode(), apiResult.getMessage(), toUserValue, title); + log.error("【即时通推送-sendBatch】发送失败, code: {}, message: {}, toUserCount: {}, title: {}", + apiResult.getCode(), apiResult.getMessage(), + toUserValueList != null ? toUserValueList.size() : 0, title); return Result.error(apiResult.getCode(), "即时通发送失败: " + apiResult.getMessage()); } String messageId = apiResult.getDataBody() != null ? apiResult.getDataBody().getString("messageId") : ""; - log.info("【即时通推送-消息发送】成功, messageId: {}, toUser: {}, title: {}", messageId, toUserValue, title); + log.info("【即时通推送-sendBatch】成功, messageId: {}, toUserCount: {}, title: {}", messageId, + toUserValueList != null ? toUserValueList.size() : 0, title); return Result.OK(messageId); } } diff --git a/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/token/KsimTokenManager.java b/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/token/KsimTokenManager.java index d6b30b7..0a6cc77 100644 --- a/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/token/KsimTokenManager.java +++ b/jeecg-boot-module/jeecg-module-im/src/main/java/org/jeecg/modules/ksim/token/KsimTokenManager.java @@ -6,12 +6,15 @@ import org.jeecg.common.exception.JeecgBootException; import org.jeecg.modules.ksim.client.KsimApiClient; import org.jeecg.modules.ksim.config.KsimProperties; import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.util.concurrent.TimeUnit; +/** + * Token 管理:首次获取或 Redis 过期时自动创建,无定时任务。 + * Redis TTL = tokenTimeoutSeconds - 600,提前 10 分钟过期确保 Token 始终有效。 + */ @Slf4j @Component public class KsimTokenManager { @@ -35,32 +38,8 @@ public class KsimTokenManager { return createAndCache(); } - @Scheduled(cron = "0 0 * * * ?") - public void refreshTask() { - log.info("【即时通推送-Token定时刷新】开始"); - try { - Object old = redisTemplate.opsForValue().get(TOKEN_KEY); - JSONObject result; - int timeout = ksimProperties.getTokenTimeoutSeconds(); - if (old != null) { - try { - result = ksimApiClient.refreshToken(old.toString(), timeout); - } catch (Exception e) { - log.warn("【即时通推送-Token定时刷新】刷新失败,回退到创建, error: {}", e.getMessage()); - result = ksimApiClient.createToken(ksimProperties.getAppId(), ksimProperties.getAppSecret(), timeout); - } - } else { - result = ksimApiClient.createToken(ksimProperties.getAppId(), ksimProperties.getAppSecret(), timeout); - } - cacheToken(result, timeout); - log.info("【即时通推送-Token定时刷新】完成"); - } catch (Exception e) { - log.error("【即时通推送-Token定时刷新】失败, error: {}", e.getMessage(), e); - } - } - private String createAndCache() { - log.info("【即时通推送-Token懒加载】创建新Token"); + log.info("【即时通推送-Token】Redis无缓存或已过期,创建新Token"); JSONObject result = ksimApiClient.createToken( ksimProperties.getAppId(), ksimProperties.getAppSecret(), ksimProperties.getTokenTimeoutSeconds()); return cacheToken(result, ksimProperties.getTokenTimeoutSeconds()); @@ -68,13 +47,14 @@ public class KsimTokenManager { private String cacheToken(JSONObject result, int timeoutSeconds) { if (result == null || result.getJSONObject("dataBody") == null) { - throw new JeecgBootException("【即时通推送】Token创建/刷新返回为空"); + throw new JeecgBootException("【即时通推送】Token创建返回为空"); } JSONObject dataBody = result.getJSONObject("dataBody"); String token = dataBody.getString("token"); - int ttl = Math.max(timeoutSeconds - 60, 60); + // TTL 提前 600s,确保 Token 不会在有效期内被 Redis 误返回 + int ttl = Math.max(timeoutSeconds - 600, 60); redisTemplate.opsForValue().set(TOKEN_KEY, token, ttl, TimeUnit.SECONDS); - log.info("【即时通推送-Token缓存】已写入Redis, ttl: {}s", ttl); + log.info("【即时通推送-Token】已写入Redis, ttl: {}s", ttl); return token; } }