第一次提交
This commit is contained in:
@@ -0,0 +1,279 @@
|
||||
package org.jeecg.config.firewall.SqlInjection.impl;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.constant.SymbolConstant;
|
||||
import org.jeecg.common.exception.JeecgSqlInjectionException;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.config.JeecgBaseConfig;
|
||||
import org.jeecg.config.firewall.SqlInjection.IDictTableWhiteListHandler;
|
||||
import org.jeecg.config.firewall.interceptor.LowCodeModeInterceptor;
|
||||
import org.jeecg.modules.system.entity.SysTableWhiteList;
|
||||
import org.jeecg.modules.system.security.DictQueryBlackListHandler;
|
||||
import org.jeecg.modules.system.service.ISysTableWhiteListService;
|
||||
import org.jeecgframework.minidao.sqlparser.impl.vo.SelectSqlInfo;
|
||||
import org.jeecgframework.minidao.util.MiniDaoUtil;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.net.URLDecoder;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 通用情况的白名单处理,若有无法处理的情况,可以单独写实现类
|
||||
*/
|
||||
@Slf4j
|
||||
@Component("dictTableWhiteListHandlerImpl")
|
||||
public class DictTableWhiteListHandlerImpl implements IDictTableWhiteListHandler {
|
||||
|
||||
/**
|
||||
* key-表名
|
||||
* value-字段名,多个逗号隔开
|
||||
* 两种配置方式-- 全部配置成小写
|
||||
* whiteTablesRuleMap.put("sys_user", "*") sys_user所有的字段都支持查询
|
||||
* whiteTablesRuleMap.put("sys_user", "username,password") sys_user中的username和password支持查询
|
||||
*/
|
||||
private static final Map<String, String> whiteTablesRuleMap = new HashMap<>();
|
||||
/**
|
||||
* LowCode 是否为 dev 模式
|
||||
*/
|
||||
private static Boolean LOW_CODE_IS_DEV = null;
|
||||
|
||||
|
||||
@Autowired
|
||||
private ISysTableWhiteListService sysTableWhiteListService;
|
||||
@Autowired
|
||||
private JeecgBaseConfig jeecgBaseConfig;
|
||||
|
||||
|
||||
/**
|
||||
* 初始化 whiteTablesRuleMap 方法
|
||||
*/
|
||||
private void init() {
|
||||
// 如果当前为dev模式,则每次都查询数据库,防止缓存
|
||||
if (this.isDev()) {
|
||||
DictTableWhiteListHandlerImpl.whiteTablesRuleMap.clear();
|
||||
}
|
||||
// 如果map为空,则从数据库中查询
|
||||
if (DictTableWhiteListHandlerImpl.whiteTablesRuleMap.isEmpty()) {
|
||||
Map<String, String> ruleMap = sysTableWhiteListService.getAllConfigMap();
|
||||
log.debug("表字典白名单初始化完成:{}", ruleMap);
|
||||
DictTableWhiteListHandlerImpl.whiteTablesRuleMap.putAll(ruleMap);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPassBySql(String sql) {
|
||||
Map<String, SelectSqlInfo> parsedMap = null;
|
||||
try {
|
||||
parsedMap = MiniDaoUtil.parseAllSelectTable(sql);
|
||||
} catch (Exception e) {
|
||||
log.warn("校验sql语句,解析报错:{}", e.getMessage());
|
||||
}
|
||||
// 如果sql有问题,则肯定执行不了,所以直接返回true
|
||||
if (parsedMap == null) {
|
||||
return true;
|
||||
}
|
||||
log.debug("获取select sql信息 :{} ", parsedMap);
|
||||
// 遍历当前sql中的所有表名,如果有其中一个表或表的字段不在白名单中,则不通过
|
||||
for (Map.Entry<String, SelectSqlInfo> entry : parsedMap.entrySet()) {
|
||||
SelectSqlInfo sqlInfo = entry.getValue();
|
||||
if (sqlInfo.isSelectAll()) {
|
||||
log.warn("查询语句中包含 * 字段,暂时先通过");
|
||||
continue;
|
||||
}
|
||||
Set<String> queryFields = sqlInfo.getAllRealSelectFields();
|
||||
// 校验表名和字段是否允许查询
|
||||
String tableName = entry.getKey();
|
||||
if (!this.checkWhiteList(tableName, queryFields)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPassByDict(String dictCodeString) {
|
||||
if (oConvertUtils.isEmpty(dictCodeString)) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
// 针对转义字符进行解码
|
||||
dictCodeString = URLDecoder.decode(dictCodeString, "UTF-8");
|
||||
} catch (Exception e) {
|
||||
log.warn(e.getMessage());
|
||||
//this.throwException("字典code解码失败,可能是使用了非法字符,请检查!");
|
||||
}
|
||||
dictCodeString = dictCodeString.trim();
|
||||
String[] arr = dictCodeString.split(SymbolConstant.COMMA);
|
||||
// 获取表名
|
||||
String tableName = this.getTableName(arr[0]);
|
||||
// 获取查询字段
|
||||
arr = Arrays.copyOfRange(arr, 1, arr.length);
|
||||
// distinct的作用是去重,相当于 Set<String>
|
||||
String[] fields = Arrays.stream(arr).map(String::trim).distinct().toArray(String[]::new);
|
||||
// 校验表名和字段是否允许查询
|
||||
return this.isPassByDict(tableName, fields);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPassByDict(String tableName, String... fields) {
|
||||
if (oConvertUtils.isEmpty(tableName)) {
|
||||
return true;
|
||||
}
|
||||
if (fields == null || fields.length == 0) {
|
||||
fields = new String[]{"*"};
|
||||
}
|
||||
String sql = "select " + String.join(",", fields) + " from " + tableName;
|
||||
log.debug("字典拼接的查询SQL:{}", sql);
|
||||
try {
|
||||
// 进行SQL解析
|
||||
MiniDaoUtil.parseSelectSqlInfo(sql);
|
||||
} catch (Exception e) {
|
||||
// 如果SQL解析失败,则通过字段名和表名进行校验
|
||||
return checkWhiteList(tableName, new HashSet<>(Arrays.asList(fields)));
|
||||
}
|
||||
// 通过SQL解析进行校验,可防止SQL注入
|
||||
return this.isPassBySql(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验表名和字段是否在白名单内
|
||||
*
|
||||
* @param tableName
|
||||
* @param queryFields
|
||||
* @return
|
||||
*/
|
||||
public boolean checkWhiteList(String tableName, Set<String> queryFields) {
|
||||
this.init();
|
||||
// 1、判断“表名”是否通过校验,如果为空则未通过校验
|
||||
if (oConvertUtils.isEmpty(tableName)) {
|
||||
log.error("白名单校验:表名为空");
|
||||
this.throwException();
|
||||
}
|
||||
// 统一转成小写
|
||||
tableName = tableName.toLowerCase();
|
||||
String allowFieldStr = DictTableWhiteListHandlerImpl.whiteTablesRuleMap.get(tableName);
|
||||
log.debug("checkWhiteList tableName: {}", tableName);
|
||||
if (oConvertUtils.isEmpty(allowFieldStr)) {
|
||||
// 如果是dev模式,自动向数据库里添加数据
|
||||
if (this.isDev()) {
|
||||
this.autoAddWhiteList(tableName, String.join(",", queryFields));
|
||||
allowFieldStr = DictTableWhiteListHandlerImpl.whiteTablesRuleMap.get(tableName);
|
||||
} else {
|
||||
// prod模式下,直接抛出异常
|
||||
log.error("白名单校验:表\"{}\"未通过校验", tableName);
|
||||
this.throwException();
|
||||
}
|
||||
}
|
||||
// 2、判断“字段名”是否通过校验
|
||||
// 统一转成小写
|
||||
allowFieldStr = allowFieldStr.toLowerCase();
|
||||
Set<String> allowFields = new HashSet<>(Arrays.asList(allowFieldStr.split(",")));
|
||||
// 需要合并的字段
|
||||
Set<String> waitMergerFields = new HashSet<>();
|
||||
for (String field : queryFields) {
|
||||
if(oConvertUtils.isEmpty(field)){
|
||||
continue;
|
||||
}
|
||||
// 统一转成小写
|
||||
field = field.toLowerCase();
|
||||
// 如果允许的字段里不包含查询的字段,则直接抛出异常
|
||||
if (!allowFields.contains(field)) {
|
||||
// 如果是dev模式,记录需要合并的字段
|
||||
if (this.isDev()) {
|
||||
waitMergerFields.add(field);
|
||||
} else {
|
||||
log.error("白名单校验:字段 {} 不在 {} 范围内,拒绝访问!", field, allowFields);
|
||||
this.throwException();
|
||||
}
|
||||
}
|
||||
}
|
||||
// 自动向数据库中合并未通过的字段
|
||||
if (!waitMergerFields.isEmpty()) {
|
||||
this.autoAddWhiteList(tableName, String.join(",", waitMergerFields));
|
||||
}
|
||||
log.debug("白名单校验:查询表\"{}\",查询字段 {} 通过校验", tableName, queryFields);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动添加白名单,如果数据库已有,则字段会自动合并
|
||||
*
|
||||
* @param tableName
|
||||
* @param allowFieldStr
|
||||
*/
|
||||
private void autoAddWhiteList(String tableName, String allowFieldStr) {
|
||||
try {
|
||||
SysTableWhiteList entity = sysTableWhiteListService.autoAdd(tableName, allowFieldStr);
|
||||
DictTableWhiteListHandlerImpl.whiteTablesRuleMap.put(tableName, entity.getFieldName());
|
||||
log.warn("表\"{}\"未通过校验,且当前为 dev 模式,已自动向数据库中增加白名单数据。查询字段:{}", tableName, allowFieldStr);
|
||||
} catch (Exception e) {
|
||||
log.error("表\"{}\"未通过校验,且当前为 dev 模式,但自动向数据库中增加白名单数据失败,请排查后重试。错误原因:{}", tableName, e.getMessage(), e);
|
||||
this.throwException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前 LowCode 是否为 dev 模式
|
||||
*/
|
||||
private boolean isDev() {
|
||||
if (DictTableWhiteListHandlerImpl.LOW_CODE_IS_DEV == null) {
|
||||
if (this.jeecgBaseConfig.getFirewall() != null) {
|
||||
String lowCodeMode = this.jeecgBaseConfig.getFirewall().getLowCodeMode();
|
||||
DictTableWhiteListHandlerImpl.LOW_CODE_IS_DEV = LowCodeModeInterceptor.LOW_CODE_MODE_DEV.equals(lowCodeMode);
|
||||
} else {
|
||||
// 如果没有 firewall 配置,则默认为 false
|
||||
DictTableWhiteListHandlerImpl.LOW_CODE_IS_DEV = false;
|
||||
}
|
||||
}
|
||||
return DictTableWhiteListHandlerImpl.LOW_CODE_IS_DEV;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean clear() {
|
||||
DictTableWhiteListHandlerImpl.whiteTablesRuleMap.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 取where前面的为:table name
|
||||
*
|
||||
* @param str
|
||||
* @see DictQueryBlackListHandler#getTableName(String)
|
||||
*/
|
||||
@SuppressWarnings("JavadocReference")
|
||||
private String getTableName(String str) {
|
||||
String[] arr = str.split("\\s+(?i)where\\s+");
|
||||
String tableName = arr[0].trim();
|
||||
//【20230814】解决使用参数tableName=sys_user t&复测,漏洞仍然存在
|
||||
if (tableName.contains(".")) {
|
||||
tableName = tableName.substring(tableName.indexOf(".") + 1, tableName.length()).trim();
|
||||
}
|
||||
if (tableName.contains(" ")) {
|
||||
tableName = tableName.substring(0, tableName.indexOf(" ")).trim();
|
||||
}
|
||||
|
||||
//【issues/4393】 sys_user , (sys_user), sys_user%20, %60sys_user%60
|
||||
String reg = "\\s+|\\(|\\)|`";
|
||||
return tableName.replaceAll(reg, "");
|
||||
}
|
||||
|
||||
private void throwException() throws JeecgSqlInjectionException {
|
||||
this.throwException(this.getErrorMsg());
|
||||
}
|
||||
|
||||
private void throwException(String message) throws JeecgSqlInjectionException {
|
||||
if (oConvertUtils.isEmpty(message)) {
|
||||
message = this.getErrorMsg();
|
||||
}
|
||||
log.error(message);
|
||||
throw new JeecgSqlInjectionException(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getErrorMsg() {
|
||||
return "白名单校验未通过!";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package org.jeecg.config.init;
|
||||
|
||||
import com.alibaba.druid.filter.config.ConfigTools;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jeecgframework.codegenerate.database.CodegenDatasourceConfig;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @Description: 代码生成器,自定义DB配置
|
||||
* 【加了此类,则online模式DB连接,使用平台的配置,jeecg_database.properties配置无效;
|
||||
* 但是使用GUI模式代码生成,还是走jeecg_database.properties配置】
|
||||
* 提醒: 达梦数据库需要修改下面的参数${spring.datasource.dynamic.datasource.master.url:}配置
|
||||
* @author: scott
|
||||
* @date: 2021年02月18日 16:30
|
||||
*
|
||||
* 重要说明:此类改路径或者名称,需要同步修改
|
||||
* org/jeecg/interceptor/OnlineRepairCodeGenerateDbConfig.java里面的注解
|
||||
* @ConditionalOnMissingClass("org.jeecg.config.init.CodeGenerateDbConfig")
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
public class CodeGenerateDbConfig {
|
||||
@Value("${spring.datasource.dynamic.datasource.master.url:}")
|
||||
private String url;
|
||||
@Value("${spring.datasource.dynamic.datasource.master.username:}")
|
||||
private String username;
|
||||
@Value("${spring.datasource.dynamic.datasource.master.password:}")
|
||||
private String password;
|
||||
@Value("${spring.datasource.dynamic.datasource.master.driver-class-name:}")
|
||||
private String driverClassName;
|
||||
@Value("${spring.datasource.dynamic.datasource.master.druid.public-key:}")
|
||||
private String publicKey;
|
||||
|
||||
|
||||
@Bean
|
||||
public CodeGenerateDbConfig initCodeGenerateDbConfig() {
|
||||
if(StringUtils.isNotBlank(url)){
|
||||
if(StringUtils.isNotBlank(publicKey)){
|
||||
try {
|
||||
password = ConfigTools.decrypt(publicKey, password);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
log.error(" 代码生成器数据库连接,数据库密码解密失败!");
|
||||
}
|
||||
}
|
||||
CodegenDatasourceConfig.initDbConfig(driverClassName,url, username, password);
|
||||
log.info(" Init CodeGenerate Config [ Get Db Config From application.yml ] ");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package org.jeecg.config.init;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* 自动初始化代码生成器模板
|
||||
* <p>
|
||||
* 解决JAR发布需要手工配置代码生成器模板问题
|
||||
* @author zhang
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class CodeTemplateInitListener implements ApplicationListener<ApplicationReadyEvent> {
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ApplicationReadyEvent event) {
|
||||
try {
|
||||
long startTime = System.currentTimeMillis(); // 记录开始时间
|
||||
log.info(" Init Code Generate Template [ 检测如果是JAR启动,Copy模板到config目录 ] ");
|
||||
this.initJarConfigCodeGeneratorTemplate();
|
||||
long endTime = System.currentTimeMillis(); // 记录结束时间
|
||||
log.info(" Init Code Generate Template completed in " + (endTime - startTime) + " ms"); // 计算并记录耗时
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ::Jar包启动模式下::
|
||||
* 初始化代码生成器模板文件
|
||||
*/
|
||||
private void initJarConfigCodeGeneratorTemplate() throws Exception {
|
||||
//1.获取jar同级下的config路径
|
||||
String configPath = System.getProperty("user.dir") + File.separator + "config" + File.separator;
|
||||
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
|
||||
Resource[] resources = resolver.getResources("classpath*:jeecg/code-template-online/**/*");
|
||||
for (Resource re : resources) {
|
||||
URL url = re.getURL();
|
||||
String filepath = url.getPath();
|
||||
//System.out.println("native url= " + filepath);
|
||||
filepath = java.net.URLDecoder.decode(filepath, "utf-8");
|
||||
//System.out.println("decode url= " + filepath);
|
||||
|
||||
//2.在config下,创建jeecg/code-template-online/*模板
|
||||
String createFilePath = configPath + filepath.substring(filepath.indexOf("jeecg/code-template-online"));
|
||||
|
||||
// 非jar模式不生成模板
|
||||
// 不生成目录,只生成具体模板文件
|
||||
if ((!filepath.contains(".jar!/BOOT-INF/lib/") && !filepath.contains(".jar/!BOOT-INF/lib/")) || !createFilePath.contains(".")) {
|
||||
continue;
|
||||
}
|
||||
if (!FileUtil.exist(createFilePath)) {
|
||||
log.info("create file codeTemplate = " + createFilePath);
|
||||
FileUtil.writeString(IOUtils.toString(url, StandardCharsets.UTF_8), createFilePath, "UTF-8");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.jeecg.config.init;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.common.util.RedisUtil;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.ApplicationArguments;
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Shiro缓存清理
|
||||
* 在应用启动时清除所有的Shiro授权缓存
|
||||
* 主要用于解决重启项目,用户未重新登录,按钮权限不生效的问题
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class ShiroCacheClearRunner implements ApplicationRunner {
|
||||
|
||||
@Autowired
|
||||
private RedisUtil redisUtil;
|
||||
|
||||
@Override
|
||||
public void run(ApplicationArguments args) {
|
||||
// 清空所有授权redis缓存
|
||||
log.info("——— Service restart, clearing all user shiro authorization cache ——— ");
|
||||
redisUtil.removeAll(CommonConstant.PREFIX_USER_SHIRO_CACHE);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package org.jeecg.config.init;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.constant.CacheConstant;
|
||||
import org.jeecg.config.JeecgCloudCondition;
|
||||
import org.jeecg.modules.system.service.ISysGatewayRouteService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @desc: 启动程序,初始化路由配置
|
||||
* @author: flyme
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@Conditional(JeecgCloudCondition.class)
|
||||
public class SystemInitListener implements ApplicationListener<ApplicationReadyEvent>, Ordered {
|
||||
|
||||
|
||||
@Autowired
|
||||
private ISysGatewayRouteService sysGatewayRouteService;
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
|
||||
|
||||
log.info(" 服务已启动,初始化路由配置 ###################");
|
||||
String context = "AnnotationConfigServletWebServerApplicationContext";
|
||||
if (applicationReadyEvent.getApplicationContext().getDisplayName().indexOf(context) > -1) {
|
||||
sysGatewayRouteService.addRoute2Redis(CacheConstant.GATEWAY_ROUTES);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.jeecg.config.init;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.tomcat.util.scan.StandardJarScanner;
|
||||
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @Description: TomcatFactoryConfig
|
||||
* @author: scott
|
||||
* @date: 2021年01月25日 11:40
|
||||
*/
|
||||
@Configuration
|
||||
public class TomcatFactoryConfig {
|
||||
/**
|
||||
* tomcat-embed-jasper引用后提示jar找不到的问题
|
||||
*/
|
||||
@Bean
|
||||
public TomcatServletWebServerFactory tomcatFactory() {
|
||||
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory() {
|
||||
@Override
|
||||
protected void postProcessContext(Context context) {
|
||||
((StandardJarScanner) context.getJarScanner()).setScanManifest(false);
|
||||
}
|
||||
};
|
||||
factory.addConnectorCustomizers(connector -> {
|
||||
connector.setProperty("relaxedPathChars", "[]{}");
|
||||
connector.setProperty("relaxedQueryChars", "[]{}");
|
||||
});
|
||||
return factory;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
//package org.jeecg.config.init;
|
||||
//
|
||||
//import io.undertow.UndertowOptions;
|
||||
//import io.undertow.server.DefaultByteBufferPool;
|
||||
//import io.undertow.server.handlers.BlockingHandler;
|
||||
//import io.undertow.websockets.jsr.WebSocketDeploymentInfo;
|
||||
//import org.jeecg.modules.monitor.actuator.undertow.CustomUndertowMetricsHandler;
|
||||
//import org.springframework.beans.factory.annotation.Autowired;
|
||||
//import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
|
||||
//import org.springframework.boot.web.server.WebServerFactoryCustomizer;
|
||||
//import org.springframework.context.annotation.Configuration;
|
||||
//
|
||||
///**
|
||||
// * Undertow配置
|
||||
// *
|
||||
// * 解决启动提示: WARN io.undertow.websockets.jsr:68 - UT026010: Buffer pool was not set on WebSocketDeploymentInfo, the default pool will be used
|
||||
// */
|
||||
//@Configuration
|
||||
//public class UndertowConfiguration implements WebServerFactoryCustomizer<UndertowServletWebServerFactory> {
|
||||
//
|
||||
// /**
|
||||
// * 自定义undertow监控指标工具类
|
||||
// * for [QQYUN-11902]tomcat 替换undertow 这里的功能还没修改
|
||||
// */
|
||||
// @Autowired
|
||||
// private CustomUndertowMetricsHandler customUndertowMetricsHandler;
|
||||
//
|
||||
// @Override
|
||||
// public void customize(UndertowServletWebServerFactory factory) {
|
||||
// // 设置 Undertow 服务器参数(底层网络配置)
|
||||
// factory.addBuilderCustomizers(builder -> {
|
||||
// builder.setServerOption(UndertowOptions.MAX_HEADER_SIZE, 65536); // header 最大64KB
|
||||
// builder.setServerOption(UndertowOptions.MAX_PARAMETERS, 10000); // 最大参数数
|
||||
// });
|
||||
// factory.addDeploymentInfoCustomizers(deploymentInfo -> {
|
||||
//
|
||||
// WebSocketDeploymentInfo webSocketDeploymentInfo = new WebSocketDeploymentInfo();
|
||||
//
|
||||
// // 设置合理的参数
|
||||
// webSocketDeploymentInfo.setBuffers(new DefaultByteBufferPool(true, 8192));
|
||||
//
|
||||
// deploymentInfo.addServletContextAttribute("io.undertow.websockets.jsr.WebSocketDeploymentInfo", webSocketDeploymentInfo);
|
||||
//
|
||||
// // 添加自定义 监控 handler
|
||||
// deploymentInfo.addInitialHandlerChainWrapper(next -> new BlockingHandler(customUndertowMetricsHandler.wrap(next)));
|
||||
// });
|
||||
// }
|
||||
//}
|
||||
@@ -0,0 +1,124 @@
|
||||
package org.jeecg.config.jimureport;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.api.dto.LogDTO;
|
||||
import org.jeecg.common.system.api.ISysBaseAPI;
|
||||
import org.jeecg.common.system.vo.DictModel;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.modules.base.service.BaseCommonService;
|
||||
import org.jeecg.modules.drag.service.IOnlDragExternalService;
|
||||
import org.jeecg.modules.drag.vo.DragDictModel;
|
||||
import org.jeecg.modules.drag.vo.DragLogDTO;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @Description: 字典处理
|
||||
* @Author: lsq
|
||||
* @Date:2023-01-09
|
||||
* @Version:V1.0
|
||||
*/
|
||||
@Slf4j
|
||||
@Service("onlDragExternalServiceImpl")
|
||||
public class JimuDragExternalServiceImpl implements IOnlDragExternalService {
|
||||
|
||||
@Autowired
|
||||
@Lazy
|
||||
private BaseCommonService baseCommonService;
|
||||
|
||||
@Autowired
|
||||
@Lazy
|
||||
private ISysBaseAPI sysBaseApi;
|
||||
/**
|
||||
* 根据多个字典code查询多个字典项
|
||||
* @param codeList
|
||||
* @return key = dictCode ; value=对应的字典项
|
||||
*/
|
||||
@Override
|
||||
public Map<String, List<DragDictModel>> getManyDictItems(List<String> codeList, List<JSONObject> tableDictList) {
|
||||
Map<String, List<DragDictModel>> manyDragDictItems = new HashMap<>();
|
||||
if(!CollectionUtils.isEmpty(codeList)){
|
||||
Map<String, List<DictModel>> dictItemsMap = sysBaseApi.getManyDictItems(codeList);
|
||||
dictItemsMap.forEach((k,v)->{
|
||||
List<DragDictModel> dictItems = new ArrayList<>();
|
||||
v.forEach(dictItem->{
|
||||
DragDictModel dictModel = new DragDictModel();
|
||||
BeanUtils.copyProperties(dictItem,dictModel);
|
||||
dictItems.add(dictModel);
|
||||
});
|
||||
manyDragDictItems.put(k,dictItems);
|
||||
});
|
||||
}
|
||||
|
||||
if(!CollectionUtils.isEmpty(tableDictList)){
|
||||
tableDictList.forEach(item->{
|
||||
List<DragDictModel> dictItems = new ArrayList<>();
|
||||
JSONObject object = JSONObject.parseObject(item.toString());
|
||||
String dictField = object.getString("dictField");
|
||||
String dictTable = object.getString("dictTable");
|
||||
String dictText = object.getString("dictText");
|
||||
String fieldName = object.getString("fieldName");
|
||||
List<DictModel> dictItemsList = sysBaseApi.queryTableDictItemsByCode(dictTable,dictText,dictField);
|
||||
dictItemsList.forEach(dictItem->{
|
||||
DragDictModel dictModel = new DragDictModel();
|
||||
BeanUtils.copyProperties(dictItem,dictModel);
|
||||
dictItems.add(dictModel);
|
||||
});
|
||||
manyDragDictItems.put(fieldName,dictItems);
|
||||
});
|
||||
}
|
||||
return manyDragDictItems;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param dictCode
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public List<DragDictModel> getDictItems(String dictCode) {
|
||||
List<DragDictModel> dictItems = new ArrayList<>();
|
||||
if(oConvertUtils.isNotEmpty(dictCode)){
|
||||
List<DictModel> dictItemsList = sysBaseApi.getDictItems(dictCode);
|
||||
dictItemsList.forEach(dictItem->{
|
||||
DragDictModel dictModel = new DragDictModel();
|
||||
BeanUtils.copyProperties(dictItem,dictModel);
|
||||
dictItems.add(dictModel);
|
||||
});
|
||||
}
|
||||
return dictItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加日志
|
||||
* @param dragLogDTO
|
||||
*/
|
||||
@Override
|
||||
public void addLog(DragLogDTO dragLogDTO) {
|
||||
if(oConvertUtils.isNotEmpty(dragLogDTO)){
|
||||
LogDTO dto = new LogDTO();
|
||||
BeanUtils.copyProperties(dragLogDTO,dto);
|
||||
baseCommonService.addLog(dto);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存日志
|
||||
* @param logMsg
|
||||
* @param logType
|
||||
* @param operateType
|
||||
*/
|
||||
@Override
|
||||
public void addLog(String logMsg, int logType, int operateType) {
|
||||
baseCommonService.addLog(logMsg,logType,operateType);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package org.jeecg.config.jimureport;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.system.util.JwtUtil;
|
||||
import org.jeecg.common.system.vo.DictModel;
|
||||
import org.jeecg.common.system.vo.SysUserCacheInfo;
|
||||
import org.jeecg.common.util.RedisUtil;
|
||||
import org.jeecg.common.util.TokenUtils;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.modules.jmreport.api.JmReportTokenServiceI;
|
||||
import org.jeecg.modules.jmreport.common.vo.JmDictModel;
|
||||
import org.jeecg.modules.system.service.impl.SysBaseApiImpl;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 自定义积木报表鉴权(如果不进行自定义,则所有请求不做权限控制)
|
||||
* * 1.自定义获取登录token
|
||||
* * 2.自定义获取登录用户
|
||||
* @author: jeecg-boot
|
||||
*/
|
||||
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class JimuReportTokenService implements JmReportTokenServiceI {
|
||||
@Autowired
|
||||
private SysBaseApiImpl sysBaseApi;
|
||||
@Autowired
|
||||
@Lazy
|
||||
private RedisUtil redisUtil;
|
||||
|
||||
@Override
|
||||
public String getToken(HttpServletRequest request) {
|
||||
try {
|
||||
return TokenUtils.getTokenByRequest(request);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername(String token) {
|
||||
return JwtUtil.getUsername(token);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getRoles(String token) {
|
||||
String username = JwtUtil.getUsername(token);
|
||||
Set roles = sysBaseApi.getUserRoleSet(username);
|
||||
if(CollectionUtils.isEmpty(roles)){
|
||||
return null;
|
||||
}
|
||||
return (String[]) roles.toArray(new String[roles.size()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean verifyToken(String token) {
|
||||
return TokenUtils.verifyToken(token, sysBaseApi, redisUtil);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getUserInfo(String token) {
|
||||
Map<String, Object> map = new HashMap(5);
|
||||
String username = JwtUtil.getUsername(token);
|
||||
//此处通过token只能拿到一个信息 用户账号 后面的就是根据账号获取其他信息 查询数据或是走redis 用户根据自身业务可自定义
|
||||
SysUserCacheInfo userInfo = null;
|
||||
try {
|
||||
userInfo = sysBaseApi.getCacheUser(username);
|
||||
} catch (Exception e) {
|
||||
log.error("获取用户信息异常:"+ e.getMessage());
|
||||
return map;
|
||||
}
|
||||
//设置账号名
|
||||
map.put(SYS_USER_CODE, userInfo.getSysUserCode());
|
||||
//设置部门编码
|
||||
map.put(SYS_ORG_CODE, userInfo.getSysOrgCode());
|
||||
// 将所有信息存放至map 解析sql/api会根据map的键值解析
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将jeecgboot平台的权限传递给积木报表
|
||||
* @param token
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public String[] getPermissions(String token) {
|
||||
// 获取用户信息
|
||||
String username = JwtUtil.getUsername(token);
|
||||
SysUserCacheInfo userInfo = null;
|
||||
try {
|
||||
userInfo = sysBaseApi.getCacheUser(username);
|
||||
} catch (Exception e) {
|
||||
log.error("获取用户信息异常:"+ e.getMessage());
|
||||
}
|
||||
if(userInfo == null){
|
||||
return null;
|
||||
}
|
||||
// 查询权限
|
||||
Set<String> userPermissions = sysBaseApi.getUserPermissionSet(userInfo.getSysUserId());
|
||||
if(CollectionUtils.isEmpty(userPermissions)){
|
||||
return null;
|
||||
}
|
||||
return userPermissions.toArray(new String[0]);
|
||||
}
|
||||
|
||||
//TODO 待升级积木报表依赖版本后启用
|
||||
// @Override
|
||||
public List<JmDictModel> getDictItems(String dictCode) {
|
||||
List<JmDictModel> dictItems = new ArrayList<>();
|
||||
if(oConvertUtils.isNotEmpty(dictCode)){
|
||||
List<DictModel> dictItemsList = sysBaseApi.getDictItems(dictCode);
|
||||
dictItemsList.forEach(dictItem->{
|
||||
JmDictModel dictModel = new JmDictModel();
|
||||
dictModel.setText(dictItem.getText());
|
||||
dictModel.setValue(dictItem.getValue());
|
||||
dictModel.setDictCode(dictCode);
|
||||
dictItems.add(dictModel);
|
||||
});
|
||||
}
|
||||
return dictItems;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
package org.jeecg.modules.airag;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import dev.langchain4j.agent.tool.ToolSpecification;
|
||||
import dev.langchain4j.model.chat.request.json.JsonObjectSchema;
|
||||
import dev.langchain4j.service.tool.ToolExecutor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.common.util.PasswordUtil;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.modules.airag.llm.handler.JeecgToolsProvider;
|
||||
import org.jeecg.modules.base.service.BaseCommonService;
|
||||
import org.jeecg.modules.system.controller.SysUserController;
|
||||
import org.jeecg.modules.system.entity.SysRole;
|
||||
import org.jeecg.modules.system.entity.SysUser;
|
||||
import org.jeecg.modules.system.mapper.SysUserMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* for [QQYUN-13565]【AI助手】新增创建用户和查询用户的工具扩展
|
||||
* @Description: jeecg llm工具提供者
|
||||
* @Author: chenrui
|
||||
* @Date: 2025/8/26 18:06
|
||||
*/
|
||||
@Component
|
||||
public class JeecgBizToolsProvider implements JeecgToolsProvider {
|
||||
|
||||
@Autowired
|
||||
SysUserMapper userMapper;
|
||||
|
||||
@Autowired
|
||||
private BaseCommonService baseCommonService;
|
||||
|
||||
@Autowired
|
||||
private org.jeecg.modules.system.service.ISysRoleService sysRoleService;
|
||||
|
||||
@Autowired
|
||||
private org.jeecg.modules.system.service.ISysUserRoleService sysUserRoleService;
|
||||
|
||||
@Autowired
|
||||
private org.jeecg.modules.system.service.ISysUserService sysUserService;
|
||||
|
||||
public Map<ToolSpecification, ToolExecutor> getDefaultTools(){
|
||||
Map<ToolSpecification, ToolExecutor> tools = new HashMap<>();
|
||||
JeecgLlmTools userTool = queryUserTool();
|
||||
tools.put(userTool.getToolSpecification(), userTool.getToolExecutor());
|
||||
JeecgLlmTools addUser = addUserTool();
|
||||
tools.put(addUser.getToolSpecification(), addUser.getToolExecutor());
|
||||
// 新增:查询所有角色
|
||||
JeecgLlmTools queryRoles = queryAllRolesTool();
|
||||
tools.put(queryRoles.getToolSpecification(), queryRoles.getToolExecutor());
|
||||
// 新增:给用户授予角色
|
||||
JeecgLlmTools grantRoles = grantUserRolesTool();
|
||||
tools.put(grantRoles.getToolSpecification(), grantRoles.getToolExecutor());
|
||||
return tools;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加用户
|
||||
* @return
|
||||
* @author chenrui
|
||||
* @date 2025/8/27 09:51
|
||||
*/
|
||||
private JeecgLlmTools addUserTool(){
|
||||
ToolSpecification toolSpecification = ToolSpecification.builder()
|
||||
.name("add_user")
|
||||
.description("添加用户,返回添加结果;" +
|
||||
"\n\n - 缺少必要字段时,请向用户索要." +
|
||||
"\n\n - 你应该提前判断用户的输入是否合法,比如用户名是否符合规范,手机号和邮箱是否正确等." +
|
||||
"\n\n - 提前使用用户名查询用户是否存在,如果存在则不能添加." +
|
||||
"\n\n - 添加成功后返回成功消息,如果失败则返回失败原因." +
|
||||
"\n\n - 用户名,手机号均要求唯一,提前通过查询用户工具确认唯一性." )
|
||||
.parameters(
|
||||
JsonObjectSchema.builder()
|
||||
.addStringProperty("username", "用户名,必填,只允许使用字母、数字、下划线,且必须以字母开头,唯一")
|
||||
.addStringProperty("password", "用户密码,必填")
|
||||
.addStringProperty("realname", "真实姓名,必填")
|
||||
//.addStringProperty("email", "邮箱,必填,唯一")
|
||||
.addStringProperty("phone", "手机号,必填,唯一")
|
||||
.required("username","password","realname","phone")
|
||||
.build()
|
||||
)
|
||||
.build();
|
||||
ToolExecutor toolExecutor = (toolExecutionRequest, memoryId) -> {
|
||||
JSONObject arguments = JSONObject.parseObject(toolExecutionRequest.arguments());
|
||||
arguments.put("confirmPassword",arguments.get("password"));
|
||||
arguments.put("userIdentity",1);
|
||||
arguments.put("activitiSync",1);
|
||||
arguments.put("departIds","");
|
||||
String selectedRoles = arguments.getString("selectedroles");
|
||||
String selectedDeparts = arguments.getString("selecteddeparts");
|
||||
String msg = "添加用户失败";
|
||||
try {
|
||||
SysUser user = JSON.parseObject(arguments.toJSONString(), SysUser.class);
|
||||
user.setCreateTime(new Date());//设置创建时间
|
||||
String salt = oConvertUtils.randomGen(8);
|
||||
user.setSalt(salt);
|
||||
String passwordEncode = PasswordUtil.encrypt(user.getUsername(), user.getPassword(), salt);
|
||||
user.setPassword(passwordEncode);
|
||||
user.setStatus(1);
|
||||
user.setDelFlag(CommonConstant.DEL_FLAG_0);
|
||||
//用户表字段org_code不能在这里设置他的值
|
||||
user.setOrgCode(null);
|
||||
// 保存用户走一个service 保证事务
|
||||
//获取租户ids
|
||||
String relTenantIds = arguments.getString("relTenantIds");
|
||||
sysUserService.saveUser(user, selectedRoles, selectedDeparts, relTenantIds, false);
|
||||
baseCommonService.addLog("添加用户,username: " +user.getUsername() ,CommonConstant.LOG_TYPE_2, 2);
|
||||
msg = "添加用户成功";
|
||||
// 用户变更,触发同步工作流
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
msg = "添加用户失败";
|
||||
}
|
||||
return msg;
|
||||
};
|
||||
return new JeecgLlmTools(toolSpecification,toolExecutor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询用户信息
|
||||
*
|
||||
* @return 用户列表JSON字符串
|
||||
* @author chenrui
|
||||
* @date 2025/8/26 18:52
|
||||
*/
|
||||
private JeecgLlmTools queryUserTool() {
|
||||
ToolSpecification toolSpecification = ToolSpecification.builder()
|
||||
.name("query_user_by_name")
|
||||
.description("查询用户详细信息,返回json数组。支持用户名、真实姓名、邮箱、手机号 多字段组合查询,用户名、真实姓名、邮箱、手机号均为模糊查询。无条件则返回全部用户。")
|
||||
.parameters(
|
||||
JsonObjectSchema.builder()
|
||||
.addStringProperty("username", "用户名")
|
||||
.addStringProperty("realname", "真实姓名")
|
||||
.addStringProperty("email", "电子邮件")
|
||||
.addStringProperty("phone", "手机号")
|
||||
.build()
|
||||
)
|
||||
.build();
|
||||
ToolExecutor toolExecutor = (toolExecutionRequest, memoryId) -> {
|
||||
SysUser args = JSONObject.parseObject(toolExecutionRequest.arguments(), SysUser.class);
|
||||
QueryWrapper<SysUser> qw = new QueryWrapper<>();
|
||||
if (StringUtils.isNotBlank(args.getUsername())) {
|
||||
qw.like("username", args.getUsername());
|
||||
}
|
||||
if (StringUtils.isNotBlank(args.getRealname())) {
|
||||
qw.like("realname", args.getRealname());
|
||||
}
|
||||
if (StringUtils.isNotBlank(args.getEmail())) {
|
||||
qw.like("email", args.getEmail());
|
||||
}
|
||||
if (StringUtils.isNotBlank(args.getPhone())) {
|
||||
qw.like("phone", args.getPhone());
|
||||
}
|
||||
if (StringUtils.isNotBlank(args.getWorkNo())) {
|
||||
qw.eq("work_no", args.getWorkNo());
|
||||
}
|
||||
qw.eq("del_flag", 0);
|
||||
List<SysUser> users = userMapper.selectList(qw);
|
||||
users.forEach(u -> { u.setPassword(null); u.setSalt(null); });
|
||||
return JSONObject.toJSONString(users);
|
||||
};
|
||||
return new JeecgLlmTools(toolSpecification, toolExecutor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询所有角色
|
||||
* @return
|
||||
* @author chenrui
|
||||
* @date 2025/8/27 09:52
|
||||
*/
|
||||
private JeecgLlmTools queryAllRolesTool() {
|
||||
ToolSpecification spec = ToolSpecification.builder()
|
||||
.name("query_all_roles")
|
||||
.description("查询所有角色,返回json数组。包含字段:id、roleName、roleCode;默认按创建时间/排序号规则由后端决定。")
|
||||
.parameters(
|
||||
JsonObjectSchema.builder()
|
||||
.addStringProperty("roleName", "角色姓名")
|
||||
.addStringProperty("roleCode", "角色编码")
|
||||
.build()
|
||||
)
|
||||
.build();
|
||||
ToolExecutor exec = (toolExecutionRequest, memoryId) -> {
|
||||
// 做租户隔离查询(若开启)
|
||||
SysRole sysRole = JSONObject.parseObject(toolExecutionRequest.arguments(), SysRole.class);
|
||||
QueryWrapper<SysRole> qw = Wrappers.query();
|
||||
if (StringUtils.isNotBlank(sysRole.getRoleName())) {
|
||||
qw.like("role_name", sysRole.getRoleName());
|
||||
}
|
||||
if (StringUtils.isNotBlank(sysRole.getRoleCode())) {
|
||||
qw.like("role_code", sysRole.getRoleCode());
|
||||
}
|
||||
// 未删除
|
||||
List<SysRole> roles = sysRoleService.list(qw);
|
||||
// 仅返回核心字段
|
||||
JSONArray arr = new JSONArray();
|
||||
for (SysRole r : roles) {
|
||||
JSONObject o = new JSONObject();
|
||||
o.put("id", r.getId());
|
||||
o.put("roleName", r.getRoleName());
|
||||
o.put("roleCode", r.getRoleCode());
|
||||
arr.add(o);
|
||||
}
|
||||
return arr.toJSONString();
|
||||
};
|
||||
return new JeecgLlmTools(spec, exec);
|
||||
}
|
||||
|
||||
/**
|
||||
* 给用户授予角色
|
||||
* @return
|
||||
* @author chenrui
|
||||
* @date 2025/8/27 09:52
|
||||
*/
|
||||
private JeecgLlmTools grantUserRolesTool() {
|
||||
ToolSpecification spec = ToolSpecification.builder()
|
||||
.name("grant_user_roles")
|
||||
.description("给用户授予角色,支持一次授予多个角色;如果关系已存在则跳过。返回授予结果统计。")
|
||||
.parameters(
|
||||
JsonObjectSchema.builder()
|
||||
.addStringProperty("userId", "用户ID,必填")
|
||||
.addStringProperty("roleIds", "角色ID列表,必填,使用英文逗号分隔")
|
||||
.required("userId","roleIds")
|
||||
.build()
|
||||
)
|
||||
.build();
|
||||
ToolExecutor exec = (toolExecutionRequest, memoryId) -> {
|
||||
JSONObject args = JSONObject.parseObject(toolExecutionRequest.arguments());
|
||||
String userId = args.getString("userId");
|
||||
String roleIdsStr = args.getString("roleIds");
|
||||
if (StringUtils.isAnyBlank(userId, roleIdsStr)) {
|
||||
return "参数缺失:userId 或 roleIds";
|
||||
}
|
||||
SysUser user = sysUserService.getById(userId);
|
||||
if (user == null) {
|
||||
return "用户不存在:" + userId;
|
||||
}
|
||||
String[] roleIds = roleIdsStr.split(",");
|
||||
int added = 0, existed = 0, invalid = 0;
|
||||
for (String roleId : roleIds) {
|
||||
roleId = roleId.trim();
|
||||
if (roleId.isEmpty()) continue;
|
||||
SysRole role = sysRoleService.getById(roleId);
|
||||
if (role == null) { invalid++; continue; }
|
||||
QueryWrapper<org.jeecg.modules.system.entity.SysUserRole> q = new QueryWrapper<>();
|
||||
q.eq("role_id", roleId).eq("user_id", userId);
|
||||
org.jeecg.modules.system.entity.SysUserRole one = sysUserRoleService.getOne(q);
|
||||
if (one == null) {
|
||||
org.jeecg.modules.system.entity.SysUserRole rel = new org.jeecg.modules.system.entity.SysUserRole(userId, roleId);
|
||||
boolean ok = sysUserRoleService.save(rel);
|
||||
if (ok) { added++; } else { invalid++; }
|
||||
} else {
|
||||
existed++;
|
||||
}
|
||||
}
|
||||
return String.format("授予完成:新增%d,已存在%d,无效/失败%d", added, existed, invalid);
|
||||
};
|
||||
return new JeecgLlmTools(spec, exec);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package org.jeecg.modules.airag;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.exception.JeecgBootException;
|
||||
import org.jeecg.common.util.DateUtils;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.modules.airag.flow.component.enhance.IAiRagEnhanceJava;
|
||||
import org.jeecg.modules.airag.wordtpl.entity.EoaWordTemplate;
|
||||
import org.jeecg.modules.airag.wordtpl.service.IEoaWordTemplateService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @Description: JavaAIFlow增强节点:生成在线word文档
|
||||
* @Author: chenrui
|
||||
* @Date: 2025-08-06 16:39
|
||||
*/
|
||||
@Slf4j
|
||||
@Component("jeecgDemoAiWordGen")
|
||||
public class TestAiGenWordEnhance implements IAiRagEnhanceJava {
|
||||
|
||||
@Autowired
|
||||
IEoaWordTemplateService eoaWordTemplateService;
|
||||
|
||||
@Override
|
||||
public Map<String, Object> process(Map<String, Object> inputParams) {
|
||||
Object resp = inputParams.get("resp");
|
||||
String respStr = String.valueOf(resp);
|
||||
log.info("AI生成word响应内容:{}", respStr);
|
||||
if(oConvertUtils.isEmpty(respStr)){
|
||||
throw new JeecgBootException("AI生成内容失败。请稍后再试或查看后台日志。");
|
||||
}
|
||||
String mainStr = null;
|
||||
Matcher matcher = Pattern.compile("\\[.*]", Pattern.DOTALL).matcher(respStr);
|
||||
if (matcher.find()) {
|
||||
mainStr = matcher.group();
|
||||
// 替换中文双引号为英文双引号
|
||||
mainStr = mainStr.replaceAll("[“”]", "\"");
|
||||
// 替换 NBSP 为普通空格
|
||||
mainStr = mainStr.replaceAll("\\u00A0", " ");
|
||||
|
||||
log.info("生成word json:{}", mainStr);
|
||||
// 校验是否为合法 JSON 字符串
|
||||
try {
|
||||
JSON.parse(mainStr);
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
throw new JeecgBootException("AI生成的内容不是合法的 JSON 字符串,请稍后再试或优化提示词。");
|
||||
}
|
||||
}else{
|
||||
throw new JeecgBootException("AI生成的内容不是合法的 JSON 字符串,请稍后再试或优化提示词。");
|
||||
}
|
||||
|
||||
EoaWordTemplate template = new EoaWordTemplate();
|
||||
String dateFormat = DateUtils.formatDate();
|
||||
template.setName("AI生成的简历_"+dateFormat);
|
||||
template.setCode("AI_GEN_"+System.currentTimeMillis());
|
||||
template.setHeader("[]");
|
||||
template.setFooter("[]");
|
||||
template.setMain(mainStr);
|
||||
template.setWidth(794);
|
||||
template.setHeight(1123);
|
||||
template.setMargins("[100,120,100,120]");
|
||||
template.setPaperDirection("vertical");
|
||||
eoaWordTemplateService.save(template);
|
||||
return Collections.singletonMap("result","success");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.jeecg.modules.aop;
|
||||
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.common.constant.enums.ModuleType;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 系统日志注解
|
||||
*
|
||||
* @Author scott
|
||||
* @email jeecgos@163.com
|
||||
* @Date 2019年1月14日
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface TenantLog {
|
||||
|
||||
/**
|
||||
* 操作日志类型(1查询,2添加,3修改,4删除)
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
int value() default 0;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package org.jeecg.modules.aop;
|
||||
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.AfterThrowing;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.jeecg.common.api.dto.LogDTO;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.common.system.vo.LoginUser;
|
||||
import org.jeecg.modules.base.service.BaseCommonService;
|
||||
import org.jeecg.modules.system.entity.SysTenantPack;
|
||||
import org.jeecg.modules.system.entity.SysTenantPackUser;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @Author taoYan
|
||||
* @Date 2023/2/16 14:27
|
||||
**/
|
||||
@Aspect
|
||||
@Component
|
||||
public class TenantPackUserLogAspect {
|
||||
|
||||
@Resource
|
||||
private BaseCommonService baseCommonService;
|
||||
|
||||
@Pointcut("@annotation(org.jeecg.modules.aop.TenantLog)")
|
||||
public void tenantLogPointCut() {
|
||||
|
||||
}
|
||||
|
||||
@Around("tenantLogPointCut()")
|
||||
public Object aroundMethod(ProceedingJoinPoint joinPoint)throws Throwable {
|
||||
//System.out.println("环绕通知>>>>>>>>>");
|
||||
|
||||
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
|
||||
Method method = signature.getMethod();
|
||||
TenantLog log = method.getAnnotation(TenantLog.class);
|
||||
if(log != null){
|
||||
int opType = log.value();
|
||||
Integer logType = null;
|
||||
String content = null;
|
||||
Integer tenantId = null;
|
||||
//获取参数
|
||||
Object[] args = joinPoint.getArgs();
|
||||
if(args.length>0){
|
||||
for(Object obj: args){
|
||||
if(obj instanceof SysTenantPack){
|
||||
// logType=3 租户操作日志
|
||||
logType = CommonConstant.LOG_TYPE_3;
|
||||
SysTenantPack pack = (SysTenantPack)obj;
|
||||
if(opType==2){
|
||||
content = "创建了角色权限 "+ pack.getPackName();
|
||||
}
|
||||
tenantId = pack.getTenantId();
|
||||
break;
|
||||
}else if(obj instanceof SysTenantPackUser){
|
||||
logType = CommonConstant.LOG_TYPE_3;
|
||||
SysTenantPackUser packUser = (SysTenantPackUser)obj;
|
||||
if(opType==2){
|
||||
content = "将 "+packUser.getRealname()+" 添加到角色 "+ packUser.getPackName();
|
||||
}else if(opType==4){
|
||||
content = "移除了 "+packUser.getPackName()+" 成员 "+ packUser.getRealname();
|
||||
}
|
||||
tenantId = packUser.getTenantId();
|
||||
}
|
||||
}
|
||||
}
|
||||
if(logType!=null){
|
||||
LogDTO dto = new LogDTO();
|
||||
dto.setLogType(logType);
|
||||
dto.setLogContent(content);
|
||||
dto.setOperateType(opType);
|
||||
dto.setTenantId(tenantId);
|
||||
//获取登录用户信息
|
||||
LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
||||
if(sysUser!=null){
|
||||
dto.setUserid(sysUser.getUsername());
|
||||
dto.setUsername(sysUser.getRealname());
|
||||
|
||||
}
|
||||
dto.setCreateTime(new Date());
|
||||
//保存系统日志
|
||||
baseCommonService.addLog(dto);
|
||||
}
|
||||
}
|
||||
return joinPoint.proceed();
|
||||
}
|
||||
|
||||
@AfterThrowing("tenantLogPointCut()")
|
||||
public void afterThrowing()throws Throwable{
|
||||
System.out.println("异常通知");
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,111 @@
|
||||
package org.jeecg.modules.cas.controller;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.common.system.util.JwtUtil;
|
||||
import org.jeecg.common.util.RedisUtil;
|
||||
import org.jeecg.modules.cas.util.CasServiceUtil;
|
||||
import org.jeecg.modules.cas.util.XmlUtils;
|
||||
import org.jeecg.modules.system.entity.SysDepart;
|
||||
import org.jeecg.modules.system.entity.SysUser;
|
||||
import org.jeecg.modules.system.service.ISysDepartService;
|
||||
import org.jeecg.modules.system.service.ISysUserService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* CAS单点登录客户端登录认证
|
||||
* </p>
|
||||
*
|
||||
* @Author zhoujf
|
||||
* @since 2018-12-20
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/sys/cas/client")
|
||||
public class CasClientController {
|
||||
|
||||
@Autowired
|
||||
private ISysUserService sysUserService;
|
||||
@Autowired
|
||||
private ISysDepartService sysDepartService;
|
||||
@Autowired
|
||||
private RedisUtil redisUtil;
|
||||
|
||||
@Value("${cas.prefixUrl}")
|
||||
private String prefixUrl;
|
||||
|
||||
|
||||
@GetMapping("/validateLogin")
|
||||
public Object validateLogin(@RequestParam(name="ticket") String ticket,
|
||||
@RequestParam(name="service") String service,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response) throws Exception {
|
||||
Result<JSONObject> result = new Result<JSONObject>();
|
||||
log.info("Rest api login.");
|
||||
try {
|
||||
String validateUrl = prefixUrl+"/p3/serviceValidate";
|
||||
String res = CasServiceUtil.getStValidate(validateUrl, ticket, service);
|
||||
log.info("res."+res);
|
||||
final String error = XmlUtils.getTextForElement(res, "authenticationFailure");
|
||||
if(StringUtils.isNotEmpty(error)) {
|
||||
throw new Exception(error);
|
||||
}
|
||||
final String principal = XmlUtils.getTextForElement(res, "user");
|
||||
if (StringUtils.isEmpty(principal)) {
|
||||
throw new Exception("No principal was found in the response from the CAS server.");
|
||||
}
|
||||
log.info("-------token----username---"+principal);
|
||||
//1. 校验用户是否有效
|
||||
SysUser sysUser = sysUserService.getUserByName(principal);
|
||||
result = sysUserService.checkUserIsEffective(sysUser);
|
||||
if(!result.isSuccess()) {
|
||||
return result;
|
||||
}
|
||||
String token = JwtUtil.sign(sysUser.getUsername(), sysUser.getPassword(), CommonConstant.CLIENT_TYPE_PC);
|
||||
// 设置超时时间
|
||||
redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, token);
|
||||
redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, JwtUtil.EXPIRE_TIME*2 / 1000);
|
||||
|
||||
//获取用户部门信息
|
||||
JSONObject obj = new JSONObject();
|
||||
List<SysDepart> departs = sysDepartService.queryUserDeparts(sysUser.getId());
|
||||
obj.put("departs", departs);
|
||||
if (departs == null || departs.size() == 0) {
|
||||
obj.put("multi_depart", 0);
|
||||
} else if (departs.size() == 1) {
|
||||
sysUserService.updateUserDepart(principal, departs.get(0).getOrgCode(),null);
|
||||
obj.put("multi_depart", 1);
|
||||
} else {
|
||||
obj.put("multi_depart", 2);
|
||||
}
|
||||
obj.put("token", token);
|
||||
obj.put("userInfo", sysUser);
|
||||
result.setResult(obj);
|
||||
result.success("登录成功");
|
||||
|
||||
} catch (Exception e) {
|
||||
//e.printStackTrace();
|
||||
result.error500(e.getMessage());
|
||||
}
|
||||
return new HttpEntity<>(result);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package org.jeecg.modules.cas.util;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
|
||||
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
|
||||
/**
|
||||
* @Description: CasServiceUtil
|
||||
* @author: jeecg-boot
|
||||
*/
|
||||
public class CasServiceUtil {
|
||||
|
||||
public static void main(String[] args) {
|
||||
String serviceUrl = "https://cas.8f8.com.cn:8443/cas/p3/serviceValidate";
|
||||
String service = "http://localhost:3003/user/login";
|
||||
String ticket = "ST-5-1g-9cNES6KXNRwq-GuRET103sm0-DESKTOP-VKLS8B3";
|
||||
String res = getStValidate(serviceUrl,ticket, service);
|
||||
|
||||
System.out.println("---------res-----"+res);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 验证ST
|
||||
*/
|
||||
public static String getStValidate(String url, String st, String service){
|
||||
try {
|
||||
url = url+"?service="+service+"&ticket="+st;
|
||||
CloseableHttpClient httpclient = createHttpClientWithNoSsl();
|
||||
HttpGet httpget = new HttpGet(url);
|
||||
HttpResponse response = httpclient.execute(httpget);
|
||||
String res = readResponse(response);
|
||||
return res == null ? null : (res == "" ? null : res);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 读取 response body 内容为字符串
|
||||
*
|
||||
* @param response
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
private static String readResponse(HttpResponse response) throws IOException {
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
|
||||
String result = new String();
|
||||
String line;
|
||||
while ((line = in.readLine()) != null) {
|
||||
result += line;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建模拟客户端(针对 https 客户端禁用 SSL 验证)
|
||||
*
|
||||
* @param cookieStore 缓存的 Cookies 信息
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
private static CloseableHttpClient createHttpClientWithNoSsl() throws Exception {
|
||||
// Create a trust manager that does not validate certificate chains
|
||||
TrustManager[] trustAllCerts = new TrustManager[]{
|
||||
new X509TrustManager() {
|
||||
@Override
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate[] certs, String authType) {
|
||||
// don't check
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] certs, String authType) {
|
||||
// don't check
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
SSLContext ctx = SSLContext.getInstance("TLS");
|
||||
ctx.init(null, trustAllCerts, null);
|
||||
LayeredConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(ctx);
|
||||
return HttpClients.custom()
|
||||
.setSSLSocketFactory(sslSocketFactory)
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,304 @@
|
||||
package org.jeecg.modules.cas.util;
|
||||
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.xml.XMLConstants;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.parsers.SAXParser;
|
||||
import javax.xml.parsers.SAXParserFactory;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.w3c.dom.Document;
|
||||
import org.xml.sax.Attributes;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.XMLReader;
|
||||
import org.xml.sax.helpers.DefaultHandler;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 解析cas,ST验证后的xml
|
||||
* @author: jeecg-boot
|
||||
*/
|
||||
@Slf4j
|
||||
public final class XmlUtils {
|
||||
|
||||
/**
|
||||
* attributes
|
||||
*/
|
||||
private static final String ATTRIBUTES = "attributes";
|
||||
|
||||
/**
|
||||
* Creates a new namespace-aware DOM document object by parsing the given XML.
|
||||
*
|
||||
* @param xml XML content.
|
||||
*
|
||||
* @return DOM document.
|
||||
*/
|
||||
public static Document newDocument(final String xml) {
|
||||
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
final Map<String, Boolean> features = new HashMap(5);
|
||||
features.put(XMLConstants.FEATURE_SECURE_PROCESSING, true);
|
||||
features.put("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
|
||||
for (final Map.Entry<String, Boolean> entry : features.entrySet()) {
|
||||
try {
|
||||
factory.setFeature(entry.getKey(), entry.getValue());
|
||||
} catch (ParserConfigurationException e) {
|
||||
log.warn("Failed setting XML feature {}: {}", entry.getKey(), e);
|
||||
}
|
||||
}
|
||||
factory.setNamespaceAware(true);
|
||||
try {
|
||||
return factory.newDocumentBuilder().parse(new InputSource(new StringReader(xml)));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("XML parsing error: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an instance of an XML reader from the XMLReaderFactory.
|
||||
*
|
||||
* @return the XMLReader.
|
||||
*/
|
||||
public static XMLReader getXmlReader() {
|
||||
try {
|
||||
final XMLReader reader = SAXParserFactory.newInstance().newSAXParser().getXMLReader();
|
||||
reader.setFeature("http://xml.org/sax/features/namespaces", true);
|
||||
reader.setFeature("http://xml.org/sax/features/namespace-prefixes", false);
|
||||
reader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
|
||||
return reader;
|
||||
} catch (final Exception e) {
|
||||
throw new RuntimeException("Unable to create XMLReader", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the text for a group of elements. Each text element is an entry
|
||||
* in a list.
|
||||
* <p>This method is currently optimized for the use case of two elements in a list.
|
||||
*
|
||||
* @param xmlAsString the xml response
|
||||
* @param element the element to look for
|
||||
* @return the list of text from the elements.
|
||||
*/
|
||||
public static List<String> getTextForElements(final String xmlAsString, final String element) {
|
||||
final List<String> elements = new ArrayList<String>(2);
|
||||
final XMLReader reader = getXmlReader();
|
||||
|
||||
final DefaultHandler handler = new DefaultHandler() {
|
||||
|
||||
private boolean foundElement = false;
|
||||
|
||||
private StringBuilder buffer = new StringBuilder();
|
||||
|
||||
@Override
|
||||
public void startElement(final String uri, final String localName, final String qName,
|
||||
final Attributes attributes) throws SAXException {
|
||||
if (localName.equals(element)) {
|
||||
this.foundElement = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endElement(final String uri, final String localName, final String qName) throws SAXException {
|
||||
if (localName.equals(element)) {
|
||||
this.foundElement = false;
|
||||
elements.add(this.buffer.toString());
|
||||
this.buffer = new StringBuilder();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void characters(char[] ch, int start, int length) throws SAXException {
|
||||
if (this.foundElement) {
|
||||
this.buffer.append(ch, start, length);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
reader.setContentHandler(handler);
|
||||
reader.setErrorHandler(handler);
|
||||
|
||||
try {
|
||||
reader.parse(new InputSource(new StringReader(xmlAsString)));
|
||||
} catch (final Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the text for a specific element (when we know there is only
|
||||
* one).
|
||||
*
|
||||
* @param xmlAsString the xml response
|
||||
* @param element the element to look for
|
||||
* @return the text value of the element.
|
||||
*/
|
||||
public static String getTextForElement(final String xmlAsString, final String element) {
|
||||
final XMLReader reader = getXmlReader();
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
|
||||
final DefaultHandler handler = new DefaultHandler() {
|
||||
|
||||
private boolean foundElement = false;
|
||||
|
||||
@Override
|
||||
public void startElement(final String uri, final String localName, final String qName,
|
||||
final Attributes attributes) throws SAXException {
|
||||
if (localName.equals(element)) {
|
||||
this.foundElement = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endElement(final String uri, final String localName, final String qName) throws SAXException {
|
||||
if (localName.equals(element)) {
|
||||
this.foundElement = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void characters(char[] ch, int start, int length) throws SAXException {
|
||||
if (this.foundElement) {
|
||||
builder.append(ch, start, length);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
reader.setContentHandler(handler);
|
||||
reader.setErrorHandler(handler);
|
||||
|
||||
try {
|
||||
reader.parse(new InputSource(new StringReader(xmlAsString)));
|
||||
} catch (final Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
|
||||
public static Map<String, Object> extractCustomAttributes(final String xml) {
|
||||
final SAXParserFactory spf = SAXParserFactory.newInstance();
|
||||
spf.setNamespaceAware(true);
|
||||
spf.setValidating(false);
|
||||
try {
|
||||
final SAXParser saxParser = spf.newSAXParser();
|
||||
final XMLReader xmlReader = saxParser.getXMLReader();
|
||||
final CustomAttributeHandler handler = new CustomAttributeHandler();
|
||||
xmlReader.setContentHandler(handler);
|
||||
xmlReader.parse(new InputSource(new StringReader(xml)));
|
||||
return handler.getAttributes();
|
||||
} catch (final Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
}
|
||||
|
||||
private static class CustomAttributeHandler extends DefaultHandler {
|
||||
|
||||
private Map<String, Object> attributes;
|
||||
|
||||
private boolean foundAttributes;
|
||||
|
||||
private String currentAttribute;
|
||||
|
||||
private StringBuilder value;
|
||||
|
||||
@Override
|
||||
public void startDocument() throws SAXException {
|
||||
this.attributes = new HashMap(5);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startElement(final String nameSpaceUri, final String localName, final String qName,
|
||||
final Attributes attributes) throws SAXException {
|
||||
if (ATTRIBUTES.equals(localName)) {
|
||||
this.foundAttributes = true;
|
||||
} else if (this.foundAttributes) {
|
||||
this.value = new StringBuilder();
|
||||
this.currentAttribute = localName;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void characters(final char[] chars, final int start, final int length) throws SAXException {
|
||||
if (this.currentAttribute != null) {
|
||||
value.append(chars, start, length);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endElement(final String nameSpaceUri, final String localName, final String qName)
|
||||
throws SAXException {
|
||||
if (ATTRIBUTES.equals(localName)) {
|
||||
this.foundAttributes = false;
|
||||
this.currentAttribute = null;
|
||||
} else if (this.foundAttributes) {
|
||||
final Object o = this.attributes.get(this.currentAttribute);
|
||||
|
||||
if (o == null) {
|
||||
this.attributes.put(this.currentAttribute, this.value.toString());
|
||||
} else {
|
||||
final List<Object> items;
|
||||
if (o instanceof List) {
|
||||
items = (List<Object>) o;
|
||||
} else {
|
||||
items = new LinkedList<Object>();
|
||||
items.add(o);
|
||||
this.attributes.put(this.currentAttribute, items);
|
||||
}
|
||||
items.add(this.value.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Object> getAttributes() {
|
||||
return this.attributes;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void main(String[] args) {
|
||||
String result = "<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>\r\n" +
|
||||
" <cas:authenticationSuccess>\r\n" +
|
||||
" <cas:user>admin</cas:user>\r\n" +
|
||||
" <cas:attributes>\r\n" +
|
||||
" <cas:credentialType>UsernamePasswordCredential</cas:credentialType>\r\n" +
|
||||
" <cas:isFromNewLogin>true</cas:isFromNewLogin>\r\n" +
|
||||
" <cas:authenticationDate>2019-08-01T19:33:21.527+08:00[Asia/Shanghai]</cas:authenticationDate>\r\n" +
|
||||
" <cas:authenticationMethod>RestAuthenticationHandler</cas:authenticationMethod>\r\n" +
|
||||
" <cas:successfulAuthenticationHandlers>RestAuthenticationHandler</cas:successfulAuthenticationHandlers>\r\n" +
|
||||
" <cas:longTermAuthenticationRequestTokenUsed>false</cas:longTermAuthenticationRequestTokenUsed>\r\n" +
|
||||
" </cas:attributes>\r\n" +
|
||||
" </cas:authenticationSuccess>\r\n" +
|
||||
"</cas:serviceResponse>";
|
||||
|
||||
String errorRes = "<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>\r\n" +
|
||||
" <cas:authenticationFailure code=\"INVALID_TICKET\">未能够识别出目标 'ST-5-1g-9cNES6KXNRwq-GuRET103sm0-DESKTOP-VKLS8B3'票根</cas:authenticationFailure>\r\n" +
|
||||
"</cas:serviceResponse>";
|
||||
|
||||
String error = XmlUtils.getTextForElement(errorRes, "authenticationFailure");
|
||||
//System.out.println("------"+error);
|
||||
|
||||
String error2 = XmlUtils.getTextForElement(result, "authenticationFailure");
|
||||
//System.out.println("------"+error2);
|
||||
String principal = XmlUtils.getTextForElement(result, "user");
|
||||
//System.out.println("---principal---"+principal);
|
||||
Map<String, Object> attributes = XmlUtils.extractCustomAttributes(result);
|
||||
System.out.println("---attributes---"+attributes);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
package org.jeecg.modules.message.controller;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.system.base.controller.JeecgController;
|
||||
import org.jeecg.common.system.query.QueryGenerator;
|
||||
import org.jeecg.modules.message.entity.SysMessage;
|
||||
import org.jeecg.modules.message.service.ISysMessageService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* @Description: 消息
|
||||
* @author: jeecg-boot
|
||||
* @date: 2019-04-09
|
||||
* @version: V1.0
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/sys/message/sysMessage")
|
||||
public class SysMessageController extends JeecgController<SysMessage, ISysMessageService> {
|
||||
@Autowired
|
||||
private ISysMessageService sysMessageService;
|
||||
|
||||
/**
|
||||
* 分页列表查询
|
||||
*
|
||||
* @param sysMessage
|
||||
* @param pageNo
|
||||
* @param pageSize
|
||||
* @param req
|
||||
* @return
|
||||
*/
|
||||
@GetMapping(value = "/list")
|
||||
public Result<?> queryPageList(SysMessage sysMessage, @RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize, HttpServletRequest req) {
|
||||
QueryWrapper<SysMessage> queryWrapper = QueryGenerator.initQueryWrapper(sysMessage, req.getParameterMap());
|
||||
Page<SysMessage> page = new Page<SysMessage>(pageNo, pageSize);
|
||||
IPage<SysMessage> pageList = sysMessageService.page(page, queryWrapper);
|
||||
return Result.ok(pageList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
*
|
||||
* @param sysMessage
|
||||
* @return
|
||||
*/
|
||||
@PostMapping(value = "/add")
|
||||
public Result<?> add(@RequestBody SysMessage sysMessage) {
|
||||
sysMessageService.save(sysMessage);
|
||||
return Result.ok("添加成功!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑
|
||||
*
|
||||
* @param sysMessage
|
||||
* @return
|
||||
*/
|
||||
@PutMapping(value = "/edit")
|
||||
public Result<?> edit(@RequestBody SysMessage sysMessage) {
|
||||
sysMessageService.updateById(sysMessage);
|
||||
return Result.ok("修改成功!");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过id删除
|
||||
*
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
@DeleteMapping(value = "/delete")
|
||||
public Result<?> delete(@RequestParam(name = "id", required = true) String id) {
|
||||
sysMessageService.removeById(id);
|
||||
return Result.ok("删除成功!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
*
|
||||
* @param ids
|
||||
* @return
|
||||
*/
|
||||
@DeleteMapping(value = "/deleteBatch")
|
||||
public Result<?> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
|
||||
|
||||
this.sysMessageService.removeByIds(Arrays.asList(ids.split(",")));
|
||||
return Result.ok("批量删除成功!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过id查询
|
||||
*
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
@GetMapping(value = "/queryById")
|
||||
public Result<?> queryById(@RequestParam(name = "id", required = true) String id) {
|
||||
SysMessage sysMessage = sysMessageService.getById(id);
|
||||
return Result.ok(sysMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出excel
|
||||
*
|
||||
* @param request
|
||||
*/
|
||||
@GetMapping(value = "/exportXls")
|
||||
public ModelAndView exportXls(HttpServletRequest request, SysMessage sysMessage) {
|
||||
return super.exportXls(request,sysMessage,SysMessage.class, "推送消息模板");
|
||||
}
|
||||
|
||||
/**
|
||||
* excel导入
|
||||
*
|
||||
* @param request
|
||||
* @param response
|
||||
* @return
|
||||
*/
|
||||
@PostMapping(value = "/importExcel")
|
||||
public Result<?> importExcel(HttpServletRequest request, HttpServletResponse response) {
|
||||
return super.importExcel(request, response, SysMessage.class);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
package org.jeecg.modules.message.controller;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.jeecg.common.api.dto.message.MessageDTO;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.system.api.ISysBaseAPI;
|
||||
import org.jeecg.common.system.base.controller.JeecgController;
|
||||
import org.jeecg.common.system.query.QueryGenerator;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.modules.message.entity.MsgParams;
|
||||
import org.jeecg.modules.message.entity.SysMessageTemplate;
|
||||
import org.jeecg.modules.message.service.ISysMessageTemplateService;
|
||||
import org.jeecg.modules.message.util.PushMsgUtil;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* @Description: 消息模板
|
||||
* @Author: jeecg-boot
|
||||
* @Sate: 2019-04-09
|
||||
* @Version: V1.0
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/sys/message/sysMessageTemplate")
|
||||
public class SysMessageTemplateController extends JeecgController<SysMessageTemplate, ISysMessageTemplateService> {
|
||||
@Autowired
|
||||
private ISysMessageTemplateService sysMessageTemplateService;
|
||||
@Autowired
|
||||
private PushMsgUtil pushMsgUtil;
|
||||
|
||||
@Autowired
|
||||
private ISysBaseAPI sysBaseApi;
|
||||
|
||||
/**
|
||||
* 分页列表查询
|
||||
*
|
||||
* @param sysMessageTemplate
|
||||
* @param pageNo
|
||||
* @param pageSize
|
||||
* @param req
|
||||
* @return
|
||||
*/
|
||||
@GetMapping(value = "/list")
|
||||
public Result<?> queryPageList(SysMessageTemplate sysMessageTemplate, @RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize, HttpServletRequest req) {
|
||||
QueryWrapper<SysMessageTemplate> queryWrapper = QueryGenerator.initQueryWrapper(sysMessageTemplate, req.getParameterMap());
|
||||
Page<SysMessageTemplate> page = new Page<SysMessageTemplate>(pageNo, pageSize);
|
||||
IPage<SysMessageTemplate> pageList = sysMessageTemplateService.page(page, queryWrapper);
|
||||
return Result.ok(pageList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
*
|
||||
* @param sysMessageTemplate
|
||||
* @return
|
||||
*/
|
||||
@PostMapping(value = "/add")
|
||||
public Result<?> add(@RequestBody SysMessageTemplate sysMessageTemplate) {
|
||||
sysMessageTemplateService.save(sysMessageTemplate);
|
||||
return Result.ok("添加成功!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑
|
||||
*
|
||||
* @param sysMessageTemplate
|
||||
* @return
|
||||
*/
|
||||
@PutMapping(value = "/edit")
|
||||
public Result<?> edit(@RequestBody SysMessageTemplate sysMessageTemplate) {
|
||||
sysMessageTemplateService.updateById(sysMessageTemplate);
|
||||
return Result.ok("更新成功!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过id删除
|
||||
*
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
@DeleteMapping(value = "/delete")
|
||||
public Result<?> delete(@RequestParam(name = "id", required = true) String id) {
|
||||
sysMessageTemplateService.removeById(id);
|
||||
return Result.ok("删除成功!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
*
|
||||
* @param ids
|
||||
* @return
|
||||
*/
|
||||
@DeleteMapping(value = "/deleteBatch")
|
||||
public Result<?> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
|
||||
this.sysMessageTemplateService.removeByIds(Arrays.asList(ids.split(",")));
|
||||
return Result.ok("批量删除成功!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过id查询
|
||||
*
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
@GetMapping(value = "/queryById")
|
||||
public Result<?> queryById(@RequestParam(name = "id", required = true) String id) {
|
||||
SysMessageTemplate sysMessageTemplate = sysMessageTemplateService.getById(id);
|
||||
return Result.ok(sysMessageTemplate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出excel
|
||||
*
|
||||
* @param request
|
||||
*/
|
||||
@GetMapping(value = "/exportXls")
|
||||
public ModelAndView exportXls(HttpServletRequest request,SysMessageTemplate sysMessageTemplate) {
|
||||
return super.exportXls(request, sysMessageTemplate, SysMessageTemplate.class,"推送消息模板");
|
||||
}
|
||||
|
||||
/**
|
||||
* excel导入
|
||||
*
|
||||
* @param request
|
||||
* @param response
|
||||
* @return
|
||||
*/
|
||||
@PostMapping(value = "/importExcel")
|
||||
public Result<?> importExcel(HttpServletRequest request, HttpServletResponse response) {
|
||||
return super.importExcel(request, response, SysMessageTemplate.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
*/
|
||||
@PostMapping(value = "/sendMsg")
|
||||
public Result<SysMessageTemplate> sendMessage(@RequestBody MsgParams msgParams) {
|
||||
Result<SysMessageTemplate> result = new Result<SysMessageTemplate>();
|
||||
try {
|
||||
MessageDTO md = new MessageDTO();
|
||||
md.setToAll(false);
|
||||
md.setTitle("消息发送测试");
|
||||
md.setTemplateCode(msgParams.getTemplateCode());
|
||||
md.setToUser(msgParams.getReceiver());
|
||||
md.setType(msgParams.getMsgType());
|
||||
String testData = msgParams.getTestData();
|
||||
if(oConvertUtils.isNotEmpty(testData)){
|
||||
Map<String, Object> data = JSON.parseObject(testData, Map.class);
|
||||
md.setData(data);
|
||||
}
|
||||
sysBaseApi.sendTemplateMessage(md);
|
||||
return result.success("消息发送成功!");
|
||||
} catch (Exception e) {
|
||||
log.error("发送消息出错:" + e.getMessage(), e);
|
||||
return result.error500("发送消息出错!");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package org.jeecg.modules.message.controller;
|
||||
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.constant.WebsocketConst;
|
||||
import org.jeecg.modules.message.websocket.WebSocket;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
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 com.alibaba.fastjson.JSONObject;
|
||||
|
||||
/**
|
||||
* @Description: TestSocketController
|
||||
* @author: jeecg-boot
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/sys/socketTest")
|
||||
public class TestSocketController {
|
||||
|
||||
@Autowired
|
||||
private WebSocket webSocket;
|
||||
|
||||
@PostMapping("/sendAll")
|
||||
public Result<String> sendAll(@RequestBody JSONObject jsonObject) {
|
||||
Result<String> result = new Result<String>();
|
||||
String message = jsonObject.getString("message");
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put(WebsocketConst.MSG_CMD, WebsocketConst.CMD_TOPIC);
|
||||
obj.put(WebsocketConst.MSG_ID, "M0001");
|
||||
obj.put(WebsocketConst.MSG_TXT, message);
|
||||
webSocket.sendMessage(obj.toJSONString());
|
||||
result.setResult("群发!");
|
||||
return result;
|
||||
}
|
||||
|
||||
@PostMapping("/sendUser")
|
||||
public Result<String> sendUser(@RequestBody JSONObject jsonObject) {
|
||||
Result<String> result = new Result<String>();
|
||||
String userId = jsonObject.getString("userId");
|
||||
String message = jsonObject.getString("message");
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put(WebsocketConst.MSG_CMD, WebsocketConst.CMD_USER);
|
||||
obj.put(WebsocketConst.MSG_USER_ID, userId);
|
||||
obj.put(WebsocketConst.MSG_ID, "M0001");
|
||||
obj.put(WebsocketConst.MSG_TXT, message);
|
||||
webSocket.sendMessage(userId, obj.toJSONString());
|
||||
result.setResult("单发");
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package org.jeecg.modules.message.entity;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 发送消息实体
|
||||
* @author: jeecg-boot
|
||||
*/
|
||||
@Data
|
||||
public class MsgParams implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* 消息类型
|
||||
*/
|
||||
private String msgType;
|
||||
|
||||
/**
|
||||
* 消息接收方
|
||||
*/
|
||||
private String receiver;
|
||||
|
||||
/**
|
||||
* 消息模板码
|
||||
*/
|
||||
private String templateCode;
|
||||
|
||||
/**
|
||||
* 测试数据
|
||||
*/
|
||||
private String testData;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package org.jeecg.modules.message.entity;
|
||||
|
||||
import org.jeecg.common.aspect.annotation.Dict;
|
||||
import org.jeecg.common.system.base.entity.JeecgEntity;
|
||||
import org.jeecgframework.poi.excel.annotation.Excel;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* @Description: 消息
|
||||
* @Author: jeecg-boot
|
||||
* @Date: 2019-04-09
|
||||
* @Version: V1.0
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Accessors(chain = true)
|
||||
@TableName("sys_sms")
|
||||
public class SysMessage extends JeecgEntity {
|
||||
/**推送内容*/
|
||||
@Excel(name = "推送内容", width = 15)
|
||||
private java.lang.String esContent;
|
||||
/**推送所需参数Json格式*/
|
||||
@Excel(name = "推送所需参数Json格式", width = 15)
|
||||
private java.lang.String esParam;
|
||||
/**接收人*/
|
||||
@Excel(name = "接收人", width = 15)
|
||||
private java.lang.String esReceiver;
|
||||
/**推送失败原因*/
|
||||
@Excel(name = "推送失败原因", width = 15)
|
||||
private java.lang.String esResult;
|
||||
/**发送次数*/
|
||||
@Excel(name = "发送次数", width = 15)
|
||||
private java.lang.Integer esSendNum;
|
||||
/**推送状态 0未推送 1推送成功 2推送失败*/
|
||||
@Excel(name = "推送状态 0未推送 1推送成功 2推送失败", width = 15)
|
||||
@Dict(dicCode = "msgSendStatus")
|
||||
private java.lang.String esSendStatus;
|
||||
/**推送时间*/
|
||||
@Excel(name = "推送时间", width = 20, format = "yyyy-MM-dd HH:mm:ss")
|
||||
@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
|
||||
private java.util.Date esSendTime;
|
||||
/**消息标题*/
|
||||
@Excel(name = "消息标题", width = 15)
|
||||
private java.lang.String esTitle;
|
||||
/**
|
||||
* 推送方式:参考枚举类MessageTypeEnum
|
||||
*/
|
||||
@Excel(name = "推送方式", width = 15)
|
||||
@Dict(dicCode = "messageType")
|
||||
private java.lang.String esType;
|
||||
/**备注*/
|
||||
@Excel(name = "备注", width = 15)
|
||||
private java.lang.String remark;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package org.jeecg.modules.message.entity;
|
||||
|
||||
import org.jeecg.common.system.base.entity.JeecgEntity;
|
||||
import org.jeecgframework.poi.excel.annotation.Excel;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* @Description: 消息模板
|
||||
* @Author: jeecg-boot
|
||||
* @Date: 2019-04-09
|
||||
* @Version: V1.0
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Accessors(chain = true)
|
||||
@TableName("sys_sms_template")
|
||||
public class SysMessageTemplate extends JeecgEntity{
|
||||
/**模板CODE*/
|
||||
@Excel(name = "模板CODE", width = 15)
|
||||
private java.lang.String templateCode;
|
||||
/**模板标题*/
|
||||
@Excel(name = "模板标题", width = 30)
|
||||
private java.lang.String templateName;
|
||||
/**模板内容*/
|
||||
@Excel(name = "模板内容", width = 50)
|
||||
private java.lang.String templateContent;
|
||||
/**模板测试json*/
|
||||
@Excel(name = "模板测试json", width = 15)
|
||||
private java.lang.String templateTestJson;
|
||||
/**模板类型*/
|
||||
@Excel(name = "模板类型", width = 15)
|
||||
private java.lang.String templateType;
|
||||
/**模板分类*/
|
||||
@Excel(name = "模板类型(notice通知公告 other其他)", width = 15)
|
||||
private java.lang.String templateCategory;
|
||||
|
||||
/**已经应用/未应用 1是0否*/
|
||||
@Excel(name = "应用状态", width = 15)
|
||||
private String useStatus;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package org.jeecg.modules.message.enums;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.constant.enums.MessageTypeEnum;
|
||||
import org.jeecg.common.system.annotation.EnumDict;
|
||||
import org.jeecg.common.system.vo.DictModel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 用于消息数据查询【vue3】
|
||||
* 新版系统通知查询条件
|
||||
* @Author taoYan
|
||||
* @Date 2022/8/19 20:41
|
||||
**/
|
||||
@Slf4j
|
||||
@EnumDict("rangeDate")
|
||||
public enum RangeDateEnum {
|
||||
|
||||
JT("jt", "今天"),
|
||||
ZT("zt", "昨天"),
|
||||
QT("qt", "前天"),
|
||||
BZ("bz","本周"),
|
||||
SZ("sz", "上周"),
|
||||
BY("by", "本月"),
|
||||
SY("sy", "上月"),
|
||||
SEVENDAYS("7day", "7日"),
|
||||
ZDY("zdy", "自定义日期");
|
||||
|
||||
String key;
|
||||
|
||||
String title;
|
||||
|
||||
RangeDateEnum(String key, String title){
|
||||
this.key = key;
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字典数据
|
||||
* @return
|
||||
*/
|
||||
public static List<DictModel> getDictList(){
|
||||
List<DictModel> list = new ArrayList<>();
|
||||
DictModel dictModel = null;
|
||||
for(RangeDateEnum e: RangeDateEnum.values()){
|
||||
dictModel = new DictModel();
|
||||
dictModel.setValue(e.key);
|
||||
dictModel.setText(e.title);
|
||||
list.add(dictModel);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据key 获取范围时间值
|
||||
* @param key
|
||||
* @return
|
||||
*/
|
||||
public static Date[] getRangeArray(String key){
|
||||
Calendar calendar1 = Calendar.getInstance();
|
||||
Calendar calendar2 = Calendar.getInstance();
|
||||
Date[] array = new Date[2];
|
||||
boolean flag = false;
|
||||
if(JT.key.equals(key)){
|
||||
//今天
|
||||
} else if(ZT.key.equals(key)){
|
||||
//昨天
|
||||
calendar1.add(Calendar.DAY_OF_YEAR, -1);
|
||||
calendar2.add(Calendar.DAY_OF_YEAR, -1);
|
||||
} else if(QT.key.equals(key)){
|
||||
//前天
|
||||
calendar1.add(Calendar.DAY_OF_YEAR, -2);
|
||||
calendar2.add(Calendar.DAY_OF_YEAR, -2);
|
||||
} else if(BZ.key.equals(key)){
|
||||
//本周
|
||||
calendar1.set(Calendar.DAY_OF_WEEK, 2);
|
||||
|
||||
calendar2.add(Calendar.WEEK_OF_MONTH,1);
|
||||
calendar2.add(Calendar.DAY_OF_WEEK,-1);
|
||||
} else if(SZ.key.equals(key)){
|
||||
//本周一减一周
|
||||
calendar1.set(Calendar.DAY_OF_WEEK, 2);
|
||||
calendar1.add(Calendar.WEEK_OF_MONTH, -1);
|
||||
|
||||
// 本周一减一天
|
||||
calendar2.set(Calendar.DAY_OF_WEEK, 2);
|
||||
calendar2.add(Calendar.DAY_OF_WEEK,-1);
|
||||
} else if(BY.key.equals(key)){
|
||||
//本月
|
||||
calendar1.set(Calendar.DAY_OF_MONTH, 1);
|
||||
|
||||
calendar2.set(Calendar.DAY_OF_MONTH, 1);
|
||||
calendar2.add(Calendar.MONTH, 1);
|
||||
calendar2.add(Calendar.DAY_OF_MONTH, -1);
|
||||
} else if(SY.key.equals(key)){
|
||||
//本月第一天减一月
|
||||
calendar1.set(Calendar.DAY_OF_MONTH, 1);
|
||||
calendar1.add(Calendar.MONTH, -1);
|
||||
|
||||
//本月第一天减一天
|
||||
calendar2.set(Calendar.DAY_OF_MONTH, 1);
|
||||
calendar2.add(Calendar.DAY_OF_MONTH, -1);
|
||||
} else if (SEVENDAYS.key.equals(key)){
|
||||
//七日第一天
|
||||
calendar1.setTime(new Date());
|
||||
calendar1.add(Calendar.DATE, -7);
|
||||
}else{
|
||||
flag = true;
|
||||
}
|
||||
if(flag){
|
||||
return null;
|
||||
}
|
||||
// 开始时间00:00:00 结束时间23:59:59
|
||||
calendar1.set(Calendar.HOUR, 0);
|
||||
calendar1.set(Calendar.MINUTE, 0);
|
||||
calendar1.set(Calendar.SECOND, 0);
|
||||
calendar1.set(Calendar.MILLISECOND, 0);
|
||||
calendar2.set(Calendar.HOUR, 23);
|
||||
calendar2.set(Calendar.MINUTE, 59);
|
||||
calendar2.set(Calendar.SECOND, 59);
|
||||
calendar2.set(Calendar.MILLISECOND, 999);
|
||||
array[0] = calendar1.getTime();
|
||||
array[1] = calendar2.getTime();
|
||||
return array;
|
||||
}
|
||||
|
||||
public String getKey(){
|
||||
return this.key;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.jeecg.modules.message.handle;
|
||||
|
||||
import org.jeecg.common.api.dto.message.MessageDTO;
|
||||
|
||||
/**
|
||||
* @Description: 发送信息接口
|
||||
* @author: jeecg-boot
|
||||
*/
|
||||
public interface ISendMsgHandle {
|
||||
|
||||
/**
|
||||
* 发送信息
|
||||
* @param esReceiver 接受人
|
||||
* @param esTitle 标题
|
||||
* @param esContent 内容
|
||||
*/
|
||||
void sendMsg(String esReceiver, String esTitle, String esContent);
|
||||
|
||||
/**
|
||||
* 发送信息
|
||||
* @param messageDTO
|
||||
*/
|
||||
default void sendMessage(MessageDTO messageDTO){
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.jeecg.modules.message.handle.enums;
|
||||
|
||||
/**
|
||||
* 推送状态枚举
|
||||
* @author: jeecg-boot
|
||||
*/
|
||||
public enum SendMsgStatusEnum {
|
||||
|
||||
//推送状态 0未推送 1推送成功 2推送失败
|
||||
WAIT("0"), SUCCESS("1"), FAIL("2");
|
||||
|
||||
private String code;
|
||||
|
||||
private SendMsgStatusEnum(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setStatusCode(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package org.jeecg.modules.message.handle.enums;
|
||||
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
|
||||
/**
|
||||
* 发送消息类型枚举
|
||||
* @author: jeecg-boot
|
||||
*/
|
||||
public enum SendMsgTypeEnum {
|
||||
|
||||
/**
|
||||
* 短信
|
||||
*/
|
||||
SMS("1", "org.jeecg.modules.message.handle.impl.SmsSendMsgHandle"),
|
||||
/**
|
||||
* 邮件
|
||||
*/
|
||||
EMAIL("2", "org.jeecg.modules.message.handle.impl.EmailSendMsgHandle"),
|
||||
/**
|
||||
* 微信
|
||||
*/
|
||||
WX("3","org.jeecg.modules.message.handle.impl.WxSendMsgHandle"),
|
||||
/**
|
||||
* 系统消息
|
||||
*/
|
||||
SYSTEM_MESSAGE("4","org.jeecg.modules.message.handle.impl.SystemSendMsgHandle");
|
||||
|
||||
private String type;
|
||||
|
||||
private String implClass;
|
||||
|
||||
private SendMsgTypeEnum(String type, String implClass) {
|
||||
this.type = type;
|
||||
this.implClass = implClass;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getImplClass() {
|
||||
return implClass;
|
||||
}
|
||||
|
||||
public void setImplClass(String implClass) {
|
||||
this.implClass = implClass;
|
||||
}
|
||||
|
||||
public static SendMsgTypeEnum getByType(String type) {
|
||||
if (oConvertUtils.isEmpty(type)) {
|
||||
return null;
|
||||
}
|
||||
for (SendMsgTypeEnum val : values()) {
|
||||
if (val.getType().equals(type)) {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.jeecg.modules.message.handle.impl;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.api.dto.message.MessageDTO;
|
||||
import org.jeecg.modules.message.handle.ISendMsgHandle;
|
||||
import org.jeecg.modules.system.service.impl.ThirdAppDingtalkServiceImpl;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @Description: 发钉钉消息模板
|
||||
* @author: jeecg-boot
|
||||
*/
|
||||
@Slf4j
|
||||
@Component("ddSendMsgHandle")
|
||||
public class DdSendMsgHandle implements ISendMsgHandle {
|
||||
|
||||
@Autowired
|
||||
private ThirdAppDingtalkServiceImpl dingtalkService;
|
||||
|
||||
@Override
|
||||
public void sendMsg(String esReceiver, String esTitle, String esContent) {
|
||||
log.info("发微信消息模板");
|
||||
MessageDTO messageDTO = new MessageDTO();
|
||||
messageDTO.setToUser(esReceiver);
|
||||
messageDTO.setTitle(esTitle);
|
||||
messageDTO.setContent(esContent);
|
||||
messageDTO.setToAll(false);
|
||||
sendMessage(messageDTO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(MessageDTO messageDTO) {
|
||||
dingtalkService.sendMessage(messageDTO, true);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
package org.jeecg.modules.message.handle.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.jeecg.common.api.dto.message.MessageDTO;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.common.constant.enums.MessageTypeEnum;
|
||||
import org.jeecg.common.system.util.JwtUtil;
|
||||
import org.jeecg.common.util.RedisUtil;
|
||||
import org.jeecg.common.util.SpringContextUtils;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.config.StaticConfig;
|
||||
import org.jeecg.modules.message.entity.SysMessage;
|
||||
import org.jeecg.modules.message.handle.ISendMsgHandle;
|
||||
import org.jeecg.modules.message.mapper.SysMessageMapper;
|
||||
import org.jeecg.modules.system.entity.SysUser;
|
||||
import org.jeecg.modules.system.mapper.SysUserMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.mail.javamail.JavaMailSender;
|
||||
import org.springframework.mail.javamail.MimeMessageHelper;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import jakarta.mail.MessagingException;
|
||||
import jakarta.mail.internet.MimeMessage;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @Description: 邮箱发送信息
|
||||
* @author: jeecg-boot
|
||||
*/
|
||||
@Slf4j
|
||||
@Component("emailSendMsgHandle")
|
||||
public class EmailSendMsgHandle implements ISendMsgHandle {
|
||||
static String emailFrom;
|
||||
|
||||
public static void setEmailFrom(String emailFrom) {
|
||||
EmailSendMsgHandle.emailFrom = emailFrom;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
SysUserMapper sysUserMapper;
|
||||
|
||||
@Autowired
|
||||
private RedisUtil redisUtil;
|
||||
|
||||
@Autowired
|
||||
private SysMessageMapper sysMessageMapper;
|
||||
|
||||
/**
|
||||
* 真实姓名变量
|
||||
*/
|
||||
private static final String realNameExp = "{REALNAME}";
|
||||
/**
|
||||
* 线程池用于异步发送消息
|
||||
*/
|
||||
public static ExecutorService cachedThreadPool = new ThreadPoolExecutor(0, 1024, 60L, TimeUnit.SECONDS, new SynchronousQueue<>());
|
||||
|
||||
|
||||
@Override
|
||||
public void sendMsg(String esReceiver, String esTitle, String esContent) {
|
||||
JavaMailSender mailSender = (JavaMailSender) SpringContextUtils.getBean("mailSender");
|
||||
MimeMessage message = mailSender.createMimeMessage();
|
||||
// 代码逻辑说明: 配置类数据获取
|
||||
if(oConvertUtils.isEmpty(emailFrom)){
|
||||
StaticConfig staticConfig = SpringContextUtils.getBean(StaticConfig.class);
|
||||
setEmailFrom(staticConfig.getEmailFrom());
|
||||
}
|
||||
cachedThreadPool.execute(()->{
|
||||
try {
|
||||
log.info("============> 开始邮件发送,接收人:"+esReceiver);
|
||||
MimeMessageHelper helper = new MimeMessageHelper(message, true);
|
||||
// 设置发送方邮箱地址
|
||||
helper.setFrom(emailFrom);
|
||||
helper.setTo(esReceiver);
|
||||
helper.setSubject(esTitle);
|
||||
helper.setText(esContent, true);
|
||||
mailSender.send(message);
|
||||
log.info("============> 邮件发送成功,接收人:"+esReceiver);
|
||||
} catch (MessagingException e) {
|
||||
log.error("============> 邮件发送失败,接收人:"+esReceiver, e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(MessageDTO messageDTO) {
|
||||
String content = messageDTO.getContent();
|
||||
String title = messageDTO.getTitle();
|
||||
// 代码逻辑说明: 【QQYUN-8523】敲敲云发邮件通知,不稳定---
|
||||
boolean timeJobSendEmail = this.isTimeJobSendEmail(messageDTO.getToUser(), title, content);
|
||||
if(timeJobSendEmail){
|
||||
return;
|
||||
}
|
||||
this.sendEmailMessage(messageDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 直接发送邮件
|
||||
*
|
||||
* @param messageDTO
|
||||
*/
|
||||
public void sendEmailMessage(MessageDTO messageDTO) {
|
||||
String[] arr = messageDTO.getToUser().split(",");
|
||||
LambdaQueryWrapper<SysUser> query = new LambdaQueryWrapper<SysUser>().in(SysUser::getUsername, arr);
|
||||
List<SysUser> list = sysUserMapper.selectList(query);
|
||||
String content = messageDTO.getContent();
|
||||
String title = messageDTO.getTitle();
|
||||
for(SysUser user: list){
|
||||
String email = user.getEmail();
|
||||
if (ObjectUtils.isEmpty(email)) {
|
||||
continue;
|
||||
}
|
||||
content=replaceContent(user,content);
|
||||
log.info("邮件内容:"+ content);
|
||||
sendMsg(email, title, content);
|
||||
}
|
||||
|
||||
// 代码逻辑说明: QQYUN-5557【简流】通知节点 发送邮箱 表单上有一个邮箱字段,流程中,邮件发送节点,邮件接收人 不可选择邮箱
|
||||
Set<String> toEmailList = messageDTO.getToEmailList();
|
||||
if(toEmailList!=null && toEmailList.size()>0){
|
||||
for(String email: toEmailList){
|
||||
if (ObjectUtils.isEmpty(email)) {
|
||||
continue;
|
||||
}
|
||||
log.info("邮件内容:"+ content);
|
||||
sendMsg(email, title, content);
|
||||
}
|
||||
}
|
||||
|
||||
//发送给抄送人
|
||||
sendMessageToCopyUser(messageDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送邮件给抄送人
|
||||
* @param messageDTO
|
||||
*/
|
||||
public void sendMessageToCopyUser(MessageDTO messageDTO) {
|
||||
String copyToUser = messageDTO.getCopyToUser();
|
||||
if(ObjectUtils.isNotEmpty(copyToUser)) {
|
||||
LambdaQueryWrapper<SysUser> query = new LambdaQueryWrapper<SysUser>().in(SysUser::getUsername, copyToUser.split(","));
|
||||
List<SysUser> list = sysUserMapper.selectList(query);
|
||||
String content = messageDTO.getContent();
|
||||
String title = messageDTO.getTitle();
|
||||
|
||||
for (SysUser user : list) {
|
||||
String email = user.getEmail();
|
||||
if (ObjectUtils.isEmpty(email)) {
|
||||
continue;
|
||||
}
|
||||
content=replaceContent(user,content);
|
||||
log.info("邮件内容:" + content);
|
||||
|
||||
// 代码逻辑说明: QQYUN-5557【简流】通知节点 发送邮箱 表单上有一个邮箱字段,流程中,邮件发送节点,邮件接收人 不可选择邮箱
|
||||
sendEmail(email, content, title);
|
||||
}
|
||||
|
||||
Set<String> ccEmailList = messageDTO.getCcEmailList();
|
||||
if(ccEmailList!=null && ccEmailList.size()>0){
|
||||
for(String email: ccEmailList){
|
||||
if (ObjectUtils.isEmpty(email)) {
|
||||
continue;
|
||||
}
|
||||
log.info("邮件内容:"+ content);
|
||||
sendEmail(email, content, title);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送邮件给抄送人调用
|
||||
* @param email
|
||||
* @param content
|
||||
* @param title
|
||||
*/
|
||||
private void sendEmail(String email, String content, String title){
|
||||
JavaMailSender mailSender = (JavaMailSender) SpringContextUtils.getBean("mailSender");
|
||||
MimeMessage message = mailSender.createMimeMessage();
|
||||
if (oConvertUtils.isEmpty(emailFrom)) {
|
||||
StaticConfig staticConfig = SpringContextUtils.getBean(StaticConfig.class);
|
||||
setEmailFrom(staticConfig.getEmailFrom());
|
||||
}
|
||||
cachedThreadPool.execute(()->{
|
||||
try {
|
||||
MimeMessageHelper helper = new MimeMessageHelper(message, true);
|
||||
// 设置发送方邮箱地址
|
||||
helper.setFrom(emailFrom);
|
||||
helper.setTo(email);
|
||||
//设置抄送人
|
||||
helper.setCc(email);
|
||||
helper.setSubject(title);
|
||||
helper.setText(content, true);
|
||||
mailSender.send(message);
|
||||
log.info("============> 邮件发送成功,接收人:"+email);
|
||||
} catch (MessagingException e) {
|
||||
log.warn("============> 邮件发送失败,接收人:"+email, e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 替换邮件内容变量
|
||||
* @param user
|
||||
* @param content
|
||||
* @return
|
||||
*/
|
||||
private String replaceContent(SysUser user,String content){
|
||||
if (content.indexOf(realNameExp) > 0) {
|
||||
content = content.replace("$"+realNameExp,user.getRealname()).replace(realNameExp, user.getRealname());
|
||||
}
|
||||
if (content.indexOf(CommonConstant.LOGIN_TOKEN) > 0) {
|
||||
String token = getToken(user);
|
||||
try {
|
||||
content = content.replace(CommonConstant.LOGIN_TOKEN, URLEncoder.encode(token, "UTF-8"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
log.error("邮件消息token编码失败", e.getMessage());
|
||||
}
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取token
|
||||
* @param user
|
||||
* @return
|
||||
*/
|
||||
private String getToken(SysUser user) {
|
||||
// 生成token
|
||||
String token = JwtUtil.sign(user.getUsername(), user.getPassword(), CommonConstant.CLIENT_TYPE_PC);
|
||||
redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, token);
|
||||
// 设置超时时间 1个小时
|
||||
redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, JwtUtil.EXPIRE_TIME * 1 / 1000);
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否定时发送邮箱
|
||||
* @param toUser
|
||||
* @param title
|
||||
* @param content
|
||||
* @return
|
||||
*/
|
||||
private boolean isTimeJobSendEmail(String toUser, String title, String content) {
|
||||
StaticConfig staticConfig = SpringContextUtils.getBean(StaticConfig.class);
|
||||
Boolean timeJobSend = staticConfig.getTimeJobSend();
|
||||
if(null != timeJobSend && timeJobSend){
|
||||
this.addSysSmsSend(toUser,title,content);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存到短信发送表
|
||||
*/
|
||||
private void addSysSmsSend(String toUser, String title, String content) {
|
||||
SysMessage sysMessage = new SysMessage();
|
||||
sysMessage.setEsTitle(title);
|
||||
sysMessage.setEsContent(content);
|
||||
sysMessage.setEsReceiver(toUser);
|
||||
sysMessage.setEsSendStatus("0");
|
||||
sysMessage.setEsSendNum(0);
|
||||
sysMessage.setEsType(MessageTypeEnum.YJ.getType());
|
||||
sysMessageMapper.insert(sysMessage);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.jeecg.modules.message.handle.impl;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.api.dto.message.MessageDTO;
|
||||
import org.jeecg.modules.message.handle.ISendMsgHandle;
|
||||
import org.jeecg.modules.system.service.impl.ThirdAppWechatEnterpriseServiceImpl;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @Description: 发企业微信消息模板
|
||||
* @author: jeecg-boot
|
||||
*/
|
||||
@Slf4j
|
||||
@Component("qywxSendMsgHandle")
|
||||
public class QywxSendMsgHandle implements ISendMsgHandle {
|
||||
|
||||
@Autowired
|
||||
private ThirdAppWechatEnterpriseServiceImpl wechatEnterpriseService;
|
||||
|
||||
@Override
|
||||
public void sendMsg(String esReceiver, String esTitle, String esContent) {
|
||||
log.info("发微信消息模板");
|
||||
MessageDTO messageDTO = new MessageDTO();
|
||||
messageDTO.setToUser(esReceiver);
|
||||
messageDTO.setTitle(esTitle);
|
||||
messageDTO.setContent(esContent);
|
||||
messageDTO.setToAll(false);
|
||||
sendMessage(messageDTO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(MessageDTO messageDTO) {
|
||||
wechatEnterpriseService.sendMessage(messageDTO, true);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.jeecg.modules.message.handle.impl;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.modules.message.handle.ISendMsgHandle;
|
||||
|
||||
/**
|
||||
* @Description: 短信发送
|
||||
* @author: jeecg-boot
|
||||
*/
|
||||
@Slf4j
|
||||
public class SmsSendMsgHandle implements ISendMsgHandle {
|
||||
|
||||
@Override
|
||||
public void sendMsg(String esReceiver, String esTitle, String esContent) {
|
||||
// TODO Auto-generated method stub
|
||||
log.info("发短信");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package org.jeecg.modules.message.handle.impl;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import org.jeecg.common.api.dto.message.MessageDTO;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.common.constant.WebsocketConst;
|
||||
import org.jeecg.common.constant.enums.NoticeTypeEnum;
|
||||
import org.jeecg.common.exception.JeecgBootException;
|
||||
import org.jeecg.common.system.api.ISysBaseAPI;
|
||||
import org.jeecg.common.util.SpringContextUtils;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.common.constant.enums.Vue3MessageHrefEnum;
|
||||
import org.jeecg.modules.message.handle.ISendMsgHandle;
|
||||
import org.jeecg.modules.message.websocket.WebSocket;
|
||||
import org.jeecg.modules.system.entity.SysAnnouncement;
|
||||
import org.jeecg.modules.system.entity.SysAnnouncementSend;
|
||||
import org.jeecg.modules.system.entity.SysUser;
|
||||
import org.jeecg.modules.system.mapper.SysAnnouncementMapper;
|
||||
import org.jeecg.modules.system.mapper.SysAnnouncementSendMapper;
|
||||
import org.jeecg.modules.system.mapper.SysUserMapper;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @Description: 发送系统消息
|
||||
* @Author: wangshuai
|
||||
* @Date: 2022年3月22日 18:48:20
|
||||
*/
|
||||
@Component("systemSendMsgHandle")
|
||||
public class SystemSendMsgHandle implements ISendMsgHandle {
|
||||
|
||||
public static final String FROM_USER="system";
|
||||
|
||||
@Resource
|
||||
private SysAnnouncementMapper sysAnnouncementMapper;
|
||||
|
||||
@Resource
|
||||
private SysUserMapper userMapper;
|
||||
|
||||
@Resource
|
||||
private SysAnnouncementSendMapper sysAnnouncementSendMapper;
|
||||
|
||||
@Resource
|
||||
private WebSocket webSocket;
|
||||
|
||||
/**
|
||||
* 该方法会发送3种消息:系统消息、企业微信 钉钉
|
||||
* @param esReceiver 发送人
|
||||
* @param esTitle 标题
|
||||
* @param esContent 内容
|
||||
*/
|
||||
@Override
|
||||
public void sendMsg(String esReceiver, String esTitle, String esContent) {
|
||||
if(oConvertUtils.isEmpty(esReceiver)){
|
||||
throw new JeecgBootException("被发送人不能为空");
|
||||
}
|
||||
ISysBaseAPI sysBaseApi = SpringContextUtils.getBean(ISysBaseAPI.class);
|
||||
MessageDTO messageDTO = new MessageDTO(FROM_USER,esReceiver,esTitle,esContent);
|
||||
sysBaseApi.sendSysAnnouncement(messageDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 仅发送系统消息
|
||||
* @param messageDTO
|
||||
*/
|
||||
@Override
|
||||
public void sendMessage(MessageDTO messageDTO) {
|
||||
//原方法不支持 sysBaseApi.sendSysAnnouncement(messageDTO); 有企业微信消息逻辑,
|
||||
String title = messageDTO.getTitle();
|
||||
String content = messageDTO.getContent();
|
||||
String fromUser = messageDTO.getFromUser();
|
||||
Map<String,Object> data = messageDTO.getData();
|
||||
String[] arr = messageDTO.getToUser().split(",");
|
||||
for(String username: arr){
|
||||
// 代码逻辑说明: 【QQYUN-12162】OA项目改造,系统重消息拆分,目前消息都在一起 需按分类进行拆分---
|
||||
doSend(title, content, fromUser, username, data, messageDTO.getNoticeType());
|
||||
}
|
||||
}
|
||||
|
||||
private void doSend(String title, String msgContent, String fromUser, String toUser, Map<String, Object> data, String noticeType){
|
||||
SysAnnouncement announcement = new SysAnnouncement();
|
||||
if(data!=null){
|
||||
//摘要信息
|
||||
Object msgAbstract = data.get(CommonConstant.NOTICE_MSG_SUMMARY);
|
||||
if(msgAbstract!=null){
|
||||
announcement.setMsgAbstract(msgAbstract.toString());
|
||||
}
|
||||
// 任务节点ID
|
||||
Object taskId = data.get(CommonConstant.NOTICE_MSG_BUS_ID);
|
||||
if(taskId!=null){
|
||||
announcement.setBusId(taskId.toString());
|
||||
announcement.setBusType(Vue3MessageHrefEnum.BPM_TASK.getBusType());
|
||||
noticeType = NoticeTypeEnum.NOTICE_TYPE_FLOW.getValue();
|
||||
}
|
||||
|
||||
// 流程内消息节点 发消息会传一个busType
|
||||
Object busType = data.get(CommonConstant.NOTICE_MSG_BUS_TYPE);
|
||||
if(busType!=null){
|
||||
announcement.setBusType(busType.toString());
|
||||
noticeType = NoticeTypeEnum.NOTICE_TYPE_FLOW.getValue();
|
||||
}
|
||||
}
|
||||
announcement.setTitile(title);
|
||||
announcement.setMsgContent(msgContent);
|
||||
announcement.setSender(fromUser);
|
||||
announcement.setPriority(CommonConstant.PRIORITY_M);
|
||||
announcement.setMsgType(CommonConstant.MSG_TYPE_UESR);
|
||||
announcement.setSendStatus(CommonConstant.HAS_SEND);
|
||||
announcement.setSendTime(new Date());
|
||||
//系统消息
|
||||
announcement.setMsgCategory("2");
|
||||
announcement.setDelFlag(String.valueOf(CommonConstant.DEL_FLAG_0));
|
||||
if(oConvertUtils.isEmpty(noticeType)){
|
||||
noticeType = NoticeTypeEnum.NOTICE_TYPE_SYSTEM.getValue();
|
||||
}
|
||||
announcement.setNoticeType(noticeType);
|
||||
announcement.setIzTop(CommonConstant.IZ_TOP_0);
|
||||
sysAnnouncementMapper.insert(announcement);
|
||||
// 2.插入用户通告阅读标记表记录
|
||||
String userId = toUser;
|
||||
String[] userIds = userId.split(",");
|
||||
String anntId = announcement.getId();
|
||||
for(int i=0;i<userIds.length;i++) {
|
||||
if(oConvertUtils.isNotEmpty(userIds[i])) {
|
||||
SysUser sysUser = userMapper.getUserByName(userIds[i]);
|
||||
if(sysUser==null) {
|
||||
continue;
|
||||
}
|
||||
SysAnnouncementSend announcementSend = new SysAnnouncementSend();
|
||||
announcementSend.setAnntId(anntId);
|
||||
announcementSend.setUserId(sysUser.getId());
|
||||
announcementSend.setReadFlag(CommonConstant.NO_READ_FLAG);
|
||||
sysAnnouncementSendMapper.insert(announcementSend);
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put(WebsocketConst.MSG_CMD, WebsocketConst.CMD_USER);
|
||||
obj.put(WebsocketConst.MSG_USER_ID, sysUser.getId());
|
||||
obj.put(WebsocketConst.MSG_ID, announcement.getId());
|
||||
obj.put(WebsocketConst.MSG_TXT, announcement.getTitile());
|
||||
obj.put(CommonConstant.NOTICE_TYPE,noticeType);
|
||||
webSocket.sendMessage(sysUser.getId(), obj.toJSONString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.jeecg.modules.message.handle.impl;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.modules.message.handle.ISendMsgHandle;
|
||||
|
||||
/**
|
||||
* @Description: 发微信消息模板
|
||||
* @author: jeecg-boot
|
||||
*/
|
||||
@Slf4j
|
||||
public class WxSendMsgHandle implements ISendMsgHandle {
|
||||
|
||||
@Override
|
||||
public void sendMsg(String esReceiver, String esTitle, String esContent) {
|
||||
// TODO Auto-generated method stub
|
||||
log.info("发微信消息模板");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package org.jeecg.modules.message.job;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.api.dto.message.MessageDTO;
|
||||
import org.jeecg.common.system.api.ISysBaseAPI;
|
||||
import org.jeecg.common.util.DateUtils;
|
||||
import org.jeecg.modules.message.entity.SysMessage;
|
||||
import org.jeecg.modules.message.handle.enums.SendMsgStatusEnum;
|
||||
import org.jeecg.modules.message.service.ISysMessageService;
|
||||
import org.quartz.Job;
|
||||
import org.quartz.JobExecutionContext;
|
||||
import org.quartz.JobExecutionException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 发送消息任务
|
||||
* @author: jeecg-boot
|
||||
*/
|
||||
|
||||
@Slf4j
|
||||
public class SendMsgJob implements Job {
|
||||
|
||||
@Autowired
|
||||
private ISysMessageService sysMessageService;
|
||||
|
||||
@Autowired
|
||||
private ISysBaseAPI sysBaseAPI;
|
||||
|
||||
@Override
|
||||
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
|
||||
|
||||
log.info(String.format(" Jeecg-Boot 发送消息任务 SendMsgJob ! 时间:" + DateUtils.getTimestamp()));
|
||||
|
||||
// 1.读取消息中心数据,只查询未发送的和发送失败不超过次数的
|
||||
QueryWrapper<SysMessage> queryWrapper = new QueryWrapper<SysMessage>();
|
||||
queryWrapper.eq("es_send_status", SendMsgStatusEnum.WAIT.getCode())
|
||||
.or(i -> i.eq("es_send_status", SendMsgStatusEnum.FAIL.getCode()).lt("es_send_num", 6));
|
||||
List<SysMessage> sysMessages = sysMessageService.list(queryWrapper);
|
||||
System.out.println(sysMessages);
|
||||
// 2.根据不同的类型走不通的发送实现类
|
||||
for (SysMessage sysMessage : sysMessages) {
|
||||
// 代码逻辑说明: 模板消息发送测试调用方法修改
|
||||
Integer sendNum = sysMessage.getEsSendNum();
|
||||
try {
|
||||
MessageDTO md = new MessageDTO();
|
||||
md.setTitle(sysMessage.getEsTitle());
|
||||
md.setContent(sysMessage.getEsContent());
|
||||
md.setToUser(sysMessage.getEsReceiver());
|
||||
md.setType(sysMessage.getEsType());
|
||||
md.setToAll(false);
|
||||
// 代码逻辑说明: 【QQYUN-8523】敲敲云发邮件通知,不稳定---
|
||||
md.setIsTimeJob(true);
|
||||
sysBaseAPI.sendTemplateMessage(md);
|
||||
//发送消息成功
|
||||
sysMessage.setEsSendStatus(SendMsgStatusEnum.SUCCESS.getCode());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
// 发送消息出现异常
|
||||
sysMessage.setEsSendStatus(SendMsgStatusEnum.FAIL.getCode());
|
||||
}
|
||||
sysMessage.setEsSendNum(++sendNum);
|
||||
// 发送结果回写到数据库
|
||||
sysMessageService.updateById(sysMessage);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.jeecg.modules.message.mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.jeecg.modules.message.entity.SysMessage;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
|
||||
/**
|
||||
* @Description: 消息
|
||||
* @Author: jeecg-boot
|
||||
* @Date: 2019-04-09
|
||||
* @Version: V1.0
|
||||
*/
|
||||
public interface SysMessageMapper extends BaseMapper<SysMessage> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.jeecg.modules.message.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.jeecg.modules.message.entity.SysMessageTemplate;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Description: 消息模板
|
||||
* @Author: jeecg-boot
|
||||
* @Date: 2019-04-09
|
||||
* @Version: V1.0
|
||||
*/
|
||||
public interface SysMessageTemplateMapper extends BaseMapper<SysMessageTemplate> {
|
||||
|
||||
/**
|
||||
* 通过模板CODE查询消息模板
|
||||
* @param code 模板CODE
|
||||
* @return List<SysMessageTemplate>
|
||||
*/
|
||||
@Select("SELECT * FROM SYS_SMS_TEMPLATE WHERE TEMPLATE_CODE = #{code}")
|
||||
List<SysMessageTemplate> selectByCode(String code);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="org.jeecg.modules.message.mapper.SysMessageMapper">
|
||||
|
||||
</mapper>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="org.jeecg.modules.message.mapper.SysMessageTemplateMapper">
|
||||
|
||||
</mapper>
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.jeecg.modules.message.service;
|
||||
|
||||
import org.jeecg.common.system.base.service.JeecgService;
|
||||
import org.jeecg.modules.message.entity.SysMessage;
|
||||
|
||||
/**
|
||||
* @Description: 消息
|
||||
* @Author: jeecg-boot
|
||||
* @Date: 2019-04-09
|
||||
* @Version: V1.0
|
||||
*/
|
||||
public interface ISysMessageService extends JeecgService<SysMessage> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package org.jeecg.modules.message.service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.jeecg.common.system.base.service.JeecgService;
|
||||
import org.jeecg.modules.message.entity.SysMessageTemplate;
|
||||
|
||||
/**
|
||||
* @Description: 消息模板
|
||||
* @Author: jeecg-boot
|
||||
* @Date: 2019-04-09
|
||||
* @Version: V1.0
|
||||
*/
|
||||
public interface ISysMessageTemplateService extends JeecgService<SysMessageTemplate> {
|
||||
|
||||
/**
|
||||
* 通过模板CODE查询消息模板
|
||||
* @param code 模板CODE
|
||||
* @return
|
||||
*/
|
||||
List<SysMessageTemplate> selectByCode(String code);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.jeecg.modules.message.service.impl;
|
||||
|
||||
import org.jeecg.common.system.base.service.impl.JeecgServiceImpl;
|
||||
import org.jeecg.modules.message.entity.SysMessage;
|
||||
import org.jeecg.modules.message.mapper.SysMessageMapper;
|
||||
import org.jeecg.modules.message.service.ISysMessageService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* @Description: 消息
|
||||
* @Author: jeecg-boot
|
||||
* @Date: 2019-04-09
|
||||
* @Version: V1.0
|
||||
*/
|
||||
@Service
|
||||
public class SysMessageServiceImpl extends JeecgServiceImpl<SysMessageMapper, SysMessage> implements ISysMessageService {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.jeecg.modules.message.service.impl;
|
||||
|
||||
import org.jeecg.common.system.base.service.impl.JeecgServiceImpl;
|
||||
import org.jeecg.modules.message.entity.SysMessageTemplate;
|
||||
import org.jeecg.modules.message.mapper.SysMessageTemplateMapper;
|
||||
import org.jeecg.modules.message.service.ISysMessageTemplateService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Description: 消息模板
|
||||
* @Author: jeecg-boot
|
||||
* @Date: 2019-04-09
|
||||
* @Version: V1.0
|
||||
*/
|
||||
@Service
|
||||
public class SysMessageTemplateServiceImpl extends JeecgServiceImpl<SysMessageTemplateMapper, SysMessageTemplate> implements ISysMessageTemplateService {
|
||||
|
||||
@Autowired
|
||||
private SysMessageTemplateMapper sysMessageTemplateMapper;
|
||||
|
||||
|
||||
@Override
|
||||
public List<SysMessageTemplate> selectByCode(String code) {
|
||||
return sysMessageTemplateMapper.selectByCode(code);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package org.jeecg.modules.message.util;
|
||||
|
||||
import freemarker.template.Configuration;
|
||||
import freemarker.template.Template;
|
||||
import freemarker.template.TemplateException;
|
||||
import org.jeecg.modules.message.entity.SysMessage;
|
||||
import org.jeecg.modules.message.entity.SysMessageTemplate;
|
||||
import org.jeecg.modules.message.handle.enums.SendMsgStatusEnum;
|
||||
import org.jeecg.modules.message.service.ISysMessageService;
|
||||
import org.jeecg.modules.message.service.ISysMessageTemplateService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 消息生成工具
|
||||
* @author: jeecg-boot
|
||||
*/
|
||||
|
||||
@Component
|
||||
public class PushMsgUtil {
|
||||
|
||||
@Autowired
|
||||
private ISysMessageService sysMessageService;
|
||||
|
||||
@Autowired
|
||||
private ISysMessageTemplateService sysMessageTemplateService;
|
||||
|
||||
@Autowired
|
||||
private Configuration freemarkerConfig;
|
||||
/**
|
||||
* @param msgType 消息类型 1短信 2邮件 3微信
|
||||
* @param templateCode 消息模板码
|
||||
* @param map 消息参数
|
||||
* @param sentTo 接收消息方
|
||||
*/
|
||||
public boolean sendMessage(String msgType, String templateCode, Map<String, String> map, String sentTo) {
|
||||
List<SysMessageTemplate> sysSmsTemplates = sysMessageTemplateService.selectByCode(templateCode);
|
||||
SysMessage sysMessage = new SysMessage();
|
||||
if (sysSmsTemplates.size() > 0) {
|
||||
SysMessageTemplate sysSmsTemplate = sysSmsTemplates.get(0);
|
||||
sysMessage.setEsType(msgType);
|
||||
sysMessage.setEsReceiver(sentTo);
|
||||
//模板标题
|
||||
String title = sysSmsTemplate.getTemplateName();
|
||||
//模板内容
|
||||
String content = sysSmsTemplate.getTemplateContent();
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
Template template = null;
|
||||
try {
|
||||
template = new Template("SysMessageTemplate", content, freemarkerConfig);
|
||||
template.process(map, stringWriter);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
} catch (TemplateException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
content = stringWriter.toString();
|
||||
sysMessage.setEsTitle(title);
|
||||
sysMessage.setEsContent(content);
|
||||
sysMessage.setEsParam(JSONObject.toJSONString(map));
|
||||
sysMessage.setEsSendTime(new Date());
|
||||
sysMessage.setEsSendStatus(SendMsgStatusEnum.WAIT.getCode());
|
||||
sysMessage.setEsSendNum(0);
|
||||
if(sysMessageService.save(sysMessage)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package org.jeecg.modules.message.websocket;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.base.BaseMap;
|
||||
import org.jeecg.common.constant.CommonSendStatus;
|
||||
import org.jeecg.common.modules.redis.listener.JeecgRedisListener;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 监听消息(通过redis发布订阅,推送消息)
|
||||
* 此方案:解决集群部署的问题,多实例节点(也就是发送消息端先发送消息到redis中,每个服务节点收到redis消息,再触发具体的ws推送)
|
||||
* @author: jeecg-boot
|
||||
*/
|
||||
@Slf4j
|
||||
@Component(WebSocket.REDIS_TOPIC_NAME)
|
||||
public class SocketHandler implements JeecgRedisListener {
|
||||
|
||||
@Autowired
|
||||
private WebSocket webSocket;
|
||||
|
||||
@Override
|
||||
public void onMessage(BaseMap map) {
|
||||
log.debug("【Redis发布订阅模式】redis Listener: {},参数:{}",WebSocket.REDIS_TOPIC_NAME, map.toString());
|
||||
|
||||
String userId = map.get("userId");
|
||||
String message = map.get("message");
|
||||
if (ObjectUtil.isNotEmpty(userId)) {
|
||||
//pc端消息推送具体人
|
||||
webSocket.pushMessage(userId, message);
|
||||
//app端消息推送具体人
|
||||
webSocket.pushMessage(userId+CommonSendStatus.APP_SESSION_SUFFIX, message);
|
||||
} else {
|
||||
//推送全部
|
||||
webSocket.pushMessage(message);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
package org.jeecg.modules.message.websocket;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import jakarta.websocket.*;
|
||||
import jakarta.websocket.server.PathParam;
|
||||
import jakarta.websocket.server.ServerEndpoint;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import org.jeecg.common.base.BaseMap;
|
||||
import org.jeecg.common.constant.WebsocketConst;
|
||||
import org.jeecg.common.modules.redis.client.JeecgRedisClient;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* @Author scott
|
||||
* @Date 2019/11/29 9:41
|
||||
* @Description: 此注解相当于设置访问URL
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
@ServerEndpoint("/websocket/{userId}")
|
||||
public class WebSocket {
|
||||
|
||||
/**线程安全Map*/
|
||||
private static ConcurrentHashMap<String, Session> sessionPool = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Redis触发监听名字
|
||||
*/
|
||||
public static final String REDIS_TOPIC_NAME = "socketHandler";
|
||||
|
||||
//避免初次调用出现空指针的情况
|
||||
private static JeecgRedisClient jeecgRedisClient;
|
||||
@Autowired
|
||||
private void setJeecgRedisClient(JeecgRedisClient jeecgRedisClient){
|
||||
WebSocket.jeecgRedisClient = jeecgRedisClient;
|
||||
}
|
||||
|
||||
|
||||
//==========【websocket接受、推送消息等方法 —— 具体服务节点推送ws消息】========================================================================================
|
||||
@OnOpen
|
||||
public void onOpen(Session session, @PathParam(value = "userId") String userId) {
|
||||
try {
|
||||
sessionPool.put(userId, session);
|
||||
log.debug("【系统 WebSocket】有新的连接,总数为:" + sessionPool.size());
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
|
||||
@OnClose
|
||||
public void onClose(@PathParam("userId") String userId) {
|
||||
try {
|
||||
sessionPool.remove(userId);
|
||||
log.debug("【系统 WebSocket】连接断开,总数为:" + sessionPool.size());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ws推送消息
|
||||
*
|
||||
* @param userId
|
||||
* @param message
|
||||
*/
|
||||
public void pushMessage(String userId, String message) {
|
||||
for (Map.Entry<String, Session> item : sessionPool.entrySet()) {
|
||||
//userId key值= {用户id + "_"+ 登录token的md5串}
|
||||
//TODO vue2未改key新规则,暂时不影响逻辑
|
||||
if (item.getKey().contains(userId)) {
|
||||
Session session = item.getValue();
|
||||
try {
|
||||
// 代码逻辑说明: websocket报错 https://gitee.com/jeecg/jeecg-boot/issues/I4C0MU
|
||||
synchronized (session){
|
||||
log.debug("【系统 WebSocket】推送单人消息:" + message);
|
||||
session.getBasicRemote().sendText(message);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(),e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ws遍历群发消息
|
||||
*/
|
||||
public void pushMessage(String message) {
|
||||
try {
|
||||
for (Map.Entry<String, Session> item : sessionPool.entrySet()) {
|
||||
try {
|
||||
item.getValue().getAsyncRemote().sendText(message);
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
log.debug("【系统 WebSocket】群发消息:" + message);
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ws接受客户端消息
|
||||
*/
|
||||
@OnMessage
|
||||
public void onMessage(String message, @PathParam(value = "userId") String userId) {
|
||||
if(!"ping".equals(message) && !WebsocketConst.CMD_CHECK.equals(message)){
|
||||
log.debug("【系统 WebSocket】收到客户端消息:" + message);
|
||||
}else{
|
||||
log.debug("【系统 WebSocket】收到客户端消息:" + message);
|
||||
// 代码逻辑说明: 【issues/1161】前端websocket因心跳导致监听不起作用---
|
||||
this.sendMessage(userId, "ping");
|
||||
}
|
||||
|
||||
// //------------------------------------------------------------------------------
|
||||
// JSONObject obj = new JSONObject();
|
||||
// //业务类型
|
||||
// obj.put(WebsocketConst.MSG_CMD, WebsocketConst.CMD_CHECK);
|
||||
// //消息内容
|
||||
// obj.put(WebsocketConst.MSG_TXT, "心跳响应");
|
||||
// this.pushMessage(userId, obj.toJSONString());
|
||||
// //------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置错误信息处理
|
||||
*
|
||||
* @param session
|
||||
* @param t
|
||||
*/
|
||||
@OnError
|
||||
public void onError(Session session, Throwable t) {
|
||||
log.warn("【系统 WebSocket】消息出现错误");
|
||||
t.printStackTrace();
|
||||
}
|
||||
//==========【系统 WebSocket接受、推送消息等方法 —— 具体服务节点推送ws消息】========================================================================================
|
||||
|
||||
|
||||
//==========【采用redis发布订阅模式——推送消息】========================================================================================
|
||||
/**
|
||||
* 后台发送消息到redis
|
||||
*
|
||||
* @param message
|
||||
*/
|
||||
public void sendMessage(String message) {
|
||||
//log.debug("【系统 WebSocket】广播消息:" + message);
|
||||
BaseMap baseMap = new BaseMap();
|
||||
baseMap.put("userId", "");
|
||||
baseMap.put("message", message);
|
||||
jeecgRedisClient.sendMessage(WebSocket.REDIS_TOPIC_NAME, baseMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 此为单点消息 redis
|
||||
*
|
||||
* @param userId
|
||||
* @param message
|
||||
*/
|
||||
public void sendMessage(String userId, String message) {
|
||||
BaseMap baseMap = new BaseMap();
|
||||
baseMap.put("userId", userId);
|
||||
baseMap.put("message", message);
|
||||
jeecgRedisClient.sendMessage(WebSocket.REDIS_TOPIC_NAME, baseMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 此为单点消息(多人) redis
|
||||
*
|
||||
* @param userIds
|
||||
* @param message
|
||||
*/
|
||||
public void sendMessage(String[] userIds, String message) {
|
||||
for (String userId : userIds) {
|
||||
sendMessage(userId, message);
|
||||
}
|
||||
}
|
||||
//=======【采用redis发布订阅模式——推送消息】==========================================================================================
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package org.jeecg.modules.monitor.actuator;
|
||||
|
||||
import org.jeecg.modules.monitor.actuator.httptrace.CustomInMemoryHttpTraceRepository;
|
||||
import org.springframework.boot.actuate.autoconfigure.web.exchanges.HttpExchangesAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.web.exchanges.HttpExchangesProperties;
|
||||
import org.springframework.boot.actuate.web.exchanges.HttpExchangeRepository;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 自定义健康监控配置类
|
||||
*
|
||||
* @Author: chenrui
|
||||
* @Date: 2024/5/13 17:20
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(HttpExchangesProperties.class)
|
||||
@AutoConfigureBefore(HttpExchangesAutoConfiguration.class)
|
||||
public class CustomActuatorConfig {
|
||||
|
||||
/**
|
||||
* 请求追踪
|
||||
* @return
|
||||
* @author chenrui
|
||||
* @date 2024/5/14 14:52
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix = "management.trace.http", name = "enabled", matchIfMissing = true)
|
||||
@ConditionalOnMissingBean(HttpExchangeRepository.class)
|
||||
public CustomInMemoryHttpTraceRepository traceRepository() {
|
||||
return new CustomInMemoryHttpTraceRepository();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package org.jeecg.modules.monitor.actuator.httptrace;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Selector;
|
||||
import org.springframework.boot.actuate.web.exchanges.HttpExchange;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.springframework.boot.actuate.endpoint.annotation.Selector.Match.ALL_REMAINING;
|
||||
|
||||
/**
|
||||
* @Description: ENDPOINT: 请求追踪(新),支持通过responseCode筛选
|
||||
* @Author: chenrui
|
||||
* @Date: 2024/5/13 17:02
|
||||
*/
|
||||
@Component
|
||||
@Endpoint(id = "jeecghttptrace")
|
||||
public class CustomHttpTraceEndpoint{
|
||||
private final CustomInMemoryHttpTraceRepository repository;
|
||||
|
||||
public CustomHttpTraceEndpoint(CustomInMemoryHttpTraceRepository repository) {
|
||||
Assert.notNull(repository, "Repository must not be null");
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public HttpTraceDescriptor traces(@Selector(match = ALL_REMAINING) String query) {
|
||||
return new HttpTraceDescriptor(this.repository.findAll(query));
|
||||
}
|
||||
|
||||
@Getter
|
||||
public static final class HttpTraceDescriptor {
|
||||
private final List<HttpExchange> traces;
|
||||
|
||||
private HttpTraceDescriptor(List<HttpExchange> traces) {
|
||||
this.traces = traces;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package org.jeecg.modules.monitor.actuator.httptrace;
|
||||
|
||||
import org.springframework.boot.actuate.web.exchanges.HttpExchange;
|
||||
import org.springframework.boot.actuate.web.exchanges.InMemoryHttpExchangeRepository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @Description: 自定义内存请求追踪存储
|
||||
* @Author: chenrui
|
||||
* @Date: 2024/5/13 17:02
|
||||
*/
|
||||
public class CustomInMemoryHttpTraceRepository extends InMemoryHttpExchangeRepository {
|
||||
|
||||
@Override
|
||||
public List<HttpExchange> findAll() {
|
||||
return super.findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* for [issues/8309]系统监控>请求追踪,列表每刷新一下,总数据就减一#8309
|
||||
* @param trace
|
||||
* @author chenrui
|
||||
* @date 2025/6/4 19:38
|
||||
*/
|
||||
@Override
|
||||
public void add(HttpExchange trace) {
|
||||
// 只有当请求不是OPTIONS方法,并且URI不包含httptrace时才记录数据
|
||||
if (!"OPTIONS".equals(trace.getRequest().getMethod()) &&
|
||||
!trace.getRequest().getUri().toString().contains("httptrace")) {
|
||||
super.add(trace);
|
||||
}
|
||||
}
|
||||
|
||||
public List<HttpExchange> findAll(String query) {
|
||||
List<HttpExchange> allTrace = super.findAll();
|
||||
if (null != allTrace && !allTrace.isEmpty()) {
|
||||
Stream<HttpExchange> stream = allTrace.stream();
|
||||
String[] params = query.split(",");
|
||||
stream = filter(params, stream);
|
||||
stream = sort(params, stream);
|
||||
allTrace = stream.collect(Collectors.toList());
|
||||
}
|
||||
return allTrace;
|
||||
}
|
||||
|
||||
private Stream<HttpExchange> sort(String[] params, Stream<HttpExchange> stream) {
|
||||
if (params.length < 2) {
|
||||
return stream;
|
||||
}
|
||||
String sortBy = params[1];
|
||||
String order;
|
||||
if (params.length > 2) {
|
||||
order = params[2];
|
||||
} else {
|
||||
order = "desc";
|
||||
}
|
||||
return stream.sorted((o1, o2) -> {
|
||||
int i = 0;
|
||||
if("timeTaken".equalsIgnoreCase(sortBy)) {
|
||||
i = o1.getTimeTaken().compareTo(o2.getTimeTaken());
|
||||
}else if("timestamp".equalsIgnoreCase(sortBy)){
|
||||
i = o1.getTimestamp().compareTo(o2.getTimestamp());
|
||||
}
|
||||
if("desc".equalsIgnoreCase(order)){
|
||||
i *=-1;
|
||||
}
|
||||
return i;
|
||||
});
|
||||
}
|
||||
|
||||
private static Stream<HttpExchange> filter(String[] params, Stream<HttpExchange> stream) {
|
||||
if (params.length == 0) {
|
||||
return stream;
|
||||
}
|
||||
String statusQuery = params[0];
|
||||
if (null != statusQuery && !statusQuery.isEmpty()) {
|
||||
statusQuery = statusQuery.toLowerCase().trim();
|
||||
switch (statusQuery) {
|
||||
case "error":
|
||||
stream = stream.filter(httpTrace -> {
|
||||
int status = httpTrace.getResponse().getStatus();
|
||||
return status >= 404 && status < 501;
|
||||
});
|
||||
break;
|
||||
case "warn":
|
||||
stream = stream.filter(httpTrace -> {
|
||||
int status = httpTrace.getResponse().getStatus();
|
||||
return status >= 201 && status < 404;
|
||||
});
|
||||
break;
|
||||
case "success":
|
||||
stream = stream.filter(httpTrace -> {
|
||||
int status = httpTrace.getResponse().getStatus();
|
||||
return status == 200;
|
||||
});
|
||||
break;
|
||||
case "all":
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
//package org.jeecg.modules.monitor.actuator.undertow;
|
||||
//
|
||||
//import io.micrometer.core.instrument.MeterRegistry;
|
||||
//import io.undertow.server.HttpHandler;
|
||||
//import io.undertow.server.HttpServerExchange;
|
||||
//import io.undertow.server.session.*;
|
||||
//import org.springframework.stereotype.Component;
|
||||
//
|
||||
//import java.util.concurrent.atomic.AtomicInteger;
|
||||
//import java.util.concurrent.atomic.LongAdder;
|
||||
//
|
||||
///**
|
||||
// * 自定义undertow监控指标工具类
|
||||
// * for [QQYUN-11902]tomcat 替换undertow 这里的功能还没修改
|
||||
// * @author chenrui
|
||||
// * @date 2025/4/8 19:06
|
||||
// */
|
||||
//@Component("jeecgCustomUndertowMetricsHandler")
|
||||
//public class CustomUndertowMetricsHandler {
|
||||
//
|
||||
// // 用于统计已创建的 session 数量
|
||||
// private final LongAdder sessionsCreated = new LongAdder();
|
||||
//
|
||||
// // 用于统计已销毁的 session 数量
|
||||
// private final LongAdder sessionsExpired = new LongAdder();
|
||||
//
|
||||
// // 当前活跃的 session 数量
|
||||
// private final AtomicInteger activeSessions = new AtomicInteger();
|
||||
//
|
||||
// // 历史最大活跃 session 数
|
||||
// private final AtomicInteger maxActiveSessions = new AtomicInteger();
|
||||
//
|
||||
// // Undertow 内存 session 管理器(用于创建与管理 session)
|
||||
// private final InMemorySessionManager sessionManager = new InMemorySessionManager("undertow-session-manager");
|
||||
//
|
||||
// // 使用 Cookie 存储 session ID
|
||||
// private final SessionConfig sessionConfig = new SessionCookieConfig();
|
||||
//
|
||||
// /**
|
||||
// * 构造函数
|
||||
// * @param meterRegistry
|
||||
// * @author chenrui
|
||||
// * @date 2025/4/8 19:07
|
||||
// */
|
||||
// public CustomUndertowMetricsHandler(MeterRegistry meterRegistry) {
|
||||
// // 注册 Micrometer 指标
|
||||
// meterRegistry.gauge("undertow.sessions.created", sessionsCreated, LongAdder::longValue);
|
||||
// meterRegistry.gauge("undertow.sessions.expired", sessionsExpired, LongAdder::longValue);
|
||||
// meterRegistry.gauge("undertow.sessions.active.current", activeSessions, AtomicInteger::get);
|
||||
// meterRegistry.gauge("undertow.sessions.active.max", maxActiveSessions, AtomicInteger::get);
|
||||
//
|
||||
// // 添加 session 生命周期监听器,统计 session 创建与销毁
|
||||
// sessionManager.registerSessionListener(new SessionListener() {
|
||||
// @Override
|
||||
// public void sessionCreated(Session session, HttpServerExchange exchange) {
|
||||
// sessionsCreated.increment();
|
||||
// int now = activeSessions.incrementAndGet();
|
||||
// maxActiveSessions.getAndUpdate(max -> Math.max(max, now));
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void sessionDestroyed(Session session, HttpServerExchange exchange, SessionDestroyedReason reason) {
|
||||
// sessionsExpired.increment();
|
||||
// activeSessions.decrementAndGet();
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 包装 Undertow 的 HttpHandler,实现 session 自动创建逻辑
|
||||
// * @param next
|
||||
// * @return
|
||||
// * @author chenrui
|
||||
// * @date 2025/4/8 19:07
|
||||
// */
|
||||
// public HttpHandler wrap(HttpHandler next) {
|
||||
// return exchange -> {
|
||||
// // 获取当前 session,如果不存在则创建
|
||||
// Session session = sessionManager.getSession(exchange, sessionConfig);
|
||||
// if (session == null) {
|
||||
// sessionManager.createSession(exchange, sessionConfig);
|
||||
// }
|
||||
//
|
||||
// // 执行下一个 Handler
|
||||
// next.handleRequest(exchange);
|
||||
// };
|
||||
// }
|
||||
//}
|
||||
@@ -0,0 +1,55 @@
|
||||
package org.jeecg.modules.monitor.controller;
|
||||
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.management.OperatingSystemMXBean;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @Description: 内存健康检查
|
||||
* @author: chenrui
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/sys/actuator/memory")
|
||||
public class ActuatorMemoryController {
|
||||
|
||||
|
||||
/**
|
||||
* 内存详情
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("/info")
|
||||
public Result<?> getRedisInfo() {
|
||||
Runtime runtime = Runtime.getRuntime();
|
||||
Map<String,Number> result = new HashMap<>();
|
||||
result.put("memory.runtime.total", runtime.totalMemory());
|
||||
result.put("memory.runtime.used", runtime.freeMemory());
|
||||
result.put("memory.runtime.max", runtime.totalMemory() - runtime.freeMemory());
|
||||
result.put("memory.runtime.free", runtime.maxMemory() - runtime.totalMemory() + runtime.freeMemory());
|
||||
result.put("memory.runtime.usage", NumberUtil.div(runtime.totalMemory() - runtime.freeMemory(), runtime.totalMemory()));
|
||||
// 代码逻辑说明: [TV360X-1695]内存信息-立即更新 功能报错 #6635------------
|
||||
OperatingSystemMXBean operatingSystemMXBean = ManagementFactory.getOperatingSystemMXBean();
|
||||
if (operatingSystemMXBean instanceof com.sun.management.OperatingSystemMXBean) {
|
||||
com.sun.management.OperatingSystemMXBean opBean = (com.sun.management.OperatingSystemMXBean) operatingSystemMXBean;
|
||||
// JSONObject operatingSystemJson = JSONObject.parseObject(JSONObject.toJSONString(operatingSystemMXBean));
|
||||
long totalPhysicalMemory = opBean.getTotalPhysicalMemorySize();
|
||||
long freePhysicalMemory = opBean.getFreePhysicalMemorySize();
|
||||
long usedPhysicalMemory = totalPhysicalMemory - freePhysicalMemory;
|
||||
result.put("memory.physical.total", totalPhysicalMemory);
|
||||
result.put("memory.physical.used", freePhysicalMemory);
|
||||
result.put("memory.physical.free", usedPhysicalMemory);
|
||||
result.put("memory.physical.usage", NumberUtil.div(usedPhysicalMemory, totalPhysicalMemory));
|
||||
}
|
||||
return Result.ok(result);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package org.jeecg.modules.monitor.controller;
|
||||
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.modules.monitor.domain.RedisInfo;
|
||||
import org.jeecg.modules.monitor.service.RedisService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import javax.swing.filechooser.FileSystemView;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @Description: ActuatorRedisController
|
||||
* @author: jeecg-boot
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/sys/actuator/redis")
|
||||
public class ActuatorRedisController {
|
||||
|
||||
@Autowired
|
||||
private RedisService redisService;
|
||||
|
||||
/**
|
||||
* Redis详细信息
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
@GetMapping("/info")
|
||||
public Result<?> getRedisInfo() throws Exception {
|
||||
List<RedisInfo> infoList = this.redisService.getRedisInfo();
|
||||
//log.info(infoList.toString());
|
||||
return Result.ok(infoList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Redis历史性能指标查询(过去一小时)
|
||||
* @return
|
||||
* @throws Exception
|
||||
* @author chenrui
|
||||
* @date 2024/5/14 14:56
|
||||
*/
|
||||
@GetMapping(value = "/metrics/history")
|
||||
public Result<?> getMetricsHistory() throws Exception {
|
||||
Map<String,List<Map<String,Object>>> metricsHistory = this.redisService.getMetricsHistory();
|
||||
return Result.OK(metricsHistory);
|
||||
}
|
||||
|
||||
@GetMapping("/keysSize")
|
||||
public Map<String, Object> getKeysSize() throws Exception {
|
||||
return redisService.getKeysSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取redis key数量 for 报表
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
@GetMapping("/keysSizeForReport")
|
||||
public Map<String, JSONArray> getKeysSizeReport() throws Exception {
|
||||
return redisService.getMapForReport("1");
|
||||
}
|
||||
/**
|
||||
* 获取redis 内存 for 报表
|
||||
*
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
@GetMapping("/memoryForReport")
|
||||
public Map<String, JSONArray> memoryForReport() throws Exception {
|
||||
return redisService.getMapForReport("2");
|
||||
}
|
||||
/**
|
||||
* 获取redis 全部信息 for 报表
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
@GetMapping("/infoForReport")
|
||||
public Map<String, JSONArray> infoForReport() throws Exception {
|
||||
return redisService.getMapForReport("3");
|
||||
}
|
||||
|
||||
@GetMapping("/memoryInfo")
|
||||
public Map<String, Object> getMemoryInfo() throws Exception {
|
||||
return redisService.getMemoryInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
* @功能:获取磁盘信息
|
||||
* @param request
|
||||
* @param response
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("/queryDiskInfo")
|
||||
public Result<List<Map<String,Object>>> queryDiskInfo(HttpServletRequest request, HttpServletResponse response){
|
||||
Result<List<Map<String,Object>>> res = new Result<>();
|
||||
try {
|
||||
// 当前文件系统类
|
||||
FileSystemView fsv = FileSystemView.getFileSystemView();
|
||||
// 列出所有windows 磁盘
|
||||
File[] fs = File.listRoots();
|
||||
log.info("查询磁盘信息:"+fs.length+"个");
|
||||
List<Map<String,Object>> list = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < fs.length; i++) {
|
||||
if(fs[i].getTotalSpace()==0) {
|
||||
continue;
|
||||
}
|
||||
Map<String,Object> map = new HashMap(5);
|
||||
map.put("name", fsv.getSystemDisplayName(fs[i]));
|
||||
map.put("max", fs[i].getTotalSpace());
|
||||
map.put("rest", fs[i].getFreeSpace());
|
||||
map.put("restPPT", (fs[i].getTotalSpace()-fs[i].getFreeSpace())*100/fs[i].getTotalSpace());
|
||||
list.add(map);
|
||||
log.info(map.toString());
|
||||
}
|
||||
res.setResult(list);
|
||||
res.success("查询成功");
|
||||
} catch (Exception e) {
|
||||
res.error500("查询失败"+e.getMessage());
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
package org.jeecg.modules.monitor.domain;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @Description: redis信息
|
||||
* @author: jeecg-boot
|
||||
*/
|
||||
public class RedisInfo {
|
||||
|
||||
private static Map<String, String> map = new HashMap(5);
|
||||
|
||||
static {
|
||||
map.put("redis_version", "Redis 服务器版本");
|
||||
map.put("redis_git_sha1", "Git SHA1");
|
||||
map.put("redis_git_dirty", "Git dirty flag");
|
||||
map.put("os", "Redis 服务器的宿主操作系统");
|
||||
map.put("arch_bits", " 架构(32 或 64 位)");
|
||||
map.put("multiplexing_api", "Redis 所使用的事件处理机制");
|
||||
map.put("gcc_version", "编译 Redis 时所使用的 GCC 版本");
|
||||
map.put("process_id", "服务器进程的 PID");
|
||||
map.put("run_id", "Redis 服务器的随机标识符(用于 Sentinel 和集群)");
|
||||
map.put("tcp_port", "TCP/IP 监听端口");
|
||||
map.put("uptime_in_seconds", "自 Redis 服务器启动以来,经过的秒数");
|
||||
map.put("uptime_in_days", "自 Redis 服务器启动以来,经过的天数");
|
||||
map.put("lru_clock", " 以分钟为单位进行自增的时钟,用于 LRU 管理");
|
||||
map.put("connected_clients", "已连接客户端的数量(不包括通过从属服务器连接的客户端)");
|
||||
map.put("client_longest_output_list", "当前连接的客户端当中,最长的输出列表");
|
||||
map.put("client_longest_input_buf", "当前连接的客户端当中,最大输入缓存");
|
||||
map.put("blocked_clients", "正在等待阻塞命令(BLPOP、BRPOP、BRPOPLPUSH)的客户端的数量");
|
||||
map.put("used_memory", "由 Redis 分配器分配的内存总量,以字节(byte)为单位");
|
||||
map.put("used_memory_human", "以人类可读的格式返回 Redis 分配的内存总量");
|
||||
map.put("used_memory_rss", "从操作系统的角度,返回 Redis 已分配的内存总量(俗称常驻集大小)。这个值和 top 、 ps 等命令的输出一致");
|
||||
map.put("used_memory_peak", " Redis 的内存消耗峰值(以字节为单位)");
|
||||
map.put("used_memory_peak_human", "以人类可读的格式返回 Redis 的内存消耗峰值");
|
||||
map.put("used_memory_lua", "Lua 引擎所使用的内存大小(以字节为单位)");
|
||||
map.put("mem_fragmentation_ratio", "sed_memory_rss 和 used_memory 之间的比率");
|
||||
map.put("mem_allocator", "在编译时指定的, Redis 所使用的内存分配器。可以是 libc 、 jemalloc 或者 tcmalloc");
|
||||
|
||||
map.put("redis_build_id", "redis_build_id");
|
||||
map.put("redis_mode", "运行模式,单机(standalone)或者集群(cluster)");
|
||||
map.put("atomicvar_api", "atomicvar_api");
|
||||
map.put("hz", "redis内部调度(进行关闭timeout的客户端,删除过期key等等)频率,程序规定serverCron每秒运行10次。");
|
||||
map.put("executable", "server脚本目录");
|
||||
map.put("config_file", "配置文件目录");
|
||||
map.put("client_biggest_input_buf", "当前连接的客户端当中,最大输入缓存,用client list命令观察qbuf和qbuf-free两个字段最大值");
|
||||
map.put("used_memory_rss_human", "以人类可读的方式返回 Redis 已分配的内存总量");
|
||||
map.put("used_memory_peak_perc", "内存使用率峰值");
|
||||
map.put("total_system_memory", "系统总内存");
|
||||
map.put("total_system_memory_human", "以人类可读的方式返回系统总内存");
|
||||
map.put("used_memory_lua_human", "以人类可读的方式返回Lua 引擎所使用的内存大小");
|
||||
map.put("maxmemory", "最大内存限制,0表示无限制");
|
||||
map.put("maxmemory_human", "以人类可读的方式返回最大限制内存");
|
||||
map.put("maxmemory_policy", "超过内存限制后的处理策略");
|
||||
map.put("loading", "服务器是否正在载入持久化文件");
|
||||
map.put("rdb_changes_since_last_save", "离最近一次成功生成rdb文件,写入命令的个数,即有多少个写入命令没有持久化");
|
||||
map.put("rdb_bgsave_in_progress", "服务器是否正在创建rdb文件");
|
||||
map.put("rdb_last_save_time", "离最近一次成功创建rdb文件的时间戳。当前时间戳 - rdb_last_save_time=多少秒未成功生成rdb文件");
|
||||
map.put("rdb_last_bgsave_status", "最近一次rdb持久化是否成功");
|
||||
map.put("rdb_last_bgsave_time_sec", "最近一次成功生成rdb文件耗时秒数");
|
||||
map.put("rdb_current_bgsave_time_sec", "如果服务器正在创建rdb文件,那么这个域记录的就是当前的创建操作已经耗费的秒数");
|
||||
map.put("aof_enabled", "是否开启了aof");
|
||||
map.put("aof_rewrite_in_progress", "标识aof的rewrite操作是否在进行中");
|
||||
map.put("aof_rewrite_scheduled", "rewrite任务计划,当客户端发送bgrewriteaof指令,如果当前rewrite子进程正在执行,那么将客户端请求的bgrewriteaof变为计划任务,待aof子进程结束后执行rewrite ");
|
||||
|
||||
map.put("aof_last_rewrite_time_sec", "最近一次aof rewrite耗费的时长");
|
||||
map.put("aof_current_rewrite_time_sec", "如果rewrite操作正在进行,则记录所使用的时间,单位秒");
|
||||
map.put("aof_last_bgrewrite_status", "上次bgrewrite aof操作的状态");
|
||||
map.put("aof_last_write_status", "上次aof写入状态");
|
||||
|
||||
map.put("total_commands_processed", "redis处理的命令数");
|
||||
map.put("total_connections_received", "新创建连接个数,如果新创建连接过多,过度地创建和销毁连接对性能有影响,说明短连接严重或连接池使用有问题,需调研代码的连接设置");
|
||||
map.put("instantaneous_ops_per_sec", "redis当前的qps,redis内部较实时的每秒执行的命令数");
|
||||
map.put("total_net_input_bytes", "redis网络入口流量字节数");
|
||||
map.put("total_net_output_bytes", "redis网络出口流量字节数");
|
||||
|
||||
map.put("instantaneous_input_kbps", "redis网络入口kps");
|
||||
map.put("instantaneous_output_kbps", "redis网络出口kps");
|
||||
map.put("rejected_connections", "拒绝的连接个数,redis连接个数达到maxclients限制,拒绝新连接的个数");
|
||||
map.put("sync_full", "主从完全同步成功次数");
|
||||
|
||||
map.put("sync_partial_ok", "主从部分同步成功次数");
|
||||
map.put("sync_partial_err", "主从部分同步失败次数");
|
||||
map.put("expired_keys", "运行以来过期的key的数量");
|
||||
map.put("evicted_keys", "运行以来剔除(超过了maxmemory后)的key的数量");
|
||||
map.put("keyspace_hits", "命中次数");
|
||||
map.put("keyspace_misses", "没命中次数");
|
||||
map.put("pubsub_channels", "当前使用中的频道数量");
|
||||
map.put("pubsub_patterns", "当前使用的模式的数量");
|
||||
map.put("latest_fork_usec", "最近一次fork操作阻塞redis进程的耗时数,单位微秒");
|
||||
map.put("role", "实例的角色,是master or slave");
|
||||
map.put("connected_slaves", "连接的slave实例个数");
|
||||
map.put("master_repl_offset", "主从同步偏移量,此值如果和上面的offset相同说明主从一致没延迟");
|
||||
map.put("repl_backlog_active", "复制积压缓冲区是否开启");
|
||||
map.put("repl_backlog_size", "复制积压缓冲大小");
|
||||
map.put("repl_backlog_first_byte_offset", "复制缓冲区里偏移量的大小");
|
||||
map.put("repl_backlog_histlen", "此值等于 master_repl_offset - repl_backlog_first_byte_offset,该值不会超过repl_backlog_size的大小");
|
||||
map.put("used_cpu_sys", "将所有redis主进程在核心态所占用的CPU时求和累计起来");
|
||||
map.put("used_cpu_user", "将所有redis主进程在用户态所占用的CPU时求和累计起来");
|
||||
map.put("used_cpu_sys_children", "将后台进程在核心态所占用的CPU时求和累计起来");
|
||||
map.put("used_cpu_user_children", "将后台进程在用户态所占用的CPU时求和累计起来");
|
||||
map.put("cluster_enabled", "实例是否启用集群模式");
|
||||
map.put("db0", "db0的key的数量,以及带有生存期的key的数,平均存活时间");
|
||||
|
||||
}
|
||||
|
||||
private String key;
|
||||
private String value;
|
||||
private String description;
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public void setKey(String key) {
|
||||
this.key = key;
|
||||
this.description = map.get(this.key);
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RedisInfo{" + "key='" + key + '\'' + ", value='" + value + '\'' + ", desctiption='" + description + '\'' + '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.jeecg.modules.monitor.exception;
|
||||
|
||||
/**
|
||||
* Redis 连接异常
|
||||
* @author: jeecg-boot
|
||||
*/
|
||||
public class RedisConnectException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1639374111871115063L;
|
||||
|
||||
public RedisConnectException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package org.jeecg.modules.monitor.service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import org.jeecg.modules.monitor.domain.RedisInfo;
|
||||
import org.jeecg.modules.monitor.exception.RedisConnectException;
|
||||
|
||||
/**
|
||||
* @Description: redis信息service接口
|
||||
* @author: jeecg-boot
|
||||
*/
|
||||
public interface RedisService {
|
||||
|
||||
/**
|
||||
* 获取 redis 的详细信息
|
||||
*
|
||||
* @return List
|
||||
* @throws RedisConnectException
|
||||
*/
|
||||
List<RedisInfo> getRedisInfo() throws RedisConnectException;
|
||||
|
||||
/**
|
||||
* 获取 redis key 数量
|
||||
*
|
||||
* @return Map
|
||||
* @throws RedisConnectException
|
||||
*/
|
||||
Map<String, Object> getKeysSize() throws RedisConnectException;
|
||||
|
||||
/**
|
||||
* 获取 redis 内存信息
|
||||
*
|
||||
* @return Map
|
||||
* @throws RedisConnectException
|
||||
*/
|
||||
Map<String, Object> getMemoryInfo() throws RedisConnectException;
|
||||
/**
|
||||
* 获取 报表需要个redis信息
|
||||
* @param type
|
||||
* @return Map
|
||||
* @throws RedisConnectException
|
||||
*/
|
||||
Map<String, JSONArray> getMapForReport(String type) throws RedisConnectException ;
|
||||
|
||||
/**
|
||||
* 获取历史性能指标
|
||||
* @return
|
||||
* @author chenrui
|
||||
* @date 2024/5/14 14:57
|
||||
*/
|
||||
Map<String, List<Map<String, Object>>> getMetricsHistory();
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.jeecg.modules.monitor.service.impl;
|
||||
|
||||
import org.springframework.boot.actuate.health.Health;
|
||||
import org.springframework.boot.actuate.health.HealthIndicator;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 功能说明:自定义邮件检测
|
||||
*
|
||||
* @author: 李波
|
||||
* @email: 503378406@qq.com
|
||||
* @date: 2019-06-29
|
||||
*/
|
||||
@Component
|
||||
public class MailHealthIndicator implements HealthIndicator {
|
||||
|
||||
|
||||
@Override public Health health() {
|
||||
int errorCode = check();
|
||||
if (errorCode != 0) {
|
||||
return Health.down().withDetail("Error Code", errorCode) .build();
|
||||
}
|
||||
return Health.up().build();
|
||||
}
|
||||
int check(){
|
||||
//可以实现自定义的数据库检测逻辑
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
package org.jeecg.modules.monitor.service.impl;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.google.common.collect.Maps;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.modules.monitor.domain.RedisInfo;
|
||||
import org.jeecg.modules.monitor.exception.RedisConnectException;
|
||||
import org.jeecg.modules.monitor.service.RedisService;
|
||||
import org.springframework.cglib.beans.BeanMap;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Redis 监控信息获取
|
||||
*
|
||||
* @Author MrBird
|
||||
*/
|
||||
@Service("redisService")
|
||||
@Slf4j
|
||||
public class RedisServiceImpl implements RedisService {
|
||||
|
||||
@Resource
|
||||
private RedisConnectionFactory redisConnectionFactory;
|
||||
|
||||
/**
|
||||
* redis信息
|
||||
*/
|
||||
private static final String REDIS_MESSAGE = "3";
|
||||
|
||||
/**
|
||||
* redis性能信息记录
|
||||
*/
|
||||
private static final Map<String,List<Map<String, Object>>> REDIS_METRICS = new HashMap<>(2);
|
||||
|
||||
/**
|
||||
* Redis详细信息
|
||||
*/
|
||||
@Override
|
||||
public List<RedisInfo> getRedisInfo() throws RedisConnectException {
|
||||
Properties info = redisConnectionFactory.getConnection().info();
|
||||
List<RedisInfo> infoList = new ArrayList<>();
|
||||
RedisInfo redisInfo = null;
|
||||
for (Map.Entry<Object, Object> entry : info.entrySet()) {
|
||||
redisInfo = new RedisInfo();
|
||||
redisInfo.setKey(oConvertUtils.getString(entry.getKey()));
|
||||
redisInfo.setValue(oConvertUtils.getString(entry.getValue()));
|
||||
infoList.add(redisInfo);
|
||||
}
|
||||
return infoList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getKeysSize() throws RedisConnectException {
|
||||
Long dbSize = redisConnectionFactory.getConnection().dbSize();
|
||||
Map<String, Object> map = new HashMap(5);
|
||||
map.put("create_time", System.currentTimeMillis());
|
||||
map.put("dbSize", dbSize);
|
||||
|
||||
log.debug("--getKeysSize--: " + map.toString());
|
||||
return map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getMemoryInfo() throws RedisConnectException {
|
||||
Map<String, Object> map = null;
|
||||
Properties info = redisConnectionFactory.getConnection().info();
|
||||
for (Map.Entry<Object, Object> entry : info.entrySet()) {
|
||||
String key = oConvertUtils.getString(entry.getKey());
|
||||
if ("used_memory".equals(key)) {
|
||||
map = new HashMap(5);
|
||||
map.put("used_memory", entry.getValue());
|
||||
map.put("create_time", System.currentTimeMillis());
|
||||
}
|
||||
}
|
||||
log.debug("--getMemoryInfo--: " + map.toString());
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询redis信息for报表
|
||||
* @param type 1redis key数量 2 占用内存 3redis信息
|
||||
* @return
|
||||
* @throws RedisConnectException
|
||||
*/
|
||||
@Override
|
||||
public Map<String, JSONArray> getMapForReport(String type) throws RedisConnectException {
|
||||
Map<String,JSONArray> mapJson=new HashMap(5);
|
||||
JSONArray json = new JSONArray();
|
||||
if(REDIS_MESSAGE.equals(type)){
|
||||
List<RedisInfo> redisInfo = getRedisInfo();
|
||||
for(RedisInfo info:redisInfo){
|
||||
Map<String, Object> map= Maps.newHashMap();
|
||||
BeanMap beanMap = BeanMap.create(info);
|
||||
for (Object key : beanMap.keySet()) {
|
||||
map.put(key+"", beanMap.get(key));
|
||||
}
|
||||
json.add(map);
|
||||
}
|
||||
mapJson.put("data",json);
|
||||
return mapJson;
|
||||
}
|
||||
int length = 5;
|
||||
for(int i = 0; i < length; i++){
|
||||
JSONObject jo = new JSONObject();
|
||||
Map<String, Object> map;
|
||||
if("1".equals(type)){
|
||||
map= getKeysSize();
|
||||
jo.put("value",map.get("dbSize"));
|
||||
}else{
|
||||
map = getMemoryInfo();
|
||||
Integer usedMemory = Integer.valueOf(map.get("used_memory").toString());
|
||||
jo.put("value",usedMemory/1000);
|
||||
}
|
||||
String createTime = DateUtil.formatTime(DateUtil.date((Long) map.get("create_time")-(4-i)*1000));
|
||||
jo.put("name",createTime);
|
||||
json.add(jo);
|
||||
}
|
||||
mapJson.put("data",json);
|
||||
return mapJson;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取历史性能指标
|
||||
* @return
|
||||
* @author chenrui
|
||||
* @date 2024/5/14 14:57
|
||||
*/
|
||||
@Override
|
||||
public Map<String, List<Map<String, Object>>> getMetricsHistory() {
|
||||
return REDIS_METRICS;
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录近一小时redis监控数据 <br/>
|
||||
* 60s一次,,记录存储keysize和内存
|
||||
* @throws RedisConnectException
|
||||
* @author chenrui
|
||||
* @date 2024/5/14 14:09
|
||||
*/
|
||||
@Scheduled(fixedRate = 60000)
|
||||
public void recordCustomMetric() throws RedisConnectException {
|
||||
List<Map<String, Object>> list= new ArrayList<>();
|
||||
if(REDIS_METRICS.containsKey("dbSize")){
|
||||
list = REDIS_METRICS.get("dbSize");
|
||||
}else{
|
||||
REDIS_METRICS.put("dbSize",list);
|
||||
}
|
||||
if(list.size()>60){
|
||||
list.remove(0);
|
||||
}
|
||||
list.add(getKeysSize());
|
||||
list= new ArrayList<>();
|
||||
if(REDIS_METRICS.containsKey("memory")){
|
||||
list = REDIS_METRICS.get("memory");
|
||||
}else{
|
||||
REDIS_METRICS.put("memory",list);
|
||||
}
|
||||
if(list.size()>60){
|
||||
list.remove(0);
|
||||
}
|
||||
list.add(getMemoryInfo());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
//package org.jeecg.modules.ngalain.aop;
|
||||
//
|
||||
//import jakarta.servlet.http.HttpServletRequest;
|
||||
//
|
||||
//import org.aspectj.lang.ProceedingJoinPoint;
|
||||
//import org.aspectj.lang.annotation.Around;
|
||||
//import org.aspectj.lang.annotation.Aspect;
|
||||
//import org.aspectj.lang.annotation.Pointcut;
|
||||
//import org.springframework.context.annotation.Configuration;
|
||||
//import org.springframework.web.context.request.RequestAttributes;
|
||||
//import org.springframework.web.context.request.RequestContextHolder;
|
||||
//import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
//import org.slf4j.Logger;
|
||||
//import org.slf4j.LoggerFactory;;
|
||||
//
|
||||
//
|
||||
//// 暂时注释掉,提高系统性能
|
||||
////@Aspect //定义一个切面
|
||||
////@Configuration
|
||||
//public class LogRecordAspect {
|
||||
//private static final Logger logger = LoggerFactory.getLogger(LogRecordAspect.class);
|
||||
//
|
||||
// // 定义切点Pointcut
|
||||
// @Pointcut("execution(public * org.jeecg.modules.*.*.*Controller.*(..))")
|
||||
// public void excudeService() {
|
||||
// }
|
||||
//
|
||||
// @Around("excudeService()")
|
||||
// public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
|
||||
// RequestAttributes ra = RequestContextHolder.getRequestAttributes();
|
||||
// ServletRequestAttributes sra = (ServletRequestAttributes) ra;
|
||||
// HttpServletRequest request = sra.getRequest();
|
||||
//
|
||||
// String url = request.getRequestURL().toString();
|
||||
// String method = request.getMethod();
|
||||
// String uri = request.getRequestURI();
|
||||
// String queryString = request.getQueryString();
|
||||
// logger.info("请求开始, 各个参数, url: {}, method: {}, uri: {}, params: {}", url, method, uri, queryString);
|
||||
//
|
||||
// // result的值就是被拦截方法的返回值
|
||||
// Object result = pjp.proceed();
|
||||
//
|
||||
// logger.info("请求结束,controller的返回值是 " + result);
|
||||
// return result;
|
||||
// }
|
||||
//}
|
||||
@@ -0,0 +1,86 @@
|
||||
//package org.jeecg.modules.ngalain.controller;
|
||||
//
|
||||
//import java.util.ArrayList;
|
||||
//import java.util.List;
|
||||
//import java.util.Map;
|
||||
//
|
||||
//import jakarta.servlet.http.HttpServletRequest;
|
||||
//
|
||||
//import org.apache.shiro.SecurityUtils;
|
||||
//import org.jeecg.common.api.vo.Result;
|
||||
//import org.jeecg.common.system.vo.DictModel;
|
||||
//import org.jeecg.common.system.vo.LoginUser;
|
||||
//import org.jeecg.modules.ngalain.service.NgAlainService;
|
||||
//import org.jeecg.modules.system.service.ISysDictService;
|
||||
//import org.springframework.beans.factory.annotation.Autowired;
|
||||
//import org.springframework.web.bind.annotation.PathVariable;
|
||||
//import org.springframework.web.bind.annotation.RequestMapping;
|
||||
//import org.springframework.web.bind.annotation.RequestMethod;
|
||||
//import org.springframework.web.bind.annotation.ResponseBody;
|
||||
//import org.springframework.web.bind.annotation.RestController;
|
||||
//
|
||||
//import com.alibaba.fastjson.JSONObject;
|
||||
//
|
||||
//import lombok.extern.slf4j.Slf4j;
|
||||
//
|
||||
//@Slf4j
|
||||
//@RestController
|
||||
//@RequestMapping("/sys/ng-alain")
|
||||
//public class NgAlainController {
|
||||
// @Autowired
|
||||
// private NgAlainService ngAlainService;
|
||||
// @Autowired
|
||||
// private ISysDictService sysDictService;
|
||||
//
|
||||
// @RequestMapping(value = "/getAppData")
|
||||
// @ResponseBody
|
||||
// public JSONObject getAppData(HttpServletRequest request) throws Exception {
|
||||
// String token=request.getHeader("X-Access-Token");
|
||||
// JSONObject j = new JSONObject();
|
||||
// LoginUser user = (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
||||
// JSONObject userObjcet = new JSONObject();
|
||||
// userObjcet.put("name", user.getUsername());
|
||||
// userObjcet.put("avatar", user.getAvatar());
|
||||
// userObjcet.put("email", user.getEmail());
|
||||
// userObjcet.put("token", token);
|
||||
// j.put("user", userObjcet);
|
||||
// j.put("menu",ngAlainService.getMenu(user.getUsername()));
|
||||
// JSONObject app = new JSONObject();
|
||||
// app.put("name", "jeecg-boot-angular");
|
||||
// app.put("description", "jeecg+ng-alain整合版本");
|
||||
// j.put("app", app);
|
||||
// return j;
|
||||
// }
|
||||
//
|
||||
// @RequestMapping(value = "/getDictItems/{dictCode}", method = RequestMethod.GET)
|
||||
// public Object getDictItems(@PathVariable String dictCode) {
|
||||
// log.info(" dictCode : "+ dictCode);
|
||||
// Result<List<DictModel>> result = new Result<List<DictModel>>();
|
||||
// List<DictModel> ls = null;
|
||||
// try {
|
||||
// ls = sysDictService.queryDictItemsByCode(dictCode);
|
||||
// result.setSuccess(true);
|
||||
// result.setResult(ls);
|
||||
// } catch (Exception e) {
|
||||
// log.error(e.getMessage(),e);
|
||||
// result.error500("操作失败");
|
||||
// return result;
|
||||
// }
|
||||
// List<JSONObject> dictlist=new ArrayList<>();
|
||||
// for (DictModel l : ls) {
|
||||
// JSONObject dict=new JSONObject();
|
||||
// try {
|
||||
// dict.put("value",Integer.parseInt(l.getValue()));
|
||||
// } catch (NumberFormatException e) {
|
||||
// dict.put("value",l.getValue());
|
||||
// }
|
||||
// dict.put("label",l.getText());
|
||||
// dictlist.add(dict);
|
||||
// }
|
||||
// return dictlist;
|
||||
// }
|
||||
// @RequestMapping(value = "/getDictItemsByTable/{table}/{key}/{value}", method = RequestMethod.GET)
|
||||
// public Object getDictItemsByTable(@PathVariable String table,@PathVariable String key,@PathVariable String value) {
|
||||
// return this.ngAlainService.getDictByTable(table,key,value);
|
||||
// }
|
||||
//}
|
||||
@@ -0,0 +1,37 @@
|
||||
//package org.jeecg.modules.ngalain.service;
|
||||
//
|
||||
//import com.alibaba.fastjson.JSONArray;
|
||||
//
|
||||
//import java.util.List;
|
||||
//import java.util.Map;
|
||||
//
|
||||
///**
|
||||
// * @Description: NgAlainService接口
|
||||
// * @author: jeecg-boot
|
||||
// */
|
||||
//public interface NgAlainService {
|
||||
// /**
|
||||
// * 菜单
|
||||
// * @param id
|
||||
// * @return JSONArray
|
||||
// * @throws Exception
|
||||
// */
|
||||
// public JSONArray getMenu(String id) throws Exception;
|
||||
//
|
||||
// /**
|
||||
// * jeecg菜单
|
||||
// * @param id
|
||||
// * @return JSONArray
|
||||
// * @throws Exception
|
||||
// */
|
||||
// public JSONArray getJeecgMenu(String id) throws Exception;
|
||||
//
|
||||
// /**
|
||||
// * 获取字典值
|
||||
// * @param table
|
||||
// * @param key
|
||||
// * @param value
|
||||
// * @return List<Map<String, String>>
|
||||
// */
|
||||
// public List<Map<String, String>> getDictByTable(String table, String key, String value);
|
||||
//}
|
||||
@@ -0,0 +1,187 @@
|
||||
//package org.jeecg.modules.ngalain.service.impl;
|
||||
//
|
||||
//import com.alibaba.fastjson.JSONArray;
|
||||
//import com.alibaba.fastjson.JSONObject;
|
||||
//import org.jeecg.common.constant.CommonConstant;
|
||||
//import org.jeecg.common.constant.SymbolConstant;
|
||||
//import org.jeecg.common.util.oConvertUtils;
|
||||
//import org.jeecg.modules.ngalain.service.NgAlainService;
|
||||
//import org.jeecg.modules.system.entity.SysPermission;
|
||||
//import org.jeecg.modules.system.mapper.SysDictMapper;
|
||||
//import org.jeecg.modules.system.service.ISysPermissionService;
|
||||
//import org.springframework.beans.factory.annotation.Autowired;
|
||||
//import org.springframework.stereotype.Service;
|
||||
//import org.springframework.transaction.annotation.Transactional;
|
||||
//
|
||||
//import java.util.Base64;
|
||||
//import java.util.List;
|
||||
//import java.util.Map;
|
||||
//
|
||||
///**
|
||||
// * @Description: NgAlainServiceImpl 实现类
|
||||
// * @author: jeecg-boot
|
||||
// */
|
||||
//@Service("ngAlainService")
|
||||
//public class NgAlainServiceImpl implements NgAlainService {
|
||||
// @Autowired
|
||||
// private ISysPermissionService sysPermissionService;
|
||||
// @Autowired
|
||||
// private SysDictMapper mapper;
|
||||
// @Override
|
||||
// public JSONArray getMenu(String id) throws Exception {
|
||||
// return getJeecgMenu(id);
|
||||
// }
|
||||
// @Override
|
||||
// public JSONArray getJeecgMenu(String id) throws Exception {
|
||||
// List<SysPermission> metaList = sysPermissionService.queryByUser(id);
|
||||
// JSONArray jsonArray = new JSONArray();
|
||||
// getPermissionJsonArray(jsonArray, metaList, null);
|
||||
// JSONArray menulist= parseNgAlain(jsonArray);
|
||||
// JSONObject jeecgMenu = new JSONObject();
|
||||
// jeecgMenu.put("text", "jeecg菜单");
|
||||
// jeecgMenu.put("group",true);
|
||||
// jeecgMenu.put("children", menulist);
|
||||
// JSONArray jeecgMenuList=new JSONArray();
|
||||
// jeecgMenuList.add(jeecgMenu);
|
||||
// return jeecgMenuList;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public List<Map<String, String>> getDictByTable(String table, String key, String value) {
|
||||
// return this.mapper.getDictByTableNgAlain(table,key,value);
|
||||
// }
|
||||
//
|
||||
// private JSONArray parseNgAlain(JSONArray jsonArray) {
|
||||
// JSONArray menulist=new JSONArray();
|
||||
// for (Object object : jsonArray) {
|
||||
// JSONObject jsonObject= (JSONObject) object;
|
||||
// String path= (String) jsonObject.get("path");
|
||||
// JSONObject meta= (JSONObject) jsonObject.get("meta");
|
||||
// JSONObject menu=new JSONObject();
|
||||
// menu.put("text",meta.get("title"));
|
||||
// menu.put("reuse",true);
|
||||
// if (jsonObject.get("children")!=null){
|
||||
// JSONArray child= parseNgAlain((JSONArray) jsonObject.get("children"));
|
||||
// menu.put("children",child);
|
||||
// JSONObject icon=new JSONObject();
|
||||
// icon.put("type", "icon");
|
||||
// icon.put("value", meta.get("icon"));
|
||||
// menu.put("icon",icon);
|
||||
// }else {
|
||||
// menu.put("link",path);
|
||||
// }
|
||||
// menulist.add(menu);
|
||||
// }
|
||||
// return menulist;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 获取菜单JSON数组
|
||||
// * @param jsonArray
|
||||
// * @param metaList
|
||||
// * @param parentJson
|
||||
// */
|
||||
// private void getPermissionJsonArray(JSONArray jsonArray,List<SysPermission> metaList,JSONObject parentJson) {
|
||||
// for (SysPermission permission : metaList) {
|
||||
// if(permission.getMenuType()==null) {
|
||||
// continue;
|
||||
// }
|
||||
// String tempPid = permission.getParentId();
|
||||
// JSONObject json = getPermissionJsonObject(permission);
|
||||
// if(parentJson==null && oConvertUtils.isEmpty(tempPid)) {
|
||||
// jsonArray.add(json);
|
||||
// if(!permission.isLeaf()) {
|
||||
// getPermissionJsonArray(jsonArray, metaList, json);
|
||||
// }
|
||||
// }else if(parentJson!=null && oConvertUtils.isNotEmpty(tempPid) && tempPid.equals(parentJson.getString("id"))){
|
||||
// if(permission.getMenuType()==0) {
|
||||
// JSONObject metaJson = parentJson.getJSONObject("meta");
|
||||
// if(metaJson.containsKey("permissionList")) {
|
||||
// metaJson.getJSONArray("permissionList").add(json);
|
||||
// }else {
|
||||
// JSONArray permissionList = new JSONArray();
|
||||
// permissionList.add(json);
|
||||
// metaJson.put("permissionList", permissionList);
|
||||
// }
|
||||
//
|
||||
// }else if(permission.getMenuType()==1) {
|
||||
// if(parentJson.containsKey("children")) {
|
||||
// parentJson.getJSONArray("children").add(json);
|
||||
// }else {
|
||||
// JSONArray children = new JSONArray();
|
||||
// children.add(json);
|
||||
// parentJson.put("children", children);
|
||||
// }
|
||||
//
|
||||
// if(!permission.isLeaf()) {
|
||||
// getPermissionJsonArray(jsonArray, metaList, json);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//
|
||||
// }
|
||||
// }
|
||||
// private JSONObject getPermissionJsonObject(SysPermission permission) {
|
||||
// JSONObject json = new JSONObject();
|
||||
// //类型(0:一级菜单 1:子菜单 2:按钮)
|
||||
// if(CommonConstant.MENU_TYPE_2.equals(permission.getMenuType())) {
|
||||
// json.put("action", permission.getPerms());
|
||||
// json.put("describe", permission.getName());
|
||||
// }else if(CommonConstant.MENU_TYPE_0.equals(permission.getMenuType()) || CommonConstant.MENU_TYPE_1.equals(permission.getMenuType())) {
|
||||
// json.put("id", permission.getId());
|
||||
// boolean flag = permission.getUrl()!=null&&(permission.getUrl().startsWith(CommonConstant.HTTP_PROTOCOL)||permission.getUrl().startsWith(CommonConstant.HTTPS_PROTOCOL));
|
||||
// if(flag) {
|
||||
// String url= new String(Base64.getUrlEncoder().encode(permission.getUrl().getBytes()));
|
||||
// json.put("path", "/sys/link/" +url.replaceAll("=",""));
|
||||
// }else {
|
||||
// json.put("path", permission.getUrl());
|
||||
// }
|
||||
//
|
||||
// //重要规则:路由name (通过URL生成路由name,路由name供前端开发,页面跳转使用)
|
||||
// json.put("name", urlToRouteName(permission.getUrl()));
|
||||
//
|
||||
// //是否隐藏路由,默认都是显示的
|
||||
// if(permission.isHidden()) {
|
||||
// json.put("hidden",true);
|
||||
// }
|
||||
// //聚合路由
|
||||
// if(permission.isAlwaysShow()) {
|
||||
// json.put("alwaysShow",true);
|
||||
// }
|
||||
// json.put("component", permission.getComponent());
|
||||
// JSONObject meta = new JSONObject();
|
||||
// meta.put("title", permission.getName());
|
||||
// if(oConvertUtils.isEmpty(permission.getParentId())) {
|
||||
// //一级菜单跳转地址
|
||||
// json.put("redirect",permission.getRedirect());
|
||||
// meta.put("icon", oConvertUtils.getString(permission.getIcon(), ""));
|
||||
// }else {
|
||||
// meta.put("icon", oConvertUtils.getString(permission.getIcon(), ""));
|
||||
// }
|
||||
// if(flag) {
|
||||
// meta.put("url", permission.getUrl());
|
||||
// }
|
||||
// json.put("meta", meta);
|
||||
// }
|
||||
//
|
||||
// return json;
|
||||
// }
|
||||
// /**
|
||||
// * 通过URL生成路由name(去掉URL前缀斜杠,替换内容中的斜杠‘/’为-)
|
||||
// * 举例: URL = /isystem/role
|
||||
// * RouteName = isystem-role
|
||||
// * @return
|
||||
// */
|
||||
// private String urlToRouteName(String url) {
|
||||
// if(oConvertUtils.isNotEmpty(url)) {
|
||||
// if(url.startsWith(SymbolConstant.SINGLE_SLASH)) {
|
||||
// url = url.substring(1);
|
||||
// }
|
||||
// url = url.replace("/", "-");
|
||||
// return url;
|
||||
// }else {
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
@@ -0,0 +1,112 @@
|
||||
package org.jeecg.modules.openapi.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.system.base.controller.JeecgController;
|
||||
import org.jeecg.common.system.query.QueryGenerator;
|
||||
import org.jeecg.modules.openapi.entity.OpenApiAuth;
|
||||
import org.jeecg.modules.openapi.generator.AKSKGenerator;
|
||||
import org.jeecg.modules.openapi.service.OpenApiAuthService;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* @date 2024/12/10 9:54
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/openapi/auth")
|
||||
public class OpenApiAuthController extends JeecgController<OpenApiAuth, OpenApiAuthService> {
|
||||
|
||||
/**
|
||||
* 分页列表查询
|
||||
*
|
||||
* @param openApiAuth
|
||||
* @param pageNo
|
||||
* @param pageSize
|
||||
* @param req
|
||||
* @return
|
||||
*/
|
||||
@GetMapping(value = "/list")
|
||||
public Result<?> queryPageList(OpenApiAuth openApiAuth, @RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize, HttpServletRequest req) {
|
||||
QueryWrapper<OpenApiAuth> queryWrapper = QueryGenerator.initQueryWrapper(openApiAuth, req.getParameterMap());
|
||||
Page<OpenApiAuth> page = new Page<>(pageNo, pageSize);
|
||||
IPage<OpenApiAuth> pageList = service.page(page, queryWrapper);
|
||||
return Result.ok(pageList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
*
|
||||
* @param openApiAuth
|
||||
* @return
|
||||
*/
|
||||
@PostMapping(value = "/add")
|
||||
public Result<?> add(@RequestBody OpenApiAuth openApiAuth) {
|
||||
service.save(openApiAuth);
|
||||
return Result.ok("添加成功!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑
|
||||
*
|
||||
* @param openApiAuth
|
||||
* @return
|
||||
*/
|
||||
@PutMapping(value = "/edit")
|
||||
public Result<?> edit(@RequestBody OpenApiAuth openApiAuth) {
|
||||
service.updateById(openApiAuth);
|
||||
return Result.ok("修改成功!");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过id删除
|
||||
*
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
@DeleteMapping(value = "/delete")
|
||||
public Result<?> delete(@RequestParam(name = "id", required = true) String id) {
|
||||
service.removeById(id);
|
||||
return Result.ok("删除成功!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
*
|
||||
* @param ids
|
||||
* @return
|
||||
*/
|
||||
@DeleteMapping(value = "/deleteBatch")
|
||||
public Result<?> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
|
||||
|
||||
this.service.removeByIds(Arrays.asList(ids.split(",")));
|
||||
return Result.ok("批量删除成功!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过id查询
|
||||
*
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
@GetMapping(value = "/queryById")
|
||||
public Result<?> queryById(@RequestParam(name = "id", required = true) String id) {
|
||||
OpenApiAuth openApiAuth = service.getById(id);
|
||||
return Result.ok(openApiAuth);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成AKSK
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("genAKSK")
|
||||
public Result<String[]> genAKSK() {
|
||||
return Result.ok(AKSKGenerator.genAKSKPair());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,415 @@
|
||||
package org.jeecg.modules.openapi.controller;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.common.system.base.controller.JeecgController;
|
||||
import org.jeecg.common.system.query.QueryGenerator;
|
||||
import org.jeecg.common.system.util.JwtUtil;
|
||||
import org.jeecg.common.util.RedisUtil;
|
||||
import org.jeecg.common.util.RestUtil;
|
||||
import org.jeecg.modules.openapi.entity.OpenApi;
|
||||
import org.jeecg.modules.openapi.entity.OpenApiAuth;
|
||||
import org.jeecg.modules.openapi.entity.OpenApiHeader;
|
||||
import org.jeecg.modules.openapi.entity.OpenApiParam;
|
||||
import org.jeecg.modules.openapi.generator.PathGenerator;
|
||||
import org.jeecg.modules.openapi.service.OpenApiAuthService;
|
||||
import org.jeecg.modules.openapi.service.OpenApiService;
|
||||
import org.jeecg.modules.openapi.swagger.*;
|
||||
import org.jeecg.modules.system.entity.SysUser;
|
||||
import org.jeecg.modules.system.service.ISysUserService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.net.URI;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @date 2024/12/10 9:11
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/openapi")
|
||||
public class OpenApiController extends JeecgController<OpenApi, OpenApiService> {
|
||||
|
||||
@Autowired
|
||||
private RestTemplate restTemplate;
|
||||
@Autowired
|
||||
private RedisUtil redisUtil;
|
||||
@Autowired
|
||||
private ISysUserService sysUserService;
|
||||
@Autowired
|
||||
private OpenApiAuthService openApiAuthService;
|
||||
|
||||
/**
|
||||
* 分页列表查询
|
||||
*
|
||||
* @param openApi
|
||||
* @param pageNo
|
||||
* @param pageSize
|
||||
* @param req
|
||||
* @return
|
||||
*/
|
||||
@GetMapping(value = "/list")
|
||||
public Result<?> queryPageList(OpenApi openApi, @RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize, HttpServletRequest req) {
|
||||
QueryWrapper<OpenApi> queryWrapper = QueryGenerator.initQueryWrapper(openApi, req.getParameterMap());
|
||||
Page<OpenApi> page = new Page<>(pageNo, pageSize);
|
||||
IPage<OpenApi> pageList = service.page(page, queryWrapper);
|
||||
return Result.ok(pageList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
*
|
||||
* @param openApi
|
||||
* @return
|
||||
*/
|
||||
@PostMapping(value = "/add")
|
||||
public Result<?> add(@RequestBody OpenApi openApi) {
|
||||
service.save(openApi);
|
||||
return Result.ok("添加成功!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑
|
||||
*
|
||||
* @param openApi
|
||||
* @return
|
||||
*/
|
||||
@PutMapping(value = "/edit")
|
||||
public Result<?> edit(@RequestBody OpenApi openApi) {
|
||||
service.updateById(openApi);
|
||||
return Result.ok("修改成功!");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过id删除
|
||||
*
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
@DeleteMapping(value = "/delete")
|
||||
public Result<?> delete(@RequestParam(name = "id", required = true) String id) {
|
||||
service.removeById(id);
|
||||
return Result.ok("删除成功!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
*
|
||||
* @param ids
|
||||
* @return
|
||||
*/
|
||||
@DeleteMapping(value = "/deleteBatch")
|
||||
public Result<?> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
|
||||
|
||||
this.service.removeByIds(Arrays.asList(ids.split(",")));
|
||||
return Result.ok("批量删除成功!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过id查询
|
||||
*
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
@GetMapping(value = "/queryById")
|
||||
public Result<?> queryById(@RequestParam(name = "id", required = true) String id) {
|
||||
OpenApi OpenApi = service.getById(id);
|
||||
return Result.ok(OpenApi);
|
||||
}
|
||||
|
||||
/**
|
||||
* 接口调用
|
||||
* @param path
|
||||
* @return
|
||||
*/
|
||||
@RequestMapping(value = "/call/{path}", method = {RequestMethod.GET,RequestMethod.POST})
|
||||
public Result<?> call(@PathVariable String path, @RequestBody(required = false) String json, HttpServletRequest request) {
|
||||
OpenApi openApi = service.findByPath(path);
|
||||
if (Objects.isNull(openApi)) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("code", 404);
|
||||
result.put("data", null);
|
||||
return Result.error("失败", result);
|
||||
}
|
||||
HttpHeaders httpHeaders = new HttpHeaders();
|
||||
if (StrUtil.isNotEmpty(openApi.getHeadersJson())) {
|
||||
List<OpenApiHeader> headers = JSON.parseArray(openApi.getHeadersJson(),OpenApiHeader.class);
|
||||
if (headers.size()>0) {
|
||||
for (OpenApiHeader header : headers) {
|
||||
httpHeaders.put(header.getHeaderKey(), Lists.newArrayList(request.getHeader(header.getHeaderKey())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String url = openApi.getOriginUrl();
|
||||
String method = openApi.getRequestMethod();
|
||||
String appkey = request.getHeader("appkey");
|
||||
OpenApiAuth openApiAuth = openApiAuthService.getByAppkey(appkey);
|
||||
SysUser systemUser = sysUserService.getUserByName(openApiAuth.getCreateBy());
|
||||
String token = this.getToken(systemUser.getUsername(), systemUser.getPassword());
|
||||
httpHeaders.put("X-Access-Token", Lists.newArrayList(token));
|
||||
httpHeaders.put("Content-Type",Lists.newArrayList("application/json"));
|
||||
HttpEntity<String> httpEntity = new HttpEntity<>(json, httpHeaders);
|
||||
url = RestUtil.getBaseUrl() + url;
|
||||
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
|
||||
if (HttpMethod.GET.matches(method)
|
||||
|| HttpMethod.DELETE.matches(method)
|
||||
|| HttpMethod.OPTIONS.matches(method)
|
||||
|| HttpMethod.TRACE.matches(method)) {
|
||||
//拼接参数
|
||||
if (!request.getParameterMap().isEmpty()) {
|
||||
if (StrUtil.isNotEmpty(openApi.getParamsJson())) {
|
||||
List<OpenApiParam> params = JSON.parseArray(openApi.getParamsJson(),OpenApiParam.class);
|
||||
if (params.size()>0) {
|
||||
Map<String, OpenApiParam> openApiParamMap = params.stream().collect(Collectors.toMap(p -> p.getParamKey(), p -> p, (e, r) -> e));
|
||||
request.getParameterMap().forEach((k, v) -> {
|
||||
OpenApiParam openApiParam = openApiParamMap.get(k);
|
||||
if (Objects.nonNull(openApiParam)) {
|
||||
if(v==null&&StrUtil.isNotEmpty(openApiParam.getDefaultValue())){
|
||||
builder.queryParam(openApiParam.getParamKey(), openApiParam.getDefaultValue());
|
||||
}
|
||||
if (v!=null){
|
||||
builder.queryParam(openApiParam.getParamKey(), v);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
URI targetUrl = builder.build().encode().toUri();
|
||||
return restTemplate.exchange(targetUrl.toString(), Objects.requireNonNull(HttpMethod.valueOf(method)), httpEntity, Result.class, request.getParameterMap()).getBody();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成接口访问令牌 Token
|
||||
*
|
||||
* @param USERNAME
|
||||
* @param PASSWORD
|
||||
* @return
|
||||
*/
|
||||
private String getToken(String USERNAME, String PASSWORD) {
|
||||
String token = JwtUtil.sign(USERNAME, PASSWORD, CommonConstant.CLIENT_TYPE_PC);
|
||||
redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, token);
|
||||
redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, 60);
|
||||
return token;
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/json")
|
||||
public SwaggerModel swaggerModel() {
|
||||
|
||||
SwaggerModel swaggerModel = new SwaggerModel();
|
||||
swaggerModel.setSwagger("2.0");
|
||||
swaggerModel.setInfo(swaggerInfo());
|
||||
swaggerModel.setHost("jeecg.com");
|
||||
swaggerModel.setBasePath("/jeecg-boot");
|
||||
swaggerModel.setSchemes(Lists.newArrayList("http", "https"));
|
||||
|
||||
SwaggerTag swaggerTag = new SwaggerTag();
|
||||
swaggerTag.setName("openapi");
|
||||
swaggerModel.setTags(Lists.newArrayList(swaggerTag));
|
||||
|
||||
pathsAndDefinitions(swaggerModel);
|
||||
|
||||
return swaggerModel;
|
||||
}
|
||||
|
||||
private void pathsAndDefinitions(SwaggerModel swaggerModel) {
|
||||
Map<String, Map<String, SwaggerOperation>> paths = new HashMap<>();
|
||||
Map<String, SwaggerDefinition> definitions = new HashMap<>();
|
||||
List<OpenApi> openapis = service.list();
|
||||
for (OpenApi openApi : openapis) {
|
||||
Map<String, SwaggerOperation> operations = new HashMap<>();
|
||||
SwaggerOperation operation = new SwaggerOperation();
|
||||
operation.setTags(Lists.newArrayList("openapi"));
|
||||
operation.setSummary(openApi.getName());
|
||||
operation.setDescription(openApi.getName());
|
||||
operation.setOperationId(openApi.getRequestUrl()+"Using"+openApi.getRequestMethod());
|
||||
operation.setProduces(Lists.newArrayList("application/json"));
|
||||
parameters(operation, openApi);
|
||||
|
||||
// body入参
|
||||
if (StringUtils.hasText(openApi.getBody())) {
|
||||
SwaggerDefinition definition = new SwaggerDefinition();
|
||||
definition.setType("object");
|
||||
Map<String, SwaggerDefinitionProperties> definitionProperties = new HashMap<>();
|
||||
definition.setProperties(definitionProperties);
|
||||
if (openApi.getBody()!=null){
|
||||
JSONObject jsonObject = JSONObject.parseObject(openApi.getBody());
|
||||
if (jsonObject.size()>0){
|
||||
for (Map.Entry<String, Object> properties : jsonObject.entrySet()) {
|
||||
SwaggerDefinitionProperties swaggerDefinitionProperties = new SwaggerDefinitionProperties();
|
||||
swaggerDefinitionProperties.setType("string");
|
||||
swaggerDefinitionProperties.setDescription(properties.getValue()+"");
|
||||
definitionProperties.put(properties.getKey(), swaggerDefinitionProperties);
|
||||
}
|
||||
}
|
||||
}
|
||||
// body的definition构建完成
|
||||
definitions.put(openApi.getRequestUrl()+"Using"+openApi.getRequestMethod()+"body", definition);
|
||||
|
||||
SwaggerOperationParameter bodyParameter = new SwaggerOperationParameter();
|
||||
bodyParameter.setDescription(openApi.getName() + " body");
|
||||
bodyParameter.setIn("body");
|
||||
bodyParameter.setName(openApi.getName() + " body");
|
||||
bodyParameter.setRequired(true);
|
||||
|
||||
Map<String, String> bodySchema = new HashMap<>();
|
||||
bodySchema.put("$ref", "#/definitions/" + openApi.getRequestUrl()+"Using"+openApi.getRequestMethod()+"body");
|
||||
bodyParameter.setSchema(bodySchema);
|
||||
|
||||
// 构建参数构建完成
|
||||
operation.getParameters().add(bodyParameter);
|
||||
|
||||
}
|
||||
|
||||
// 响应
|
||||
Map<String, SwaggerOperationResponse> responses = new HashMap<>();
|
||||
SwaggerOperationResponse resp200 = new SwaggerOperationResponse();
|
||||
resp200.setDescription("OK");
|
||||
Map<String, String> respSchema = new HashMap<>();
|
||||
respSchema.put("$ref", "#/definitions/OpenApiResult");
|
||||
resp200.setSchema(respSchema);
|
||||
|
||||
responses.put("200", resp200);
|
||||
|
||||
Map<String, String> emptySchema = new HashMap<>();
|
||||
SwaggerOperationResponse resp201 = new SwaggerOperationResponse();
|
||||
resp201.setDescription("Created");
|
||||
resp201.setSchema(emptySchema);
|
||||
responses.put("201", resp201);
|
||||
SwaggerOperationResponse resp401 = new SwaggerOperationResponse();
|
||||
resp401.setDescription("Unauthorized");
|
||||
resp401.setSchema(emptySchema);
|
||||
responses.put("401", resp401);
|
||||
SwaggerOperationResponse resp403 = new SwaggerOperationResponse();
|
||||
resp403.setDescription("Forbidden");
|
||||
resp403.setSchema(emptySchema);
|
||||
responses.put("403", resp403);
|
||||
SwaggerOperationResponse resp404 = new SwaggerOperationResponse();
|
||||
resp404.setDescription("Not Found");
|
||||
resp404.setSchema(emptySchema);
|
||||
responses.put("404", resp404);
|
||||
|
||||
// 构建响应definition
|
||||
SwaggerDefinition respDefinition = new SwaggerDefinition();
|
||||
respDefinition.setType("object");
|
||||
|
||||
Map<String, SwaggerDefinitionProperties> definitionProperties = new HashMap<>();
|
||||
respDefinition.setProperties(definitionProperties);
|
||||
|
||||
SwaggerDefinitionProperties codeProperties = new SwaggerDefinitionProperties();
|
||||
codeProperties.setType("integer");
|
||||
codeProperties.setDescription("返回代码");
|
||||
definitionProperties.put("code", codeProperties);
|
||||
SwaggerDefinitionProperties messageProperties = new SwaggerDefinitionProperties();
|
||||
messageProperties.setType("string");
|
||||
messageProperties.setDescription("返回处理消息");
|
||||
definitionProperties.put("message", messageProperties);
|
||||
SwaggerDefinitionProperties resultProperties = new SwaggerDefinitionProperties();
|
||||
resultProperties.setType("object");
|
||||
resultProperties.setDescription("返回数据对象");
|
||||
definitionProperties.put("result", resultProperties);
|
||||
SwaggerDefinitionProperties successProperties = new SwaggerDefinitionProperties();
|
||||
successProperties.setType("boolean");
|
||||
successProperties.setDescription("成功标志");
|
||||
definitionProperties.put("success", successProperties);
|
||||
SwaggerDefinitionProperties timestampProperties = new SwaggerDefinitionProperties();
|
||||
timestampProperties.setType("integer");
|
||||
timestampProperties.setDescription("时间戳");
|
||||
definitionProperties.put("timestamp", timestampProperties);
|
||||
|
||||
definitions.put("OpenApiResult", respDefinition);
|
||||
|
||||
|
||||
operation.setResponses(responses);
|
||||
operations.put(openApi.getRequestMethod().toLowerCase(), operation);
|
||||
paths.put("/openapi/call/"+openApi.getRequestUrl(), operations);
|
||||
}
|
||||
|
||||
swaggerModel.setDefinitions(definitions);
|
||||
swaggerModel.setPaths(paths);
|
||||
|
||||
}
|
||||
|
||||
private void parameters(SwaggerOperation operation, OpenApi openApi) {
|
||||
List<SwaggerOperationParameter> parameters = new ArrayList<>();
|
||||
if (openApi.getParamsJson()!=null) {
|
||||
List<OpenApiParam> openApiParams = JSON.parseArray(openApi.getParamsJson(), OpenApiParam.class);
|
||||
for (OpenApiParam openApiParam : openApiParams) {
|
||||
SwaggerOperationParameter parameter = new SwaggerOperationParameter();
|
||||
parameter.setIn("path");
|
||||
parameter.setName(openApiParam.getParamKey());
|
||||
parameter.setRequired(openApiParam.getRequired() == 1);
|
||||
parameter.setDescription(openApiParam.getNote());
|
||||
parameters.add(parameter);
|
||||
}
|
||||
}
|
||||
if (openApi.getHeadersJson()!=null) {
|
||||
List<OpenApiHeader> openApiHeaders = JSON.parseArray(openApi.getHeadersJson(), OpenApiHeader.class);
|
||||
for (OpenApiHeader openApiHeader : openApiHeaders) {
|
||||
SwaggerOperationParameter parameter = new SwaggerOperationParameter();
|
||||
parameter.setIn("header");
|
||||
parameter.setName(openApiHeader.getHeaderKey());
|
||||
parameter.setRequired(openApiHeader.getRequired() == 1);
|
||||
parameter.setDescription(openApiHeader.getNote());
|
||||
parameters.add(parameter);
|
||||
}
|
||||
}
|
||||
operation.setParameters(parameters);
|
||||
}
|
||||
|
||||
private SwaggerInfo swaggerInfo() {
|
||||
SwaggerInfo info = new SwaggerInfo();
|
||||
|
||||
info.setDescription("OpenAPI 接口列表");
|
||||
info.setVersion("3.9.1");
|
||||
info.setTitle("OpenAPI 接口列表");
|
||||
info.setTermsOfService("https://jeecg.com");
|
||||
|
||||
SwaggerInfoContact contact = new SwaggerInfoContact();
|
||||
contact.setName("jeecg@qq.com");
|
||||
|
||||
info.setContact(contact);
|
||||
|
||||
SwaggerInfoLicense license = new SwaggerInfoLicense();
|
||||
license.setName("Apache 2.0");
|
||||
license.setUrl("http://www.apache.org/licenses/LICENSE-2.0.html");
|
||||
|
||||
info.setLicense(license);
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成接口路径
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("genPath")
|
||||
public Result<String> genPath() {
|
||||
Result<String> r = new Result<String>();
|
||||
r.setSuccess(true);
|
||||
r.setCode(CommonConstant.SC_OK_200);
|
||||
r.setResult(PathGenerator.genPath());
|
||||
return r;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.jeecg.modules.openapi.controller;
|
||||
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.config.shiro.IgnoreAuth;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @date 2024/12/20 14:04
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/openapi/demo")
|
||||
public class OpenApiIndexController {
|
||||
|
||||
@GetMapping("index")
|
||||
@IgnoreAuth
|
||||
public Result<Map<String, String>> index() {
|
||||
Map<String, String> result = new HashMap<>();
|
||||
result.put("first", "Hello World");
|
||||
return Result.ok(result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package org.jeecg.modules.openapi.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.system.base.controller.JeecgController;
|
||||
import org.jeecg.common.system.query.QueryGenerator;
|
||||
import org.jeecg.modules.openapi.entity.OpenApiLog;
|
||||
import org.jeecg.modules.openapi.service.OpenApiLogService;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* @date 2024/12/10 9:57
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/openapi/record")
|
||||
public class OpenApiLogController extends JeecgController<OpenApiLog, OpenApiLogService> {
|
||||
|
||||
/**
|
||||
* 分页列表查询
|
||||
*
|
||||
* @param OpenApiLog
|
||||
* @param pageNo
|
||||
* @param pageSize
|
||||
* @param req
|
||||
* @return
|
||||
*/
|
||||
@GetMapping(value = "/list")
|
||||
public Result<?> queryPageList(OpenApiLog OpenApiLog, @RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize, HttpServletRequest req) {
|
||||
QueryWrapper<OpenApiLog> queryWrapper = QueryGenerator.initQueryWrapper(OpenApiLog, req.getParameterMap());
|
||||
Page<OpenApiLog> page = new Page<>(pageNo, pageSize);
|
||||
IPage<OpenApiLog> pageList = service.page(page, queryWrapper);
|
||||
return Result.ok(pageList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
*
|
||||
* @param OpenApiLog
|
||||
* @return
|
||||
*/
|
||||
@PostMapping(value = "/add")
|
||||
public Result<?> add(@RequestBody OpenApiLog OpenApiLog) {
|
||||
service.save(OpenApiLog);
|
||||
return Result.ok("添加成功!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑
|
||||
*
|
||||
* @param OpenApiLog
|
||||
* @return
|
||||
*/
|
||||
@PutMapping(value = "/edit")
|
||||
public Result<?> edit(@RequestBody OpenApiLog OpenApiLog) {
|
||||
service.updateById(OpenApiLog);
|
||||
return Result.ok("修改成功!");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过id删除
|
||||
*
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
@DeleteMapping(value = "/delete")
|
||||
public Result<?> delete(@RequestParam(name = "id", required = true) String id) {
|
||||
service.removeById(id);
|
||||
return Result.ok("删除成功!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
*
|
||||
* @param ids
|
||||
* @return
|
||||
*/
|
||||
@DeleteMapping(value = "/deleteBatch")
|
||||
public Result<?> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
|
||||
|
||||
this.service.removeByIds(Arrays.asList(ids.split(",")));
|
||||
return Result.ok("批量删除成功!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过id查询
|
||||
*
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
@GetMapping(value = "/queryById")
|
||||
public Result<?> queryById(@RequestParam(name = "id", required = true) String id) {
|
||||
OpenApiLog OpenApiLog = service.getById(id);
|
||||
return Result.ok(OpenApiLog);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package org.jeecg.modules.openapi.controller;
|
||||
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.system.base.controller.JeecgController;
|
||||
import org.jeecg.modules.openapi.entity.OpenApiPermission;
|
||||
import org.jeecg.modules.openapi.service.OpenApiPermissionService;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/openapi/permission")
|
||||
public class OpenApiPermissionController extends JeecgController<OpenApiPermission, OpenApiPermissionService> {
|
||||
|
||||
@PostMapping("add")
|
||||
public Result add(@RequestBody OpenApiPermission openApiPermission) {
|
||||
service.add(openApiPermission);
|
||||
return Result.ok("保存成功");
|
||||
}
|
||||
@GetMapping("/getOpenApi")
|
||||
public Result<?> getOpenApi( String apiAuthId) {
|
||||
return service.getOpenApi(apiAuthId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package org.jeecg.modules.openapi.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 接口表
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Accessors(chain = true)
|
||||
public class OpenApi implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* id
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 接口名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 请求方式,如POST、GET
|
||||
*/
|
||||
private String requestMethod;
|
||||
|
||||
/**
|
||||
* 对外开放的相对接口路径
|
||||
*/
|
||||
private String requestUrl;
|
||||
|
||||
/**
|
||||
* IP 黑名单
|
||||
*/
|
||||
private String blackList;
|
||||
/**
|
||||
* 请求头json
|
||||
*/
|
||||
private String headersJson;
|
||||
/**
|
||||
* 请求参数json
|
||||
*/
|
||||
private String paramsJson;
|
||||
|
||||
|
||||
/**
|
||||
* 目前仅支持json
|
||||
*/
|
||||
private String body;
|
||||
|
||||
/**
|
||||
* 原始接口路径
|
||||
*/
|
||||
private String originUrl;
|
||||
|
||||
/**
|
||||
* 状态(1:正常 2:废弃 )
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 删除状态(0,正常,1已删除)
|
||||
*/
|
||||
@TableLogic
|
||||
private Integer delFlag;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
private String createBy;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新人
|
||||
*/
|
||||
private String updateBy;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private Date updateTime;
|
||||
/**
|
||||
* 历史已选接口
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private String ifCheckBox = "0";
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package org.jeecg.modules.openapi.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.jeecg.common.aspect.annotation.Dict;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 权限表
|
||||
* @date 2024/12/10 9:38
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Accessors(chain = true)
|
||||
public class OpenApiAuth implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -5933153354153738498L;
|
||||
|
||||
/**
|
||||
* id
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 受权名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* access key
|
||||
*/
|
||||
private String ak;
|
||||
|
||||
/**
|
||||
* secret key
|
||||
*/
|
||||
private String sk;
|
||||
|
||||
/**
|
||||
* 系统用户ID
|
||||
*/
|
||||
@Dict(dictTable = "sys_user",dicCode = "id",dicText = "username")
|
||||
private String systemUserId;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
private String createBy;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新人
|
||||
*/
|
||||
private String updateBy;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private Date updateTime;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package org.jeecg.modules.openapi.entity;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 请求头表
|
||||
* @date 2024/12/10 14:37
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Accessors(chain = true)
|
||||
public class OpenApiHeader implements Serializable {
|
||||
private static final long serialVersionUID = 5032708503120184683L;
|
||||
|
||||
|
||||
/**
|
||||
* key
|
||||
*/
|
||||
private String headerKey;
|
||||
|
||||
/**
|
||||
* 是否必填(0:否,1:是)
|
||||
*/
|
||||
private Integer required;
|
||||
|
||||
/**
|
||||
* 默认值
|
||||
*/
|
||||
private String defaultValue;
|
||||
|
||||
/**
|
||||
* 说明
|
||||
*/
|
||||
private String note;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package org.jeecg.modules.openapi.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 调用记录表
|
||||
* @date 2024/12/10 9:41
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Accessors(chain = true)
|
||||
public class OpenApiLog implements Serializable {
|
||||
private static final long serialVersionUID = -5870384488947863579L;
|
||||
|
||||
/**
|
||||
* id
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 接口ID
|
||||
*/
|
||||
private String apiId;
|
||||
|
||||
/**
|
||||
* 调用ID
|
||||
*/
|
||||
private String callAuthId;
|
||||
|
||||
/**
|
||||
* 调用时间
|
||||
*/
|
||||
private Date callTime;
|
||||
|
||||
/**
|
||||
* 耗时
|
||||
*/
|
||||
private Long usedTime;
|
||||
|
||||
/**
|
||||
* 响应时间
|
||||
*/
|
||||
private Date responseTime;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package org.jeecg.modules.openapi.entity;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* query部分参数表
|
||||
* @date 2024/12/10 14:37
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Accessors(chain = true)
|
||||
public class OpenApiParam implements Serializable {
|
||||
private static final long serialVersionUID = -6174831468578022357L;
|
||||
|
||||
/**
|
||||
* key
|
||||
*/
|
||||
private String paramKey;
|
||||
|
||||
/**
|
||||
* 是否必填(0:否,1:是)
|
||||
*/
|
||||
private Integer required;
|
||||
|
||||
/**
|
||||
* 默认值
|
||||
*/
|
||||
private String defaultValue;
|
||||
|
||||
/**
|
||||
* 说明
|
||||
*/
|
||||
private String note;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package org.jeecg.modules.openapi.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
*
|
||||
* @date 2024/12/19 17:41
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Accessors(chain = true)
|
||||
public class OpenApiPermission implements Serializable {
|
||||
/**
|
||||
* id
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 接口ID
|
||||
*/
|
||||
private String apiId;
|
||||
|
||||
/**
|
||||
* 认证ID
|
||||
*/
|
||||
private String apiAuthId;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
private String createBy;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新人
|
||||
*/
|
||||
private String updateBy;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private Date updateTime;
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
package org.jeecg.modules.openapi.filter;
|
||||
|
||||
import jakarta.servlet.*;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.exception.JeecgBootException;
|
||||
import org.jeecg.modules.openapi.entity.OpenApi;
|
||||
import org.jeecg.modules.openapi.entity.OpenApiAuth;
|
||||
import org.jeecg.modules.openapi.entity.OpenApiLog;
|
||||
import org.jeecg.modules.openapi.entity.OpenApiPermission;
|
||||
import org.jeecg.modules.openapi.service.OpenApiAuthService;
|
||||
import org.jeecg.modules.openapi.service.OpenApiLogService;
|
||||
import org.jeecg.modules.openapi.service.OpenApiPermissionService;
|
||||
import org.jeecg.modules.openapi.service.OpenApiService;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @date 2024/12/19 16:55
|
||||
*/
|
||||
@Slf4j
|
||||
public class ApiAuthFilter implements Filter {
|
||||
|
||||
private OpenApiLogService openApiLogService;
|
||||
private OpenApiAuthService openApiAuthService;
|
||||
private OpenApiPermissionService openApiPermissionService;
|
||||
private OpenApiService openApiService;
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
|
||||
long startTime = System.currentTimeMillis();
|
||||
Date callTime = new Date();
|
||||
|
||||
HttpServletRequest request = (HttpServletRequest)servletRequest;
|
||||
String ip = request.getRemoteAddr();
|
||||
|
||||
String appkey = request.getHeader("appkey");
|
||||
String signature = request.getHeader("signature");
|
||||
String timestamp = request.getHeader("timestamp");
|
||||
|
||||
OpenApi openApi = findOpenApi(request);
|
||||
|
||||
// IP 黑名单核验
|
||||
checkBlackList(openApi, ip);
|
||||
|
||||
// 签名核验
|
||||
checkSignValid(appkey, signature, timestamp);
|
||||
|
||||
OpenApiAuth openApiAuth = openApiAuthService.getByAppkey(appkey);
|
||||
// 认证信息核验
|
||||
checkSignature(appkey, signature, timestamp, openApiAuth);
|
||||
// 业务核验
|
||||
checkPermission(openApi, openApiAuth);
|
||||
|
||||
filterChain.doFilter(servletRequest, servletResponse);
|
||||
long endTime = System.currentTimeMillis();
|
||||
|
||||
OpenApiLog openApiLog = new OpenApiLog();
|
||||
openApiLog.setApiId(openApi.getId());
|
||||
openApiLog.setCallAuthId(openApiAuth.getId());
|
||||
openApiLog.setCallTime(callTime);
|
||||
openApiLog.setUsedTime(endTime - startTime);
|
||||
openApiLog.setResponseTime(new Date());
|
||||
openApiLogService.save(openApiLog);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) throws ServletException {
|
||||
ServletContext servletContext = filterConfig.getServletContext();
|
||||
WebApplicationContext applicationContext = (WebApplicationContext)servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
|
||||
this.openApiService = applicationContext.getBean(OpenApiService.class);
|
||||
this.openApiLogService = applicationContext.getBean(OpenApiLogService.class);
|
||||
this.openApiAuthService = applicationContext.getBean(OpenApiAuthService.class);
|
||||
this.openApiPermissionService = applicationContext.getBean(OpenApiPermissionService.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* IP 黑名单核验
|
||||
* @param openApi
|
||||
* @param ip
|
||||
*/
|
||||
protected void checkBlackList(OpenApi openApi, String ip) {
|
||||
if (!StringUtils.hasText(openApi.getBlackList())) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<String> blackList = Arrays.asList(openApi.getBlackList().split(","));
|
||||
if (blackList.contains(ip)) {
|
||||
throw new JeecgBootException("目标接口限制IP[" + ip + "]进行访问,IP已记录,请停止访问");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 签名验证
|
||||
* @param appkey
|
||||
* @param signature
|
||||
* @param timestamp
|
||||
* @return
|
||||
*/
|
||||
protected void checkSignValid(String appkey, String signature, String timestamp) {
|
||||
if (!StringUtils.hasText(appkey)) {
|
||||
throw new JeecgBootException("appkey为空");
|
||||
}
|
||||
if (!StringUtils.hasText(signature)) {
|
||||
throw new JeecgBootException("signature为空");
|
||||
}
|
||||
if (!StringUtils.hasText(timestamp)) {
|
||||
throw new JeecgBootException("timastamp时间戳为空");
|
||||
}
|
||||
if (!timestamp.matches("[0-9]*")) {
|
||||
throw new JeecgBootException("timastamp时间戳不合法");
|
||||
}
|
||||
if (System.currentTimeMillis() - Long.parseLong(timestamp) > 5 * 60 * 1000) {
|
||||
throw new JeecgBootException("signature签名已过期(超过五分钟)");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 认证信息核验
|
||||
* @param appKey
|
||||
* @param signature
|
||||
* @param timestamp
|
||||
* @param openApiAuth
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
protected void checkSignature(String appKey, String signature, String timestamp, OpenApiAuth openApiAuth) {
|
||||
if(openApiAuth==null){
|
||||
throw new JeecgBootException("不存在认证信息");
|
||||
}
|
||||
|
||||
if(!appKey.equals(openApiAuth.getAk())){
|
||||
throw new JeecgBootException("appkey错误");
|
||||
}
|
||||
|
||||
if (!signature.equals(md5(appKey + openApiAuth.getSk() + timestamp))) {
|
||||
throw new JeecgBootException("signature签名错误");
|
||||
}
|
||||
}
|
||||
|
||||
protected void checkPermission(OpenApi openApi, OpenApiAuth openApiAuth) {
|
||||
List<OpenApiPermission> permissionList = openApiPermissionService.findByAuthId(openApiAuth.getId());
|
||||
|
||||
boolean hasPermission = false;
|
||||
for (OpenApiPermission permission : permissionList) {
|
||||
if (permission.getApiId().equals(openApi.getId())) {
|
||||
hasPermission = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasPermission) {
|
||||
throw new JeecgBootException("该appKey未授权当前接口");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return String 返回类型
|
||||
* @Title: MD5
|
||||
* @Description: 【MD5加密】
|
||||
*/
|
||||
protected static String md5(String sourceStr) {
|
||||
String result = "";
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
md.update(sourceStr.getBytes("utf-8"));
|
||||
byte[] hash = md.digest();
|
||||
int i;
|
||||
StringBuffer buf = new StringBuffer(32);
|
||||
for (int offset = 0; offset < hash.length; offset++) {
|
||||
i = hash[offset];
|
||||
if (i < 0) {
|
||||
i += 256;
|
||||
}
|
||||
if (i < 16) {
|
||||
buf.append("0");
|
||||
}
|
||||
buf.append(Integer.toHexString(i));
|
||||
}
|
||||
result = buf.toString();
|
||||
} catch (Exception e) {
|
||||
log.error("sign签名错误", e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected OpenApi findOpenApi(HttpServletRequest request) {
|
||||
String uri = request.getRequestURI();
|
||||
String path = uri.substring(uri.lastIndexOf("/") + 1);
|
||||
return openApiService.findByPath(path);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
System.out.println("timestamp:" + timestamp);
|
||||
System.out.println("signature:" + md5("ak-eAU25mrMxhtaZsyS" + "rjxMqB6YyUXpSHAz4DCIz8vZ5aozQQiV" + timestamp));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.jeecg.modules.openapi.filter;
|
||||
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @date 2024/12/19 17:09
|
||||
*/
|
||||
@Configuration
|
||||
public class ApiFilterConfig {
|
||||
|
||||
/**
|
||||
*
|
||||
* @Description: 【注册api加密过滤器】
|
||||
*/
|
||||
@Bean
|
||||
public FilterRegistrationBean<ApiAuthFilter> authFilter() {
|
||||
FilterRegistrationBean<ApiAuthFilter> registration = new FilterRegistrationBean<>();
|
||||
registration.setFilter(new ApiAuthFilter());
|
||||
registration.setName("apiAuthFilter");
|
||||
registration.addUrlPatterns("/openapi/call/*");
|
||||
return registration;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.jeecg.modules.openapi.generator;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
/**
|
||||
* AK/SK生成器
|
||||
*/
|
||||
public class AKSKGenerator {
|
||||
private static final String CHAR_POOL = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
private static final int AK_LENGTH = 16; // Adjust as per requirements
|
||||
private static final int SK_LENGTH = 32;
|
||||
|
||||
public static String[] genAKSKPair() {
|
||||
return new String[]{genAK(), genSK()};
|
||||
}
|
||||
|
||||
public static String genAK() {
|
||||
return "ak-" + generateRandomString(AK_LENGTH);
|
||||
}
|
||||
|
||||
public static String genSK() {
|
||||
return generateRandomString(SK_LENGTH);
|
||||
}
|
||||
|
||||
|
||||
private static String generateRandomString(int length) {
|
||||
SecureRandom random = new SecureRandom();
|
||||
StringBuilder sb = new StringBuilder(length);
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
sb.append(CHAR_POOL.charAt(random.nextInt(CHAR_POOL.length())));
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.jeecg.modules.openapi.generator;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* @date 2024/12/10 10:00
|
||||
*/
|
||||
@UtilityClass
|
||||
public class PathGenerator {
|
||||
|
||||
// Base62字符集
|
||||
private static final String BASE62 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
|
||||
/**
|
||||
* 生成随机路径
|
||||
* @return
|
||||
*/
|
||||
public static String genPath() {
|
||||
StringBuilder result = new StringBuilder();
|
||||
Random random = new Random();
|
||||
for (int i=0; i<8; i++) {
|
||||
result.append(BASE62.charAt(random.nextInt(62)));
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.jeecg.modules.openapi.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.jeecg.modules.openapi.entity.OpenApiAuth;
|
||||
|
||||
/**
|
||||
* @date 2024/12/10 9:49
|
||||
*/
|
||||
@Mapper
|
||||
public interface OpenApiAuthMapper extends BaseMapper<OpenApiAuth> {
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.jeecg.modules.openapi.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.jeecg.modules.openapi.entity.OpenApiLog;
|
||||
|
||||
/**
|
||||
* @date 2024/12/10 9:50
|
||||
*/
|
||||
@Mapper
|
||||
public interface OpenApiLogMapper extends BaseMapper<OpenApiLog> {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.jeecg.modules.openapi.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.jeecg.modules.openapi.entity.OpenApi;
|
||||
|
||||
@Mapper
|
||||
public interface OpenApiMapper extends BaseMapper<OpenApi> {
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.jeecg.modules.openapi.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.jeecg.modules.openapi.entity.OpenApiPermission;
|
||||
|
||||
/**
|
||||
* @date 2024/12/19 17:43
|
||||
*/
|
||||
@Mapper
|
||||
public interface OpenApiPermissionMapper extends BaseMapper<OpenApiPermission> {
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.jeecg.modules.openapi.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import org.jeecg.modules.openapi.entity.OpenApiAuth;
|
||||
|
||||
/**
|
||||
* @date 2024/12/10 9:50
|
||||
*/
|
||||
public interface OpenApiAuthService extends IService<OpenApiAuth> {
|
||||
OpenApiAuth getByAppkey(String appkey);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.jeecg.modules.openapi.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import org.jeecg.modules.openapi.entity.OpenApiLog;
|
||||
|
||||
/**
|
||||
* @date 2024/12/10 9:51
|
||||
*/
|
||||
public interface OpenApiLogService extends IService<OpenApiLog> {
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.jeecg.modules.openapi.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.modules.openapi.entity.OpenApiPermission;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @date 2024/12/19 17:44
|
||||
*/
|
||||
public interface OpenApiPermissionService extends IService<OpenApiPermission> {
|
||||
List<OpenApiPermission> findByAuthId(String authId);
|
||||
|
||||
Result<?> getOpenApi(String apiAuthId);
|
||||
|
||||
void add(OpenApiPermission openApiPermission);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package org.jeecg.modules.openapi.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import org.jeecg.modules.openapi.entity.OpenApi;
|
||||
|
||||
public interface OpenApiService extends IService<OpenApi> {
|
||||
OpenApi findByPath(String path);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.jeecg.modules.openapi.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.jeecg.modules.openapi.entity.OpenApiAuth;
|
||||
import org.jeecg.modules.openapi.mapper.OpenApiAuthMapper;
|
||||
import org.jeecg.modules.openapi.service.OpenApiAuthService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* @date 2024/12/10 9:51
|
||||
*/
|
||||
@Service
|
||||
public class OpenApiAuthServiceImpl extends ServiceImpl<OpenApiAuthMapper, OpenApiAuth> implements OpenApiAuthService {
|
||||
@Override
|
||||
public OpenApiAuth getByAppkey(String appkey) {
|
||||
return baseMapper.selectOne(Wrappers.lambdaUpdate(OpenApiAuth.class).eq(OpenApiAuth::getAk, appkey), false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.jeecg.modules.openapi.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.jeecg.modules.openapi.entity.OpenApiLog;
|
||||
import org.jeecg.modules.openapi.mapper.OpenApiLogMapper;
|
||||
import org.jeecg.modules.openapi.service.OpenApiLogService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* @date 2024/12/10 9:53
|
||||
*/
|
||||
@Service
|
||||
public class OpenApiLogServiceImpl extends ServiceImpl<OpenApiLogMapper, OpenApiLog> implements OpenApiLogService {
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package org.jeecg.modules.openapi.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.modules.openapi.entity.OpenApi;
|
||||
import org.jeecg.modules.openapi.entity.OpenApiPermission;
|
||||
import org.jeecg.modules.openapi.mapper.OpenApiPermissionMapper;
|
||||
import org.jeecg.modules.openapi.service.OpenApiPermissionService;
|
||||
import org.jeecg.modules.openapi.service.OpenApiService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @date 2024/12/19 17:44
|
||||
*/
|
||||
@Service
|
||||
public class OpenApiPermissionServiceImpl extends ServiceImpl<OpenApiPermissionMapper, OpenApiPermission> implements OpenApiPermissionService {
|
||||
@Resource
|
||||
private OpenApiService openApiService;
|
||||
@Override
|
||||
public List<OpenApiPermission> findByAuthId(String authId) {
|
||||
return baseMapper.selectList(Wrappers.lambdaQuery(OpenApiPermission.class).eq(OpenApiPermission::getApiAuthId, authId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<?> getOpenApi(String apiAuthId) {
|
||||
List<OpenApi> openApis = openApiService.list();
|
||||
if (CollectionUtil.isEmpty(openApis)) {
|
||||
return Result.error("接口不存在");
|
||||
}
|
||||
List<OpenApiPermission> openApiPermissions = baseMapper.selectList(Wrappers.<OpenApiPermission>lambdaQuery().eq(OpenApiPermission::getApiAuthId, apiAuthId));
|
||||
if (CollectionUtil.isNotEmpty(openApiPermissions)) {
|
||||
Map<String, OpenApi> openApiMap = openApis.stream().collect(Collectors.toMap(OpenApi::getId, o -> o));
|
||||
for (OpenApiPermission openApiPermission : openApiPermissions) {
|
||||
OpenApi openApi = openApiMap.get(openApiPermission.getApiId());
|
||||
if (openApi!=null) {
|
||||
openApi.setIfCheckBox("1");
|
||||
}
|
||||
}
|
||||
}
|
||||
return Result.ok(openApis);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(OpenApiPermission openApiPermission) {
|
||||
this.remove(Wrappers.<OpenApiPermission>lambdaQuery().eq(OpenApiPermission::getApiAuthId, openApiPermission.getApiAuthId()));
|
||||
List<String> list = Arrays.asList(openApiPermission.getApiId().split(","));
|
||||
if (CollectionUtil.isNotEmpty(list)) {
|
||||
list.forEach(l->{
|
||||
if (StrUtil.isNotEmpty(l)){
|
||||
OpenApiPermission saveApiPermission = new OpenApiPermission();
|
||||
saveApiPermission.setApiId(l);
|
||||
saveApiPermission.setApiAuthId(openApiPermission.getApiAuthId());
|
||||
this.save(saveApiPermission);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.jeecg.modules.openapi.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.jeecg.modules.openapi.entity.OpenApi;
|
||||
import org.jeecg.modules.openapi.mapper.OpenApiMapper;
|
||||
import org.jeecg.modules.openapi.service.OpenApiService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class OpenApiServiceImpl extends ServiceImpl<OpenApiMapper, OpenApi> implements OpenApiService {
|
||||
@Override
|
||||
public OpenApi findByPath(String path) {
|
||||
return baseMapper.selectOne(Wrappers.lambdaQuery(OpenApi.class).eq(OpenApi::getRequestUrl, path), false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.jeecg.modules.openapi.swagger;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @date 2025/1/26 11:17
|
||||
*/
|
||||
@Data
|
||||
public class SwaggerDefinition {
|
||||
private String type;
|
||||
private Map<String, SwaggerDefinitionProperties> properties;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.jeecg.modules.openapi.swagger;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @date 2025/1/26 13:54
|
||||
*/
|
||||
@Data
|
||||
public class SwaggerDefinitionProperties {
|
||||
private String type;
|
||||
private String example;
|
||||
private String description;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.jeecg.modules.openapi.swagger;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @date 2025/1/26 11:05
|
||||
*/
|
||||
@Data
|
||||
public class SwaggerInfo {
|
||||
private String description;
|
||||
private String version;
|
||||
private String title;
|
||||
private String termsOfService;
|
||||
private SwaggerInfoContact contact;
|
||||
private SwaggerInfoLicense license;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.jeecg.modules.openapi.swagger;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @date 2025/1/26 11:08
|
||||
*/
|
||||
@Data
|
||||
public class SwaggerInfoContact {
|
||||
private String name;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.jeecg.modules.openapi.swagger;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @date 2025/1/26 11:09
|
||||
*/
|
||||
@Data
|
||||
public class SwaggerInfoLicense {
|
||||
private String name;
|
||||
private String url;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.jeecg.modules.openapi.swagger;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @date 2025/1/26 11:05
|
||||
*/
|
||||
@Data
|
||||
public class SwaggerModel {
|
||||
private String swagger;
|
||||
private SwaggerInfo info;
|
||||
private String host;
|
||||
private String basePath;
|
||||
private List<SwaggerTag> tags;
|
||||
private List<String> schemes;
|
||||
private Map<String, Map<String, SwaggerOperation>> paths;
|
||||
private Map<String, SwaggerDefinition> definitions;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.jeecg.modules.openapi.swagger;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @date 2025/1/26 11:16
|
||||
*/
|
||||
@Data
|
||||
public class SwaggerOperation {
|
||||
private List<String> tags;
|
||||
private String summary;
|
||||
private String description;
|
||||
private String operationId;
|
||||
private List<String> produces;
|
||||
private List<SwaggerOperationParameter> parameters;
|
||||
private Map<String, SwaggerOperationResponse> responses;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.jeecg.modules.openapi.swagger;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @date 2025/1/26 11:43
|
||||
*/
|
||||
@Data
|
||||
public class SwaggerOperationParameter {
|
||||
private String name;
|
||||
private String in;
|
||||
private String description;
|
||||
private Boolean required;
|
||||
private Map<String, String> schema;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.jeecg.modules.openapi.swagger;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @date 2025/1/26 11:47
|
||||
*/
|
||||
@Data
|
||||
public class SwaggerOperationResponse {
|
||||
private String description;
|
||||
private Map<String, String> schema;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.jeecg.modules.openapi.swagger;
|
||||
|
||||
/**
|
||||
* @date 2025/1/26 11:51
|
||||
*/
|
||||
public class SwaggerSchema {
|
||||
private String $ref;
|
||||
|
||||
public String get$ref() {
|
||||
return $ref;
|
||||
}
|
||||
|
||||
public void set$ref(String $ref) {
|
||||
this.$ref = $ref;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.jeecg.modules.openapi.swagger;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @date 2025/1/26 11:15
|
||||
*/
|
||||
@Data
|
||||
public class SwaggerTag {
|
||||
private String name;
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package org.jeecg.modules.oss.controller;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.apache.shiro.authz.annotation.RequiresRoles;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.system.query.QueryGenerator;
|
||||
import org.jeecg.modules.oss.entity.OssFile;
|
||||
import org.jeecg.modules.oss.service.IOssFileService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 云存储示例 DEMO
|
||||
* @author: jeecg-boot
|
||||
*/
|
||||
@Slf4j
|
||||
@Controller
|
||||
@RequestMapping("/sys/oss/file")
|
||||
public class OssFileController {
|
||||
|
||||
@Autowired
|
||||
private IOssFileService ossFileService;
|
||||
|
||||
@ResponseBody
|
||||
@GetMapping("/list")
|
||||
public Result<IPage<OssFile>> queryPageList(OssFile file,
|
||||
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize, HttpServletRequest req) {
|
||||
Result<IPage<OssFile>> result = new Result<>();
|
||||
QueryWrapper<OssFile> queryWrapper = QueryGenerator.initQueryWrapper(file, req.getParameterMap());
|
||||
Page<OssFile> page = new Page<>(pageNo, pageSize);
|
||||
IPage<OssFile> pageList = ossFileService.page(page, queryWrapper);
|
||||
result.setSuccess(true);
|
||||
result.setResult(pageList);
|
||||
return result;
|
||||
}
|
||||
|
||||
@ResponseBody
|
||||
@PostMapping("/upload")
|
||||
//@RequiresRoles("admin")
|
||||
@RequiresPermissions("system:ossFile:upload")
|
||||
public Result upload(@RequestParam("file") MultipartFile multipartFile) {
|
||||
Result result = new Result();
|
||||
try {
|
||||
ossFileService.upload(multipartFile);
|
||||
result.success("上传成功!");
|
||||
}
|
||||
catch (Exception ex) {
|
||||
log.info(ex.getMessage(), ex);
|
||||
result.error500("上传失败");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@ResponseBody
|
||||
@DeleteMapping("/delete")
|
||||
public Result delete(@RequestParam(name = "id") String id) {
|
||||
Result result = new Result();
|
||||
OssFile file = ossFileService.getById(id);
|
||||
if (file == null) {
|
||||
result.error500("未找到对应实体");
|
||||
}else {
|
||||
boolean ok = ossFileService.delete(file);
|
||||
result.success("删除成功!");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过id查询.
|
||||
*/
|
||||
@ResponseBody
|
||||
@GetMapping("/queryById")
|
||||
public Result<OssFile> queryById(@RequestParam(name = "id") String id) {
|
||||
Result<OssFile> result = new Result<>();
|
||||
OssFile file = ossFileService.getById(id);
|
||||
if (file == null) {
|
||||
result.error500("未找到对应实体");
|
||||
}
|
||||
else {
|
||||
result.setResult(file);
|
||||
result.setSuccess(true);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.jeecg.modules.oss.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.jeecg.common.system.base.entity.JeecgEntity;
|
||||
import org.jeecgframework.poi.excel.annotation.Excel;
|
||||
|
||||
/**
|
||||
* @Description: oss云存储实体类
|
||||
* @author: jeecg-boot
|
||||
*/
|
||||
@Data
|
||||
@TableName("oss_file")
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Accessors(chain = true)
|
||||
public class OssFile extends JeecgEntity {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Excel(name = "文件名称")
|
||||
private String fileName;
|
||||
|
||||
@Excel(name = "文件地址")
|
||||
private String url;
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user