!88 czh-20260723-可选字段传了才设值不传不发ksim
* czh-20260723-可选字段传了才设值不传不发ksim * czh-20260723-测试接口字段名toUserValues统一改为toUserValueList * czh-20260723-IM模块重构为sendBatch批量发送-移除定时刷新改用RedisTTL过期 * czh-20260723-IM模块linkOpenMode增加白名单校验Exe-Web-None
This commit is contained in:
+2
-2
@@ -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;
|
||||
}
|
||||
|
||||
-9
@@ -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 {
|
||||
}
|
||||
+41
-10
@@ -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<Map<String, Object>> send(@RequestBody Map<String, Object> params) {
|
||||
private static final Set<String> ALLOWED_LINK_OPEN_MODES = Set.of("Exe", "Web", "None");
|
||||
|
||||
@PostMapping("/sendBatch")
|
||||
public Result<Map<String, Object>> sendBatch(@RequestBody Map<String, Object> params) {
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
Map<String, Object> 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<String> 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<String> 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();
|
||||
}
|
||||
|
||||
+8
-6
@@ -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<String> 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<String> getToUserValueList() { return toUserValueList; }
|
||||
public void setToUserValueList(List<String> 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; }
|
||||
|
||||
+6
-4
@@ -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<String> sendMessage(String toUserKeyType, String toUserValue,
|
||||
String title, String content,
|
||||
String linkUrl, String linkTitle,
|
||||
Object data, String linkOpenMode, String dataFormat);
|
||||
Result<String> sendBatch(String toUserKeyType, List<String> toUserValueList,
|
||||
String title, String content,
|
||||
String linkUrl, String linkTitle,
|
||||
String data, String linkOpenMode, String dataFormat);
|
||||
}
|
||||
|
||||
+31
-15
@@ -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<String> 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<String> sendMessage(String toUserKeyType, String toUserValue,
|
||||
String title, String content,
|
||||
String linkUrl, String linkTitle,
|
||||
Object data, String linkOpenMode, String dataFormat) {
|
||||
public Result<String> sendBatch(String toUserKeyType, List<String> 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<JSONObject> 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);
|
||||
}
|
||||
}
|
||||
|
||||
+9
-29
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user