!84 czh-20260717-修复-测试接口补充linkTitle参数
* czh-20260717-修复-测试接口补充linkTitle参数 * czh-20260717-新增-sendMessage增加linkTitle参数 * czh-20260717-新增-sendMessage增加data参数支持 * czh-20260717-优化-去掉bizId和createDatetime字段 * czh-20260717-修复system-start缺少jeecg-module-im依赖 * czh-20260717-修复测试接口SSRF和日志泄漏问题 * czh-20260717-新增即时通消息推送模块及测试接口
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>jeecg-boot-module</artifactId>
|
||||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<version>${jeecgProjectVersion}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>jeecg-module-im</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jeecgframework.boot</groupId>
|
||||
<artifactId>jeecg-boot-base-core</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
package org.jeecg.modules.ksim.client;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.util.RestUtil;
|
||||
import org.jeecg.modules.ksim.config.KsimProperties;
|
||||
import org.jeecg.modules.ksim.dto.KsimApiResult;
|
||||
import org.jeecg.modules.ksim.dto.KsimMsgSendDTO;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class KsimApiClient {
|
||||
|
||||
@Resource
|
||||
private KsimProperties ksimProperties;
|
||||
|
||||
private HttpHeaders jsonHeaders() {
|
||||
return RestUtil.getHeaderApplicationJson();
|
||||
}
|
||||
|
||||
public JSONObject createToken(String appId, String appSecret, int timeoutSeconds) {
|
||||
JSONObject body = new JSONObject();
|
||||
body.put("appId", appId);
|
||||
body.put("appSecret", appSecret);
|
||||
body.put("tokenTimeoutSeconds", timeoutSeconds);
|
||||
|
||||
String url = ksimProperties.getHost() + "/api-open/v1/token/create";
|
||||
ResponseEntity<JSONObject> resp = RestUtil.request(url, HttpMethod.POST, jsonHeaders(), null, body, JSONObject.class);
|
||||
|
||||
log.info("【即时通推送-Token创建】请求完成, appId: {}, 获取Token: {}",
|
||||
appId, resp.getBody() != null && resp.getBody().getJSONObject("dataBody") != null ? "成功" : "失败");
|
||||
return resp.getBody();
|
||||
}
|
||||
|
||||
public JSONObject refreshToken(String oldToken, int timeoutSeconds) {
|
||||
JSONObject body = new JSONObject();
|
||||
body.put("tokenTimeoutSeconds", timeoutSeconds);
|
||||
|
||||
HttpHeaders headers = jsonHeaders();
|
||||
headers.add("imOpenApiToken", oldToken);
|
||||
|
||||
String url = ksimProperties.getHost() + "/api-open/v1/token/refresh";
|
||||
ResponseEntity<JSONObject> resp = RestUtil.request(url, HttpMethod.POST, headers, null, body, JSONObject.class);
|
||||
|
||||
log.info("【即时通推送-Token刷新】请求完成, 获取Token: {}",
|
||||
resp.getBody() != null && resp.getBody().getJSONObject("dataBody") != null ? "成功" : "失败");
|
||||
return resp.getBody();
|
||||
}
|
||||
|
||||
public KsimApiResult<JSONObject> sendMsg(String token, KsimMsgSendDTO dto) {
|
||||
HttpHeaders headers = jsonHeaders();
|
||||
headers.add("imOpenApiToken", token);
|
||||
|
||||
String bodyJson = JSON.toJSONString(dto);
|
||||
String url = ksimProperties.getHost() + "/api-open/v1/msg/send";
|
||||
|
||||
ResponseEntity<JSONObject> resp = RestUtil.request(url, HttpMethod.POST, headers, null,
|
||||
JSON.parseObject(bodyJson), JSONObject.class);
|
||||
|
||||
JSONObject respBody = resp.getBody();
|
||||
int code = respBody != null ? respBody.getIntValue("code") : -1;
|
||||
log.info("【即时通推送-消息发送】请求完成, toUser: {}, title: {}, code: {}",
|
||||
dto.getToUserValue(), dto.getTitle(), code);
|
||||
|
||||
return respBody != null ? respBody.toJavaObject(KsimApiResult.class) : null;
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package org.jeecg.modules.ksim.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "ksim")
|
||||
public class KsimProperties {
|
||||
|
||||
/** 即时通服务地址,如 http://10.2.0.54:19010 */
|
||||
private String host = "http://10.2.0.54:19010";
|
||||
|
||||
/** 应用ID,在 application-{env}.yml 中配置 */
|
||||
private String appId;
|
||||
|
||||
/** 应用密钥,在 application-{env}.yml 中配置 */
|
||||
private String appSecret;
|
||||
|
||||
/** Token 有效期(秒),默认 7200(2小时) */
|
||||
private int tokenTimeoutSeconds = 7200;
|
||||
|
||||
public String getHost() { return host; }
|
||||
public void setHost(String host) { this.host = host; }
|
||||
public String getAppId() { return appId; }
|
||||
public void setAppId(String appId) { this.appId = appId; }
|
||||
public String getAppSecret() { return appSecret; }
|
||||
public void setAppSecret(String appSecret) { this.appSecret = appSecret; }
|
||||
public int getTokenTimeoutSeconds() { return tokenTimeoutSeconds; }
|
||||
public void setTokenTimeoutSeconds(int tokenTimeoutSeconds) { this.tokenTimeoutSeconds = tokenTimeoutSeconds; }
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package org.jeecg.modules.ksim.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
||||
@Configuration
|
||||
@EnableScheduling
|
||||
public class KsimScheduleConfig {
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
package org.jeecg.modules.ksim.controller;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.util.RestUtil;
|
||||
import org.jeecg.modules.ksim.dto.KsimMsgSendDTO;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
@Profile("dev")
|
||||
@RequestMapping("/ksim/test")
|
||||
public class KsimTestController {
|
||||
|
||||
@PostMapping("/send")
|
||||
public Result<Map<String, Object>> send(@RequestBody Map<String, Object> params) {
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
Map<String, Object> requestInfo = new LinkedHashMap<>();
|
||||
result.put("request", requestInfo);
|
||||
|
||||
try {
|
||||
String host = (String) params.getOrDefault("host", "");
|
||||
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 title = (String) params.getOrDefault("title", "");
|
||||
String content = (String) params.getOrDefault("content", "");
|
||||
String linkUrl = (String) params.getOrDefault("linkUrl", "");
|
||||
String linkTitle = (String) params.getOrDefault("linkTitle", "");
|
||||
Object data = params.get("data");
|
||||
|
||||
// --- Step 1: 创建 Token ---
|
||||
JSONObject tokenBody = new JSONObject();
|
||||
tokenBody.put("appId", appId);
|
||||
tokenBody.put("appSecret", appSecret);
|
||||
tokenBody.put("tokenTimeoutSeconds", 7200);
|
||||
|
||||
String tokenUrl = host + "/api-open/v1/token/create";
|
||||
requestInfo.put("tokenRequestUrl", tokenUrl);
|
||||
requestInfo.put("tokenRequestBody", tokenBody);
|
||||
|
||||
ResponseEntity<JSONObject> tokenResp = RestUtil.request(
|
||||
tokenUrl, HttpMethod.POST, jsonHeaders(), null, tokenBody, JSONObject.class);
|
||||
|
||||
JSONObject tokenRespBody = tokenResp.getBody();
|
||||
result.put("tokenResponse", tokenRespBody);
|
||||
log.info("【即时通测试-Token创建】完成, 成功: {}",
|
||||
tokenRespBody != null && tokenRespBody.getJSONObject("dataBody") != null);
|
||||
|
||||
if (tokenRespBody == null || tokenRespBody.getJSONObject("dataBody") == null) {
|
||||
result.put("success", false);
|
||||
result.put("errorMessage", "Token 创建失败:响应为空");
|
||||
return Result.OK(result);
|
||||
}
|
||||
String token = tokenRespBody.getJSONObject("dataBody").getString("token");
|
||||
|
||||
// --- Step 2: 发送消息 ---
|
||||
KsimMsgSendDTO dto = new KsimMsgSendDTO();
|
||||
dto.setToUserKeyType(toUserKeyType);
|
||||
dto.setToUserValue(toUserValue);
|
||||
dto.setTitle(title);
|
||||
dto.setContent(content);
|
||||
if (data != null) {
|
||||
dto.setData(data);
|
||||
}
|
||||
if (linkTitle != null && !linkTitle.isEmpty()) {
|
||||
dto.setLinkTitle(linkTitle);
|
||||
}
|
||||
if (linkUrl != null && !linkUrl.isEmpty()) {
|
||||
dto.setLinkUrl(linkUrl);
|
||||
dto.setLinkOpenMode("Web");
|
||||
}
|
||||
|
||||
String msgUrl = host + "/api-open/v1/msg/send";
|
||||
requestInfo.put("msgRequestUrl", msgUrl);
|
||||
requestInfo.put("msgRequestBody", dto);
|
||||
|
||||
HttpHeaders msgHeaders = jsonHeaders();
|
||||
msgHeaders.add("imOpenApiToken", token);
|
||||
|
||||
ResponseEntity<JSONObject> msgResp = RestUtil.request(
|
||||
msgUrl, HttpMethod.POST, msgHeaders, null,
|
||||
JSON.parseObject(JSON.toJSONString(dto)), JSONObject.class);
|
||||
|
||||
JSONObject msgRespBody = msgResp.getBody();
|
||||
result.put("msgResponse", msgRespBody);
|
||||
log.info("【即时通测试-消息发送】完成, code: {}",
|
||||
msgRespBody != null ? msgRespBody.getIntValue("code") : -1);
|
||||
|
||||
if (msgRespBody != null) {
|
||||
int code = msgRespBody.getIntValue("code");
|
||||
result.put("success", code == 0 || code == 200);
|
||||
if (msgRespBody.getJSONObject("dataBody") != null) {
|
||||
result.put("messageId", msgRespBody.getJSONObject("dataBody").getString("messageId"));
|
||||
}
|
||||
result.put("responseCode", code);
|
||||
} else {
|
||||
result.put("success", false);
|
||||
result.put("errorMessage", "消息发送失败:响应为空");
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("【即时通测试-发送】异常, error: {}", e.getMessage(), e);
|
||||
result.put("success", false);
|
||||
result.put("errorMessage", e.getMessage());
|
||||
}
|
||||
|
||||
return Result.OK(result);
|
||||
}
|
||||
|
||||
private HttpHeaders jsonHeaders() {
|
||||
return RestUtil.getHeaderApplicationJson();
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package org.jeecg.modules.ksim.dto;
|
||||
|
||||
import com.alibaba.fastjson.annotation.JSONField;
|
||||
|
||||
/**
|
||||
* 即时通 API 通用响应体
|
||||
*/
|
||||
public class KsimApiResult<T> {
|
||||
|
||||
private int code;
|
||||
private String message;
|
||||
private String timestamp;
|
||||
private String requestId;
|
||||
|
||||
@JSONField(name = "dataBody")
|
||||
private T dataBody;
|
||||
|
||||
public boolean isSuccess() {
|
||||
return code == 0 || code == 200;
|
||||
}
|
||||
|
||||
public int getCode() { return code; }
|
||||
public void setCode(int code) { this.code = code; }
|
||||
public String getMessage() { return message; }
|
||||
public void setMessage(String message) { this.message = message; }
|
||||
public String getTimestamp() { return timestamp; }
|
||||
public void setTimestamp(String timestamp) { this.timestamp = timestamp; }
|
||||
public String getRequestId() { return requestId; }
|
||||
public void setRequestId(String requestId) { this.requestId = requestId; }
|
||||
public T getDataBody() { return dataBody; }
|
||||
public void setDataBody(T dataBody) { this.dataBody = dataBody; }
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package org.jeecg.modules.ksim.dto;
|
||||
|
||||
/**
|
||||
* 对应即时通文档 3.4.3 ReqMsgSendDTO
|
||||
*/
|
||||
public class KsimMsgSendDTO {
|
||||
|
||||
private String title;
|
||||
private String content;
|
||||
private String toUserKeyType;
|
||||
private String toUserValue;
|
||||
private Object data;
|
||||
private String dataFormat;
|
||||
private String linkOpenMode;
|
||||
private String linkTitle;
|
||||
private String linkUrl;
|
||||
|
||||
public String getTitle() { return title; }
|
||||
public void setTitle(String title) { this.title = title; }
|
||||
public String getContent() { return content; }
|
||||
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 String getDataFormat() { return dataFormat; }
|
||||
public void setDataFormat(String dataFormat) { this.dataFormat = dataFormat; }
|
||||
public String getLinkOpenMode() { return linkOpenMode; }
|
||||
public void setLinkOpenMode(String linkOpenMode) { this.linkOpenMode = linkOpenMode; }
|
||||
public String getLinkTitle() { return linkTitle; }
|
||||
public void setLinkTitle(String linkTitle) { this.linkTitle = linkTitle; }
|
||||
public String getLinkUrl() { return linkUrl; }
|
||||
public void setLinkUrl(String linkUrl) { this.linkUrl = linkUrl; }
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
package org.jeecg.modules.ksim;
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package org.jeecg.modules.ksim.service;
|
||||
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
|
||||
/**
|
||||
* 即时通消息推送服务(对外唯一入口)
|
||||
*/
|
||||
public interface IKsimMessageService {
|
||||
|
||||
Result<String> sendMessage(String toUserKeyType, String toUserValue,
|
||||
String title, String content,
|
||||
String linkUrl, String linkTitle,
|
||||
Object data);
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
package org.jeecg.modules.ksim.service.impl;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.modules.ksim.client.KsimApiClient;
|
||||
import org.jeecg.modules.ksim.dto.KsimApiResult;
|
||||
import org.jeecg.modules.ksim.dto.KsimMsgSendDTO;
|
||||
import org.jeecg.modules.ksim.service.IKsimMessageService;
|
||||
import org.jeecg.modules.ksim.token.KsimTokenManager;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
@Slf4j
|
||||
@Service
|
||||
public class KsimMessageServiceImpl implements IKsimMessageService {
|
||||
|
||||
@Resource
|
||||
private KsimTokenManager tokenManager;
|
||||
|
||||
@Resource
|
||||
private KsimApiClient apiClient;
|
||||
|
||||
@Override
|
||||
public Result<String> sendMessage(String toUserKeyType, String toUserValue,
|
||||
String title, String content,
|
||||
String linkUrl, String linkTitle,
|
||||
Object data) {
|
||||
String token = tokenManager.getToken();
|
||||
|
||||
KsimMsgSendDTO dto = new KsimMsgSendDTO();
|
||||
dto.setToUserKeyType(toUserKeyType);
|
||||
dto.setToUserValue(toUserValue);
|
||||
dto.setTitle(title);
|
||||
dto.setContent(content);
|
||||
dto.setData(data);
|
||||
if (linkTitle != null && !linkTitle.isEmpty()) {
|
||||
dto.setLinkTitle(linkTitle);
|
||||
}
|
||||
if (linkUrl != null && !linkUrl.isEmpty()) {
|
||||
dto.setLinkUrl(linkUrl);
|
||||
dto.setLinkOpenMode("Web");
|
||||
}
|
||||
|
||||
KsimApiResult<JSONObject> apiResult = apiClient.sendMsg(token, dto);
|
||||
if (apiResult == null) {
|
||||
log.error("【即时通推送-消息发送】响应为空, toUser: {}, title: {}", toUserValue, title);
|
||||
return Result.error("即时通响应为空");
|
||||
}
|
||||
if (!apiResult.isSuccess()) {
|
||||
log.error("【即时通推送-消息发送】发送失败, code: {}, message: {}, toUser: {}, title: {}",
|
||||
apiResult.getCode(), apiResult.getMessage(), toUserValue, 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);
|
||||
return Result.OK(messageId);
|
||||
}
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
package org.jeecg.modules.ksim.token;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class KsimTokenManager {
|
||||
|
||||
private static final String TOKEN_KEY = "ksim:token";
|
||||
|
||||
@Resource
|
||||
private KsimProperties ksimProperties;
|
||||
|
||||
@Resource
|
||||
private KsimApiClient ksimApiClient;
|
||||
|
||||
@Resource
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
public String getToken() {
|
||||
Object cached = redisTemplate.opsForValue().get(TOKEN_KEY);
|
||||
if (cached != null) {
|
||||
return cached.toString();
|
||||
}
|
||||
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");
|
||||
JSONObject result = ksimApiClient.createToken(
|
||||
ksimProperties.getAppId(), ksimProperties.getAppSecret(), ksimProperties.getTokenTimeoutSeconds());
|
||||
return cacheToken(result, ksimProperties.getTokenTimeoutSeconds());
|
||||
}
|
||||
|
||||
private String cacheToken(JSONObject result, int timeoutSeconds) {
|
||||
if (result == null || result.getJSONObject("dataBody") == null) {
|
||||
throw new JeecgBootException("【即时通推送】Token创建/刷新返回为空");
|
||||
}
|
||||
JSONObject dataBody = result.getJSONObject("dataBody");
|
||||
String token = dataBody.getString("token");
|
||||
int ttl = Math.max(timeoutSeconds - 60, 60);
|
||||
redisTemplate.opsForValue().set(TOKEN_KEY, token, ttl, TimeUnit.SECONDS);
|
||||
log.info("【即时通推送-Token缓存】已写入Redis, ttl: {}s", ttl);
|
||||
return token;
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@
|
||||
<module>jeecg-boot-module-airag</module>
|
||||
<module>jeecg-module-flow</module>
|
||||
<module>jeecg-module-tools</module>
|
||||
<module>jeecg-module-im</module>
|
||||
</modules>
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user