!264 发布 2.6.1 版本 依赖升级漏洞修复

Merge pull request !264 from 疯狂的狮子Li/dev
This commit is contained in:
疯狂的狮子Li
2026-04-24 01:47:52 +00:00
committed by Gitee
42 changed files with 446 additions and 640 deletions

View File

@@ -2,7 +2,7 @@
<configuration default="false" name="ruoyi-auth" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="ruoyi/ruoyi-auth:2.6.0" />
<option name="imageTag" value="ruoyi/ruoyi-auth:2.6.1" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-auth/Dockerfile" />
</settings>

View File

@@ -2,7 +2,7 @@
<configuration default="false" name="ruoyi-gateway" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="ruoyi/ruoyi-gateway:2.6.0" />
<option name="imageTag" value="ruoyi/ruoyi-gateway:2.6.1" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-gateway/Dockerfile" />
</settings>

View File

@@ -2,7 +2,7 @@
<configuration default="false" name="ruoyi-gen" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="ruoyi/ruoyi-gen:2.6.0" />
<option name="imageTag" value="ruoyi/ruoyi-gen:2.6.1" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-modules/ruoyi-gen/Dockerfile" />
</settings>

View File

@@ -2,7 +2,7 @@
<configuration default="false" name="ruoyi-job" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="ruoyi/ruoyi-job:2.6.0" />
<option name="imageTag" value="ruoyi/ruoyi-job:2.6.1" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-modules/ruoyi-job/Dockerfile" />
</settings>

View File

@@ -2,7 +2,7 @@
<configuration default="false" name="ruoyi-monitor" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="ruoyi/ruoyi-monitor:2.6.0" />
<option name="imageTag" value="ruoyi/ruoyi-monitor:2.6.1" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-visual/ruoyi-monitor/Dockerfile" />
</settings>

View File

@@ -2,7 +2,7 @@
<configuration default="false" name="ruoyi-nacos" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="ruoyi/ruoyi-nacos:2.6.0" />
<option name="imageTag" value="ruoyi/ruoyi-nacos:2.6.1" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-visual/ruoyi-nacos/Dockerfile" />
</settings>

View File

@@ -2,7 +2,7 @@
<configuration default="false" name="ruoyi-resource" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="ruoyi/ruoyi-resource:2.6.0" />
<option name="imageTag" value="ruoyi/ruoyi-resource:2.6.1" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-modules/ruoyi-resource/Dockerfile" />
</settings>

View File

@@ -2,7 +2,7 @@
<configuration default="false" name="ruoyi-seata-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="ruoyi/ruoyi-seata-server:2.6.0" />
<option name="imageTag" value="ruoyi/ruoyi-seata-server:2.6.1" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-visual/ruoyi-seata-server/Dockerfile" />
</settings>

View File

@@ -2,7 +2,7 @@
<configuration default="false" name="ruoyi-snailjob-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="ruoyi/ruoyi-snailjob-server:2.6.0" />
<option name="imageTag" value="ruoyi/ruoyi-snailjob-server:2.6.1" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-visual/ruoyi-snailjob-server/Dockerfile" />
</settings>

View File

@@ -2,7 +2,7 @@
<configuration default="false" name="ruoyi-system" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="ruoyi/ruoyi-system:2.6.0" />
<option name="imageTag" value="ruoyi/ruoyi-system:2.6.1" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-modules/ruoyi-system/Dockerfile" />
</settings>

View File

@@ -2,7 +2,7 @@
<configuration default="false" name="ruoyi-workflow" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile">
<settings>
<option name="imageTag" value="ruoyi/ruoyi-workflow:2.6.0" />
<option name="imageTag" value="ruoyi/ruoyi-workflow:2.6.1" />
<option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-modules/ruoyi-workflow/Dockerfile" />
</settings>

View File

