mirror of
https://github.com/continew-org/continew-starter.git
synced 2025-11-12 05:01:28 +08:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4f3ee18bef | |||
| 13788d6f57 | |||
| 635b664d5f | |||
| 3e4b6ab3a9 | |||
| 7bc25b2f8b | |||
| 51c47751f4 | |||
| 13b3f24845 | |||
| 6b90880c21 | |||
| 0ad7b18521 | |||
| 82574cd5ce | |||
| 491721e887 | |||
| de056aa0c4 | |||
|
|
a89765f49e | ||
| 3e9a15295a | |||
|
|
ce08f28a61 |
20
CHANGELOG.md
20
CHANGELOG.md
@@ -1,3 +1,23 @@
|
||||
## [v2.2.0](https://github.com/continew-org/continew-starter/compare/v2.1.1...v2.2.0) (2024-06-30)
|
||||
|
||||
### ✨ 新特性
|
||||
|
||||
- 新增国际化及全局异常码配置 (Gitee#25) ([ce08f28](https://github.com/continew-org/continew-starter/commit/ce08f28a618bbeb2c501defe71f9018972a4828b))
|
||||
- 【core】新增 JSR 303 校验方法 ([3e9a152](https://github.com/continew-org/continew-starter/commit/3e9a15295a79901cf1c5fa603d6a7407e7e2a2ec))
|
||||
- 【security/limiter】新增限流器 ([a89765f](https://github.com/continew-org/continew-starter/commit/a89765f49ef9d3b1ce4b3a420507b43792ed69a1)) ([51c4775](https://github.com/continew-org/continew-starter/commit/51c47751f4ef92bb111619ee9ceb7c3ce4e2dba4)) ([7bc25b2](https://github.com/continew-org/continew-starter/commit/7bc25b2f8bdb74ad295c54ab82cdae88f6264096)) ([13788d6](https://github.com/continew-org/continew-starter/commit/13788d6f5796e87900dd83ece955cd921ffc3946))
|
||||
- 【core】新增表达式 SPEL 解析工具类 ([13b3f24](https://github.com/continew-org/continew-starter/commit/13b3f2484555b69dd25b280806f98d98d53f75fe))
|
||||
|
||||
### 🐛 问题修复
|
||||
|
||||
- 【api-doc】修复接口文档配置错误 ([82574cd](https://github.com/continew-org/continew-starter/commit/82574cd5cee923d6dfe447414c0a2453defc8790))
|
||||
|
||||
### 💎 功能优化
|
||||
|
||||
- 【core】重构线程池自动配置 ([de056aa](https://github.com/continew-org/continew-starter/commit/de056aa0c42621f5d5cf7690f2a42f54ffa1cd7e)) ([0ad7b18](https://github.com/continew-org/continew-starter/commit/0ad7b185212da31d8b6afdab6dd9cd8f72f83acb))
|
||||
- 优化属性前缀命名 ([6b90880](https://github.com/continew-org/continew-starter/commit/6b90880c21d4fd7e603397692cf88a98f30194a0))
|
||||
- 【captcha/behavior】默认启用行为验证码自动配置 ([635b664](https://github.com/continew-org/continew-starter/commit/635b664d5f92e5d01cadef4c868753eb41279c7d))
|
||||
- 【messaging/mail】优化邮件配置服务命名 ([3e4b6ab](https://github.com/continew-org/continew-starter/commit/3e4b6ab3a9590639e1fa606b0d52b29e83ecb890))
|
||||
|
||||
## [v2.1.1](https://github.com/continew-org/continew-starter/compare/v2.1.0...v2.1.1) (2024-06-23)
|
||||
|
||||
### ✨ 新特性
|
||||
|
||||
@@ -157,10 +157,11 @@ continew-starter.web:
|
||||
### 安全模块
|
||||
|
||||
| 模块名称 | 模块说明 | 依赖版本 |
|
||||
| ---------------------------------- | ----------------- | -------- |
|
||||
|------------------------------------|-----------| -------- |
|
||||
| continew-starter-security-password | 密码编码器 | |
|
||||
| continew-starter-security-mask | JSON 脱敏 | |
|
||||
| continew-starter-security-crypto | 数据库字段加/解密 | |
|
||||
| continew-starter-security-limiter | 限流器 | |
|
||||
|
||||
### Web模块
|
||||
|
||||
|
||||
@@ -30,7 +30,6 @@ import org.slf4j.LoggerFactory;
|
||||
import org.springdoc.core.customizers.GlobalOpenApiCustomizer;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
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.PropertySource;
|
||||
@@ -39,7 +38,6 @@ import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
import top.continew.starter.core.autoconfigure.project.ProjectProperties;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
import top.continew.starter.core.util.GeneralPropertySourceFactory;
|
||||
|
||||
import java.util.List;
|
||||
@@ -54,7 +52,6 @@ import java.util.concurrent.TimeUnit;
|
||||
*/
|
||||
@EnableWebMvc
|
||||
@AutoConfiguration
|
||||
@ConditionalOnProperty(prefix = PropertiesConstants.SPRINGDOC_SWAGGER_UI, name = PropertiesConstants.ENABLED, matchIfMissing = true)
|
||||
@EnableConfigurationProperties(SpringDocExtensionProperties.class)
|
||||
@PropertySource(value = "classpath:default-api-doc.yml", factory = GeneralPropertySourceFactory.class)
|
||||
public class SpringDocAutoConfiguration implements WebMvcConfigurer {
|
||||
|
||||
@@ -19,7 +19,6 @@ package top.continew.starter.apidoc.autoconfigure;
|
||||
import io.swagger.v3.oas.models.Components;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.NestedConfigurationProperty;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
|
||||
/**
|
||||
* API 文档扩展配置属性
|
||||
@@ -27,7 +26,7 @@ import top.continew.starter.core.constant.PropertiesConstants;
|
||||
* @author Charles7c
|
||||
* @since 1.0.1
|
||||
*/
|
||||
@ConfigurationProperties(prefix = PropertiesConstants.SPRINGDOC)
|
||||
@ConfigurationProperties("springdoc")
|
||||
public class SpringDocExtensionProperties {
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
--- ### 接口文档配置
|
||||
springdoc:
|
||||
swagger-ui:
|
||||
enabled: true
|
||||
path: /swagger-ui.html
|
||||
tags-sorter: alpha
|
||||
operations-sorter: alpha
|
||||
|
||||
@@ -26,7 +26,7 @@ import top.continew.starter.auth.satoken.autoconfigure.dao.SaTokenDaoProperties;
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "sa-token.extension")
|
||||
@ConfigurationProperties("sa-token.extension")
|
||||
public class SaTokenExtensionProperties {
|
||||
|
||||
/**
|
||||
|
||||
@@ -28,7 +28,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "spring.data.redisson")
|
||||
@ConfigurationProperties("spring.data.redisson")
|
||||
public class RedissonProperties {
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package top.continew.starter.cache.redisson.util;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import org.redisson.api.*;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
@@ -213,6 +214,6 @@ public class RedisUtils {
|
||||
* @return 键
|
||||
*/
|
||||
public static String formatKey(String... subKeys) {
|
||||
return String.join(StringConstants.COLON, subKeys);
|
||||
return String.join(StringConstants.COLON, ArrayUtil.removeBlank(subKeys));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ import java.util.Properties;
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@EnableConfigurationProperties(BehaviorCaptchaProperties.class)
|
||||
@ConditionalOnProperty(prefix = PropertiesConstants.CAPTCHA_BEHAVIOR, name = PropertiesConstants.ENABLED, havingValue = "true")
|
||||
@ConditionalOnProperty(prefix = PropertiesConstants.CAPTCHA_BEHAVIOR, name = PropertiesConstants.ENABLED, matchIfMissing = true)
|
||||
public class BehaviorCaptchaAutoConfiguration {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(BehaviorCaptchaAutoConfiguration.class);
|
||||
|
||||
@@ -36,7 +36,7 @@ public class BehaviorCaptchaProperties {
|
||||
/**
|
||||
* 是否启用行为验证码
|
||||
*/
|
||||
private boolean enabled = false;
|
||||
private boolean enabled = true;
|
||||
|
||||
/**
|
||||
* 是否开启 AES 坐标加密(默认:true)
|
||||
|
||||
@@ -23,6 +23,13 @@
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Hibernate Validator -->
|
||||
<dependency>
|
||||
<groupId>org.hibernate.validator</groupId>
|
||||
<artifactId>hibernate-validator</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- 第三方封装 Ip2region(离线 IP 数据管理框架和定位库,支持亿级别的数据段,10 微秒级别的查询性能,提供了许多主流编程语言的 xdb 数据管理引擎的实现) -->
|
||||
<dependency>
|
||||
<groupId>net.dreamlu</groupId>
|
||||
|
||||
@@ -24,7 +24,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "project")
|
||||
@ConfigurationProperties("project")
|
||||
public class ProjectProperties {
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package top.continew.starter.core.autoconfigure.threadpool;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
|
||||
@@ -25,40 +26,39 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.scheduling.annotation.AsyncConfigurer;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
import top.continew.starter.core.exception.BaseException;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
/**
|
||||
* 异步任务自动配置
|
||||
*
|
||||
* @author Charles7c
|
||||
* @author Lion Li(<a href="https://gitee.com/dromara/RuoYi-Vue-Plus">RuoYi-Vue-Plus</a>)
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Lazy
|
||||
@AutoConfiguration
|
||||
@EnableAsync(proxyTargetClass = true)
|
||||
@ConditionalOnProperty(prefix = PropertiesConstants.THREAD_POOL, name = PropertiesConstants.ENABLED, havingValue = "true")
|
||||
@ConditionalOnProperty(prefix = "spring.task.execution.extension", name = PropertiesConstants.ENABLED, matchIfMissing = true)
|
||||
public class AsyncAutoConfiguration implements AsyncConfigurer {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(AsyncAutoConfiguration.class);
|
||||
|
||||
private final ScheduledExecutorService scheduledExecutorService;
|
||||
private final ThreadPoolTaskExecutor threadPoolTaskExecutor;
|
||||
|
||||
public AsyncAutoConfiguration(ScheduledExecutorService scheduledExecutorService) {
|
||||
this.scheduledExecutorService = scheduledExecutorService;
|
||||
public AsyncAutoConfiguration(ThreadPoolTaskExecutor threadPoolTaskExecutor) {
|
||||
this.threadPoolTaskExecutor = threadPoolTaskExecutor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步任务 @Async 执行时,使用 Java 内置线程池
|
||||
* 异步任务线程池配置
|
||||
*/
|
||||
@Override
|
||||
public Executor getAsyncExecutor() {
|
||||
log.debug("[ContiNew Starter] - Auto Configuration 'AsyncConfigurer' completed initialization.");
|
||||
return scheduledExecutorService;
|
||||
return threadPoolTaskExecutor;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -79,4 +79,9 @@ public class AsyncAutoConfiguration implements AsyncConfigurer {
|
||||
throw new BaseException(sb.toString());
|
||||
};
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
log.debug("[ContiNew Starter] - Auto Configuration 'AsyncConfigurer' completed initialization.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,148 +16,71 @@
|
||||
|
||||
package top.continew.starter.core.autoconfigure.threadpool;
|
||||
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.task.TaskExecutorCustomizer;
|
||||
import org.springframework.boot.task.TaskSchedulerCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
import top.continew.starter.core.util.ExceptionUtils;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.RunnableFuture;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 线程池自动配置
|
||||
*
|
||||
* @author Charles7c
|
||||
* @author Lion Li(<a href="https://gitee.com/dromara/RuoYi-Vue-Plus">RuoYi-Vue-Plus</a>)
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Lazy
|
||||
@AutoConfiguration
|
||||
@ConditionalOnProperty(prefix = PropertiesConstants.THREAD_POOL, name = PropertiesConstants.ENABLED, havingValue = "true")
|
||||
@EnableConfigurationProperties(ThreadPoolProperties.class)
|
||||
@EnableConfigurationProperties(ThreadPoolExtensionProperties.class)
|
||||
public class ThreadPoolAutoConfiguration {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ThreadPoolAutoConfiguration.class);
|
||||
|
||||
/**
|
||||
* 核心(最小)线程数 = CPU 核心数 + 1
|
||||
*/
|
||||
private final int corePoolSize = Runtime.getRuntime().availableProcessors() + 1;
|
||||
@Value("${spring.task.execution.pool.core-size:#{T(java.lang.Runtime).getRuntime().availableProcessors() + 1}}")
|
||||
private int corePoolSize;
|
||||
|
||||
@Value("${spring.task.execution.pool.max-size:#{T(java.lang.Runtime).getRuntime().availableProcessors() * 2}}")
|
||||
private int maxPoolSize;
|
||||
|
||||
/**
|
||||
* Spring 内置线程池:ThreadPoolTaskExecutor
|
||||
* 异步任务线程池配置
|
||||
*/
|
||||
@Bean
|
||||
public ThreadPoolTaskExecutor threadPoolTaskExecutor(ThreadPoolProperties properties) {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setThreadNamePrefix("thread-pool");
|
||||
@ConditionalOnProperty(prefix = "spring.task.execution.extension", name = PropertiesConstants.ENABLED, matchIfMissing = true)
|
||||
public TaskExecutorCustomizer taskExecutorCustomizer(ThreadPoolExtensionProperties properties) {
|
||||
return executor -> {
|
||||
// 核心(最小)线程数
|
||||
executor.setCorePoolSize(ObjectUtil.defaultIfNull(properties.getCorePoolSize(), corePoolSize));
|
||||
executor.setCorePoolSize(corePoolSize);
|
||||
// 最大线程数
|
||||
executor.setMaxPoolSize(ObjectUtil.defaultIfNull(properties.getMaxPoolSize(), corePoolSize * 2));
|
||||
// 队列容量
|
||||
executor.setQueueCapacity(properties.getQueueCapacity());
|
||||
// 活跃时间
|
||||
executor.setKeepAliveSeconds(properties.getKeepAliveSeconds());
|
||||
// 配置当池内线程数已达到上限的时候,该如何处理新任务:不在新线程中执行任务,而是由调用者所在的线程来执行
|
||||
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
||||
// 关闭线程池是否等待任务完成
|
||||
executor.setWaitForTasksToCompleteOnShutdown(properties.isWaitForTasksToCompleteOnShutdown());
|
||||
// 执行器在关闭时阻塞的最长毫秒数,以等待剩余任务完成执行
|
||||
executor.setAwaitTerminationMillis(properties.getAwaitTerminationMillis());
|
||||
log.debug("[ContiNew Starter] - Auto Configuration 'ThreadPoolTaskExecutor' completed initialization.");
|
||||
return executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Java 内置线程池:ScheduledExecutorService(适用于执行周期性或定时任务)
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public ScheduledExecutorService scheduledExecutorService(ThreadPoolProperties properties) {
|
||||
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(ObjectUtil.defaultIfNull(properties
|
||||
.getCorePoolSize(), corePoolSize), ThreadUtil
|
||||
.newNamedThreadFactory("schedule-pool-%d", true), new ThreadPoolExecutor.CallerRunsPolicy()) {
|
||||
@Override
|
||||
protected void afterExecute(Runnable runnable, Throwable throwable) {
|
||||
super.afterExecute(runnable, throwable);
|
||||
ExceptionUtils.printException(runnable, throwable);
|
||||
}
|
||||
executor.setMaxPoolSize(maxPoolSize);
|
||||
// 当线程池的任务缓存队列已满并且线程池中的线程数已达到 maxPoolSize 时采取的任务拒绝策略
|
||||
executor.setRejectedExecutionHandler(properties.getExecution()
|
||||
.getRejectedPolicy()
|
||||
.getRejectedExecutionHandler());
|
||||
log.debug("[ContiNew Starter] - Auto Configuration 'TaskExecutor' completed initialization.");
|
||||
};
|
||||
// 应用关闭时,关闭线程池
|
||||
SpringApplication.getShutdownHandlers().add(() -> this.shutdown(executor, properties));
|
||||
log.debug("[ContiNew Starter] - Auto Configuration 'ScheduledExecutorService' completed initialization.");
|
||||
return executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据相应的配置设置关闭 ExecutorService
|
||||
*
|
||||
* @see org.springframework.scheduling.concurrent.ExecutorConfigurationSupport#shutdown()
|
||||
* @since 2.0.0
|
||||
* 定时任务线程池配置
|
||||
*/
|
||||
public void shutdown(ExecutorService executor, ThreadPoolProperties properties) {
|
||||
log.debug("[ContiNew Starter] - Shutting down ScheduledExecutorService start.");
|
||||
if (executor != null) {
|
||||
if (properties.isWaitForTasksToCompleteOnShutdown()) {
|
||||
executor.shutdown();
|
||||
} else {
|
||||
for (Runnable remainingTask : executor.shutdownNow()) {
|
||||
cancelRemainingTask(remainingTask);
|
||||
}
|
||||
}
|
||||
awaitTerminationIfNecessary(executor, properties);
|
||||
log.debug("[ContiNew Starter] - Shutting down ScheduledExecutorService complete.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel the given remaining task which never commenced execution,
|
||||
* as returned from {@link ExecutorService#shutdownNow()}.
|
||||
*
|
||||
* @param task the task to cancel (typically a {@link RunnableFuture})
|
||||
* @see RunnableFuture#cancel(boolean)
|
||||
* @since 2.0.0
|
||||
*/
|
||||
protected void cancelRemainingTask(Runnable task) {
|
||||
if (task instanceof Future<?> future) {
|
||||
future.cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the executor to terminate, according to the value of the properties
|
||||
*
|
||||
* @since 2.0.0
|
||||
*/
|
||||
private void awaitTerminationIfNecessary(ExecutorService executor, ThreadPoolProperties properties) {
|
||||
if (properties.getAwaitTerminationMillis() > 0) {
|
||||
try {
|
||||
if (!executor.awaitTermination(properties.getAwaitTerminationMillis(), TimeUnit.MILLISECONDS)) {
|
||||
if (log.isWarnEnabled()) {
|
||||
log.warn("[ContiNew Starter] - Timed out while waiting for executor 'ScheduledExecutorService' to terminate.");
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException ex) {
|
||||
if (log.isWarnEnabled()) {
|
||||
log.warn("[ContiNew Starter] - Interrupted while waiting for executor 'ScheduledExecutorService' to terminate");
|
||||
}
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
@EnableScheduling
|
||||
@ConditionalOnProperty(prefix = "spring.task.scheduling.extension", name = PropertiesConstants.ENABLED, matchIfMissing = true)
|
||||
public static class TaskSchedulerConfiguration {
|
||||
@Bean
|
||||
public TaskSchedulerCustomizer taskSchedulerCustomizer(ThreadPoolExtensionProperties properties) {
|
||||
return executor -> {
|
||||
executor.setRejectedExecutionHandler(properties.getScheduling()
|
||||
.getRejectedPolicy()
|
||||
.getRejectedExecutionHandler());
|
||||
log.debug("[ContiNew Starter] - Auto Configuration 'TaskScheduler' completed initialization.");
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* 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 top.continew.starter.core.autoconfigure.threadpool;
|
||||
|
||||
import java.util.concurrent.RejectedExecutionHandler;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
|
||||
/**
|
||||
* 线程池拒绝策略
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.2.0
|
||||
*/
|
||||
public enum ThreadPoolExecutorRejectedPolicy {
|
||||
|
||||
/**
|
||||
* ThreadPoolTaskExecutor 默认的拒绝策略,不执行新任务,直接抛出 RejectedExecutionException 异常
|
||||
*/
|
||||
ABORT {
|
||||
@Override
|
||||
public RejectedExecutionHandler getRejectedExecutionHandler() {
|
||||
return new ThreadPoolExecutor.AbortPolicy();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 提交的任务在执行被拒绝时,会由提交任务的线程去执行
|
||||
*/
|
||||
CALLER_RUNS {
|
||||
@Override
|
||||
public RejectedExecutionHandler getRejectedExecutionHandler() {
|
||||
return new ThreadPoolExecutor.CallerRunsPolicy();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 不执行新任务,也不抛出异常
|
||||
*/
|
||||
DISCARD {
|
||||
@Override
|
||||
public RejectedExecutionHandler getRejectedExecutionHandler() {
|
||||
return new ThreadPoolExecutor.DiscardPolicy();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 拒绝新任务,但是会抛弃队列中最老的任务,然后尝试再次提交新任务
|
||||
*/
|
||||
DISCARD_OLDEST {
|
||||
@Override
|
||||
public RejectedExecutionHandler getRejectedExecutionHandler() {
|
||||
return new ThreadPoolExecutor.DiscardOldestPolicy();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取拒绝处理器
|
||||
*
|
||||
* @return 拒绝处理器
|
||||
*/
|
||||
public abstract RejectedExecutionHandler getRejectedExecutionHandler();
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* 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 top.continew.starter.core.autoconfigure.threadpool;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* 线程池扩展配置属性
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@ConfigurationProperties("spring.task")
|
||||
public class ThreadPoolExtensionProperties {
|
||||
|
||||
/**
|
||||
* 异步任务扩展配置属性
|
||||
*/
|
||||
private ExecutorExtensionProperties execution = new ExecutorExtensionProperties();
|
||||
|
||||
/**
|
||||
* 调度任务扩展配置属性
|
||||
*/
|
||||
private SchedulerExtensionProperties scheduling = new SchedulerExtensionProperties();
|
||||
|
||||
/**
|
||||
* 异步任务扩展配置属性
|
||||
*/
|
||||
public static class ExecutorExtensionProperties {
|
||||
/**
|
||||
* 拒绝策略
|
||||
*/
|
||||
private ThreadPoolExecutorRejectedPolicy rejectedPolicy = ThreadPoolExecutorRejectedPolicy.CALLER_RUNS;
|
||||
|
||||
public ThreadPoolExecutorRejectedPolicy getRejectedPolicy() {
|
||||
return rejectedPolicy;
|
||||
}
|
||||
|
||||
public void setRejectedPolicy(ThreadPoolExecutorRejectedPolicy rejectedPolicy) {
|
||||
this.rejectedPolicy = rejectedPolicy;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调度任务扩展配置属性
|
||||
*/
|
||||
public static class SchedulerExtensionProperties {
|
||||
/**
|
||||
* 拒绝策略
|
||||
*/
|
||||
private ThreadPoolExecutorRejectedPolicy rejectedPolicy = ThreadPoolExecutorRejectedPolicy.CALLER_RUNS;
|
||||
|
||||
public ThreadPoolExecutorRejectedPolicy getRejectedPolicy() {
|
||||
return rejectedPolicy;
|
||||
}
|
||||
|
||||
public void setRejectedPolicy(ThreadPoolExecutorRejectedPolicy rejectedPolicy) {
|
||||
this.rejectedPolicy = rejectedPolicy;
|
||||
}
|
||||
}
|
||||
|
||||
public ExecutorExtensionProperties getExecution() {
|
||||
return execution;
|
||||
}
|
||||
|
||||
public void setExecution(ExecutorExtensionProperties execution) {
|
||||
this.execution = execution;
|
||||
}
|
||||
|
||||
public SchedulerExtensionProperties getScheduling() {
|
||||
return scheduling;
|
||||
}
|
||||
|
||||
public void setScheduling(SchedulerExtensionProperties scheduling) {
|
||||
this.scheduling = scheduling;
|
||||
}
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* 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 top.continew.starter.core.autoconfigure.threadpool;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
|
||||
/**
|
||||
* 线程池配置属性
|
||||
*
|
||||
* @author Charles7c
|
||||
* @author Lion Li(<a href="https://gitee.com/dromara/RuoYi-Vue-Plus">RuoYi-Vue-Plus</a>)
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@ConfigurationProperties(PropertiesConstants.THREAD_POOL)
|
||||
public class ThreadPoolProperties {
|
||||
|
||||
/**
|
||||
* 是否启用线程池配置
|
||||
*/
|
||||
private boolean enabled = false;
|
||||
|
||||
/**
|
||||
* 核心/最小线程数(默认:CPU 核心数 + 1)
|
||||
*/
|
||||
private Integer corePoolSize;
|
||||
|
||||
/**
|
||||
* 最大线程数(默认:(CPU 核心数 + 1) * 2)
|
||||
*/
|
||||
private Integer maxPoolSize;
|
||||
|
||||
/**
|
||||
* 队列容量
|
||||
*/
|
||||
private int queueCapacity = 128;
|
||||
|
||||
/**
|
||||
* 活跃时间(单位:秒)
|
||||
*/
|
||||
private int keepAliveSeconds = 300;
|
||||
|
||||
/**
|
||||
* 关闭线程池是否等待任务完成
|
||||
*/
|
||||
private boolean waitForTasksToCompleteOnShutdown = false;
|
||||
|
||||
/**
|
||||
* 执行器在关闭时阻塞的最长毫秒数,以等待剩余任务完成执行
|
||||
*/
|
||||
private long awaitTerminationMillis = 0;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public Integer getCorePoolSize() {
|
||||
return corePoolSize;
|
||||
}
|
||||
|
||||
public void setCorePoolSize(Integer corePoolSize) {
|
||||
this.corePoolSize = corePoolSize;
|
||||
}
|
||||
|
||||
public Integer getMaxPoolSize() {
|
||||
return maxPoolSize;
|
||||
}
|
||||
|
||||
public void setMaxPoolSize(Integer maxPoolSize) {
|
||||
this.maxPoolSize = maxPoolSize;
|
||||
}
|
||||
|
||||
public int getQueueCapacity() {
|
||||
return queueCapacity;
|
||||
}
|
||||
|
||||
public void setQueueCapacity(int queueCapacity) {
|
||||
this.queueCapacity = queueCapacity;
|
||||
}
|
||||
|
||||
public int getKeepAliveSeconds() {
|
||||
return keepAliveSeconds;
|
||||
}
|
||||
|
||||
public void setKeepAliveSeconds(int keepAliveSeconds) {
|
||||
this.keepAliveSeconds = keepAliveSeconds;
|
||||
}
|
||||
|
||||
public boolean isWaitForTasksToCompleteOnShutdown() {
|
||||
return waitForTasksToCompleteOnShutdown;
|
||||
}
|
||||
|
||||
public void setWaitForTasksToCompleteOnShutdown(boolean waitForTasksToCompleteOnShutdown) {
|
||||
this.waitForTasksToCompleteOnShutdown = waitForTasksToCompleteOnShutdown;
|
||||
}
|
||||
|
||||
public long getAwaitTerminationMillis() {
|
||||
return awaitTerminationMillis;
|
||||
}
|
||||
|
||||
public void setAwaitTerminationMillis(long awaitTerminationMillis) {
|
||||
this.awaitTerminationMillis = awaitTerminationMillis;
|
||||
}
|
||||
}
|
||||
@@ -34,21 +34,6 @@ public class PropertiesConstants {
|
||||
*/
|
||||
public static final String ENABLED = "enabled";
|
||||
|
||||
/**
|
||||
* 线程池配置
|
||||
*/
|
||||
public static final String THREAD_POOL = CONTINEW_STARTER + StringConstants.DOT + "thread-pool";
|
||||
|
||||
/**
|
||||
* Spring Doc 配置
|
||||
*/
|
||||
public static final String SPRINGDOC = "springdoc";
|
||||
|
||||
/**
|
||||
* Spring Doc Swagger UI 配置
|
||||
*/
|
||||
public static final String SPRINGDOC_SWAGGER_UI = SPRINGDOC + StringConstants.DOT + "swagger-ui";
|
||||
|
||||
/**
|
||||
* 安全配置
|
||||
*/
|
||||
@@ -57,12 +42,17 @@ public class PropertiesConstants {
|
||||
/**
|
||||
* 密码编解码配置
|
||||
*/
|
||||
public static final String PASSWORD = SECURITY + StringConstants.DOT + "password";
|
||||
public static final String SECURITY_PASSWORD = SECURITY + StringConstants.DOT + "password";
|
||||
|
||||
/**
|
||||
* 加/解密配置
|
||||
*/
|
||||
public static final String CRYPTO = SECURITY + StringConstants.DOT + "crypto";
|
||||
public static final String SECURITY_CRYPTO = SECURITY + StringConstants.DOT + "crypto";
|
||||
|
||||
/**
|
||||
* 限流器配置
|
||||
*/
|
||||
public static final String SECURITY_LIMITER = SECURITY + StringConstants.DOT + "limiter";
|
||||
|
||||
/**
|
||||
* Web 配置
|
||||
@@ -72,17 +62,22 @@ public class PropertiesConstants {
|
||||
/**
|
||||
* 跨域配置
|
||||
*/
|
||||
public static final String CORS = WEB + StringConstants.DOT + "cors";
|
||||
public static final String WEB_CORS = WEB + StringConstants.DOT + "cors";
|
||||
|
||||
/**
|
||||
* 链路配置
|
||||
*/
|
||||
public static final String TRACE = WEB + StringConstants.DOT + "trace";
|
||||
public static final String WEB_TRACE = WEB + StringConstants.DOT + "trace";
|
||||
|
||||
/**
|
||||
* XSS 配置
|
||||
*/
|
||||
public static final String XSS = WEB + StringConstants.DOT + "xss";
|
||||
public static final String WEB_XSS = WEB + StringConstants.DOT + "xss";
|
||||
|
||||
/**
|
||||
* 国际化配置
|
||||
*/
|
||||
public static final String WEB_I18N = WEB + StringConstants.DOT + "i18n";
|
||||
|
||||
/**
|
||||
* 日志配置
|
||||
|
||||
@@ -262,6 +262,16 @@ public class StringConstants {
|
||||
*/
|
||||
public static final String CHINESE_COMMA = ",";
|
||||
|
||||
/**
|
||||
* 圆括号(左) {@code "("}
|
||||
*/
|
||||
public static final String ROUND_BRACKET_START = "(";
|
||||
|
||||
/**
|
||||
* 圆括号(右) {@code ")"}
|
||||
*/
|
||||
public static final String ROUND_BRACKET_END = ")";
|
||||
|
||||
/**
|
||||
* 路径模式
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* 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 top.continew.starter.core.exception;
|
||||
|
||||
/**
|
||||
* 统一错误码异常
|
||||
*
|
||||
* @author Jasmine
|
||||
* @since 2.2.0
|
||||
*/
|
||||
public class GlobalException extends Exception {
|
||||
|
||||
private ResultInfoInterface resultInfo;
|
||||
|
||||
public GlobalException() {
|
||||
}
|
||||
|
||||
public GlobalException(ResultInfoInterface resultInfo) {
|
||||
this.resultInfo = resultInfo;
|
||||
}
|
||||
|
||||
public ResultInfoInterface getResultInfo() {
|
||||
return this.resultInfo;
|
||||
}
|
||||
|
||||
public void setResultInfo(ResultInfoInterface resultInfo) {
|
||||
this.resultInfo = resultInfo;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* 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 top.continew.starter.core.exception;
|
||||
|
||||
/**
|
||||
* 接口返回码 所有业务异常都要继承该接口
|
||||
*
|
||||
* @author Jasmine
|
||||
* @since 2.2.0
|
||||
*/
|
||||
public enum GlobalResultInfoEnum implements ResultInfoInterface {
|
||||
|
||||
/**
|
||||
* 操作成功
|
||||
*/
|
||||
SUCCESS(200, "操作成功"),
|
||||
|
||||
/**
|
||||
* 操作失败
|
||||
*/
|
||||
FAILED(500, "操作失败");
|
||||
|
||||
private int code;
|
||||
private String messageKey;
|
||||
private String defaultMessage;
|
||||
|
||||
GlobalResultInfoEnum(int code, String defaultMessage) {
|
||||
this.code = code;
|
||||
this.defaultMessage = defaultMessage;
|
||||
}
|
||||
|
||||
GlobalResultInfoEnum(int code, String messageKey, String defaultMessage) {
|
||||
this.code = code;
|
||||
this.messageKey = messageKey;
|
||||
this.defaultMessage = defaultMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCode() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessageKey() {
|
||||
return this.messageKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDefaultMessage() {
|
||||
return this.defaultMessage;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* 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 top.continew.starter.core.exception;
|
||||
|
||||
/**
|
||||
* 接口返回码与消息 所有业务异常都要继承该接口
|
||||
*
|
||||
* @author Jasmine
|
||||
* @since 2.2.0
|
||||
*/
|
||||
public interface ResultInfoInterface {
|
||||
|
||||
/**
|
||||
* 获取编码
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
int getCode();
|
||||
|
||||
/**
|
||||
* 国际化消息key
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
default String getMessageKey() {
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认消息 若从国际化文件里没有获取到值,就取默认值
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String getDefaultMessage();
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* 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 top.continew.starter.core.util.expression;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* 表达式解析器
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.2.0
|
||||
*/
|
||||
public class ExpressionEvaluator implements Function<Object, Object> {
|
||||
|
||||
private final Function<Object, Object> evaluator;
|
||||
|
||||
public ExpressionEvaluator(String script, Method defineMethod) {
|
||||
this.evaluator = new SpelEvaluator(script, defineMethod);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object apply(Object rootObject) {
|
||||
return evaluator.apply(rootObject);
|
||||
}
|
||||
|
||||
Function<Object, Object> getEvaluator() {
|
||||
return evaluator;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* 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 top.continew.starter.core.util.expression;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* 表达式上下文
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.2.0
|
||||
*/
|
||||
public class ExpressionInvokeContext {
|
||||
|
||||
/**
|
||||
* 目标方法
|
||||
*/
|
||||
private Method method;
|
||||
|
||||
/**
|
||||
* 方法参数
|
||||
*/
|
||||
private Object[] args;
|
||||
|
||||
/**
|
||||
* 目标对象
|
||||
*/
|
||||
private Object target;
|
||||
|
||||
public ExpressionInvokeContext(Method method, Object[] args, Object target) {
|
||||
this.method = method;
|
||||
this.args = args;
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public Method getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
public void setMethod(Method method) {
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
public void setArgs(Object[] args) {
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
public Object[] getArgs() {
|
||||
return args;
|
||||
}
|
||||
|
||||
public Object getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
public void setTarget(Object target) {
|
||||
this.target = target;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* 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 top.continew.starter.core.util.expression;
|
||||
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* 表达式解析工具类
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.2.0
|
||||
*/
|
||||
public class ExpressionUtils {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ExpressionUtils.class);
|
||||
|
||||
private ExpressionUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析
|
||||
*
|
||||
* @param script 表达式
|
||||
* @param target 目标对象
|
||||
* @param method 目标方法
|
||||
* @param args 方法参数
|
||||
* @return 解析结果
|
||||
*/
|
||||
public static Object eval(String script, Object target, Method method, Object... args) {
|
||||
try {
|
||||
if (CharSequenceUtil.isBlank(script)) {
|
||||
return null;
|
||||
}
|
||||
ExpressionEvaluator expressionEvaluator = new ExpressionEvaluator(script, method);
|
||||
ExpressionInvokeContext invokeContext = new ExpressionInvokeContext(method, args, target);
|
||||
return expressionEvaluator.apply(invokeContext);
|
||||
} catch (Exception e) {
|
||||
log.error("Error occurs when eval script \"{}\" in {} : {}", script, method, e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* 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 top.continew.starter.core.util.expression;
|
||||
|
||||
import org.springframework.core.DefaultParameterNameDiscoverer;
|
||||
import org.springframework.core.ParameterNameDiscoverer;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Spring EL 表达式解析器
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.2.0
|
||||
*/
|
||||
public class SpelEvaluator implements Function<Object, Object> {
|
||||
|
||||
private static final ExpressionParser PARSER;
|
||||
private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER;
|
||||
|
||||
static {
|
||||
PARSER = new SpelExpressionParser();
|
||||
PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer();
|
||||
}
|
||||
|
||||
private final Expression expression;
|
||||
private String[] parameterNames;
|
||||
|
||||
public SpelEvaluator(String script, Method defineMethod) {
|
||||
expression = PARSER.parseExpression(script);
|
||||
if (defineMethod.getParameterCount() > 0) {
|
||||
parameterNames = PARAMETER_NAME_DISCOVERER.getParameterNames(defineMethod);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object apply(Object rootObject) {
|
||||
EvaluationContext context = new StandardEvaluationContext(rootObject);
|
||||
ExpressionInvokeContext invokeContext = (ExpressionInvokeContext)rootObject;
|
||||
if (parameterNames != null) {
|
||||
for (int i = 0; i < parameterNames.length; i++) {
|
||||
context.setVariable(parameterNames[i], invokeContext.getArgs()[i]);
|
||||
}
|
||||
}
|
||||
return expression.getValue(context);
|
||||
}
|
||||
}
|
||||
@@ -16,9 +16,14 @@
|
||||
|
||||
package top.continew.starter.core.util.validate;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import jakarta.validation.ConstraintViolation;
|
||||
import top.continew.starter.core.exception.BadRequestException;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
/**
|
||||
@@ -173,4 +178,21 @@ public class ValidationUtils extends Validator {
|
||||
public static void throwIf(BooleanSupplier conditionSupplier, String template, Object... params) {
|
||||
throwIf(conditionSupplier, CharSequenceUtil.format(template, params), EXCEPTION_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* JSR 303 校验
|
||||
*
|
||||
* @param obj 被校验对象
|
||||
* @param groups 分组
|
||||
*/
|
||||
public static void validate(Object obj, Class<?>... groups) {
|
||||
jakarta.validation.Validator validator = SpringUtil.getBean(jakarta.validation.Validator.class);
|
||||
Set<ConstraintViolation<Object>> violations = validator.validate(obj, groups);
|
||||
if (CollUtil.isEmpty(violations)) {
|
||||
return;
|
||||
}
|
||||
throw ReflectUtil.newInstance(EXCEPTION_TYPE, violations.stream()
|
||||
.map(ConstraintViolation::getMessage)
|
||||
.findFirst());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "mybatis-flex.extension")
|
||||
@ConfigurationProperties("mybatis-flex.extension")
|
||||
public class MyBatisFlexExtensionProperties {
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,7 +27,7 @@ import top.continew.starter.data.mybatis.plus.autoconfigure.idgenerator.MyBatisP
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "mybatis-plus.extension")
|
||||
@ConfigurationProperties("mybatis-plus.extension")
|
||||
public class MyBatisPlusExtensionProperties {
|
||||
|
||||
/**
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
|
||||
<properties>
|
||||
<!-- 项目版本号 -->
|
||||
<revision>2.1.1</revision>
|
||||
<revision>2.2.0</revision>
|
||||
<sa-token.version>1.38.0</sa-token.version>
|
||||
<just-auth.version>1.16.6</just-auth.version>
|
||||
<mybatis-plus.version>3.5.5</mybatis-plus.version>
|
||||
@@ -473,6 +473,13 @@
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 安全模块 - 限流 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-security-limiter</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- API 文档模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
|
||||
@@ -20,12 +20,12 @@ import org.springframework.mail.javamail.JavaMailSenderImpl;
|
||||
import top.continew.starter.core.util.validate.ValidationUtils;
|
||||
|
||||
/**
|
||||
* 邮件配置服务
|
||||
* 邮件配置
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.1.0
|
||||
*/
|
||||
public interface MailConfigService {
|
||||
public interface MailConfigurer {
|
||||
|
||||
/**
|
||||
* 获取邮件配置
|
||||
@@ -27,7 +27,7 @@ import org.springframework.mail.javamail.JavaMailSenderImpl;
|
||||
import org.springframework.mail.javamail.MimeMessageHelper;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.core.util.ExceptionUtils;
|
||||
import top.continew.starter.messaging.mail.core.MailConfigService;
|
||||
import top.continew.starter.messaging.mail.core.MailConfigurer;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -217,10 +217,9 @@ public class MailUtils {
|
||||
*/
|
||||
public static JavaMailSenderImpl getMailSender() {
|
||||
JavaMailSenderImpl mailSender = SpringUtil.getBean(JavaMailSenderImpl.class);
|
||||
MailConfigService mailConfigService = ExceptionUtils.exToNull(() -> SpringUtil
|
||||
.getBean(MailConfigService.class));
|
||||
if (mailConfigService != null && mailConfigService.getMailConfig() != null) {
|
||||
mailConfigService.apply(mailConfigService.getMailConfig(), mailSender);
|
||||
MailConfigurer mailConfigurer = ExceptionUtils.exToNull(() -> SpringUtil.getBean(MailConfigurer.class));
|
||||
if (mailConfigurer != null && mailConfigurer.getMailConfig() != null) {
|
||||
mailConfigurer.apply(mailConfigurer.getMailConfig(), mailSender);
|
||||
}
|
||||
return mailSender;
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ import top.continew.starter.security.crypto.core.MyBatisEncryptInterceptor;
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@EnableConfigurationProperties(CryptoProperties.class)
|
||||
@ConditionalOnProperty(prefix = PropertiesConstants.CRYPTO, name = PropertiesConstants.ENABLED, matchIfMissing = true)
|
||||
@ConditionalOnProperty(prefix = PropertiesConstants.SECURITY_CRYPTO, name = PropertiesConstants.ENABLED, matchIfMissing = true)
|
||||
public class CryptoAutoConfiguration {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(CryptoAutoConfiguration.class);
|
||||
|
||||
@@ -25,7 +25,7 @@ import top.continew.starter.core.constant.PropertiesConstants;
|
||||
* @author Charles7c
|
||||
* @since 1.4.0
|
||||
*/
|
||||
@ConfigurationProperties(PropertiesConstants.CRYPTO)
|
||||
@ConfigurationProperties(PropertiesConstants.SECURITY_CRYPTO)
|
||||
public class CryptoProperties {
|
||||
|
||||
/**
|
||||
|
||||
@@ -65,7 +65,7 @@ public abstract class AbstractMyBatisInterceptor implements Interceptor {
|
||||
*/
|
||||
public Map<String, FieldEncrypt> getEncryptParams(String mappedStatementId, Integer parameterIndex) {
|
||||
return ENCRYPT_PARAM_CACHE
|
||||
.computeIfAbsent(mappedStatementId, m -> getEncryptParamsNoCached(mappedStatementId, parameterIndex));
|
||||
.computeIfAbsent(mappedStatementId, key -> getEncryptParamsNoCached(mappedStatementId, parameterIndex));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-security</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>continew-starter-security-limiter</artifactId>
|
||||
<description>ContiNew Starter 安全模块 - 限流</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Web 模块 -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 缓存模块 - Redisson -->
|
||||
<dependency>
|
||||
<groupId>top.continew</groupId>
|
||||
<artifactId>continew-starter-cache-redisson</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* 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 top.continew.starter.security.limiter.annotation;
|
||||
|
||||
import org.redisson.api.RateIntervalUnit;
|
||||
import top.continew.starter.security.limiter.enums.LimitType;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 限流注解
|
||||
*
|
||||
* @author KAI
|
||||
* @since 2.2.0
|
||||
*/
|
||||
@Documented
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface RateLimiter {
|
||||
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
LimitType type() default LimitType.DEFAULT;
|
||||
|
||||
/**
|
||||
* 名称
|
||||
*/
|
||||
String name() default "";
|
||||
|
||||
/**
|
||||
* 键(支持 Spring EL 表达式)
|
||||
*/
|
||||
String key() default "";
|
||||
|
||||
/**
|
||||
* 速率(指定时间间隔产生的令牌数)
|
||||
*/
|
||||
int rate() default Integer.MAX_VALUE;
|
||||
|
||||
/**
|
||||
* 速率间隔(时间间隔)
|
||||
*/
|
||||
int interval() default 0;
|
||||
|
||||
/**
|
||||
* 速率间隔时间单位(默认:毫秒)
|
||||
*/
|
||||
RateIntervalUnit unit() default RateIntervalUnit.MILLISECONDS;
|
||||
|
||||
/**
|
||||
* 提示信息
|
||||
*/
|
||||
String message() default "操作过于频繁,请稍后再试";
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* 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 top.continew.starter.security.limiter.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 限流组注解
|
||||
*
|
||||
* @author KAI
|
||||
* @since 2.2.0
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface RateLimiters {
|
||||
|
||||
/**
|
||||
* 限流组
|
||||
*/
|
||||
RateLimiter[] value();
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* 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 top.continew.starter.security.limiter.autoconfigure;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
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.ComponentScan;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
import top.continew.starter.security.limiter.core.DefaultRateLimiterNameGenerator;
|
||||
import top.continew.starter.security.limiter.core.RateLimiterNameGenerator;
|
||||
|
||||
/**
|
||||
* 限流器自动配置
|
||||
*
|
||||
* @author KAI
|
||||
* @author Charles7c
|
||||
* @since 2.2.0
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@EnableConfigurationProperties(RateLimiterProperties.class)
|
||||
@ComponentScan({"top.continew.starter.security.limiter.core"})
|
||||
@ConditionalOnProperty(prefix = PropertiesConstants.SECURITY_LIMITER, name = PropertiesConstants.ENABLED, matchIfMissing = true)
|
||||
public class RateLimiterAutoConfiguration {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(RateLimiterAutoConfiguration.class);
|
||||
|
||||
/**
|
||||
* 限流器名称生成器
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public RateLimiterNameGenerator nameGenerator() {
|
||||
return new DefaultRateLimiterNameGenerator();
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
log.debug("[ContiNew Starter] - Auto Configuration 'Security-RateLimiter' completed initialization.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* 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 top.continew.starter.security.limiter.autoconfigure;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
|
||||
/**
|
||||
* 限流器配置属性
|
||||
*
|
||||
* @author KAI
|
||||
* @since 2.2.0
|
||||
*/
|
||||
@ConfigurationProperties(PropertiesConstants.SECURITY_LIMITER)
|
||||
public class RateLimiterProperties {
|
||||
|
||||
/**
|
||||
* Key 前缀
|
||||
*/
|
||||
private String keyPrefix = "RateLimiter";
|
||||
|
||||
public String getKeyPrefix() {
|
||||
return keyPrefix;
|
||||
}
|
||||
|
||||
public void setKeyPrefix(String keyPrefix) {
|
||||
this.keyPrefix = keyPrefix;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* 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 top.continew.starter.security.limiter.core;
|
||||
|
||||
import cn.hutool.core.util.ClassUtil;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 默认限流器名称生成器
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.2.0
|
||||
*/
|
||||
public class DefaultRateLimiterNameGenerator implements RateLimiterNameGenerator {
|
||||
|
||||
protected final ConcurrentHashMap<Method, String> nameMap = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public String generate(Object target, Method method, Object... args) {
|
||||
return nameMap.computeIfAbsent(method, key -> {
|
||||
final StringBuilder nameSb = new StringBuilder();
|
||||
String className = method.getDeclaringClass().getName();
|
||||
nameSb.append(ClassUtil.getShortClassName(className));
|
||||
nameSb.append(StringConstants.DOT);
|
||||
nameSb.append(method.getName());
|
||||
nameSb.append(StringConstants.ROUND_BRACKET_START);
|
||||
for (Class<?> clazz : method.getParameterTypes()) {
|
||||
this.getDescriptor(nameSb, clazz);
|
||||
}
|
||||
nameSb.append(StringConstants.ROUND_BRACKET_END);
|
||||
return nameSb.toString();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定数据类型的描述
|
||||
*
|
||||
* @param sb 名称字符串缓存
|
||||
* @param typeClass 数据类型
|
||||
*/
|
||||
private void getDescriptor(final StringBuilder sb, final Class<?> typeClass) {
|
||||
Class<?> clazz = typeClass;
|
||||
while (true) {
|
||||
if (clazz.isPrimitive()) {
|
||||
sb.append(this.getPrimitiveChar(clazz));
|
||||
return;
|
||||
} else if (clazz.isArray()) {
|
||||
sb.append(StringConstants.BRACKET_START);
|
||||
clazz = clazz.getComponentType();
|
||||
} else {
|
||||
sb.append('L');
|
||||
String name = clazz.getName();
|
||||
name = ClassUtil.getShortClassName(name);
|
||||
sb.append(name);
|
||||
sb.append(StringConstants.SEMICOLON);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据基本数据获取类型字符
|
||||
*
|
||||
* @param clazz 数据类型
|
||||
* @return 类型字符
|
||||
*/
|
||||
private char getPrimitiveChar(Class<?> clazz) {
|
||||
char c;
|
||||
if (clazz == Integer.TYPE) {
|
||||
c = 'I';
|
||||
} else if (clazz == Void.TYPE) {
|
||||
c = 'V';
|
||||
} else if (clazz == Boolean.TYPE) {
|
||||
c = 'Z';
|
||||
} else if (clazz == Byte.TYPE) {
|
||||
c = 'B';
|
||||
} else if (clazz == Character.TYPE) {
|
||||
c = 'C';
|
||||
} else if (clazz == Short.TYPE) {
|
||||
c = 'S';
|
||||
} else if (clazz == Double.TYPE) {
|
||||
c = 'D';
|
||||
} else if (clazz == Float.TYPE) {
|
||||
c = 'F';
|
||||
} else {
|
||||
c = 'J';
|
||||
}
|
||||
return c;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* 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 top.continew.starter.security.limiter.core;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.extra.servlet.JakartaServletUtil;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
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.aspectj.lang.reflect.MethodSignature;
|
||||
import org.redisson.api.*;
|
||||
import org.springframework.stereotype.Component;
|
||||
import top.continew.starter.cache.redisson.util.RedisUtils;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.core.util.expression.ExpressionUtils;
|
||||
import top.continew.starter.security.limiter.annotation.RateLimiter;
|
||||
import top.continew.starter.security.limiter.annotation.RateLimiters;
|
||||
import top.continew.starter.security.limiter.autoconfigure.RateLimiterProperties;
|
||||
import top.continew.starter.security.limiter.enums.LimitType;
|
||||
import top.continew.starter.security.limiter.exception.RateLimiterException;
|
||||
import top.continew.starter.web.util.ServletUtils;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 限流器切面
|
||||
*
|
||||
* @author KAI
|
||||
* @author Charles7c
|
||||
* @since 2.2.0
|
||||
*/
|
||||
@Aspect
|
||||
@Component
|
||||
public class RateLimiterAspect {
|
||||
|
||||
private static final ConcurrentHashMap<String, RRateLimiter> RATE_LIMITER_CACHE = new ConcurrentHashMap<>();
|
||||
private final RateLimiterProperties properties;
|
||||
private final RateLimiterNameGenerator nameGenerator;
|
||||
private final RedissonClient redissonClient;
|
||||
|
||||
public RateLimiterAspect(RateLimiterProperties properties,
|
||||
RateLimiterNameGenerator nameGenerator,
|
||||
RedissonClient redissonClient) {
|
||||
this.properties = properties;
|
||||
this.nameGenerator = nameGenerator;
|
||||
this.redissonClient = redissonClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* 单个限流注解切点
|
||||
*/
|
||||
@Pointcut("@annotation(top.continew.starter.security.limiter.annotation.RateLimiter)")
|
||||
public void rateLimiterPointCut() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 多个限流注解切点
|
||||
*/
|
||||
@Pointcut("@annotation(top.continew.starter.security.limiter.annotation.RateLimiters)")
|
||||
public void rateLimitersPointCut() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 单限流场景
|
||||
*
|
||||
* @param joinPoint 切点
|
||||
* @param rateLimiter 限流注解
|
||||
* @return 目标方法的执行结果
|
||||
* @throws Throwable /
|
||||
*/
|
||||
@Around("@annotation(rateLimiter)")
|
||||
public Object aroundRateLimiter(ProceedingJoinPoint joinPoint, RateLimiter rateLimiter) throws Throwable {
|
||||
if (isRateLimited(joinPoint, rateLimiter)) {
|
||||
throw new RateLimiterException(rateLimiter.message());
|
||||
}
|
||||
return joinPoint.proceed();
|
||||
}
|
||||
|
||||
/**
|
||||
* 多限流场景
|
||||
*
|
||||
* @param joinPoint 切点
|
||||
* @param rateLimiters 限流组注解
|
||||
* @return 目标方法的执行结果
|
||||
* @throws Throwable /
|
||||
*/
|
||||
@Around("@annotation(rateLimiters)")
|
||||
public Object aroundRateLimiters(ProceedingJoinPoint joinPoint, RateLimiters rateLimiters) throws Throwable {
|
||||
for (RateLimiter rateLimiter : rateLimiters.value()) {
|
||||
if (isRateLimited(joinPoint, rateLimiter)) {
|
||||
throw new RateLimiterException(rateLimiter.message());
|
||||
}
|
||||
}
|
||||
return joinPoint.proceed();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否需要限流
|
||||
*
|
||||
* @param joinPoint 切点
|
||||
* @param rateLimiter 限流注解
|
||||
* @return true: 需要限流;false:不需要限流
|
||||
*/
|
||||
private boolean isRateLimited(ProceedingJoinPoint joinPoint, RateLimiter rateLimiter) {
|
||||
try {
|
||||
String cacheKey = this.getCacheKey(joinPoint, rateLimiter);
|
||||
RRateLimiter rRateLimiter = RATE_LIMITER_CACHE.computeIfAbsent(cacheKey, key -> redissonClient
|
||||
.getRateLimiter(cacheKey));
|
||||
// 限流器配置
|
||||
RateType rateType = rateLimiter.type() == LimitType.CLUSTER ? RateType.PER_CLIENT : RateType.OVERALL;
|
||||
int rate = rateLimiter.rate();
|
||||
int rateInterval = rateLimiter.interval();
|
||||
RateIntervalUnit rateIntervalUnit = rateLimiter.unit();
|
||||
// 判断是否需要更新限流器
|
||||
if (this.isConfigurationUpdateNeeded(rRateLimiter, rateType, rate, rateInterval, rateIntervalUnit)) {
|
||||
// 更新限流器
|
||||
rRateLimiter.setRate(rateType, rate, rateInterval, rateIntervalUnit);
|
||||
}
|
||||
// 尝试获取令牌
|
||||
return !rRateLimiter.tryAcquire();
|
||||
} catch (Exception e) {
|
||||
throw new RateLimiterException("服务器限流异常,请稍候再试", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取限流缓存 Key
|
||||
*
|
||||
* @param joinPoint 切点
|
||||
* @param rateLimiter 限流注解
|
||||
* @return 限流缓存 Key
|
||||
*/
|
||||
private String getCacheKey(JoinPoint joinPoint, RateLimiter rateLimiter) {
|
||||
Object target = joinPoint.getTarget();
|
||||
MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
|
||||
Method method = methodSignature.getMethod();
|
||||
Object[] args = joinPoint.getArgs();
|
||||
// 获取限流名称
|
||||
String name = rateLimiter.name();
|
||||
if (CharSequenceUtil.isBlank(name)) {
|
||||
name = nameGenerator.generate(target, method, args);
|
||||
}
|
||||
// 解析限流 Key
|
||||
String key = rateLimiter.key();
|
||||
if (CharSequenceUtil.isNotBlank(key)) {
|
||||
Object eval = ExpressionUtils.eval(key, target, method, args);
|
||||
if (ObjectUtil.isNull(eval)) {
|
||||
throw new RateLimiterException("限流 Key 解析错误");
|
||||
}
|
||||
key = Convert.toStr(eval);
|
||||
}
|
||||
// 获取后缀
|
||||
String suffix = switch (rateLimiter.type()) {
|
||||
case IP -> JakartaServletUtil.getClientIP(ServletUtils.getRequest());
|
||||
case CLUSTER -> redissonClient.getId();
|
||||
default -> StringConstants.EMPTY;
|
||||
};
|
||||
return RedisUtils.formatKey(properties.getKeyPrefix(), name, key, suffix);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否需要更新限流器配置
|
||||
*
|
||||
* @param rRateLimiter 限流器
|
||||
* @param rateType 限流类型(OVERALL:全局限流;PER_CLIENT:单机限流)
|
||||
* @param rate 速率(指定时间间隔产生的令牌数)
|
||||
* @param rateInterval 速率间隔
|
||||
* @param rateIntervalUnit 时间单位
|
||||
* @return 是否需要更新配置
|
||||
*/
|
||||
private boolean isConfigurationUpdateNeeded(RRateLimiter rRateLimiter,
|
||||
RateType rateType,
|
||||
long rate,
|
||||
long rateInterval,
|
||||
RateIntervalUnit rateIntervalUnit) {
|
||||
RateLimiterConfig config = rRateLimiter.getConfig();
|
||||
return !Objects.equals(config.getRateType(), rateType) || !Objects.equals(config.getRate(), rate) || !Objects
|
||||
.equals(config.getRateInterval(), rateIntervalUnit.toMillis(rateInterval));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* 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 top.continew.starter.security.limiter.core;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* 限流器名称生成器
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 2.2.0
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface RateLimiterNameGenerator {
|
||||
|
||||
/**
|
||||
* Generate a rate limiter name for the given method and its parameters.
|
||||
*
|
||||
* @param target the target instance
|
||||
* @param method the method being called
|
||||
* @param args the method parameters (with any var-args expanded)
|
||||
* @return a generated rate limiter name
|
||||
*/
|
||||
String generate(Object target, Method method, Object... args);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* 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 top.continew.starter.security.limiter.enums;
|
||||
|
||||
/**
|
||||
* 限流类型
|
||||
*
|
||||
* @author KAI
|
||||
* @since 2.2.0
|
||||
*/
|
||||
public enum LimitType {
|
||||
|
||||
/**
|
||||
* 全局限流
|
||||
*/
|
||||
DEFAULT,
|
||||
|
||||
/**
|
||||
* 根据 IP 限流
|
||||
*/
|
||||
IP,
|
||||
|
||||
/**
|
||||
* 根据实例限流(支持集群多实例)
|
||||
*/
|
||||
CLUSTER
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* 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 top.continew.starter.security.limiter.exception;
|
||||
|
||||
import top.continew.starter.core.exception.BaseException;
|
||||
|
||||
/**
|
||||
* 限流异常
|
||||
*
|
||||
* @author KAI
|
||||
* @since 2.2.0
|
||||
*/
|
||||
public class RateLimiterException extends BaseException {
|
||||
|
||||
public RateLimiterException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public RateLimiterException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
top.continew.starter.security.limiter.autoconfigure.RateLimiterAutoConfiguration
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"top.continew.starter.security.limiter.annotation.RateLimiter@key":{
|
||||
"method":{
|
||||
"parameters": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,7 @@ import java.util.Map;
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@EnableConfigurationProperties(PasswordEncoderProperties.class)
|
||||
@ConditionalOnProperty(prefix = PropertiesConstants.PASSWORD, name = PropertiesConstants.ENABLED, matchIfMissing = true)
|
||||
@ConditionalOnProperty(prefix = PropertiesConstants.SECURITY_PASSWORD, name = PropertiesConstants.ENABLED, matchIfMissing = true)
|
||||
public class PasswordEncoderAutoConfiguration {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(PasswordEncoderAutoConfiguration.class);
|
||||
|
||||
@@ -25,7 +25,7 @@ import top.continew.starter.core.constant.PropertiesConstants;
|
||||
* @author Jasmine
|
||||
* @since 1.3.0
|
||||
*/
|
||||
@ConfigurationProperties(PropertiesConstants.PASSWORD)
|
||||
@ConfigurationProperties(PropertiesConstants.SECURITY_PASSWORD)
|
||||
public class PasswordEncoderProperties {
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
<module>continew-starter-security-password</module>
|
||||
<module>continew-starter-security-mask</module>
|
||||
<module>continew-starter-security-crypto</module>
|
||||
<module>continew-starter-security-limiter</module>
|
||||
</modules>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -40,7 +40,7 @@ import top.continew.starter.core.constant.StringConstants;
|
||||
@Lazy
|
||||
@AutoConfiguration
|
||||
@ConditionalOnWebApplication
|
||||
@ConditionalOnProperty(prefix = PropertiesConstants.CORS, name = PropertiesConstants.ENABLED, havingValue = "true")
|
||||
@ConditionalOnProperty(prefix = PropertiesConstants.WEB_CORS, name = PropertiesConstants.ENABLED, havingValue = "true")
|
||||
@EnableConfigurationProperties(CorsProperties.class)
|
||||
public class CorsAutoConfiguration {
|
||||
private static final Logger log = LoggerFactory.getLogger(CorsAutoConfiguration.class);
|
||||
|
||||
@@ -30,7 +30,7 @@ import java.util.List;
|
||||
* @author Charles7c
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@ConfigurationProperties(PropertiesConstants.CORS)
|
||||
@ConfigurationProperties(PropertiesConstants.WEB_CORS)
|
||||
public class CorsProperties {
|
||||
|
||||
private static final List<String> ALL = Collections.singletonList(StringConstants.ASTERISK);
|
||||
|
||||
@@ -19,6 +19,8 @@ package top.continew.starter.web.autoconfigure.exception;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.validation.ConstraintViolation;
|
||||
import jakarta.validation.ConstraintViolationException;
|
||||
@@ -36,7 +38,11 @@ import org.springframework.web.multipart.MultipartException;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.core.exception.BadRequestException;
|
||||
import top.continew.starter.core.exception.BusinessException;
|
||||
import top.continew.starter.core.exception.GlobalException;
|
||||
import top.continew.starter.core.exception.ResultInfoInterface;
|
||||
import top.continew.starter.web.autoconfigure.i18n.I18nProperties;
|
||||
import top.continew.starter.web.model.R;
|
||||
import top.continew.starter.web.util.MessageSourceUtils;
|
||||
|
||||
/**
|
||||
* 全局异常处理器
|
||||
@@ -49,6 +55,8 @@ public class GlobalExceptionHandler {
|
||||
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
|
||||
|
||||
private static final String PARAM_FAILED = "请求地址 [{}],参数验证失败。";
|
||||
@Resource
|
||||
private I18nProperties i18nProperties;
|
||||
|
||||
/**
|
||||
* 拦截自定义验证异常-错误请求
|
||||
@@ -153,6 +161,23 @@ public class GlobalExceptionHandler {
|
||||
return R.fail(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截全局应用异常
|
||||
*/
|
||||
@ExceptionHandler(GlobalException.class)
|
||||
public R<Void> handleGlobalException(GlobalException e, HttpServletRequest request) {
|
||||
log.error("请求地址 [{}],发生业务异常。", request.getRequestURI(), e);
|
||||
ResultInfoInterface resultInfo = e.getResultInfo();
|
||||
// 未开启,直接返回
|
||||
if (!i18nProperties.getEnabled()) {
|
||||
return R.fail(resultInfo.getCode(), resultInfo.getDefaultMessage());
|
||||
}
|
||||
// 以用户自定的messageKey优先,否则枚举当messageKey
|
||||
String messageKey = StrUtil.blankToDefault(resultInfo.getMessageKey(), resultInfo.toString());
|
||||
String message = MessageSourceUtils.getMessage(messageKey, resultInfo.getDefaultMessage());
|
||||
return R.fail(resultInfo.getCode(), message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截未知的运行时异常
|
||||
*/
|
||||
@@ -170,4 +195,5 @@ public class GlobalExceptionHandler {
|
||||
log.error("请求地址 [{}],发生未知异常。", request.getRequestURI(), e);
|
||||
return R.fail(e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,10 +26,12 @@ import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.validation.beanvalidation.SpringConstraintValidatorFactory;
|
||||
import top.continew.starter.web.autoconfigure.i18n.I18nProperties;
|
||||
|
||||
/**
|
||||
* 全局异常处理器自动配置
|
||||
@@ -40,6 +42,7 @@ import org.springframework.validation.beanvalidation.SpringConstraintValidatorFa
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnMissingBean(BasicErrorController.class)
|
||||
@Import({GlobalExceptionHandler.class, GlobalErrorHandler.class})
|
||||
@EnableConfigurationProperties(I18nProperties.class)
|
||||
public class GlobalExceptionHandlerAutoConfiguration {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandlerAutoConfiguration.class);
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* 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 top.continew.starter.web.autoconfigure.i18n;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
|
||||
/**
|
||||
* 国际化配置属性
|
||||
*
|
||||
* @author Jasmine
|
||||
* @since 2.2.0
|
||||
*/
|
||||
@ConfigurationProperties(PropertiesConstants.WEB_I18N)
|
||||
public class I18nProperties {
|
||||
|
||||
/**
|
||||
* 国际化开启 true-开启, false-关闭
|
||||
*/
|
||||
private Boolean enabled;
|
||||
|
||||
public Boolean getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(Boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,7 @@ import top.continew.starter.core.constant.PropertiesConstants;
|
||||
@AutoConfiguration
|
||||
@ConditionalOnWebApplication
|
||||
@EnableConfigurationProperties(TraceProperties.class)
|
||||
@ConditionalOnProperty(prefix = PropertiesConstants.TRACE, name = PropertiesConstants.ENABLED, havingValue = "true")
|
||||
@ConditionalOnProperty(prefix = PropertiesConstants.WEB_TRACE, name = PropertiesConstants.ENABLED, havingValue = "true")
|
||||
public class TraceAutoConfiguration {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(TraceAutoConfiguration.class);
|
||||
|
||||
@@ -26,7 +26,7 @@ import top.continew.starter.core.constant.PropertiesConstants;
|
||||
* @author Charles7c
|
||||
* @since 1.3.0
|
||||
*/
|
||||
@ConfigurationProperties(PropertiesConstants.TRACE)
|
||||
@ConfigurationProperties(PropertiesConstants.WEB_TRACE)
|
||||
public class TraceProperties {
|
||||
|
||||
/**
|
||||
|
||||
@@ -33,7 +33,7 @@ import top.continew.starter.core.constant.PropertiesConstants;
|
||||
@AutoConfiguration
|
||||
@ConditionalOnWebApplication
|
||||
@EnableConfigurationProperties(XssProperties.class)
|
||||
@ConditionalOnProperty(prefix = PropertiesConstants.XSS, name = PropertiesConstants.ENABLED, havingValue = "true")
|
||||
@ConditionalOnProperty(prefix = PropertiesConstants.WEB_XSS, name = PropertiesConstants.ENABLED, havingValue = "true")
|
||||
public class XssAutoConfiguration {
|
||||
|
||||
/**
|
||||
|
||||
@@ -29,7 +29,7 @@ import java.util.List;
|
||||
* @author whhya
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@ConfigurationProperties(PropertiesConstants.XSS)
|
||||
@ConfigurationProperties(PropertiesConstants.WEB_XSS)
|
||||
public class XssProperties {
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
|
||||
* <p>
|
||||
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.gnu.org/licenses/lgpl.html
|
||||
* <p>
|
||||
* 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 top.continew.starter.web.util;
|
||||
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.i18n.LocaleContextHolder;
|
||||
|
||||
/**
|
||||
* @author Jasmine
|
||||
* @since 2.2.0
|
||||
*/
|
||||
public class MessageSourceUtils {
|
||||
|
||||
private static final MessageSource messageSource = SpringUtil.getBean(MessageSource.class);
|
||||
|
||||
private static final Object[] emptyArray = new Object[] {};
|
||||
|
||||
public static String getMessage(String key) {
|
||||
return getMessage(key, emptyArray);
|
||||
}
|
||||
|
||||
public static String getMessage(String key, String defaultMessage) {
|
||||
return getMessage(key, defaultMessage, emptyArray);
|
||||
}
|
||||
|
||||
public static String getMessage(String msgKey, Object... args) {
|
||||
return getMessage(msgKey, msgKey, args);
|
||||
}
|
||||
|
||||
public static String getMessage(String msgKey, String defaultMessage, Object... args) {
|
||||
try {
|
||||
return messageSource.getMessage(msgKey, args, LocaleContextHolder.getLocale());
|
||||
} catch (Exception e) {
|
||||
return defaultMessage;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user