@@ -9,7 +9,7 @@
[![Star](https://gitcode.com/dromara/RuoYi-Cloud-Plus/star/badge.svg)](https://gitcode.com/dromara/RuoYi-Cloud-Plus)
[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://gitee.com/dromara/RuoYi-Cloud-Plus/blob/2.X/LICENSE)
<br>
[![RuoYi-Cloud-Plus](https://img.shields.io/badge/RuoYi_Cloud_Plus-2.6.0-success.svg)](https://gitee.com/dromara/RuoYi-Cloud-Plus)
[![RuoYi-Cloud-Plus](https://img.shields.io/badge/RuoYi_Cloud_Plus-2.6.1-success.svg)](https://gitee.com/dromara/RuoYi-Cloud-Plus)
[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.5-blue.svg)]()
[![JDK-17](https://img.shields.io/badge/JDK-17-green.svg)]()
[![JDK-21](https://img.shields.io/badge/JDK-21-green.svg)]()

35
pom.xml
View File

@@ -13,28 +13,28 @@
<description>Dromara RuoYi-Cloud-Plus微服务系统</description>
<properties>
<revision>2.6.0</revision>
<revision>2.6.1</revision>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>17</java.version>
<spring-boot.version>3.5.12</spring-boot.version>
<spring-cloud.version>2025.0.1</spring-cloud.version>
<spring-boot-admin.version>3.5.6</spring-boot-admin.version>
<spring-boot.version>3.5.14</spring-boot.version>
<spring-cloud.version>2025.0.2</spring-cloud.version>
<spring-boot-admin.version>3.5.8</spring-boot-admin.version>
<mybatis.version>3.5.19</mybatis.version>
<mybatis-plus.version>3.5.16</mybatis-plus.version>
<p6spy.version>3.9.1</p6spy.version>
<dynamic-ds.version>4.3.1</dynamic-ds.version>
<velocity.version>2.3</velocity.version>
<swagger.core.version>2.2.41</swagger.core.version>
<springdoc.version>2.8.15</springdoc.version>
<swagger.core.version>2.2.47</swagger.core.version>
<springdoc.version>2.8.17</springdoc.version>
<therapi-javadoc.version>0.15.0</therapi-javadoc.version>
<fastexcel.version>1.3.0</fastexcel.version>
<hutool.version>5.8.43</hutool.version>
<redisson.version>3.52.0</redisson.version>
<lock4j.version>2.2.7</lock4j.version>
<snailjob.version>1.9.0</snailjob.version>
<satoken.version>1.44.0</satoken.version>
<lombok.version>1.18.42</lombok.version>
<snailjob.version>1.10.0</snailjob.version>
<satoken.version>1.45.0</satoken.version>
<lombok.version>1.18.44</lombok.version>
<logstash.version>7.4</logstash.version>
<easy-es.version>3.0.1</easy-es.version>
<elasticsearch-client.version>7.17.28</elasticsearch-client.version>
@@ -44,19 +44,19 @@
<mapstruct-plus.lombok.version>0.2.0</mapstruct-plus.lombok.version>
<justauth.version>1.16.7</justauth.version>
<!-- 离线IP地址定位库 -->
<ip2region.version>3.3.4</ip2region.version>
<ip2region.version>3.3.7</ip2region.version>
<!-- 临时修复 fastjson 漏洞 -->
<fastjson.version>1.2.83</fastjson.version>
<!-- OSS 配置 -->
<aws.sdk.version>2.28.22</aws.sdk.version>
<!-- SMS 配置 -->
<sms4j.version>3.3.4</sms4j.version>
<sms4j.version>3.3.5</sms4j.version>
<!-- 面向运行时的D-ORM依赖 -->
<anyline.version>8.7.3-20251210</anyline.version>
<anyline.version>8.7.3-20260319</anyline.version>
<!-- 工作流配置 -->
<warm-flow.version>1.8.4</warm-flow.version>
<warm-flow.version>1.8.5</warm-flow.version>
<!-- mq配置 -->
<rocketmq.version>2.3.4</rocketmq.version>
<rocketmq.version>2.3.5</rocketmq.version>
<!-- 插件版本 -->
<maven-compiler-plugin.version>3.14.0</maven-compiler-plugin.version>
@@ -350,13 +350,6 @@
<version>${fastjson.version}</version>
</dependency>
<!-- dubbo 专用 -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>5.1.0</version>
</dependency>
<dependency>
<groupId>io.github.linpeilie</groupId>
<artifactId>mapstruct-plus-spring-boot-starter</artifactId>

View File

@@ -15,7 +15,7 @@
</description>
<properties>
<revision>2.6.0</revision>
<revision>2.6.1</revision>
</properties>
<dependencyManagement>

View File

@@ -14,6 +14,7 @@ import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.utils.MessageUtils;
import org.dromara.common.core.utils.ServletUtils;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.ip.AddressUtils;
import org.dromara.common.log.event.LogininforEvent;
import org.dromara.common.redis.utils.RedisUtils;
@@ -77,7 +78,7 @@ public class UserActionListener implements SaTokenListener {
SpringUtils.context().publishEvent(logininforEvent);
// 更新登录信息
remoteUserService.recordLoginInfo((Long) loginParameter.getExtra(LoginHelper.USER_KEY), ip);
log.info("user doLogin, useId:{}, token:{}", loginId, tokenValue);
log.info("user doLogin, useId:{}, token:***{}", loginId, StringUtils.right(tokenValue, 8));
}
/**
@@ -89,7 +90,7 @@ public class UserActionListener implements SaTokenListener {
TenantHelper.dynamic(tenantId, () -> {
RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
});
log.info("user doLogout, useId:{}, token:{}", loginId, tokenValue);
log.info("user doLogout, useId:{}, token:***{}", loginId, StringUtils.right(tokenValue, 8));
}
/**
@@ -101,7 +102,7 @@ public class UserActionListener implements SaTokenListener {
TenantHelper.dynamic(tenantId, () -> {
RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
});
log.info("user doLogoutByLoginId, useId:{}, token:{}", loginId, tokenValue);
log.info("user doLogoutByLoginId, useId:{}, token:***{}", loginId, StringUtils.right(tokenValue, 8));
}
/**
@@ -113,7 +114,7 @@ public class UserActionListener implements SaTokenListener {
TenantHelper.dynamic(tenantId, () -> {
RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
});
log.info("user doReplaced, useId:{}, token:{}", loginId, tokenValue);
log.info("user doReplaced, useId:{}, token:***{}", loginId, StringUtils.right(tokenValue, 8));
}
/**

View File

@@ -14,7 +14,7 @@
</description>
<properties>
<revision>2.6.0</revision>
<revision>2.6.1</revision>
<spring-cloud-alibaba.version>2025.0.0.0</spring-cloud-alibaba.version>
<seata.version>2.5.0</seata.version>
<nacos.client.version>2.5.1</nacos.client.version>

View File

@@ -14,7 +14,7 @@
</description>
<properties>
<revision>2.6.0</revision>
<revision>2.6.1</revision>
</properties>
<dependencyManagement>

View File

@@ -37,20 +37,10 @@
</dependency>
<dependency>
<groupId>org.apache.dubbo.extensions</groupId>
<artifactId>dubbo-metadata-report-redis</artifactId>
<exclusions>
<exclusion>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>5.2.0</version>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-redis</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>

View File

@@ -1,538 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.metadata.store.redis;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.config.configcenter.ConfigItem;
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.common.utils.*;
import org.apache.dubbo.metadata.MappingChangedEvent;
import org.apache.dubbo.metadata.MappingListener;
import org.apache.dubbo.metadata.MetadataInfo;
import org.apache.dubbo.metadata.ServiceNameMapping;
import org.apache.dubbo.metadata.report.identifier.*;
import org.apache.dubbo.metadata.report.support.AbstractMetadataReport;
import org.apache.dubbo.rpc.RpcException;
import redis.clients.jedis.*;
import redis.clients.jedis.params.SetParams;
import redis.clients.jedis.util.JedisClusterCRC16;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import static org.apache.dubbo.common.constants.CommonConstants.*;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.TRANSPORT_FAILED_RESPONSE;
import static org.apache.dubbo.metadata.MetadataConstants.META_DATA_STORE_TAG;
import static org.apache.dubbo.metadata.ServiceNameMapping.DEFAULT_MAPPING_GROUP;
import static org.apache.dubbo.metadata.ServiceNameMapping.getAppNames;
import static org.apache.dubbo.metadata.report.support.Constants.DEFAULT_METADATA_REPORT_CYCLE_REPORT;
/**
* RedisMetadataReport
*/
public class RedisMetadataReport extends AbstractMetadataReport {
private static final String REDIS_DATABASE_KEY = "database";
private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(RedisMetadataReport.class);
// protected , for test
protected JedisPool pool;
private Set<HostAndPort> jedisClusterNodes;
private int timeout;
private String username;
private String password;
private final String root;
private final ConcurrentHashMap<String, MappingDataListener> mappingDataListenerMap = new ConcurrentHashMap<>();
private SetParams jedisParams = SetParams.setParams();
public RedisMetadataReport(URL url) {
super(url);
timeout = url.getParameter(TIMEOUT_KEY, DEFAULT_TIMEOUT);
username = url.getUsername();
password = url.getPassword();
this.root = url.getGroup(DEFAULT_ROOT);
if (url.getParameter(CYCLE_REPORT_KEY, DEFAULT_METADATA_REPORT_CYCLE_REPORT)) {
// ttl default is twice the cycle-report time
jedisParams.px(ONE_DAY_IN_MILLISECONDS * 2);
}
if (url.getParameter(CLUSTER_KEY, false)) {
jedisClusterNodes = new HashSet<>();
List<URL> urls = url.getBackupUrls();
for (URL tmpUrl : urls) {
jedisClusterNodes.add(new HostAndPort(tmpUrl.getHost(), tmpUrl.getPort()));
}
} else {
int database = url.getParameter(REDIS_DATABASE_KEY, 0);
pool = new JedisPool(new JedisPoolConfig(), url.getHost(), url.getPort(), timeout, username, password, database);
}
}
@Override
protected void doStoreProviderMetadata(MetadataIdentifier providerMetadataIdentifier, String serviceDefinitions) {
this.storeMetadata(providerMetadataIdentifier, serviceDefinitions, true);
}
@Override
protected void doStoreConsumerMetadata(MetadataIdentifier consumerMetadataIdentifier, String value) {
this.storeMetadata(consumerMetadataIdentifier, value, true);
}
@Override
protected void doSaveMetadata(ServiceMetadataIdentifier serviceMetadataIdentifier, URL url) {
this.storeMetadata(serviceMetadataIdentifier, URL.encode(url.toFullString()), false);
}
@Override
protected void doRemoveMetadata(ServiceMetadataIdentifier serviceMetadataIdentifier) {
this.deleteMetadata(serviceMetadataIdentifier);
}
@Override
protected List<String> doGetExportedURLs(ServiceMetadataIdentifier metadataIdentifier) {
String content = getMetadata(metadataIdentifier);
if (StringUtils.isEmpty(content)) {
return Collections.emptyList();
}
return new ArrayList<>(Arrays.asList(URL.decode(content)));
}
@Override
protected void doSaveSubscriberData(SubscriberMetadataIdentifier subscriberMetadataIdentifier, String urlListStr) {
this.storeMetadata(subscriberMetadataIdentifier, urlListStr, false);
}
@Override
protected String doGetSubscribedURLs(SubscriberMetadataIdentifier subscriberMetadataIdentifier) {
return this.getMetadata(subscriberMetadataIdentifier);
}
@Override
public String getServiceDefinition(MetadataIdentifier metadataIdentifier) {
return this.getMetadata(metadataIdentifier);
}
private void storeMetadata(BaseMetadataIdentifier metadataIdentifier, String v, boolean ephemeral) {
if (pool != null) {
storeMetadataStandalone(metadataIdentifier, v, ephemeral);
} else {
storeMetadataInCluster(metadataIdentifier, v, ephemeral);
}
}
private void storeMetadataInCluster(BaseMetadataIdentifier metadataIdentifier, String v, boolean ephemeral) {
try (JedisCluster jedisCluster =
new JedisCluster(jedisClusterNodes, timeout, timeout, 2, password, new GenericObjectPoolConfig<>())) {
if (ephemeral) {
jedisCluster.set(metadataIdentifier.getIdentifierKey() + META_DATA_STORE_TAG, v, jedisParams);
} else {
jedisCluster.set(metadataIdentifier.getIdentifierKey() + META_DATA_STORE_TAG, v);
}
} catch (Throwable e) {
String msg =
"Failed to put " + metadataIdentifier + " to redis cluster " + v + ", cause: " + e.getMessage();
logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
throw new RpcException(msg, e);
}
}
private void storeMetadataStandalone(BaseMetadataIdentifier metadataIdentifier, String v, boolean ephemeral) {
try (Jedis jedis = pool.getResource()) {
if (ephemeral) {
jedis.set(metadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY), v, jedisParams);
} else {
jedis.set(metadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY), v);
}
} catch (Throwable e) {
String msg = "Failed to put " + metadataIdentifier + " to redis " + v + ", cause: " + e.getMessage();
logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
throw new RpcException(msg, e);
}
}
private void deleteMetadata(BaseMetadataIdentifier metadataIdentifier) {
if (pool != null) {
deleteMetadataStandalone(metadataIdentifier);
} else {
deleteMetadataInCluster(metadataIdentifier);
}
}
private void deleteMetadataInCluster(BaseMetadataIdentifier metadataIdentifier) {
try (JedisCluster jedisCluster =
new JedisCluster(jedisClusterNodes, timeout, timeout, 2, password, new GenericObjectPoolConfig<>())) {
jedisCluster.del(metadataIdentifier.getIdentifierKey() + META_DATA_STORE_TAG);
} catch (Throwable e) {
String msg = "Failed to delete " + metadataIdentifier + " from redis cluster , cause: " + e.getMessage();
logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
throw new RpcException(msg, e);
}
}
private void deleteMetadataStandalone(BaseMetadataIdentifier metadataIdentifier) {
try (Jedis jedis = pool.getResource()) {
jedis.del(metadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY));
} catch (Throwable e) {
String msg = "Failed to delete " + metadataIdentifier + " from redis , cause: " + e.getMessage();
logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
throw new RpcException(msg, e);
}
}
private String getMetadata(BaseMetadataIdentifier metadataIdentifier) {
if (pool != null) {
return getMetadataStandalone(metadataIdentifier);
} else {
return getMetadataInCluster(metadataIdentifier);
}
}
private String getMetadataInCluster(BaseMetadataIdentifier metadataIdentifier) {
try (JedisCluster jedisCluster =
new JedisCluster(jedisClusterNodes, timeout, timeout, 2, password, new GenericObjectPoolConfig<>())) {
return jedisCluster.get(metadataIdentifier.getIdentifierKey() + META_DATA_STORE_TAG);
} catch (Throwable e) {
String msg = "Failed to get " + metadataIdentifier + " from redis cluster , cause: " + e.getMessage();
logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
throw new RpcException(msg, e);
}
}
private String getMetadataStandalone(BaseMetadataIdentifier metadataIdentifier) {
try (Jedis jedis = pool.getResource()) {
return jedis.get(metadataIdentifier.getUniqueKey(KeyTypeEnum.UNIQUE_KEY));
} catch (Throwable e) {
String msg = "Failed to get " + metadataIdentifier + " from redis , cause: " + e.getMessage();
logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
throw new RpcException(msg, e);
}
}
/**
* Store class and application names using Redis hashes
* key: default 'dubbo:mapping'
* field: class (serviceInterface)
* value: application_names
* @param serviceInterface field(class)
* @param defaultMappingGroup {@link ServiceNameMapping#DEFAULT_MAPPING_GROUP}
* @param newConfigContent new application_names
* @param ticket previous application_names
* @return
*/
@Override
public boolean registerServiceAppMapping(
String serviceInterface, String defaultMappingGroup, String newConfigContent, Object ticket) {
try {
if (null != ticket && !(ticket instanceof String)) {
throw new IllegalArgumentException("redis publishConfigCas requires stat type ticket");
}
String pathKey = buildMappingKey(defaultMappingGroup);
return storeMapping(pathKey, serviceInterface, newConfigContent, (String) ticket);
} catch (Exception e) {
logger.warn(TRANSPORT_FAILED_RESPONSE, "", "", "redis publishConfigCas failed.", e);
return false;
}
}
private boolean storeMapping(String key, String field, String value, String ticket) {
if (pool != null) {
return storeMappingStandalone(key, field, value, ticket);
} else {
return storeMappingInCluster(key, field, value, ticket);
}
}
/**
* use 'watch' to implement cas.
* Find information about slot distribution by key.
*/
private boolean storeMappingInCluster(String key, String field, String value, String ticket) {
try (JedisCluster jedisCluster =
new JedisCluster(jedisClusterNodes, timeout, timeout, 2, password, new GenericObjectPoolConfig<>())) {
Jedis jedis = new Jedis(jedisCluster.getConnectionFromSlot(JedisClusterCRC16.getSlot(key)));
jedis.watch(key);
String oldValue = jedis.hget(key, field);
if (null == oldValue || null == ticket || oldValue.equals(ticket)) {
Transaction transaction = jedis.multi();
transaction.hset(key, field, value);
List<Object> result = transaction.exec();
if (null != result) {
jedisCluster.publish(buildPubSubKey(), field);
return true;
}
} else {
jedis.unwatch();
}
jedis.close();
} catch (Throwable e) {
String msg = "Failed to put " + key + ":" + field + " to redis " + value + ", cause: " + e.getMessage();
logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
throw new RpcException(msg, e);
}
return false;
}
/**
* use 'watch' to implement cas.
* Find information about slot distribution by key.
*/
private boolean storeMappingStandalone(String key, String field, String value, String ticket) {
try (Jedis jedis = pool.getResource()) {
jedis.watch(key);
String oldValue = jedis.hget(key, field);
if (null == oldValue || null == ticket || oldValue.equals(ticket)) {
Transaction transaction = jedis.multi();
transaction.hset(key, field, value);
List<Object> result = transaction.exec();
if (null != result) {
jedis.publish(buildPubSubKey(), field);
return true;
}
}
jedis.unwatch();
} catch (Throwable e) {
String msg = "Failed to put " + key + ":" + field + " to redis " + value + ", cause: " + e.getMessage();
logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
throw new RpcException(msg, e);
}
return false;
}
/**
* build mapping key
* @param defaultMappingGroup {@link ServiceNameMapping#DEFAULT_MAPPING_GROUP}
* @return
*/
private String buildMappingKey(String defaultMappingGroup) {
return this.root + GROUP_CHAR_SEPARATOR + defaultMappingGroup;
}
/**
* build pub/sub key
*/
private String buildPubSubKey() {
return buildMappingKey(DEFAULT_MAPPING_GROUP) + GROUP_CHAR_SEPARATOR + QUEUES_KEY;
}
/**
* get content and use content to complete cas
* @param serviceKey class
* @param group {@link ServiceNameMapping#DEFAULT_MAPPING_GROUP}
*/
@Override
public ConfigItem getConfigItem(String serviceKey, String group) {
String key = buildMappingKey(group);
String content = getMappingData(key, serviceKey);
return new ConfigItem(content, content);
}
/**
* get current application_names
*/
private String getMappingData(String key, String field) {
if (pool != null) {
return getMappingDataStandalone(key, field);
} else {
return getMappingDataInCluster(key, field);
}
}
private String getMappingDataInCluster(String key, String field) {
try (JedisCluster jedisCluster =
new JedisCluster(jedisClusterNodes, timeout, timeout, 2, password, new GenericObjectPoolConfig<>())) {
return jedisCluster.hget(key, field);
} catch (Throwable e) {
String msg = "Failed to get " + key + ":" + field + " from redis cluster , cause: " + e.getMessage();
logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
throw new RpcException(msg, e);
}
}
private String getMappingDataStandalone(String key, String field) {
try (Jedis jedis = pool.getResource()) {
return jedis.hget(key, field);
} catch (Throwable e) {
String msg = "Failed to get " + key + ":" + field + " from redis , cause: " + e.getMessage();
logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
throw new RpcException(msg, e);
}
}
/**
* remove listener. If have no listener,thread will dead
*/
@Override
public void removeServiceAppMappingListener(String serviceKey, MappingListener listener) {
MappingDataListener mappingDataListener = mappingDataListenerMap.get(buildPubSubKey());
if (null != mappingDataListener) {
NotifySub notifySub = mappingDataListener.getNotifySub();
notifySub.removeListener(serviceKey, listener);
if (notifySub.isEmpty()) {
mappingDataListener.shutdown();
}
}
}
/**
* Start a thread and subscribe to {@link this#buildPubSubKey()}.
* Notify {@link MappingListener} if there is a change in the 'application_names' message.
*/
@Override
public Set<String> getServiceAppMapping(String serviceKey, MappingListener listener, URL url) {
MappingDataListener mappingDataListener =
ConcurrentHashMapUtils.computeIfAbsent(mappingDataListenerMap, buildPubSubKey(), k -> {
MappingDataListener dataListener = new MappingDataListener(buildPubSubKey());
dataListener.start();
return dataListener;
});
mappingDataListener.getNotifySub().addListener(serviceKey, listener);
return this.getServiceAppMapping(serviceKey, url);
}
@Override
public Set<String> getServiceAppMapping(String serviceKey, URL url) {
String key = buildMappingKey(DEFAULT_MAPPING_GROUP);
return getAppNames(getMappingData(key, serviceKey));
}
@Override
public MetadataInfo getAppMetadata(SubscriberMetadataIdentifier identifier, Map<String, String> instanceMetadata) {
String content = this.getMetadata(identifier);
return JsonUtils.toJavaObject(content, MetadataInfo.class);
}
@Override
public void publishAppMetadata(SubscriberMetadataIdentifier identifier, MetadataInfo metadataInfo) {
this.storeMetadata(identifier, metadataInfo.getContent(), false);
}
@Override
public void unPublishAppMetadata(SubscriberMetadataIdentifier identifier, MetadataInfo metadataInfo) {
this.deleteMetadata(identifier);
}
// for test
public MappingDataListener getMappingDataListener() {
return mappingDataListenerMap.get(buildPubSubKey());
}
/**
* Listen for changes in the 'application_names' message and notify the listener.
*/
class NotifySub extends JedisPubSub {
private final Map<String, Set<MappingListener>> listeners = new ConcurrentHashMap<>();
public void addListener(String key, MappingListener listener) {
Set<MappingListener> listenerSet = listeners.computeIfAbsent(key, k -> new ConcurrentHashSet<>());
listenerSet.add(listener);
}
public void removeListener(String serviceKey, MappingListener listener) {
Set<MappingListener> listenerSet = this.listeners.get(serviceKey);
if (listenerSet != null) {
listenerSet.remove(listener);
if (listenerSet.isEmpty()) {
this.listeners.remove(serviceKey);
}
}
}
public Boolean isEmpty() {
return this.listeners.isEmpty();
}
@Override
public void onMessage(String key, String msg) {
logger.info("sub from redis:" + key + " message:" + msg);
String applicationNames = getMappingData(buildMappingKey(DEFAULT_MAPPING_GROUP), msg);
MappingChangedEvent mappingChangedEvent = new MappingChangedEvent(msg, getAppNames(applicationNames));
if (!CollectionUtils.isEmpty(listeners.get(msg))) {
for (MappingListener mappingListener : listeners.get(msg)) {
mappingListener.onEvent(mappingChangedEvent);
}
}
}
@Override
public void onPMessage(String pattern, String key, String msg) {
onMessage(key, msg);
}
@Override
public void onPSubscribe(String pattern, int subscribedChannels) {
super.onPSubscribe(pattern, subscribedChannels);
}
}
/**
* Subscribe application names change message.
*/
class MappingDataListener extends Thread {
private String path;
private final NotifySub notifySub = new NotifySub();
// for test
protected volatile boolean running = true;
public MappingDataListener(String path) {
this.path = path;
}
public NotifySub getNotifySub() {
return notifySub;
}
@Override
public void run() {
while (running) {
if (pool != null) {
try (Jedis jedis = pool.getResource()) {
jedis.subscribe(notifySub, path);
} catch (Throwable e) {
String msg = "Failed to subscribe " + path + ", cause: " + e.getMessage();
logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
throw new RpcException(msg, e);
}
} else {
try (JedisCluster jedisCluster = new JedisCluster(
jedisClusterNodes, timeout, timeout, 2, password, new GenericObjectPoolConfig<>())) {
jedisCluster.subscribe(notifySub, path);
} catch (Throwable e) {
String msg = "Failed to subscribe " + path + ", cause: " + e.getMessage();
logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
throw new RpcException(msg, e);
}
}
}
}
public void shutdown() {
try {
running = false;
notifySub.unsubscribe(path);
} catch (Throwable e) {
String msg = "Failed to unsubscribe " + path + ", cause: " + e.getMessage();
logger.error(TRANSPORT_FAILED_RESPONSE, "", "", msg, e);
}
}
}
}

View File

@@ -0,0 +1,301 @@
package org.apache.dubbo.metadata.store.redis;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.config.configcenter.ConfigItem;
import org.apache.dubbo.common.utils.CollectionUtils;
import org.apache.dubbo.common.utils.ConcurrentHashSet;
import org.apache.dubbo.metadata.MappingChangedEvent;
import org.apache.dubbo.metadata.MappingListener;
import org.apache.dubbo.metadata.MetadataInfo;
import org.apache.dubbo.metadata.report.identifier.*;
import org.apache.dubbo.metadata.report.support.AbstractMetadataReport;
import org.apache.dubbo.rpc.RpcException;
import org.dromara.common.core.utils.SpringUtils;
import org.redisson.api.RScript;
import org.redisson.api.RTopic;
import org.redisson.api.RedissonClient;
import org.redisson.client.codec.StringCodec;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import static org.apache.dubbo.common.constants.CommonConstants.*;
import static org.apache.dubbo.metadata.MetadataConstants.META_DATA_STORE_TAG;
import static org.apache.dubbo.metadata.ServiceNameMapping.DEFAULT_MAPPING_GROUP;
import static org.apache.dubbo.metadata.ServiceNameMapping.getAppNames;
import static org.apache.dubbo.metadata.report.support.Constants.DEFAULT_METADATA_REPORT_CYCLE_REPORT;
/**
* 使用 Redisson 重新实现元数据中心
*/
@Slf4j
public class RedissonMetadataReport extends AbstractMetadataReport {
// Lua script for atomic CAS on a hash field:
// If the current value equals ticket (or field is absent, or ticket is empty), update and return 1; else return 0.
private static final String CAS_LUA ="""
local old = redis.call('HGET', KEYS[1], ARGV[1])
if old == false or ARGV[3] == '' or old == ARGV[3] then
redis.call('HSET', KEYS[1], ARGV[1], ARGV[2])
return 1
end
return 0""";
private final String root;
private final long ttlMs;
// Lazily initialized — Dubbo SPI creates this class before Spring is fully ready
private volatile RedissonClient redissonClient;
// topic key → RTopic (keeps the subscription alive)
private final ConcurrentHashMap<String, RTopic> topicMap = new ConcurrentHashMap<>();
// serviceKey → listeners (for dispatching mapping change events)
private final ConcurrentHashMap<String, Set<MappingListener>> listenerMap = new ConcurrentHashMap<>();
public RedissonMetadataReport(URL url) {
super(url);
this.root = url.getGroup(DEFAULT_ROOT);
this.ttlMs = url.getParameter(CYCLE_REPORT_KEY, DEFAULT_METADATA_REPORT_CYCLE_REPORT)
? ONE_DAY_IN_MILLISECONDS * 2L
: 0L;
}
// -------------------------------------------------------------------------
// Lazy RedissonClient accessor
// -------------------------------------------------------------------------
private RedissonClient getRedisson() {
if (redissonClient == null) {
synchronized (this) {
if (redissonClient == null) {
redissonClient = SpringUtils.getBean(RedissonClient.class);
}
}
}
return redissonClient;
}
// -------------------------------------------------------------------------
// AbstractMetadataReport — provider / consumer metadata
// -------------------------------------------------------------------------
@Override
protected void doStoreProviderMetadata(MetadataIdentifier id, String serviceDefinitions) {
storeMetadata(id.getUniqueKey(KeyTypeEnum.UNIQUE_KEY), serviceDefinitions, true);
}
@Override
protected void doStoreConsumerMetadata(MetadataIdentifier id, String value) {
storeMetadata(id.getUniqueKey(KeyTypeEnum.UNIQUE_KEY), value, true);
}
@Override
protected void doSaveMetadata(ServiceMetadataIdentifier id, URL url) {
storeMetadata(id.getIdentifierKey() + META_DATA_STORE_TAG, URL.encode(url.toFullString()), false);
}
@Override
protected void doRemoveMetadata(ServiceMetadataIdentifier id) {
deleteMetadata(id.getIdentifierKey() + META_DATA_STORE_TAG);
}
@Override
protected List<String> doGetExportedURLs(ServiceMetadataIdentifier id) {
String content = getMetadata(id.getIdentifierKey() + META_DATA_STORE_TAG);
if (content == null || content.isEmpty()) {
return Collections.emptyList();
}
return Collections.singletonList(URL.decode(content));
}
@Override
protected void doSaveSubscriberData(SubscriberMetadataIdentifier id, String urlListStr) {
storeMetadata(id.getUniqueKey(KeyTypeEnum.UNIQUE_KEY), urlListStr, false);
}
@Override
protected String doGetSubscribedURLs(SubscriberMetadataIdentifier id) {
return getMetadata(id.getUniqueKey(KeyTypeEnum.UNIQUE_KEY));
}
@Override
public String getServiceDefinition(MetadataIdentifier id) {
return getMetadata(id.getUniqueKey(KeyTypeEnum.UNIQUE_KEY));
}
// -------------------------------------------------------------------------
// App-level metadata (Dubbo 3.x application-level service discovery)
// -------------------------------------------------------------------------
@Override
public void publishAppMetadata(SubscriberMetadataIdentifier id, MetadataInfo metadataInfo) {
storeMetadata(id.getUniqueKey(KeyTypeEnum.UNIQUE_KEY), metadataInfo.getContent(), false);
}
@Override
public MetadataInfo getAppMetadata(SubscriberMetadataIdentifier id, Map<String, String> instanceMetadata) {
String content = getMetadata(id.getUniqueKey(KeyTypeEnum.UNIQUE_KEY));
return org.apache.dubbo.common.utils.JsonUtils.toJavaObject(content, MetadataInfo.class);
}
@Override
public void unPublishAppMetadata(SubscriberMetadataIdentifier id, MetadataInfo metadataInfo) {
deleteMetadata(id.getUniqueKey(KeyTypeEnum.UNIQUE_KEY));
}
// -------------------------------------------------------------------------
// Service-to-application mapping
// -------------------------------------------------------------------------
@Override
public boolean registerServiceAppMapping(
String serviceInterface, String defaultMappingGroup, String newConfigContent, Object ticket) {
try {
if (ticket != null && !(ticket instanceof String)) {
throw new IllegalArgumentException("Redis CAS requires a String ticket");
}
return storeMappingWithCas(
buildMappingKey(defaultMappingGroup),
serviceInterface,
newConfigContent,
(String) ticket);
} catch (Exception e) {
log.warn("registerServiceAppMapping failed.", e);
return false;
}
}
@Override
public ConfigItem getConfigItem(String serviceKey, String group) {
String key = buildMappingKey(group);
String content = getMappingField(key, serviceKey);
return new ConfigItem(content, content);
}
@Override
public Set<String> getServiceAppMapping(String serviceKey, MappingListener listener, URL url) {
String pubSubKey = buildPubSubKey();
// Register the RTopic listener once per pubSubKey
topicMap.computeIfAbsent(pubSubKey, k -> {
RTopic topic = getRedisson().getTopic(k, StringCodec.INSTANCE);
topic.addListener(String.class, (channel, msg) -> {
String applicationNames = getMappingField(buildMappingKey(DEFAULT_MAPPING_GROUP), msg);
MappingChangedEvent event = new MappingChangedEvent(msg, getAppNames(applicationNames));
Set<MappingListener> ls = listenerMap.get(msg);
if (!CollectionUtils.isEmpty(ls)) {
ls.forEach(l -> l.onEvent(event));
}
});
return topic;
});
listenerMap.computeIfAbsent(serviceKey, k -> new ConcurrentHashSet<>()).add(listener);
return getServiceAppMapping(serviceKey, url);
}
@Override
public Set<String> getServiceAppMapping(String serviceKey, URL url) {
return getAppNames(getMappingField(buildMappingKey(DEFAULT_MAPPING_GROUP), serviceKey));
}
@Override
public void removeServiceAppMappingListener(String serviceKey, MappingListener listener) {
Set<MappingListener> ls = listenerMap.get(serviceKey);
if (ls != null) {
ls.remove(listener);
if (ls.isEmpty()) {
listenerMap.remove(serviceKey);
// If no listeners remain for any key, remove the topic subscription
if (listenerMap.isEmpty()) {
RTopic topic = topicMap.remove(buildPubSubKey());
if (topic != null) {
topic.removeAllListeners();
}
}
}
}
}
// -------------------------------------------------------------------------
// Internal Redis helpers
// -------------------------------------------------------------------------
private void storeMetadata(String key, String value, boolean ephemeral) {
try {
if (ephemeral && ttlMs > 0) {
getRedisson().<String>getBucket(key, StringCodec.INSTANCE).set(value, Duration.ofMillis(ttlMs));
} else {
getRedisson().<String>getBucket(key, StringCodec.INSTANCE).set(value);
}
} catch (Exception e) {
String msg = "Failed to store metadata key=" + key + ", cause: " + e.getMessage();
log.error(msg, e);
throw new RpcException(msg, e);
}
}
private String getMetadata(String key) {
try {
return getRedisson().<String>getBucket(key, StringCodec.INSTANCE).get();
} catch (Exception e) {
String msg = "Failed to get metadata key=" + key + ", cause: " + e.getMessage();
log.error(msg, e);
throw new RpcException(msg, e);
}
}
private void deleteMetadata(String key) {
try {
getRedisson().getBucket(key, StringCodec.INSTANCE).delete();
} catch (Exception e) {
String msg = "Failed to delete metadata key=" + key + ", cause: " + e.getMessage();
log.error(msg, e);
throw new RpcException(msg, e);
}
}
private String getMappingField(String key, String field) {
try {
return getRedisson().<String, String>getMap(key, StringCodec.INSTANCE).get(field);
} catch (Exception e) {
String msg = "Failed to get mapping key=" + key + " field=" + field + ", cause: " + e.getMessage();
log.error(msg, e);
throw new RpcException(msg, e);
}
}
/**
* Atomic CAS on a hash field via Lua script.
* Updates field to newValue only when the current value equals ticket (or field is absent / ticket is null).
* On success, publishes a change notification to the pub/sub channel.
*/
private boolean storeMappingWithCas(String key, String field, String newValue, String ticket) {
try {
Long result = getRedisson().getScript(StringCodec.INSTANCE).eval(
RScript.Mode.READ_WRITE,
CAS_LUA,
RScript.ReturnType.INTEGER,
Collections.singletonList(key),
field, newValue, ticket == null ? "" : ticket
);
if (Long.valueOf(1L).equals(result)) {
getRedisson().getTopic(buildPubSubKey(), StringCodec.INSTANCE).publish(field);
return true;
}
return false;
} catch (Exception e) {
String msg = "Failed to store mapping key=" + key + " field=" + field + ", cause: " + e.getMessage();
log.error(msg, e);
throw new RpcException(msg, e);
}
}
private String buildMappingKey(String mappingGroup) {
return this.root + GROUP_CHAR_SEPARATOR + mappingGroup;
}
private String buildPubSubKey() {
return buildMappingKey(DEFAULT_MAPPING_GROUP) + GROUP_CHAR_SEPARATOR + QUEUES_KEY;
}
}

View File

@@ -0,0 +1,17 @@
package org.apache.dubbo.metadata.store.redis;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.metadata.report.MetadataReport;
import org.apache.dubbo.metadata.report.support.AbstractMetadataReportFactory;
/**
* RedisMetadataReportFactory.
*/
public class RedissonMetadataReportFactory extends AbstractMetadataReportFactory {
@Override
public MetadataReport createMetadataReport(URL url) {
return new RedissonMetadataReport(url);
}
}

View File

@@ -0,0 +1 @@
redis=org.apache.dubbo.metadata.store.redis.RedissonMetadataReportFactory

View File

@@ -19,6 +19,8 @@ dubbo:
password: ${spring.cloud.nacos.password}
parameters:
namespace: ${spring.profiles.active}
# 已经采用框架内 Redisson 实现元数据中心
# 以下配置不用管占位用 不然 dubbo 会直接用 nacos 当注册中心 直接配好框架自带的 Redisson 即可
metadata-report:
address: redis://${spring.data.redis.host:localhost}:${spring.data.redis.port:6379}
group: DUBBO_GROUP

View File

@@ -33,7 +33,7 @@ public class ExcelBigNumberConvert implements Converter<Long> {
@Override
public Long convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
return Convert.toLong(cellData.getData());
return Convert.toLong(cellData.getStringValue());
}
@Override

View File

@@ -130,8 +130,9 @@ public class ExcelDownHandler implements SheetWriteHandler {
}
if (ObjectUtil.isNotEmpty(options)) {
// 仅当下拉可选项不为空时执行
if (options.size() > 20) {
// 这里限制如果可选项大于20则使用额外表形式
int totalCharacter = options.stream().mapToInt(String::length).sum() + options.size();
if (options.size() > 20 || totalCharacter > 255) {
// 这里限制如果可选项大于20 或 总字符数超过255则使用额外表形式
dropDownWithSheet(helper, workbook, sheet, index, options);
} else {
// 否则使用固定值形式

View File

@@ -12,6 +12,7 @@ import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.constant.HttpStatus;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.MessageUtils;
@@ -76,16 +77,16 @@ public class RepeatSubmitAspect {
*/
@AfterReturning(pointcut = "@annotation(repeatSubmit)", returning = "jsonResult")
public void doAfterReturning(JoinPoint joinPoint, RepeatSubmit repeatSubmit, Object jsonResult) {
if (jsonResult instanceof R<?> r) {
try {
try {
if (jsonResult instanceof R<?> r) {
// 成功则不删除redis数据 保证在有效时间内无法重复提交
if (r.getCode() == R.SUCCESS) {
if (r.getCode() == HttpStatus.SUCCESS) {
return;
}
RedisUtils.deleteObject(KEY_CACHE.get());
} finally {
KEY_CACHE.remove();
}
} finally {
KEY_CACHE.remove();
}
}

View File

@@ -46,6 +46,24 @@ public class DataBaseHelper {
}
}
/**
* 获取指定数据源对应的数据库类型
*
* @param dsName 数据源名称
* @return 指定数据库对应的 DataBaseType 枚举,找不到时默认返回 MY_SQL
* @throws ServiceException 当获取数据库连接或元数据出现异常时抛出业务异常
*/
public static DataBaseType getDataBaseType(String dsName) {
DataSource dataSource = DS.getDataSource(dsName);
try (Connection conn = dataSource.getConnection()) {
DatabaseMetaData metaData = conn.getMetaData();
String databaseProductName = metaData.getDatabaseProductName();
return DataBaseType.find(databaseProductName);
} catch (SQLException e) {
throw new RuntimeException("获取数据库类型失败", e);
}
}
/**
* 根据当前数据库类型,生成兼容的 FIND_IN_SET 语句片段
* <p>

View File

@@ -65,9 +65,10 @@ public class RedisConfiguration {
// 缓存 Lua 脚本 减少网络传输(redisson 大部分的功能都是基于 Lua 脚本实现)
.setUseScriptCache(true)
.setCodec(codec);
if (SpringUtils.isVirtual()) {
config.setNettyExecutor(new VirtualThreadTaskExecutor("redisson-"));
}
// netty 对虚拟线程适配有问题 暂时禁止使用
//if (SpringUtils.isVirtual()) {
// config.setNettyExecutor(new VirtualThreadTaskExecutor("redisson-"));
//}
RedissonProperties.SingleServerConfig singleServerConfig = redissonProperties.getSingleServerConfig();
if (ObjectUtil.isNotNull(singleServerConfig)) {
// 使用单机模式

View File

@@ -46,6 +46,11 @@
<artifactId>caffeine</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -2,7 +2,6 @@ package org.dromara.common.web.handler;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpStatus;
import com.fasterxml.jackson.core.JsonParseException;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolation;
@@ -12,8 +11,10 @@ import org.dromara.common.core.domain.R;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.exception.SseException;
import org.dromara.common.core.exception.base.BaseException;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.springframework.boot.json.JsonParseException;
import org.springframework.context.MessageSourceResolvable;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.expression.ExpressionException;
@@ -80,7 +81,7 @@ public class GlobalExceptionHandler {
public R<Void> handleServletException(ServletException e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址'{}',发生未知异常.", requestURI, e);
return R.fail(e.getMessage());
return R.fail("发生未知异常,请联系管理员");
}
/**
@@ -119,7 +120,7 @@ public class GlobalExceptionHandler {
public R<Void> handleNoHandlerFoundException(NoHandlerFoundException e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址'{}'不存在.", requestURI);
return R.fail(HttpStatus.HTTP_NOT_FOUND, e.getMessage());
return R.fail(HttpStatus.HTTP_NOT_FOUND, "请求地址不存在");
}
/**
@@ -150,7 +151,7 @@ public class GlobalExceptionHandler {
public R<Void> handleRuntimeException(RuntimeException e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址'{}',发生未知异常.", requestURI, e);
return R.fail(e.getMessage());
return R.fail("发生未知异常,请联系管理员");
}
/**
@@ -160,7 +161,7 @@ public class GlobalExceptionHandler {
public R<Void> handleException(Exception e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址'{}',发生系统异常.", requestURI, e);
return R.fail(e.getMessage());
return R.fail("发生系统异常,请联系管理员");
}
/**

View File

@@ -331,7 +331,7 @@ public class GenTableServiceImpl implements IGenTableService {
VelocityContext context = VelocityUtils.prepareContext(table);
// 获取模板列表
List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory());
List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory(), table.getDataName());
for (String template : templates) {
// 渲染模板
StringWriter sw = new StringWriter();
@@ -374,7 +374,7 @@ public class GenTableServiceImpl implements IGenTableService {
VelocityContext context = VelocityUtils.prepareContext(table);
// 获取模板列表
List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory());
List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory(), table.getDataName());
for (String template : templates) {
if (!StringUtils.containsAny(template, "sql.vm", "api.ts.vm", "types.ts.vm", "index.vue.vm", "index-tree.vue.vm")) {
// 渲染模板
@@ -478,7 +478,7 @@ public class GenTableServiceImpl implements IGenTableService {
VelocityContext context = VelocityUtils.prepareContext(table);
// 获取模板列表
List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory());
List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory(), table.getDataName());
for (String template : templates) {
// 渲染模板
StringWriter sw = new StringWriter();

View File

@@ -109,7 +109,7 @@ public class VelocityUtils {
*
* @return 模板列表
*/
public static List<String> getTemplateList(String tplCategory) {
public static List<String> getTemplateList(String tplCategory, String dsName) {
List<String> templates = new ArrayList<>();
templates.add("vm/java/domain.java.vm");
templates.add("vm/java/vo.java.vm");
@@ -119,7 +119,7 @@ public class VelocityUtils {
templates.add("vm/java/serviceImpl.java.vm");
templates.add("vm/java/controller.java.vm");
templates.add("vm/xml/mapper.xml.vm");
DataBaseType dataBaseType = DataBaseHelper.getDataBaseType();
DataBaseType dataBaseType = DataBaseHelper.getDataBaseType(dsName);
if (dataBaseType.isOracle()) {
templates.add("vm/sql/oracle/sql.vm");
} else if (dataBaseType.isPostgreSql()) {

View File

@@ -104,6 +104,6 @@ public class SysDeptVo implements Serializable {
/**
* 子菜单
*/
private List<SysDept> children = new ArrayList<>();
private List<SysDeptVo> children = new ArrayList<>();
}

View File

@@ -404,6 +404,7 @@ public class RemoteUserServiceImpl implements RemoteUserService {
* @param userIds 用户 ID 列表
* @return Map其中 key 为用户 IDvalue 为对应的用户昵称
*/
@Override
public Map<Long, String> selectUserNicksByIds(List<Long> userIds) {
if (CollUtil.isEmpty(userIds)) {
return Collections.emptyMap();

View File

@@ -235,8 +235,9 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
if (BusinessStatusEnum.isDraftOrCancelOrBack(ins.getFlowStatus())) {
variables.put(SUBMIT, true);
}
Map<String, Object> insVariableMap = ins.getVariableMap();
// 设置弹窗处理人
Map<String, Object> assigneeMap = setPopAssigneeMap(completeTaskBo.getAssigneeMap(), ins.getVariableMap());
Map<String, Object> assigneeMap = setPopAssigneeMap(completeTaskBo.getAssigneeMap(), insVariableMap);
if (CollUtil.isNotEmpty(assigneeMap)) {
variables.putAll(assigneeMap);
}
@@ -252,7 +253,7 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
.flowStatus(BusinessStatusEnum.WAITING.getStatus())
.hisStatus(TaskStatusEnum.PASS.getStatus())
.hisTaskExt(completeTaskBo.getFileId());
Boolean autoPass = Convert.toBool(variables.getOrDefault(AUTO_PASS, false));
Boolean autoPass = Convert.toBool(insVariableMap.getOrDefault(AUTO_PASS, false));
skipTask(taskId, flowParams, flowTask.getInstanceId(), autoPass);
return true;
}

View File

@@ -88,6 +88,7 @@
and b.del_flag = '0'
and c.del_flag = '0'
and a.node_type in ('1','3','4')
and a.flow_status <![CDATA[ <> ]]> 'copy'
) t
${ew.getCustomSqlSegment}
</select>

View File

@@ -32,13 +32,6 @@ dubbo:
consumer:
# 超时时间
timeout: 3000
metadata-report:
# Redis集群开关
cluster: false
parameters:
# 集群地址 cluster 为 true 生效
# 集群把所有Redis集群节点写到这里就行了
backup: 127.0.0.1:6379,127.0.0.1:6381
# 自定义配置
custom:
# 全局请求log

View File

@@ -27,7 +27,7 @@ services:
network_mode: "host"
nacos:
image: ruoyi/ruoyi-nacos:2.6.0
image: ruoyi/ruoyi-nacos:2.6.1
container_name: nacos
ports:
- "8848:8848"
@@ -95,7 +95,7 @@ services:
network_mode: "host"
seata-server:
image: ruoyi/ruoyi-seata-server:2.6.0
image: ruoyi/ruoyi-seata-server:2.6.1
container_name: seata-server
ports:
- "7091:7091"
@@ -134,7 +134,7 @@ services:
network_mode: "host"
ruoyi-monitor:
image: ruoyi/ruoyi-monitor:2.6.0
image: ruoyi/ruoyi-monitor:2.6.1
container_name: ruoyi-monitor
environment:
# 时区上海
@@ -150,7 +150,7 @@ services:
network_mode: "host"
ruoyi-snailjob-server:
image: ruoyi/ruoyi-snailjob-server:2.6.0
image: ruoyi/ruoyi-snailjob-server:2.6.1
container_name: ruoyi-snailjob-server
environment:
# 时区上海
@@ -164,7 +164,7 @@ services:
network_mode: "host"
ruoyi-gateway:
image: ruoyi/ruoyi-gateway:2.6.0
image: ruoyi/ruoyi-gateway:2.6.1
container_name: ruoyi-gateway
environment:
# 时区上海
@@ -180,7 +180,7 @@ services:
network_mode: "host"
ruoyi-auth:
image: ruoyi/ruoyi-auth:2.6.0
image: ruoyi/ruoyi-auth:2.6.1
container_name: ruoyi-auth
environment:
# 时区上海
@@ -196,7 +196,7 @@ services:
network_mode: "host"
ruoyi-system:
image: ruoyi/ruoyi-system:2.6.0
image: ruoyi/ruoyi-system:2.6.1
container_name: ruoyi-system
environment:
# 时区上海
@@ -212,7 +212,7 @@ services:
network_mode: "host"
ruoyi-gen:
image: ruoyi/ruoyi-gen:2.6.0
image: ruoyi/ruoyi-gen:2.6.1
container_name: ruoyi-gen
environment:
# 时区上海
@@ -228,7 +228,7 @@ services:
network_mode: "host"
ruoyi-job:
image: ruoyi/ruoyi-job:2.6.0
image: ruoyi/ruoyi-job:2.6.1
container_name: ruoyi-job
environment:
# 时区上海
@@ -246,7 +246,7 @@ services:
network_mode: "host"
ruoyi-resource:
image: ruoyi/ruoyi-resource:2.6.0
image: ruoyi/ruoyi-resource:2.6.1
container_name: ruoyi-resource
environment:
# 时区上海
@@ -262,7 +262,7 @@ services:
network_mode: "host"
ruoyi-workflow:
image: ruoyi/ruoyi-workflow:2.6.0
image: ruoyi/ruoyi-workflow:2.6.1
container_name: ruoyi-workflow
environment:
# 时区上海

View File

@@ -484,6 +484,7 @@ CREATE TABLE sj_job
(
id number GENERATED ALWAYS AS IDENTITY,
namespace_id varchar2(64) DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' NULL,
biz_id varchar2(64) NOT NULL,
group_name varchar2(64) NULL,
job_name varchar2(64) NULL,
args_str clob DEFAULT NULL NULL,
@@ -519,9 +520,11 @@ ALTER TABLE sj_job
CREATE INDEX idx_sj_job_01 ON sj_job (namespace_id, group_name);
CREATE INDEX idx_sj_job_02 ON sj_job (job_status, bucket_index);
CREATE INDEX idx_sj_job_03 ON sj_job (create_dt);
CREATE UNIQUE INDEX uk_sj_job_01 ON sj_job (namespace_id, biz_id);
COMMENT ON COLUMN sj_job.id IS '主键';
COMMENT ON COLUMN sj_job.namespace_id IS '命名空间id';
COMMENT ON COLUMN sj_job.biz_id IS '业务ID';
COMMENT ON COLUMN sj_job.group_name IS '组名称';
COMMENT ON COLUMN sj_job.job_name IS '名称';
COMMENT ON COLUMN sj_job.args_str IS '执行方法参数';
@@ -551,7 +554,7 @@ COMMENT ON COLUMN sj_job.create_dt IS '创建时间';
COMMENT ON COLUMN sj_job.update_dt IS '修改时间';
COMMENT ON TABLE sj_job IS '任务信息';
INSERT INTO sj_job(namespace_id, group_name, job_name, args_str, args_type, next_trigger_at, job_status, task_type, route_key, executor_type, executor_info, trigger_type, trigger_interval, block_strategy,executor_timeout, max_retry_times, parallel_num, retry_interval, bucket_index, resident, notify_ids, owner_id, labels, description, ext_attrs, deleted, create_dt, update_dt) VALUES ('dev', 'ruoyi_group', 'demo-job', NULL, 1, 1710344035622, 1, 1, 4, 1, 'testJobExecutor', 2, '60', 1, 60, 3, 1, 1, 116, 0, '', 1, '','', '', 0, sysdate, sysdate);
INSERT INTO sj_job(namespace_id, biz_id, group_name, job_name, args_str, args_type, next_trigger_at, job_status, task_type, route_key, executor_type, executor_info, trigger_type, trigger_interval, block_strategy,executor_timeout, max_retry_times, parallel_num, retry_interval, bucket_index, resident, notify_ids, owner_id, labels, description, ext_attrs, deleted, create_dt, update_dt) VALUES ('dev', 'demo-job', 'ruoyi_group', 'demo-job', NULL, 1, 1710344035622, 1, 1, 4, 1, 'testJobExecutor', 2, '60', 1, 60, 3, 1, 1, 116, 0, '', 1, '','', '', 0, sysdate, sysdate);
-- sj_job_log_message
CREATE TABLE sj_job_log_message
@@ -738,7 +741,7 @@ CREATE TABLE sj_retry_summary
id number GENERATED ALWAYS AS IDENTITY,
namespace_id varchar2(64) DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' NULL,
group_name varchar2(64) DEFAULT '' NULL,
scene_name varchar2(64) DEFAULT '' NULL,
scene_name varchar2(50) DEFAULT '' NULL,
trigger_at date DEFAULT CURRENT_TIMESTAMP NOT NULL,
running_num number DEFAULT 0 NOT NULL,
finish_num number DEFAULT 0 NOT NULL,
@@ -774,6 +777,7 @@ CREATE TABLE sj_workflow
id number GENERATED ALWAYS AS IDENTITY,
workflow_name varchar2(64) NULL,
namespace_id varchar2(64) DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' NULL,
biz_id varchar2(64) NOT NULL,
group_name varchar2(64) NULL,
workflow_status smallint DEFAULT 1 NOT NULL,
trigger_type smallint NOT NULL,
@@ -799,10 +803,12 @@ ALTER TABLE sj_workflow
CREATE INDEX idx_sj_workflow_01 ON sj_workflow (create_dt);
CREATE INDEX idx_sj_workflow_02 ON sj_workflow (namespace_id, group_name);
CREATE UNIQUE INDEX uk_sj_workflow_01 ON sj_workflow (namespace_id, biz_id);
COMMENT ON COLUMN sj_workflow.id IS '主键';
COMMENT ON COLUMN sj_workflow.workflow_name IS '工作流名称';
COMMENT ON COLUMN sj_workflow.namespace_id IS '命名空间id';
COMMENT ON COLUMN sj_workflow.biz_id IS '业务ID';
COMMENT ON COLUMN sj_workflow.group_name IS '组名称';
COMMENT ON COLUMN sj_workflow.workflow_status IS '工作流状态 0、关闭、1、开启';
COMMENT ON COLUMN sj_workflow.trigger_type IS '触发类型 1.CRON 表达式 2. 固定时间';

View File

@@ -445,6 +445,7 @@ CREATE TABLE sj_job
(
id bigserial PRIMARY KEY,
namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
biz_id varchar(64) NOT NULL,
group_name varchar(64) NOT NULL,
job_name varchar(64) NOT NULL,
args_str text NULL DEFAULT NULL,
@@ -477,9 +478,11 @@ CREATE TABLE sj_job
CREATE INDEX idx_sj_job_01 ON sj_job (namespace_id, group_name);
CREATE INDEX idx_sj_job_02 ON sj_job (job_status, bucket_index);
CREATE INDEX idx_sj_job_03 ON sj_job (create_dt);
CREATE UNIQUE INDEX uk_sj_job_01 ON sj_job (namespace_id, biz_id);
COMMENT ON COLUMN sj_job.id IS '主键';
COMMENT ON COLUMN sj_job.namespace_id IS '命名空间id';
COMMENT ON COLUMN sj_job.biz_id IS '业务ID';
COMMENT ON COLUMN sj_job.group_name IS '组名称';
COMMENT ON COLUMN sj_job.job_name IS '名称';
COMMENT ON COLUMN sj_job.args_str IS '执行方法参数';
@@ -509,7 +512,7 @@ COMMENT ON COLUMN sj_job.create_dt IS '创建时间';
COMMENT ON COLUMN sj_job.update_dt IS '修改时间';
COMMENT ON TABLE sj_job IS '任务信息';
INSERT INTO sj_job VALUES (1, 'dev', 'ruoyi_group', 'demo-job', null, 1, 1710344035622, 1, 1, 4, 1, 'testJobExecutor', 2, '60', 1, 60, 3, 1, 1, 116, 0, '', 1, '', '', '', 0, now(), now());
INSERT INTO sj_job VALUES (1, 'dev', 'demo-job', 'ruoyi_group', 'demo-job', null, 1, 1710344035622, 1, 1, 4, 1, 'testJobExecutor', 2, '60', 1, 60, 3, 1, 1, 116, 0, '', 1, '', '', '', 0, now(), now());
-- sj_job_log_message
CREATE TABLE sj_job_log_message
@@ -684,7 +687,7 @@ CREATE TABLE sj_retry_summary
id bigserial PRIMARY KEY,
namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
group_name varchar(64) NOT NULL DEFAULT '',
scene_name varchar(64) NOT NULL DEFAULT '',
scene_name varchar(50) NOT NULL DEFAULT '',
trigger_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
running_num int NOT NULL DEFAULT 0,
finish_num int NOT NULL DEFAULT 0,
@@ -717,6 +720,7 @@ CREATE TABLE sj_workflow
id bigserial PRIMARY KEY,
workflow_name varchar(64) NOT NULL,
namespace_id varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a',
biz_id varchar(64) NOT NULL,
group_name varchar(64) NOT NULL,
workflow_status smallint NOT NULL DEFAULT 1,
trigger_type smallint NOT NULL,
@@ -739,10 +743,12 @@ CREATE TABLE sj_workflow
CREATE INDEX idx_sj_workflow_01 ON sj_workflow (create_dt);
CREATE INDEX idx_sj_workflow_02 ON sj_workflow (namespace_id, group_name);
CREATE UNIQUE INDEX uk_sj_workflow_01 ON sj_workflow (namespace_id, biz_id);
COMMENT ON COLUMN sj_workflow.id IS '主键';
COMMENT ON COLUMN sj_workflow.workflow_name IS '工作流名称';
COMMENT ON COLUMN sj_workflow.namespace_id IS '命名空间id';
COMMENT ON COLUMN sj_workflow.biz_id IS '业务ID';
COMMENT ON COLUMN sj_workflow.group_name IS '组名称';
COMMENT ON COLUMN sj_workflow.workflow_status IS '工作流状态 0、关闭、1、开启';
COMMENT ON COLUMN sj_workflow.trigger_type IS '触发类型 1.CRON 表达式 2. 固定时间';

View File

@@ -280,6 +280,7 @@ CREATE TABLE `sj_job`
(
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id',
`biz_id` varchar(64) NOT NULL COMMENT '业务ID',
`group_name` varchar(64) NOT NULL COMMENT '组名称',
`job_name` varchar(64) NOT NULL COMMENT '名称',
`args_str` text DEFAULT NULL COMMENT '执行方法参数',
@@ -310,12 +311,13 @@ CREATE TABLE `sj_job`
PRIMARY KEY (`id`),
KEY `idx_namespace_id_group_name` (`namespace_id`, `group_name`),
KEY `idx_job_status_bucket_index` (`job_status`, `bucket_index`),
KEY `idx_create_dt` (`create_dt`)
KEY `idx_create_dt` (`create_dt`),
UNIQUE KEY `uk_sj_job_01` (`namespace_id`, `biz_id`)
) ENGINE = InnoDB
AUTO_INCREMENT = 0
DEFAULT CHARSET = utf8mb4 COMMENT ='任务信息';
INSERT INTO `sj_job` VALUES (1, 'dev', 'ruoyi_group', 'demo-job', null, 1, 1710344035622, 1, 1, 4, 1, 'testJobExecutor', 2, '60', 1, 60, 3, 1, 1, 116, 0, '', 1, '','', '', 0 , now(), now());
INSERT INTO `sj_job` VALUES (1, 'dev', 'demo-job', 'ruoyi_group', 'demo-job', null, 1, 1710344035622, 1, 1, 4, 1, 'testJobExecutor', 2, '60', 1, 60, 3, 1, 1, 116, 0, '', 1, '','', '', 0 , now(), now());
CREATE TABLE `sj_job_log_message`
(
@@ -443,6 +445,7 @@ CREATE TABLE `sj_workflow`
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`workflow_name` varchar(64) NOT NULL COMMENT '工作流名称',
`namespace_id` varchar(64) NOT NULL DEFAULT '764d604ec6fc45f68cd92514c40e9e1a' COMMENT '命名空间id',
`biz_id` varchar(64) NOT NULL COMMENT '业务ID',
`group_name` varchar(64) NOT NULL COMMENT '组名称',
`workflow_status` tinyint(4) NOT NULL DEFAULT 1 COMMENT '工作流状态 0、关闭、1、开启',
`trigger_type` tinyint(4) NOT NULL COMMENT '触发类型 1.CRON 表达式 2. 固定时间',
@@ -463,7 +466,8 @@ CREATE TABLE `sj_workflow`
`update_dt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`id`),
KEY `idx_create_dt` (`create_dt`),
KEY `idx_namespace_id_group_name` (`namespace_id`, `group_name`)
KEY `idx_namespace_id_group_name` (`namespace_id`, `group_name`),
UNIQUE KEY `uk_sj_workflow_01` (`namespace_id`, `biz_id`)
) ENGINE = InnoDB
AUTO_INCREMENT = 0
DEFAULT CHARSET = utf8mb4 COMMENT ='工作流';