init
This commit is contained in:
		| @ -0,0 +1,17 @@ | ||||
| package org.dromara.common.core.config; | ||||
|  | ||||
| import org.springframework.boot.autoconfigure.AutoConfiguration; | ||||
| import org.springframework.context.annotation.EnableAspectJAutoProxy; | ||||
| import org.springframework.scheduling.annotation.EnableAsync; | ||||
|  | ||||
| /** | ||||
|  * 程序注解配置 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @AutoConfiguration | ||||
| @EnableAspectJAutoProxy | ||||
| @EnableAsync(proxyTargetClass = true) | ||||
| public class ApplicationConfig { | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,52 @@ | ||||
| package org.dromara.common.core.config; | ||||
|  | ||||
| import cn.hutool.core.util.ArrayUtil; | ||||
| import org.dromara.common.core.exception.ServiceException; | ||||
| import org.dromara.common.core.utils.SpringUtils; | ||||
| import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; | ||||
| import org.springframework.boot.autoconfigure.AutoConfiguration; | ||||
| import org.springframework.core.task.VirtualThreadTaskExecutor; | ||||
| import org.springframework.scheduling.annotation.AsyncConfigurer; | ||||
|  | ||||
| import java.util.Arrays; | ||||
| import java.util.concurrent.Executor; | ||||
|  | ||||
| /** | ||||
|  * 异步配置 | ||||
|  * <p> | ||||
|  * 如果未使用虚拟线程则生效 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @AutoConfiguration | ||||
| public class AsyncConfig implements AsyncConfigurer { | ||||
|  | ||||
|     /** | ||||
|      * 自定义 @Async 注解使用系统线程池 | ||||
|      */ | ||||
|     @Override | ||||
|     public Executor getAsyncExecutor() { | ||||
|         if(SpringUtils.isVirtual()) { | ||||
|             return new VirtualThreadTaskExecutor("async-"); | ||||
|         } | ||||
|         return SpringUtils.getBean("scheduledExecutorService"); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 异步执行异常处理 | ||||
|      */ | ||||
|     @Override | ||||
|     public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { | ||||
|         return (throwable, method, objects) -> { | ||||
|             throwable.printStackTrace(); | ||||
|             StringBuilder sb = new StringBuilder(); | ||||
|             sb.append("Exception message - ").append(throwable.getMessage()) | ||||
|                 .append(", Method name - ").append(method.getName()); | ||||
|             if (ArrayUtil.isNotEmpty(objects)) { | ||||
|                 sb.append(", Parameter value - ").append(Arrays.toString(objects)); | ||||
|             } | ||||
|             throw new ServiceException(sb.toString()); | ||||
|         }; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,33 @@ | ||||
| package org.dromara.common.core.config; | ||||
|  | ||||
| import lombok.Data; | ||||
| import org.springframework.boot.context.properties.ConfigurationProperties; | ||||
| import org.springframework.stereotype.Component; | ||||
|  | ||||
| /** | ||||
|  * 读取项目相关配置 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
|  | ||||
| @Data | ||||
| @Component | ||||
| @ConfigurationProperties(prefix = "ruoyi") | ||||
| public class RuoYiConfig { | ||||
|  | ||||
|     /** | ||||
|      * 项目名称 | ||||
|      */ | ||||
|     private String name; | ||||
|  | ||||
|     /** | ||||
|      * 版本 | ||||
|      */ | ||||
|     private String version; | ||||
|  | ||||
|     /** | ||||
|      * 版权年份 | ||||
|      */ | ||||
|     private String copyrightYear; | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,87 @@ | ||||
| package org.dromara.common.core.config; | ||||
|  | ||||
| import jakarta.annotation.PreDestroy; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.apache.commons.lang3.concurrent.BasicThreadFactory; | ||||
| import org.dromara.common.core.config.properties.ThreadPoolProperties; | ||||
| import org.dromara.common.core.utils.SpringUtils; | ||||
| import org.dromara.common.core.utils.Threads; | ||||
| import org.springframework.boot.autoconfigure.AutoConfiguration; | ||||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | ||||
| import org.springframework.boot.context.properties.EnableConfigurationProperties; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.core.task.VirtualThreadTaskExecutor; | ||||
| import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; | ||||
|  | ||||
| import java.util.concurrent.ScheduledExecutorService; | ||||
| import java.util.concurrent.ScheduledThreadPoolExecutor; | ||||
| import java.util.concurrent.ThreadPoolExecutor; | ||||
|  | ||||
| /** | ||||
|  * 线程池配置 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  **/ | ||||
| @Slf4j | ||||
| @AutoConfiguration | ||||
| @EnableConfigurationProperties(ThreadPoolProperties.class) | ||||
| public class ThreadPoolConfig { | ||||
|  | ||||
|     /** | ||||
|      * 核心线程数 = cpu 核心数 + 1 | ||||
|      */ | ||||
|     private final int core = Runtime.getRuntime().availableProcessors() + 1; | ||||
|  | ||||
|     private ScheduledExecutorService scheduledExecutorService; | ||||
|  | ||||
|     @Bean(name = "threadPoolTaskExecutor") | ||||
|     @ConditionalOnProperty(prefix = "thread-pool", name = "enabled", havingValue = "true") | ||||
|     public ThreadPoolTaskExecutor threadPoolTaskExecutor(ThreadPoolProperties threadPoolProperties) { | ||||
|         ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); | ||||
|         executor.setCorePoolSize(core); | ||||
|         executor.setMaxPoolSize(core * 2); | ||||
|         executor.setQueueCapacity(threadPoolProperties.getQueueCapacity()); | ||||
|         executor.setKeepAliveSeconds(threadPoolProperties.getKeepAliveSeconds()); | ||||
|         executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); | ||||
|         return executor; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 执行周期性或定时任务 | ||||
|      */ | ||||
|     @Bean(name = "scheduledExecutorService") | ||||
|     protected ScheduledExecutorService scheduledExecutorService() { | ||||
|         // daemon 必须为 true | ||||
|         BasicThreadFactory.Builder builder = new BasicThreadFactory.Builder().daemon(true); | ||||
|         if (SpringUtils.isVirtual()) { | ||||
|             builder.namingPattern("virtual-schedule-pool-%d").wrappedFactory(new VirtualThreadTaskExecutor().getVirtualThreadFactory()); | ||||
|         } else { | ||||
|             builder.namingPattern("schedule-pool-%d"); | ||||
|         } | ||||
|         ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(core, | ||||
|             builder.build(), | ||||
|             new ThreadPoolExecutor.CallerRunsPolicy()) { | ||||
|             @Override | ||||
|             protected void afterExecute(Runnable r, Throwable t) { | ||||
|                 super.afterExecute(r, t); | ||||
|                 Threads.printException(r, t); | ||||
|             } | ||||
|         }; | ||||
|         this.scheduledExecutorService = scheduledThreadPoolExecutor; | ||||
|         return scheduledThreadPoolExecutor; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 销毁事件 | ||||
|      */ | ||||
|     @PreDestroy | ||||
|     public void destroy() { | ||||
|         try { | ||||
|             log.info("====关闭后台任务任务线程池===="); | ||||
|             Threads.shutdownAndAwaitTermination(scheduledExecutorService); | ||||
|         } catch (Exception e) { | ||||
|             log.error(e.getMessage(), e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,40 @@ | ||||
| package org.dromara.common.core.config; | ||||
|  | ||||
| import jakarta.validation.Validator; | ||||
| import org.hibernate.validator.HibernateValidator; | ||||
| import org.springframework.boot.autoconfigure.AutoConfiguration; | ||||
| import org.springframework.context.MessageSource; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; | ||||
|  | ||||
| import java.util.Properties; | ||||
|  | ||||
| /** | ||||
|  * 校验框架配置类 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @AutoConfiguration | ||||
| public class ValidatorConfig { | ||||
|  | ||||
|     /** | ||||
|      * 配置校验框架 快速返回模式 | ||||
|      */ | ||||
|     @Bean | ||||
|     public Validator validator(MessageSource messageSource) { | ||||
|         try (LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean()) { | ||||
|             // 国际化 | ||||
|             factoryBean.setValidationMessageSource(messageSource); | ||||
|             // 设置使用 HibernateValidator 校验器 | ||||
|             factoryBean.setProviderClass(HibernateValidator.class); | ||||
|             Properties properties = new Properties(); | ||||
|             // 设置 快速异常返回 | ||||
|             properties.setProperty("hibernate.validator.fail_fast", "true"); | ||||
|             factoryBean.setValidationProperties(properties); | ||||
|             // 加载配置 | ||||
|             factoryBean.afterPropertiesSet(); | ||||
|             return factoryBean.getValidator(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,30 @@ | ||||
| package org.dromara.common.core.config.properties; | ||||
|  | ||||
| import lombok.Data; | ||||
| import org.springframework.boot.context.properties.ConfigurationProperties; | ||||
|  | ||||
| /** | ||||
|  * 线程池 配置属性 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @Data | ||||
| @ConfigurationProperties(prefix = "thread-pool") | ||||
| public class ThreadPoolProperties { | ||||
|  | ||||
|     /** | ||||
|      * 是否开启线程池 | ||||
|      */ | ||||
|     private boolean enabled; | ||||
|  | ||||
|     /** | ||||
|      * 队列最大长度 | ||||
|      */ | ||||
|     private int queueCapacity; | ||||
|  | ||||
|     /** | ||||
|      * 线程池维护线程所允许的空闲时间 | ||||
|      */ | ||||
|     private int keepAliveSeconds; | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,30 @@ | ||||
| package org.dromara.common.core.constant; | ||||
|  | ||||
| /** | ||||
|  * 缓存的key 常量 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| public interface CacheConstants { | ||||
|  | ||||
|     /** | ||||
|      * 在线用户 redis key | ||||
|      */ | ||||
|     String ONLINE_TOKEN_KEY = "online_tokens:"; | ||||
|  | ||||
|     /** | ||||
|      * 参数管理 cache key | ||||
|      */ | ||||
|     String SYS_CONFIG_KEY = "sys_config:"; | ||||
|  | ||||
|     /** | ||||
|      * 字典管理 cache key | ||||
|      */ | ||||
|     String SYS_DICT_KEY = "sys_dict:"; | ||||
|  | ||||
|     /** | ||||
|      * 登录账户密码错误次数 redis key | ||||
|      */ | ||||
|     String PWD_ERR_CNT_KEY = "pwd_err_cnt:"; | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,83 @@ | ||||
| package org.dromara.common.core.constant; | ||||
|  | ||||
| /** | ||||
|  * 缓存组名称常量 | ||||
|  * <p> | ||||
|  * key 格式为 cacheNames#ttl#maxIdleTime#maxSize | ||||
|  * <p> | ||||
|  * ttl 过期时间 如果设置为0则不过期 默认为0 | ||||
|  * maxIdleTime 最大空闲时间 根据LRU算法清理空闲数据 如果设置为0则不检测 默认为0 | ||||
|  * maxSize 组最大长度 根据LRU算法清理溢出数据 如果设置为0则无限长 默认为0 | ||||
|  * <p> | ||||
|  * 例子: test#60s、test#0#60s、test#0#1m#1000、test#1h#0#500 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| public interface CacheNames { | ||||
|  | ||||
|     /** | ||||
|      * 演示案例 | ||||
|      */ | ||||
|     String DEMO_CACHE = "demo:cache#60s#10m#20"; | ||||
|  | ||||
|     /** | ||||
|      * 系统配置 | ||||
|      */ | ||||
|     String SYS_CONFIG = "sys_config"; | ||||
|  | ||||
|     /** | ||||
|      * 数据字典 | ||||
|      */ | ||||
|     String SYS_DICT = "sys_dict"; | ||||
|  | ||||
|     /** | ||||
|      * 租户 | ||||
|      */ | ||||
|     String SYS_TENANT = GlobalConstants.GLOBAL_REDIS_KEY + "sys_tenant#30d"; | ||||
|  | ||||
|     /** | ||||
|      * 客户端 | ||||
|      */ | ||||
|     String SYS_CLIENT = GlobalConstants.GLOBAL_REDIS_KEY + "sys_client#30d"; | ||||
|  | ||||
|     /** | ||||
|      * 用户账户 | ||||
|      */ | ||||
|     String SYS_USER_NAME = "sys_user_name#30d"; | ||||
|  | ||||
|     /** | ||||
|      * 用户名称 | ||||
|      */ | ||||
|     String SYS_NICKNAME = "sys_nickname#30d"; | ||||
|  | ||||
|     /** | ||||
|      * 部门 | ||||
|      */ | ||||
|     String SYS_DEPT = "sys_dept#30d"; | ||||
|  | ||||
|     /** | ||||
|      * OSS内容 | ||||
|      */ | ||||
|     String SYS_OSS = "sys_oss#30d"; | ||||
|  | ||||
|     /** | ||||
|      * 角色自定义权限 | ||||
|      */ | ||||
|     String SYS_ROLE_CUSTOM = "sys_role_custom#30d"; | ||||
|  | ||||
|     /** | ||||
|      * 部门及以下权限 | ||||
|      */ | ||||
|     String SYS_DEPT_AND_CHILD = "sys_dept_and_child#30d"; | ||||
|  | ||||
|     /** | ||||
|      * OSS配置 | ||||
|      */ | ||||
|     String SYS_OSS_CONFIG = GlobalConstants.GLOBAL_REDIS_KEY + "sys_oss_config"; | ||||
|  | ||||
|     /** | ||||
|      * 在线用户 | ||||
|      */ | ||||
|     String ONLINE_TOKEN = "online_tokens"; | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,76 @@ | ||||
| package org.dromara.common.core.constant; | ||||
|  | ||||
| /** | ||||
|  * 通用常量信息 | ||||
|  * | ||||
|  * @author ruoyi | ||||
|  */ | ||||
| public interface Constants { | ||||
|  | ||||
|     /** | ||||
|      * UTF-8 字符集 | ||||
|      */ | ||||
|     String UTF8 = "UTF-8"; | ||||
|  | ||||
|     /** | ||||
|      * GBK 字符集 | ||||
|      */ | ||||
|     String GBK = "GBK"; | ||||
|  | ||||
|     /** | ||||
|      * www主域 | ||||
|      */ | ||||
|     String WWW = "www."; | ||||
|  | ||||
|     /** | ||||
|      * http请求 | ||||
|      */ | ||||
|     String HTTP = "http://"; | ||||
|  | ||||
|     /** | ||||
|      * https请求 | ||||
|      */ | ||||
|     String HTTPS = "https://"; | ||||
|  | ||||
|     /** | ||||
|      * 通用成功标识 | ||||
|      */ | ||||
|     String SUCCESS = "0"; | ||||
|  | ||||
|     /** | ||||
|      * 通用失败标识 | ||||
|      */ | ||||
|     String FAIL = "1"; | ||||
|  | ||||
|     /** | ||||
|      * 登录成功 | ||||
|      */ | ||||
|     String LOGIN_SUCCESS = "Success"; | ||||
|  | ||||
|     /** | ||||
|      * 注销 | ||||
|      */ | ||||
|     String LOGOUT = "Logout"; | ||||
|  | ||||
|     /** | ||||
|      * 注册 | ||||
|      */ | ||||
|     String REGISTER = "Register"; | ||||
|  | ||||
|     /** | ||||
|      * 登录失败 | ||||
|      */ | ||||
|     String LOGIN_FAIL = "Error"; | ||||
|  | ||||
|     /** | ||||
|      * 验证码有效期(分钟) | ||||
|      */ | ||||
|     Integer CAPTCHA_EXPIRATION = 2; | ||||
|  | ||||
|     /** | ||||
|      * 顶级父级id | ||||
|      */ | ||||
|     Long TOP_PARENT_ID = 0L; | ||||
|  | ||||
| } | ||||
|  | ||||
| @ -0,0 +1,34 @@ | ||||
| package org.dromara.common.core.constant; | ||||
|  | ||||
| /** | ||||
|  * 全局的key常量 (业务无关的key) | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| public interface GlobalConstants { | ||||
|  | ||||
|     /** | ||||
|      * 全局 redis key (业务无关的key) | ||||
|      */ | ||||
|     String GLOBAL_REDIS_KEY = "global:"; | ||||
|  | ||||
|     /** | ||||
|      * 验证码 redis key | ||||
|      */ | ||||
|     String CAPTCHA_CODE_KEY = GLOBAL_REDIS_KEY + "captcha_codes:"; | ||||
|  | ||||
|     /** | ||||
|      * 防重提交 redis key | ||||
|      */ | ||||
|     String REPEAT_SUBMIT_KEY = GLOBAL_REDIS_KEY + "repeat_submit:"; | ||||
|  | ||||
|     /** | ||||
|      * 限流 redis key | ||||
|      */ | ||||
|     String RATE_LIMIT_KEY = GLOBAL_REDIS_KEY + "rate_limit:"; | ||||
|  | ||||
|     /** | ||||
|      * 三方认证 redis key | ||||
|      */ | ||||
|     String SOCIAL_AUTH_CODE_KEY = GLOBAL_REDIS_KEY + "social_auth_codes:"; | ||||
| } | ||||
| @ -0,0 +1,93 @@ | ||||
| package org.dromara.common.core.constant; | ||||
|  | ||||
| /** | ||||
|  * 返回状态码 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| public interface HttpStatus { | ||||
|     /** | ||||
|      * 操作成功 | ||||
|      */ | ||||
|     int SUCCESS = 200; | ||||
|  | ||||
|     /** | ||||
|      * 对象创建成功 | ||||
|      */ | ||||
|     int CREATED = 201; | ||||
|  | ||||
|     /** | ||||
|      * 请求已经被接受 | ||||
|      */ | ||||
|     int ACCEPTED = 202; | ||||
|  | ||||
|     /** | ||||
|      * 操作已经执行成功,但是没有返回数据 | ||||
|      */ | ||||
|     int NO_CONTENT = 204; | ||||
|  | ||||
|     /** | ||||
|      * 资源已被移除 | ||||
|      */ | ||||
|     int MOVED_PERM = 301; | ||||
|  | ||||
|     /** | ||||
|      * 重定向 | ||||
|      */ | ||||
|     int SEE_OTHER = 303; | ||||
|  | ||||
|     /** | ||||
|      * 资源没有被修改 | ||||
|      */ | ||||
|     int NOT_MODIFIED = 304; | ||||
|  | ||||
|     /** | ||||
|      * 参数列表错误(缺少,格式不匹配) | ||||
|      */ | ||||
|     int BAD_REQUEST = 400; | ||||
|  | ||||
|     /** | ||||
|      * 未授权 | ||||
|      */ | ||||
|     int UNAUTHORIZED = 401; | ||||
|  | ||||
|     /** | ||||
|      * 访问受限,授权过期 | ||||
|      */ | ||||
|     int FORBIDDEN = 403; | ||||
|  | ||||
|     /** | ||||
|      * 资源,服务未找到 | ||||
|      */ | ||||
|     int NOT_FOUND = 404; | ||||
|  | ||||
|     /** | ||||
|      * 不允许的http方法 | ||||
|      */ | ||||
|     int BAD_METHOD = 405; | ||||
|  | ||||
|     /** | ||||
|      * 资源冲突,或者资源被锁 | ||||
|      */ | ||||
|     int CONFLICT = 409; | ||||
|  | ||||
|     /** | ||||
|      * 不支持的数据,媒体类型 | ||||
|      */ | ||||
|     int UNSUPPORTED_TYPE = 415; | ||||
|  | ||||
|     /** | ||||
|      * 系统内部错误 | ||||
|      */ | ||||
|     int ERROR = 500; | ||||
|  | ||||
|     /** | ||||
|      * 接口未实现 | ||||
|      */ | ||||
|     int NOT_IMPLEMENTED = 501; | ||||
|  | ||||
|     /** | ||||
|      * 系统警告消息 | ||||
|      */ | ||||
|     int WARN = 601; | ||||
| } | ||||
| @ -0,0 +1,54 @@ | ||||
| package org.dromara.common.core.constant; | ||||
|  | ||||
| import cn.hutool.core.lang.RegexPool; | ||||
|  | ||||
| /** | ||||
|  * 常用正则表达式字符串 | ||||
|  * <p> | ||||
|  * 常用正则表达式集合,更多正则见: https://any86.github.io/any-rule/ | ||||
|  * | ||||
|  * @author Feng | ||||
|  */ | ||||
| public interface RegexConstants extends RegexPool { | ||||
|  | ||||
|     /** | ||||
|      * 字典类型必须以字母开头,且只能为(小写字母,数字,下滑线) | ||||
|      */ | ||||
|     String DICTIONARY_TYPE = "^[a-z][a-z0-9_]*$"; | ||||
|  | ||||
|     /** | ||||
|      * 权限标识必须符合 tool:build:list 格式,或者空字符串 | ||||
|      */ | ||||
|     String PERMISSION_STRING = "^(|^[a-zA-Z0-9_]+:[a-zA-Z0-9_]+:[a-zA-Z0-9_]+)$"; | ||||
|  | ||||
|     /** | ||||
|      * 身份证号码(后6位) | ||||
|      */ | ||||
|     String ID_CARD_LAST_6 = "^(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$"; | ||||
|  | ||||
|     /** | ||||
|      * QQ号码 | ||||
|      */ | ||||
|     String QQ_NUMBER = "^[1-9][0-9]\\d{4,9}$"; | ||||
|  | ||||
|     /** | ||||
|      * 邮政编码 | ||||
|      */ | ||||
|     String POSTAL_CODE = "^[1-9]\\d{5}$"; | ||||
|  | ||||
|     /** | ||||
|      * 注册账号 | ||||
|      */ | ||||
|     String ACCOUNT = "^[a-zA-Z][a-zA-Z0-9_]{4,15}$"; | ||||
|  | ||||
|     /** | ||||
|      * 密码:包含至少8个字符,包括大写字母、小写字母、数字和特殊字符 | ||||
|      */ | ||||
|     String PASSWORD = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,}$"; | ||||
|  | ||||
|     /** | ||||
|      * 通用状态(0表示正常,1表示停用) | ||||
|      */ | ||||
|     String STATUS = "^[01]$"; | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,75 @@ | ||||
| package org.dromara.common.core.constant; | ||||
|  | ||||
| /** | ||||
|  * 系统常量信息 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| public interface SystemConstants { | ||||
|  | ||||
|     /** | ||||
|      * 正常状态 | ||||
|      */ | ||||
|     String NORMAL = "0"; | ||||
|  | ||||
|     /** | ||||
|      * 异常状态 | ||||
|      */ | ||||
|     String DISABLE = "1"; | ||||
|  | ||||
|     /** | ||||
|      * 是否为系统默认(是) | ||||
|      */ | ||||
|     String YES = "Y"; | ||||
|  | ||||
|     /** | ||||
|      * 是否为系统默认(否) | ||||
|      */ | ||||
|     String NO = "N"; | ||||
|  | ||||
|     /** | ||||
|      * 是否菜单外链(是) | ||||
|      */ | ||||
|     String YES_FRAME = "0"; | ||||
|  | ||||
|     /** | ||||
|      * 是否菜单外链(否) | ||||
|      */ | ||||
|     String NO_FRAME = "1"; | ||||
|  | ||||
|     /** | ||||
|      * 菜单类型(目录) | ||||
|      */ | ||||
|     String TYPE_DIR = "M"; | ||||
|  | ||||
|     /** | ||||
|      * 菜单类型(菜单) | ||||
|      */ | ||||
|     String TYPE_MENU = "C"; | ||||
|  | ||||
|     /** | ||||
|      * 菜单类型(按钮) | ||||
|      */ | ||||
|     String TYPE_BUTTON = "F"; | ||||
|  | ||||
|     /** | ||||
|      * Layout组件标识 | ||||
|      */ | ||||
|     String LAYOUT = "Layout"; | ||||
|  | ||||
|     /** | ||||
|      * ParentView组件标识 | ||||
|      */ | ||||
|     String PARENT_VIEW = "ParentView"; | ||||
|  | ||||
|     /** | ||||
|      * InnerLink组件标识 | ||||
|      */ | ||||
|     String INNER_LINK = "InnerLink"; | ||||
|  | ||||
|     /** | ||||
|      * 超级管理员ID | ||||
|      */ | ||||
|     Long SUPER_ADMIN_ID = 1L; | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,35 @@ | ||||
| package org.dromara.common.core.constant; | ||||
|  | ||||
| /** | ||||
|  * 租户常量信息 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| public interface TenantConstants { | ||||
|  | ||||
|     /** | ||||
|      * 超级管理员ID | ||||
|      */ | ||||
|     Long SUPER_ADMIN_ID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 超级管理员角色 roleKey | ||||
|      */ | ||||
|     String SUPER_ADMIN_ROLE_KEY = "superadmin"; | ||||
|  | ||||
|     /** | ||||
|      * 租户管理员角色 roleKey | ||||
|      */ | ||||
|     String TENANT_ADMIN_ROLE_KEY = "admin"; | ||||
|  | ||||
|     /** | ||||
|      * 租户管理员角色名称 | ||||
|      */ | ||||
|     String TENANT_ADMIN_ROLE_NAME = "管理员"; | ||||
|  | ||||
|     /** | ||||
|      * 默认租户ID | ||||
|      */ | ||||
|     String DEFAULT_TENANT_ID = "000000"; | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,110 @@ | ||||
| package org.dromara.common.core.domain; | ||||
|  | ||||
| import org.dromara.common.core.constant.HttpStatus; | ||||
| import lombok.Data; | ||||
| import lombok.NoArgsConstructor; | ||||
|  | ||||
| import java.io.Serial; | ||||
| import java.io.Serializable; | ||||
|  | ||||
| /** | ||||
|  * 响应信息主体 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @Data | ||||
| @NoArgsConstructor | ||||
| public class R<T> implements Serializable { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 成功 | ||||
|      */ | ||||
|     public static final int SUCCESS = 200; | ||||
|  | ||||
|     /** | ||||
|      * 失败 | ||||
|      */ | ||||
|     public static final int FAIL = 500; | ||||
|  | ||||
|     private int code; | ||||
|  | ||||
|     private String msg; | ||||
|  | ||||
|     private T data; | ||||
|  | ||||
|     public static <T> R<T> ok() { | ||||
|         return restResult(null, SUCCESS, "操作成功"); | ||||
|     } | ||||
|  | ||||
|     public static <T> R<T> ok(T data) { | ||||
|         return restResult(data, SUCCESS, "操作成功"); | ||||
|     } | ||||
|  | ||||
|     public static <T> R<T> ok(String msg) { | ||||
|         return restResult(null, SUCCESS, msg); | ||||
|     } | ||||
|  | ||||
|     public static <T> R<T> ok(String msg, T data) { | ||||
|         return restResult(data, SUCCESS, msg); | ||||
|     } | ||||
|  | ||||
|     public static <T> R<T> fail() { | ||||
|         return restResult(null, FAIL, "操作失败"); | ||||
|     } | ||||
|  | ||||
|     public static <T> R<T> fail(String msg) { | ||||
|         return restResult(null, FAIL, msg); | ||||
|     } | ||||
|  | ||||
|     public static <T> R<T> fail(T data) { | ||||
|         return restResult(data, FAIL, "操作失败"); | ||||
|     } | ||||
|  | ||||
|     public static <T> R<T> fail(String msg, T data) { | ||||
|         return restResult(data, FAIL, msg); | ||||
|     } | ||||
|  | ||||
|     public static <T> R<T> fail(int code, String msg) { | ||||
|         return restResult(null, code, msg); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 返回警告消息 | ||||
|      * | ||||
|      * @param msg 返回内容 | ||||
|      * @return 警告消息 | ||||
|      */ | ||||
|     public static <T> R<T> warn(String msg) { | ||||
|         return restResult(null, HttpStatus.WARN, msg); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 返回警告消息 | ||||
|      * | ||||
|      * @param msg 返回内容 | ||||
|      * @param data 数据对象 | ||||
|      * @return 警告消息 | ||||
|      */ | ||||
|     public static <T> R<T> warn(String msg, T data) { | ||||
|         return restResult(data, HttpStatus.WARN, msg); | ||||
|     } | ||||
|  | ||||
|     private static <T> R<T> restResult(T data, int code, String msg) { | ||||
|         R<T> r = new R<>(); | ||||
|         r.setCode(code); | ||||
|         r.setData(data); | ||||
|         r.setMsg(msg); | ||||
|         return r; | ||||
|     } | ||||
|  | ||||
|     public static <T> Boolean isError(R<T> ret) { | ||||
|         return !isSuccess(ret); | ||||
|     } | ||||
|  | ||||
|     public static <T> Boolean isSuccess(R<T> ret) { | ||||
|         return R.SUCCESS == ret.getCode(); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,71 @@ | ||||
| package org.dromara.common.core.domain.dto; | ||||
|  | ||||
| import lombok.Data; | ||||
|  | ||||
| import java.io.Serial; | ||||
| import java.io.Serializable; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.Objects; | ||||
|  | ||||
| /** | ||||
|  * 办理任务请求对象 | ||||
|  * | ||||
|  * @author may | ||||
|  */ | ||||
| @Data | ||||
| public class CompleteTaskDTO implements Serializable { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 任务id | ||||
|      */ | ||||
|     private Long taskId; | ||||
|  | ||||
|     /** | ||||
|      * 附件id | ||||
|      */ | ||||
|     private String fileId; | ||||
|  | ||||
|     /** | ||||
|      * 抄送人员 | ||||
|      */ | ||||
|     private List<FlowCopyDTO> flowCopyList; | ||||
|  | ||||
|     /** | ||||
|      * 消息类型 | ||||
|      */ | ||||
|     private List<String> messageType; | ||||
|  | ||||
|     /** | ||||
|      * 办理意见 | ||||
|      */ | ||||
|     private String message; | ||||
|  | ||||
|     /** | ||||
|      * 消息通知 | ||||
|      */ | ||||
|     private String notice; | ||||
|  | ||||
|     /** | ||||
|      * 流程变量 | ||||
|      */ | ||||
|     private Map<String, Object> variables; | ||||
|  | ||||
|     /** | ||||
|      * 扩展变量(此处为逗号分隔的ossId) | ||||
|      */ | ||||
|     private String ext; | ||||
|  | ||||
|     public Map<String, Object> getVariables() { | ||||
|         if (variables == null) { | ||||
|             return new HashMap<>(16); | ||||
|         } | ||||
|         variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue())); | ||||
|         return variables; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,37 @@ | ||||
| package org.dromara.common.core.domain.dto; | ||||
|  | ||||
| import lombok.Data; | ||||
| import lombok.NoArgsConstructor; | ||||
|  | ||||
| import java.io.Serial; | ||||
| import java.io.Serializable; | ||||
|  | ||||
| /** | ||||
|  * 部门 | ||||
|  * | ||||
|  * @author AprilWind | ||||
|  */ | ||||
|  | ||||
| @Data | ||||
| @NoArgsConstructor | ||||
| public class DeptDTO implements Serializable { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 部门ID | ||||
|      */ | ||||
|     private Long deptId; | ||||
|  | ||||
|     /** | ||||
|      * 父部门ID | ||||
|      */ | ||||
|     private Long parentId; | ||||
|  | ||||
|     /** | ||||
|      * 部门名称 | ||||
|      */ | ||||
|     private String deptName; | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,30 @@ | ||||
| package org.dromara.common.core.domain.dto; | ||||
|  | ||||
| import lombok.Data; | ||||
|  | ||||
| import java.io.Serial; | ||||
| import java.io.Serializable; | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * 抄送 | ||||
|  * | ||||
|  * @author may | ||||
|  */ | ||||
| @Data | ||||
| public class FlowCopyDTO implements Serializable { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 用户id | ||||
|      */ | ||||
|     private Long userId; | ||||
|  | ||||
|     /** | ||||
|      * 用户名称 | ||||
|      */ | ||||
|     private String userName; | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,46 @@ | ||||
| package org.dromara.common.core.domain.dto; | ||||
|  | ||||
| import lombok.Data; | ||||
| import lombok.NoArgsConstructor; | ||||
|  | ||||
| import java.io.Serial; | ||||
| import java.io.Serializable; | ||||
|  | ||||
| /** | ||||
|  * OSS对象 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @Data | ||||
| @NoArgsConstructor | ||||
| public class OssDTO implements Serializable { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 对象存储主键 | ||||
|      */ | ||||
|     private Long ossId; | ||||
|  | ||||
|     /** | ||||
|      * 文件名 | ||||
|      */ | ||||
|     private String fileName; | ||||
|  | ||||
|     /** | ||||
|      * 原名 | ||||
|      */ | ||||
|     private String originalName; | ||||
|  | ||||
|     /** | ||||
|      * 文件后缀名 | ||||
|      */ | ||||
|     private String fileSuffix; | ||||
|  | ||||
|     /** | ||||
|      * URL地址 | ||||
|      */ | ||||
|     private String url; | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,46 @@ | ||||
| package org.dromara.common.core.domain.dto; | ||||
|  | ||||
| import lombok.Data; | ||||
| import lombok.NoArgsConstructor; | ||||
|  | ||||
| import java.io.Serial; | ||||
| import java.io.Serializable; | ||||
|  | ||||
| /** | ||||
|  * 岗位 | ||||
|  * | ||||
|  * @author AprilWind | ||||
|  */ | ||||
| @Data | ||||
| @NoArgsConstructor | ||||
| public class PostDTO implements Serializable { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 岗位ID | ||||
|      */ | ||||
|     private Long postId; | ||||
|  | ||||
|     /** | ||||
|      * 部门id | ||||
|      */ | ||||
|     private Long deptId; | ||||
|  | ||||
|     /** | ||||
|      * 岗位编码 | ||||
|      */ | ||||
|     private String postCode; | ||||
|  | ||||
|     /** | ||||
|      * 岗位名称 | ||||
|      */ | ||||
|     private String postName; | ||||
|  | ||||
|     /** | ||||
|      * 岗位类别编码 | ||||
|      */ | ||||
|     private String postCategory; | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,42 @@ | ||||
| package org.dromara.common.core.domain.dto; | ||||
|  | ||||
| import lombok.Data; | ||||
| import lombok.NoArgsConstructor; | ||||
|  | ||||
| import java.io.Serial; | ||||
| import java.io.Serializable; | ||||
|  | ||||
| /** | ||||
|  * 角色 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
|  | ||||
| @Data | ||||
| @NoArgsConstructor | ||||
| public class RoleDTO implements Serializable { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 角色ID | ||||
|      */ | ||||
|     private Long roleId; | ||||
|  | ||||
|     /** | ||||
|      * 角色名称 | ||||
|      */ | ||||
|     private String roleName; | ||||
|  | ||||
|     /** | ||||
|      * 角色权限 | ||||
|      */ | ||||
|     private String roleKey; | ||||
|  | ||||
|     /** | ||||
|      * 数据范围(1:所有数据权限;2:自定义数据权限;3:本部门数据权限;4:本部门及以下数据权限;5:仅本人数据权限) | ||||
|      */ | ||||
|     private String dataScope; | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,45 @@ | ||||
| package org.dromara.common.core.domain.dto; | ||||
|  | ||||
|  | ||||
| import lombok.Data; | ||||
|  | ||||
| import java.io.Serial; | ||||
| import java.io.Serializable; | ||||
| import java.util.HashMap; | ||||
| import java.util.Map; | ||||
| import java.util.Objects; | ||||
|  | ||||
| /** | ||||
|  * 启动流程对象 | ||||
|  * | ||||
|  * @author may | ||||
|  */ | ||||
| @Data | ||||
| public class StartProcessDTO implements Serializable { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 业务唯一值id | ||||
|      */ | ||||
|     private String businessId; | ||||
|  | ||||
|     /** | ||||
|      * 流程定义编码 | ||||
|      */ | ||||
|     private String flowCode; | ||||
|  | ||||
|     /** | ||||
|      * 流程变量,前端会提交一个元素{'entity': {业务详情数据对象}} | ||||
|      */ | ||||
|     private Map<String, Object> variables; | ||||
|  | ||||
|     public Map<String, Object> getVariables() { | ||||
|         if (variables == null) { | ||||
|             return new HashMap<>(16); | ||||
|         } | ||||
|         variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue())); | ||||
|         return variables; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,30 @@ | ||||
| package org.dromara.common.core.domain.dto; | ||||
|  | ||||
|  | ||||
| import lombok.Data; | ||||
|  | ||||
| import java.io.Serial; | ||||
| import java.io.Serializable; | ||||
|  | ||||
| /** | ||||
|  * 启动流程返回对象 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @Data | ||||
| public class StartProcessReturnDTO implements Serializable { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 流程实例id | ||||
|      */ | ||||
|     private Long processInstanceId; | ||||
|  | ||||
|     /** | ||||
|      * 任务id | ||||
|      */ | ||||
|     private Long taskId; | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,101 @@ | ||||
| package org.dromara.common.core.domain.dto; | ||||
|  | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.Data; | ||||
| import lombok.NoArgsConstructor; | ||||
|  | ||||
| import java.io.Serial; | ||||
| import java.io.Serializable; | ||||
| import java.util.Date; | ||||
| import java.util.List; | ||||
| import java.util.function.Function; | ||||
| import java.util.stream.Collectors; | ||||
|  | ||||
| /** | ||||
|  * 任务受让人 | ||||
|  * | ||||
|  * @author AprilWind | ||||
|  */ | ||||
| @Data | ||||
| @NoArgsConstructor | ||||
| public class TaskAssigneeDTO implements Serializable { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 总大小 | ||||
|      */ | ||||
|     private Long total = 0L; | ||||
|  | ||||
|     /** | ||||
|      * | ||||
|      */ | ||||
|     private List<TaskHandler> list; | ||||
|  | ||||
|     public TaskAssigneeDTO(Long total, List<TaskHandler> list) { | ||||
|         this.total = total; | ||||
|         this.list = list; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 将源列表转换为 TaskHandler 列表 | ||||
|      * | ||||
|      * @param <T>              通用类型 | ||||
|      * @param sourceList       待转换的源列表 | ||||
|      * @param storageId        提取 storageId 的函数 | ||||
|      * @param handlerCode      提取 handlerCode 的函数 | ||||
|      * @param handlerName      提取 handlerName 的函数 | ||||
|      * @param groupName        提取 groupName 的函数 | ||||
|      * @param createTimeMapper 提取 createTime 的函数 | ||||
|      * @return 转换后的 TaskHandler 列表 | ||||
|      */ | ||||
|     public static <T> List<TaskHandler> convertToHandlerList( | ||||
|         List<T> sourceList, | ||||
|         Function<T, Long> storageId, | ||||
|         Function<T, String> handlerCode, | ||||
|         Function<T, String> handlerName, | ||||
|         Function<T, Long> groupName, | ||||
|         Function<T, Date> createTimeMapper) { | ||||
|         return sourceList.stream() | ||||
|             .map(item -> new TaskHandler( | ||||
|                 String.valueOf(storageId.apply(item)), | ||||
|                 handlerCode.apply(item), | ||||
|                 handlerName.apply(item), | ||||
|                 groupName != null ? String.valueOf(groupName.apply(item)) : null, | ||||
|                 createTimeMapper.apply(item) | ||||
|             )).collect(Collectors.toList()); | ||||
|     } | ||||
|  | ||||
|     @Data | ||||
|     @NoArgsConstructor | ||||
|     @AllArgsConstructor | ||||
|     public static class TaskHandler { | ||||
|  | ||||
|         /** | ||||
|          * 主键 | ||||
|          */ | ||||
|         private String storageId; | ||||
|  | ||||
|         /** | ||||
|          * 权限编码 | ||||
|          */ | ||||
|         private String handlerCode; | ||||
|  | ||||
|         /** | ||||
|          * 权限名称 | ||||
|          */ | ||||
|         private String handlerName; | ||||
|  | ||||
|         /** | ||||
|          * 权限分组 | ||||
|          */ | ||||
|         private String groupName; | ||||
|  | ||||
|         /** | ||||
|          * 创建时间 | ||||
|          */ | ||||
|         private Date createTime; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,73 @@ | ||||
| package org.dromara.common.core.domain.dto; | ||||
|  | ||||
| import lombok.Data; | ||||
| import lombok.NoArgsConstructor; | ||||
|  | ||||
| import java.io.Serial; | ||||
| import java.io.Serializable; | ||||
| import java.util.Date; | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * 用户 | ||||
|  * | ||||
|  * @author Michelle.Chung | ||||
|  */ | ||||
| @Data | ||||
| @NoArgsConstructor | ||||
| public class UserDTO implements Serializable { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 用户ID | ||||
|      */ | ||||
|     private Long userId; | ||||
|  | ||||
|     /** | ||||
|      * 部门ID | ||||
|      */ | ||||
|     private Long deptId; | ||||
|  | ||||
|     /** | ||||
|      * 用户账号 | ||||
|      */ | ||||
|     private String userName; | ||||
|  | ||||
|     /** | ||||
|      * 用户昵称 | ||||
|      */ | ||||
|     private String nickName; | ||||
|  | ||||
|     /** | ||||
|      * 用户类型(sys_user系统用户) | ||||
|      */ | ||||
|     private String userType; | ||||
|  | ||||
|     /** | ||||
|      * 用户邮箱 | ||||
|      */ | ||||
|     private String email; | ||||
|  | ||||
|     /** | ||||
|      * 手机号码 | ||||
|      */ | ||||
|     private String phonenumber; | ||||
|  | ||||
|     /** | ||||
|      * 用户性别(0男 1女 2未知) | ||||
|      */ | ||||
|     private String sex; | ||||
|  | ||||
|     /** | ||||
|      * 帐号状态(0正常 1停用) | ||||
|      */ | ||||
|     private String status; | ||||
|  | ||||
|     /** | ||||
|      * 创建时间 | ||||
|      */ | ||||
|     private Date createTime; | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,72 @@ | ||||
| package org.dromara.common.core.domain.dto; | ||||
|  | ||||
| import lombok.Data; | ||||
| import lombok.NoArgsConstructor; | ||||
|  | ||||
| import java.io.Serial; | ||||
| import java.io.Serializable; | ||||
|  | ||||
| /** | ||||
|  * 当前在线会话 | ||||
|  * | ||||
|  * @author ruoyi | ||||
|  */ | ||||
|  | ||||
| @Data | ||||
| @NoArgsConstructor | ||||
| public class UserOnlineDTO implements Serializable { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 会话编号 | ||||
|      */ | ||||
|     private String tokenId; | ||||
|  | ||||
|     /** | ||||
|      * 部门名称 | ||||
|      */ | ||||
|     private String deptName; | ||||
|  | ||||
|     /** | ||||
|      * 用户名称 | ||||
|      */ | ||||
|     private String userName; | ||||
|  | ||||
|     /** | ||||
|      * 客户端 | ||||
|      */ | ||||
|     private String clientKey; | ||||
|  | ||||
|     /** | ||||
|      * 设备类型 | ||||
|      */ | ||||
|     private String deviceType; | ||||
|  | ||||
|     /** | ||||
|      * 登录IP地址 | ||||
|      */ | ||||
|     private String ipaddr; | ||||
|  | ||||
|     /** | ||||
|      * 登录地址 | ||||
|      */ | ||||
|     private String loginLocation; | ||||
|  | ||||
|     /** | ||||
|      * 浏览器类型 | ||||
|      */ | ||||
|     private String browser; | ||||
|  | ||||
|     /** | ||||
|      * 操作系统 | ||||
|      */ | ||||
|     private String os; | ||||
|  | ||||
|     /** | ||||
|      * 登录时间 | ||||
|      */ | ||||
|     private Long loginTime; | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,34 @@ | ||||
| package org.dromara.common.core.domain.event; | ||||
|  | ||||
| import lombok.Data; | ||||
|  | ||||
| import java.io.Serial; | ||||
| import java.io.Serializable; | ||||
|  | ||||
| /** | ||||
|  * 删除流程监听 | ||||
|  * | ||||
|  * @author AprilWind | ||||
|  */ | ||||
| @Data | ||||
| public class ProcessDeleteEvent implements Serializable { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 租户ID | ||||
|      */ | ||||
|     private String tenantId; | ||||
|  | ||||
|     /** | ||||
|      * 流程定义编码 | ||||
|      */ | ||||
|     private String flowCode; | ||||
|  | ||||
|     /** | ||||
|      * 业务id | ||||
|      */ | ||||
|     private String businessId; | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,50 @@ | ||||
| package org.dromara.common.core.domain.event; | ||||
|  | ||||
| import lombok.Data; | ||||
|  | ||||
| import java.io.Serial; | ||||
| import java.io.Serializable; | ||||
| import java.util.Map; | ||||
|  | ||||
| /** | ||||
|  * 总体流程监听 | ||||
|  * | ||||
|  * @author may | ||||
|  */ | ||||
| @Data | ||||
| public class ProcessEvent implements Serializable { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 租户ID | ||||
|      */ | ||||
|     private String tenantId; | ||||
|  | ||||
|     /** | ||||
|      * 流程定义编码 | ||||
|      */ | ||||
|     private String flowCode; | ||||
|  | ||||
|     /** | ||||
|      * 业务id | ||||
|      */ | ||||
|     private String businessId; | ||||
|  | ||||
|     /** | ||||
|      * 状态 | ||||
|      */ | ||||
|     private String status; | ||||
|  | ||||
|     /** | ||||
|      * 办理参数 | ||||
|      */ | ||||
|     private Map<String, Object> params; | ||||
|  | ||||
|     /** | ||||
|      * 当为true时为申请人节点办理 | ||||
|      */ | ||||
|     private boolean submit; | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,44 @@ | ||||
| package org.dromara.common.core.domain.event; | ||||
|  | ||||
| import lombok.Data; | ||||
|  | ||||
| import java.io.Serial; | ||||
| import java.io.Serializable; | ||||
|  | ||||
| /** | ||||
|  * 流程办理监听 | ||||
|  * | ||||
|  * @author may | ||||
|  */ | ||||
| @Data | ||||
| public class ProcessTaskEvent implements Serializable { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 租户ID | ||||
|      */ | ||||
|     private String tenantId; | ||||
|  | ||||
|     /** | ||||
|      * 流程定义编码 | ||||
|      */ | ||||
|     private String flowCode; | ||||
|  | ||||
|     /** | ||||
|      * 审批节点编码 | ||||
|      */ | ||||
|     private String nodeCode; | ||||
|  | ||||
|     /** | ||||
|      * 任务id | ||||
|      */ | ||||
|     private Long taskId; | ||||
|  | ||||
|     /** | ||||
|      * 业务id | ||||
|      */ | ||||
|     private String businessId; | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,31 @@ | ||||
| package org.dromara.common.core.domain.model; | ||||
|  | ||||
| import jakarta.validation.constraints.Email; | ||||
| import jakarta.validation.constraints.NotBlank; | ||||
| import lombok.Data; | ||||
| import lombok.EqualsAndHashCode; | ||||
|  | ||||
| /** | ||||
|  * 邮件登录对象 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
|  | ||||
| @Data | ||||
| @EqualsAndHashCode(callSuper = true) | ||||
| public class EmailLoginBody extends LoginBody { | ||||
|  | ||||
|     /** | ||||
|      * 邮箱 | ||||
|      */ | ||||
|     @NotBlank(message = "{user.email.not.blank}") | ||||
|     @Email(message = "{user.email.not.valid}") | ||||
|     private String email; | ||||
|  | ||||
|     /** | ||||
|      * 邮箱code | ||||
|      */ | ||||
|     @NotBlank(message = "{email.code.not.blank}") | ||||
|     private String emailCode; | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,48 @@ | ||||
| package org.dromara.common.core.domain.model; | ||||
|  | ||||
| import jakarta.validation.constraints.NotBlank; | ||||
| import lombok.Data; | ||||
|  | ||||
| import java.io.Serial; | ||||
| import java.io.Serializable; | ||||
|  | ||||
| /** | ||||
|  * 用户登录对象 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
|  | ||||
| @Data | ||||
| public class LoginBody implements Serializable { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 客户端id | ||||
|      */ | ||||
|     @NotBlank(message = "{auth.clientid.not.blank}") | ||||
|     private String clientId; | ||||
|  | ||||
|     /** | ||||
|      * 授权类型 | ||||
|      */ | ||||
|     @NotBlank(message = "{auth.grant.type.not.blank}") | ||||
|     private String grantType; | ||||
|  | ||||
|     /** | ||||
|      * 租户ID | ||||
|      */ | ||||
|     private String tenantId; | ||||
|  | ||||
|     /** | ||||
|      * 验证码 | ||||
|      */ | ||||
|     private String code; | ||||
|  | ||||
|     /** | ||||
|      * 唯一标识 | ||||
|      */ | ||||
|     private String uuid; | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,148 @@ | ||||
| package org.dromara.common.core.domain.model; | ||||
|  | ||||
| import lombok.Data; | ||||
| import lombok.NoArgsConstructor; | ||||
| import org.dromara.common.core.domain.dto.PostDTO; | ||||
| import org.dromara.common.core.domain.dto.RoleDTO; | ||||
|  | ||||
| import java.io.Serial; | ||||
| import java.io.Serializable; | ||||
| import java.util.List; | ||||
| import java.util.Set; | ||||
|  | ||||
| /** | ||||
|  * 登录用户身份权限 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @Data | ||||
| @NoArgsConstructor | ||||
| public class LoginUser implements Serializable { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 租户ID | ||||
|      */ | ||||
|     private String tenantId; | ||||
|  | ||||
|     /** | ||||
|      * 用户ID | ||||
|      */ | ||||
|     private Long userId; | ||||
|  | ||||
|     /** | ||||
|      * 部门ID | ||||
|      */ | ||||
|     private Long deptId; | ||||
|  | ||||
|     /** | ||||
|      * 部门类别编码 | ||||
|      */ | ||||
|     private String deptCategory; | ||||
|  | ||||
|     /** | ||||
|      * 部门名 | ||||
|      */ | ||||
|     private String deptName; | ||||
|  | ||||
|     /** | ||||
|      * 用户唯一标识 | ||||
|      */ | ||||
|     private String token; | ||||
|  | ||||
|     /** | ||||
|      * 用户类型 | ||||
|      */ | ||||
|     private String userType; | ||||
|  | ||||
|     /** | ||||
|      * 登录时间 | ||||
|      */ | ||||
|     private Long loginTime; | ||||
|  | ||||
|     /** | ||||
|      * 过期时间 | ||||
|      */ | ||||
|     private Long expireTime; | ||||
|  | ||||
|     /** | ||||
|      * 登录IP地址 | ||||
|      */ | ||||
|     private String ipaddr; | ||||
|  | ||||
|     /** | ||||
|      * 登录地点 | ||||
|      */ | ||||
|     private String loginLocation; | ||||
|  | ||||
|     /** | ||||
|      * 浏览器类型 | ||||
|      */ | ||||
|     private String browser; | ||||
|  | ||||
|     /** | ||||
|      * 操作系统 | ||||
|      */ | ||||
|     private String os; | ||||
|  | ||||
|     /** | ||||
|      * 菜单权限 | ||||
|      */ | ||||
|     private Set<String> menuPermission; | ||||
|  | ||||
|     /** | ||||
|      * 角色权限 | ||||
|      */ | ||||
|     private Set<String> rolePermission; | ||||
|  | ||||
|     /** | ||||
|      * 用户名 | ||||
|      */ | ||||
|     private String username; | ||||
|  | ||||
|     /** | ||||
|      * 用户昵称 | ||||
|      */ | ||||
|     private String nickname; | ||||
|  | ||||
|     /** | ||||
|      * 角色对象 | ||||
|      */ | ||||
|     private List<RoleDTO> roles; | ||||
|  | ||||
|     /** | ||||
|      * 岗位对象 | ||||
|      */ | ||||
|     private List<PostDTO> posts; | ||||
|  | ||||
|     /** | ||||
|      * 数据权限 当前角色ID | ||||
|      */ | ||||
|     private Long roleId; | ||||
|  | ||||
|     /** | ||||
|      * 客户端 | ||||
|      */ | ||||
|     private String clientKey; | ||||
|  | ||||
|     /** | ||||
|      * 设备类型 | ||||
|      */ | ||||
|     private String deviceType; | ||||
|  | ||||
|     /** | ||||
|      * 获取登录id | ||||
|      */ | ||||
|     public String getLoginId() { | ||||
|         if (userType == null) { | ||||
|             throw new IllegalArgumentException("用户类型不能为空"); | ||||
|         } | ||||
|         if (userId == null) { | ||||
|             throw new IllegalArgumentException("用户ID不能为空"); | ||||
|         } | ||||
|         return userType + ":" + userId; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,31 @@ | ||||
| package org.dromara.common.core.domain.model; | ||||
|  | ||||
| import jakarta.validation.constraints.NotBlank; | ||||
| import lombok.Data; | ||||
| import lombok.EqualsAndHashCode; | ||||
| import org.hibernate.validator.constraints.Length; | ||||
|  | ||||
| /** | ||||
|  * 密码登录对象 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @Data | ||||
| @EqualsAndHashCode(callSuper = true) | ||||
| public class PasswordLoginBody extends LoginBody { | ||||
|  | ||||
|     /** | ||||
|      * 用户名 | ||||
|      */ | ||||
|     @NotBlank(message = "{user.username.not.blank}") | ||||
|     @Length(min = 2, max = 20, message = "{user.username.length.valid}") | ||||
|     private String username; | ||||
|  | ||||
|     /** | ||||
|      * 用户密码 | ||||
|      */ | ||||
|     @NotBlank(message = "{user.password.not.blank}") | ||||
|     @Length(min = 5, max = 20, message = "{user.password.length.valid}") | ||||
|     private String password; | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,33 @@ | ||||
| package org.dromara.common.core.domain.model; | ||||
|  | ||||
| import jakarta.validation.constraints.NotBlank; | ||||
| import lombok.Data; | ||||
| import lombok.EqualsAndHashCode; | ||||
| import org.hibernate.validator.constraints.Length; | ||||
|  | ||||
| /** | ||||
|  * 用户注册对象 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @Data | ||||
| @EqualsAndHashCode(callSuper = true) | ||||
| public class RegisterBody extends LoginBody { | ||||
|  | ||||
|     /** | ||||
|      * 用户名 | ||||
|      */ | ||||
|     @NotBlank(message = "{user.username.not.blank}") | ||||
|     @Length(min = 2, max = 20, message = "{user.username.length.valid}") | ||||
|     private String username; | ||||
|  | ||||
|     /** | ||||
|      * 用户密码 | ||||
|      */ | ||||
|     @NotBlank(message = "{user.password.not.blank}") | ||||
|     @Length(min = 5, max = 20, message = "{user.password.length.valid}") | ||||
|     private String password; | ||||
|  | ||||
|     private String userType; | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,29 @@ | ||||
| package org.dromara.common.core.domain.model; | ||||
|  | ||||
| import jakarta.validation.constraints.NotBlank; | ||||
| import lombok.Data; | ||||
| import lombok.EqualsAndHashCode; | ||||
|  | ||||
| /** | ||||
|  * 短信登录对象 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
|  | ||||
| @Data | ||||
| @EqualsAndHashCode(callSuper = true) | ||||
| public class SmsLoginBody extends LoginBody { | ||||
|  | ||||
|     /** | ||||
|      * 手机号 | ||||
|      */ | ||||
|     @NotBlank(message = "{user.phonenumber.not.blank}") | ||||
|     private String phonenumber; | ||||
|  | ||||
|     /** | ||||
|      * 短信code | ||||
|      */ | ||||
|     @NotBlank(message = "{sms.code.not.blank}") | ||||
|     private String smsCode; | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,35 @@ | ||||
| package org.dromara.common.core.domain.model; | ||||
|  | ||||
| import jakarta.validation.constraints.NotBlank; | ||||
| import lombok.Data; | ||||
| import lombok.EqualsAndHashCode; | ||||
|  | ||||
| /** | ||||
|  * 三方登录对象 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
|  | ||||
| @Data | ||||
| @EqualsAndHashCode(callSuper = true) | ||||
| public class SocialLoginBody extends LoginBody { | ||||
|  | ||||
|     /** | ||||
|      * 第三方登录平台 | ||||
|      */ | ||||
|     @NotBlank(message = "{social.source.not.blank}") | ||||
|     private String source; | ||||
|  | ||||
|     /** | ||||
|      * 第三方登录code | ||||
|      */ | ||||
|     @NotBlank(message = "{social.code.not.blank}") | ||||
|     private String socialCode; | ||||
|  | ||||
|     /** | ||||
|      * 第三方登录socialState | ||||
|      */ | ||||
|     @NotBlank(message = "{social.state.not.blank}") | ||||
|     private String socialState; | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,56 @@ | ||||
| package org.dromara.common.core.domain.model; | ||||
|  | ||||
| import lombok.Data; | ||||
| import lombok.NoArgsConstructor; | ||||
|  | ||||
| import java.io.Serial; | ||||
| import java.io.Serializable; | ||||
|  | ||||
| /** | ||||
|  * 任务受让人 | ||||
|  * | ||||
|  * @author AprilWind | ||||
|  */ | ||||
| @Data | ||||
| @NoArgsConstructor | ||||
| public class TaskAssigneeBody implements Serializable { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 权限编码 | ||||
|      */ | ||||
|     private String handlerCode; | ||||
|  | ||||
|     /** | ||||
|      * 权限名称 | ||||
|      */ | ||||
|     private String handlerName; | ||||
|  | ||||
|     /** | ||||
|      * 权限分组 | ||||
|      */ | ||||
|     private String groupId; | ||||
|  | ||||
|     /** | ||||
|      * 开始时间 | ||||
|      */ | ||||
|     private String beginTime; | ||||
|  | ||||
|     /** | ||||
|      * 结束时间 | ||||
|      */ | ||||
|     private String endTime; | ||||
|  | ||||
|     /** | ||||
|      * 当前页 | ||||
|      */ | ||||
|     private Integer pageNum = 1; | ||||
|  | ||||
|     /** | ||||
|      * 每页显示条数 | ||||
|      */ | ||||
|     private Integer pageSize = 10; | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,28 @@ | ||||
| package org.dromara.common.core.domain.model; | ||||
|  | ||||
| import jakarta.validation.constraints.NotBlank; | ||||
| import lombok.Data; | ||||
| import lombok.EqualsAndHashCode; | ||||
|  | ||||
| /** | ||||
|  * 三方登录对象 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
|  | ||||
| @Data | ||||
| @EqualsAndHashCode(callSuper = true) | ||||
| public class XcxLoginBody extends LoginBody { | ||||
|  | ||||
|     /** | ||||
|      * 小程序id(多个小程序时使用) | ||||
|      */ | ||||
|     private String appid; | ||||
|  | ||||
|     /** | ||||
|      * 小程序code | ||||
|      */ | ||||
|     @NotBlank(message = "{xcx.code.not.blank}") | ||||
|     private String xcxCode; | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,27 @@ | ||||
| package org.dromara.common.core.domain.model; | ||||
|  | ||||
| import lombok.Data; | ||||
| import lombok.EqualsAndHashCode; | ||||
| import lombok.NoArgsConstructor; | ||||
|  | ||||
| import java.io.Serial; | ||||
|  | ||||
| /** | ||||
|  * 小程序登录用户身份权限 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @Data | ||||
| @EqualsAndHashCode(callSuper = true) | ||||
| @NoArgsConstructor | ||||
| public class XcxLoginUser extends LoginUser { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * openid | ||||
|      */ | ||||
|     private String openid; | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,215 @@ | ||||
| package org.dromara.common.core.enums; | ||||
|  | ||||
| import cn.hutool.core.util.StrUtil; | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.Getter; | ||||
| import org.dromara.common.core.exception.ServiceException; | ||||
| import org.dromara.common.core.utils.StringUtils; | ||||
|  | ||||
| import java.util.Arrays; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.function.Function; | ||||
| import java.util.stream.Collectors; | ||||
|  | ||||
| /** | ||||
|  * 业务状态枚举 | ||||
|  * | ||||
|  * @author may | ||||
|  */ | ||||
| @Getter | ||||
| @AllArgsConstructor | ||||
| public enum BusinessStatusEnum { | ||||
|  | ||||
|     /** | ||||
|      * 已撤销 | ||||
|      */ | ||||
|     CANCEL("cancel", "已撤销"), | ||||
|  | ||||
|     /** | ||||
|      * 草稿 | ||||
|      */ | ||||
|     DRAFT("draft", "草稿"), | ||||
|  | ||||
|     /** | ||||
|      * 待审核 | ||||
|      */ | ||||
|     WAITING("waiting", "待审核"), | ||||
|  | ||||
|     /** | ||||
|      * 已完成 | ||||
|      */ | ||||
|     FINISH("finish", "已完成"), | ||||
|  | ||||
|     /** | ||||
|      * 已作废 | ||||
|      */ | ||||
|     INVALID("invalid", "已作废"), | ||||
|  | ||||
|     /** | ||||
|      * 已退回 | ||||
|      */ | ||||
|     BACK("back", "已退回"), | ||||
|  | ||||
|     /** | ||||
|      * 已终止 | ||||
|      */ | ||||
|     TERMINATION("termination", "已终止"); | ||||
|  | ||||
|     /** | ||||
|      * 状态 | ||||
|      */ | ||||
|     private final String status; | ||||
|  | ||||
|     /** | ||||
|      * 描述 | ||||
|      */ | ||||
|     private final String desc; | ||||
|  | ||||
|     private static final Map<String, BusinessStatusEnum> STATUS_MAP = Arrays.stream(BusinessStatusEnum.values()) | ||||
|         .collect(Collectors.toConcurrentMap(BusinessStatusEnum::getStatus, Function.identity())); | ||||
|  | ||||
|     /** | ||||
|      * 根据状态获取对应的 BusinessStatusEnum 枚举 | ||||
|      * | ||||
|      * @param status 业务状态码 | ||||
|      * @return 对应的 BusinessStatusEnum 枚举,如果找不到则返回 null | ||||
|      */ | ||||
|     public static BusinessStatusEnum getByStatus(String status) { | ||||
|         // 使用 STATUS_MAP 获取对应的枚举,若找不到则返回 null | ||||
|         return STATUS_MAP.get(status); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 根据状态获取对应的业务状态描述信息 | ||||
|      * | ||||
|      * @param status 业务状态码 | ||||
|      * @return 返回业务状态描述,若状态码为空或未找到对应的枚举,返回空字符串 | ||||
|      */ | ||||
|     public static String findByStatus(String status) { | ||||
|         if (StringUtils.isBlank(status)) { | ||||
|             return StrUtil.EMPTY; | ||||
|         } | ||||
|         BusinessStatusEnum statusEnum = STATUS_MAP.get(status); | ||||
|         return (statusEnum != null) ? statusEnum.getDesc() : StrUtil.EMPTY; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 判断是否为指定的状态之一:草稿、已撤销或已退回 | ||||
|      * | ||||
|      * @param status 要检查的状态 | ||||
|      * @return 如果状态为草稿、已撤销或已退回之一,则返回 true;否则返回 false | ||||
|      */ | ||||
|     public static boolean isDraftOrCancelOrBack(String status) { | ||||
|         return DRAFT.status.equals(status) || CANCEL.status.equals(status) || BACK.status.equals(status); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 判断是否为撤销,退回,作废,终止 | ||||
|      * | ||||
|      * @param status status | ||||
|      * @return 结果 | ||||
|      */ | ||||
|     public static boolean initialState(String status) { | ||||
|         return CANCEL.status.equals(status) || BACK.status.equals(status) || INVALID.status.equals(status) || TERMINATION.status.equals(status); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取运行中的实例状态列表 | ||||
|      * | ||||
|      * @return 包含运行中实例状态的不可变列表 | ||||
|      * (包含 DRAFT、WAITING、BACK 和 CANCEL 状态) | ||||
|      */ | ||||
|     public static List<String> runningStatus() { | ||||
|         return Arrays.asList(DRAFT.status, WAITING.status, BACK.status, CANCEL.status); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取结束实例的状态列表 | ||||
|      * | ||||
|      * @return 包含结束实例状态的不可变列表 | ||||
|      * (包含 FINISH、INVALID 和 TERMINATION 状态) | ||||
|      */ | ||||
|     public static List<String> finishStatus() { | ||||
|         return Arrays.asList(FINISH.status, INVALID.status, TERMINATION.status); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 启动流程校验 | ||||
|      * | ||||
|      * @param status 状态 | ||||
|      */ | ||||
|     public static void checkStartStatus(String status) { | ||||
|         if (WAITING.getStatus().equals(status)) { | ||||
|             throw new ServiceException("该单据已提交过申请,正在审批中!"); | ||||
|         } else if (FINISH.getStatus().equals(status)) { | ||||
|             throw new ServiceException("该单据已完成申请!"); | ||||
|         } else if (INVALID.getStatus().equals(status)) { | ||||
|             throw new ServiceException("该单据已作废!"); | ||||
|         } else if (TERMINATION.getStatus().equals(status)) { | ||||
|             throw new ServiceException("该单据已终止!"); | ||||
|         } else if (StringUtils.isBlank(status)) { | ||||
|             throw new ServiceException("流程状态为空!"); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 撤销流程校验 | ||||
|      * | ||||
|      * @param status 状态 | ||||
|      */ | ||||
|     public static void checkCancelStatus(String status) { | ||||
|         if (CANCEL.getStatus().equals(status)) { | ||||
|             throw new ServiceException("该单据已撤销!"); | ||||
|         } else if (FINISH.getStatus().equals(status)) { | ||||
|             throw new ServiceException("该单据已完成申请!"); | ||||
|         } else if (INVALID.getStatus().equals(status)) { | ||||
|             throw new ServiceException("该单据已作废!"); | ||||
|         } else if (TERMINATION.getStatus().equals(status)) { | ||||
|             throw new ServiceException("该单据已终止!"); | ||||
|         } else if (BACK.getStatus().equals(status)) { | ||||
|             throw new ServiceException("该单据已退回!"); | ||||
|         } else if (StringUtils.isBlank(status)) { | ||||
|             throw new ServiceException("流程状态为空!"); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 驳回流程校验 | ||||
|      * | ||||
|      * @param status 状态 | ||||
|      */ | ||||
|     public static void checkBackStatus(String status) { | ||||
|         if (BACK.getStatus().equals(status)) { | ||||
|             throw new ServiceException("该单据已退回!"); | ||||
|         } else if (FINISH.getStatus().equals(status)) { | ||||
|             throw new ServiceException("该单据已完成申请!"); | ||||
|         } else if (INVALID.getStatus().equals(status)) { | ||||
|             throw new ServiceException("该单据已作废!"); | ||||
|         } else if (TERMINATION.getStatus().equals(status)) { | ||||
|             throw new ServiceException("该单据已终止!"); | ||||
|         } else if (CANCEL.getStatus().equals(status)) { | ||||
|             throw new ServiceException("该单据已撤销!"); | ||||
|         } else if (StringUtils.isBlank(status)) { | ||||
|             throw new ServiceException("流程状态为空!"); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 作废,终止流程校验 | ||||
|      * | ||||
|      * @param status 状态 | ||||
|      */ | ||||
|     public static void checkInvalidStatus(String status) { | ||||
|         if (FINISH.getStatus().equals(status)) { | ||||
|             throw new ServiceException("该单据已完成申请!"); | ||||
|         } else if (INVALID.getStatus().equals(status)) { | ||||
|             throw new ServiceException("该单据已作废!"); | ||||
|         } else if (TERMINATION.getStatus().equals(status)) { | ||||
|             throw new ServiceException("该单据已终止!"); | ||||
|         } else if (StringUtils.isBlank(status)) { | ||||
|             throw new ServiceException("流程状态为空!"); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,37 @@ | ||||
| package org.dromara.common.core.enums; | ||||
|  | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.Getter; | ||||
|  | ||||
| /** | ||||
|  * 设备类型 | ||||
|  * 针对一套 用户体系 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @Getter | ||||
| @AllArgsConstructor | ||||
| public enum DeviceType { | ||||
|  | ||||
|     /** | ||||
|      * pc端 | ||||
|      */ | ||||
|     PC("pc"), | ||||
|  | ||||
|     /** | ||||
|      * app端 | ||||
|      */ | ||||
|     APP("app"), | ||||
|  | ||||
|     /** | ||||
|      * 小程序端 | ||||
|      */ | ||||
|     XCX("xcx"), | ||||
|  | ||||
|     /** | ||||
|      * social第三方端 | ||||
|      */ | ||||
|     SOCIAL("social"); | ||||
|  | ||||
|     private final String device; | ||||
| } | ||||
| @ -0,0 +1,146 @@ | ||||
| package org.dromara.common.core.enums; | ||||
|  | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.Getter; | ||||
| import org.dromara.common.core.utils.StringUtils; | ||||
|  | ||||
| /* | ||||
|  * 日期格式 | ||||
|  * "yyyy":4位数的年份,例如:2023年表示为"2023"。 | ||||
|  * "yy":2位数的年份,例如:2023年表示为"23"。 | ||||
|  * "MM":2位数的月份,取值范围为01到12,例如:7月表示为"07"。 | ||||
|  * "M":不带前导零的月份,取值范围为1到12,例如:7月表示为"7"。 | ||||
|  * "dd":2位数的日期,取值范围为01到31,例如:22日表示为"22"。 | ||||
|  * "d":不带前导零的日期,取值范围为1到31,例如:22日表示为"22"。 | ||||
|  * "EEEE":星期的全名,例如:星期三表示为"Wednesday"。 | ||||
|  * "E":星期的缩写,例如:星期三表示为"Wed"。 | ||||
|  * "DDD" 或 "D":一年中的第几天,取值范围为001到366,例如:第200天表示为"200"。 | ||||
|  * 时间格式 | ||||
|  * "HH":24小时制的小时数,取值范围为00到23,例如:下午5点表示为"17"。 | ||||
|  * "hh":12小时制的小时数,取值范围为01到12,例如:下午5点表示为"05"。 | ||||
|  * "mm":分钟数,取值范围为00到59,例如:30分钟表示为"30"。 | ||||
|  * "ss":秒数,取值范围为00到59,例如:45秒表示为"45"。 | ||||
|  * "SSS":毫秒数,取值范围为000到999,例如:123毫秒表示为"123"。 | ||||
|  */ | ||||
|  | ||||
| /** | ||||
|  * 日期格式与时间格式枚举 | ||||
|  */ | ||||
| @Getter | ||||
| @AllArgsConstructor | ||||
| public enum FormatsType { | ||||
|  | ||||
|     /** | ||||
|      * 例如:2023年表示为"23" | ||||
|      */ | ||||
|     YY("yy"), | ||||
|  | ||||
|     /** | ||||
|      * 例如:2023年表示为"2023" | ||||
|      */ | ||||
|     YYYY("yyyy"), | ||||
|  | ||||
|     /** | ||||
|      * 例例如,2023年7月可以表示为 "2023-07" | ||||
|      */ | ||||
|     YYYY_MM("yyyy-MM"), | ||||
|  | ||||
|     /** | ||||
|      * 例如,日期 "2023年7月22日" 可以表示为 "2023-07-22" | ||||
|      */ | ||||
|     YYYY_MM_DD("yyyy-MM-dd"), | ||||
|  | ||||
|     /** | ||||
|      * 例如,当前时间如果是 "2023年7月22日下午3点30分",则可以表示为 "2023-07-22 15:30" | ||||
|      */ | ||||
|     YYYY_MM_DD_HH_MM("yyyy-MM-dd HH:mm"), | ||||
|  | ||||
|     /** | ||||
|      * 例如,当前时间如果是 "2023年7月22日下午3点30分45秒",则可以表示为 "2023-07-22 15:30:45" | ||||
|      */ | ||||
|     YYYY_MM_DD_HH_MM_SS("yyyy-MM-dd HH:mm:ss"), | ||||
|  | ||||
|     /** | ||||
|      * 例如:下午3点30分45秒,表示为 "15:30:45" | ||||
|      */ | ||||
|     HH_MM_SS("HH:mm:ss"), | ||||
|  | ||||
|     /** | ||||
|      * 例例如,2023年7月可以表示为 "2023/07" | ||||
|      */ | ||||
|     YYYY_MM_SLASH("yyyy/MM"), | ||||
|  | ||||
|     /** | ||||
|      * 例如,日期 "2023年7月22日" 可以表示为 "2023/07/22" | ||||
|      */ | ||||
|     YYYY_MM_DD_SLASH("yyyy/MM/dd"), | ||||
|  | ||||
|     /** | ||||
|      * 例如,当前时间如果是 "2023年7月22日下午3点30分45秒",则可以表示为 "2023/07/22 15:30:45" | ||||
|      */ | ||||
|     YYYY_MM_DD_HH_MM_SLASH("yyyy/MM/dd HH:mm"), | ||||
|  | ||||
|     /** | ||||
|      * 例如,当前时间如果是 "2023年7月22日下午3点30分45秒",则可以表示为 "2023/07/22 15:30:45" | ||||
|      */ | ||||
|     YYYY_MM_DD_HH_MM_SS_SLASH("yyyy/MM/dd HH:mm:ss"), | ||||
|  | ||||
|     /** | ||||
|      * 例例如,2023年7月可以表示为 "2023.07" | ||||
|      */ | ||||
|     YYYY_MM_DOT("yyyy.MM"), | ||||
|  | ||||
|     /** | ||||
|      * 例如,日期 "2023年7月22日" 可以表示为 "2023.07.22" | ||||
|      */ | ||||
|     YYYY_MM_DD_DOT("yyyy.MM.dd"), | ||||
|  | ||||
|     /** | ||||
|      * 例如,当前时间如果是 "2023年7月22日下午3点30分",则可以表示为 "2023.07.22 15:30" | ||||
|      */ | ||||
|     YYYY_MM_DD_HH_MM_DOT("yyyy.MM.dd HH:mm"), | ||||
|  | ||||
|     /** | ||||
|      * 例如,当前时间如果是 "2023年7月22日下午3点30分45秒",则可以表示为 "2023.07.22 15:30:45" | ||||
|      */ | ||||
|     YYYY_MM_DD_HH_MM_SS_DOT("yyyy.MM.dd HH:mm:ss"), | ||||
|  | ||||
|     /** | ||||
|      * 例如,2023年7月可以表示为 "202307" | ||||
|      */ | ||||
|     YYYYMM("yyyyMM"), | ||||
|  | ||||
|     /** | ||||
|      * 例如,2023年7月22日可以表示为 "20230722" | ||||
|      */ | ||||
|     YYYYMMDD("yyyyMMdd"), | ||||
|  | ||||
|     /** | ||||
|      * 例如,2023年7月22日下午3点可以表示为 "2023072215" | ||||
|      */ | ||||
|     YYYYMMDDHH("yyyyMMddHH"), | ||||
|  | ||||
|     /** | ||||
|      * 例如,2023年7月22日下午3点30分可以表示为 "202307221530" | ||||
|      */ | ||||
|     YYYYMMDDHHMM("yyyyMMddHHmm"), | ||||
|  | ||||
|     /** | ||||
|      * 例如,2023年7月22日下午3点30分45秒可以表示为 "20230722153045" | ||||
|      */ | ||||
|     YYYYMMDDHHMMSS("yyyyMMddHHmmss"); | ||||
|  | ||||
|     /** | ||||
|      * 时间格式 | ||||
|      */ | ||||
|     private final String timeFormat; | ||||
|  | ||||
|     public static FormatsType getFormatsType(String str) { | ||||
|         for (FormatsType value : values()) { | ||||
|             if (StringUtils.contains(str, value.getTimeFormat())) { | ||||
|                 return value; | ||||
|             } | ||||
|         } | ||||
|         throw new RuntimeException("'FormatsType' not found By " + str); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,44 @@ | ||||
| package org.dromara.common.core.enums; | ||||
|  | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.Getter; | ||||
|  | ||||
| /** | ||||
|  * 登录类型 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @Getter | ||||
| @AllArgsConstructor | ||||
| public enum LoginType { | ||||
|  | ||||
|     /** | ||||
|      * 密码登录 | ||||
|      */ | ||||
|     PASSWORD("user.password.retry.limit.exceed", "user.password.retry.limit.count"), | ||||
|  | ||||
|     /** | ||||
|      * 短信登录 | ||||
|      */ | ||||
|     SMS("sms.code.retry.limit.exceed", "sms.code.retry.limit.count"), | ||||
|  | ||||
|     /** | ||||
|      * 邮箱登录 | ||||
|      */ | ||||
|     EMAIL("email.code.retry.limit.exceed", "email.code.retry.limit.count"), | ||||
|  | ||||
|     /** | ||||
|      * 小程序登录 | ||||
|      */ | ||||
|     XCX("", ""); | ||||
|  | ||||
|     /** | ||||
|      * 登录重试超出限制提示 | ||||
|      */ | ||||
|     final String retryLimitExceed; | ||||
|  | ||||
|     /** | ||||
|      * 登录重试限制计数提示 | ||||
|      */ | ||||
|     final String retryLimitCount; | ||||
| } | ||||
| @ -0,0 +1,30 @@ | ||||
| package org.dromara.common.core.enums; | ||||
|  | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.Getter; | ||||
|  | ||||
| /** | ||||
|  * 用户状态 | ||||
|  * | ||||
|  * @author ruoyi | ||||
|  */ | ||||
| @Getter | ||||
| @AllArgsConstructor | ||||
| public enum UserStatus { | ||||
|     /** | ||||
|      * 正常 | ||||
|      */ | ||||
|     OK("0", "正常"), | ||||
|     /** | ||||
|      * 停用 | ||||
|      */ | ||||
|     DISABLE("1", "停用"), | ||||
|     /** | ||||
|      * 删除 | ||||
|      */ | ||||
|     DELETED("2", "删除"); | ||||
|  | ||||
|     private final String code; | ||||
|     private final String info; | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,37 @@ | ||||
| package org.dromara.common.core.enums; | ||||
|  | ||||
| import org.dromara.common.core.utils.StringUtils; | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.Getter; | ||||
|  | ||||
| /** | ||||
|  * 设备类型 | ||||
|  * 针对多套 用户体系 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @Getter | ||||
| @AllArgsConstructor | ||||
| public enum UserType { | ||||
|  | ||||
|     /** | ||||
|      * pc端 | ||||
|      */ | ||||
|     SYS_USER("sys_user"), | ||||
|  | ||||
|     /** | ||||
|      * app端 | ||||
|      */ | ||||
|     APP_USER("app_user"); | ||||
|  | ||||
|     private final String userType; | ||||
|  | ||||
|     public static UserType getUserType(String str) { | ||||
|         for (UserType value : values()) { | ||||
|             if (StringUtils.contains(str, value.getUserType())) { | ||||
|                 return value; | ||||
|             } | ||||
|         } | ||||
|         throw new RuntimeException("'UserType' not found By " + str); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,59 @@ | ||||
| package org.dromara.common.core.exception; | ||||
|  | ||||
| import lombok.*; | ||||
|  | ||||
| import java.io.Serial; | ||||
|  | ||||
| /** | ||||
|  * 业务异常 | ||||
|  * | ||||
|  * @author ruoyi | ||||
|  */ | ||||
| @Data | ||||
| @EqualsAndHashCode(callSuper = true) | ||||
| @NoArgsConstructor | ||||
| @AllArgsConstructor | ||||
| public final class ServiceException extends RuntimeException { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 错误码 | ||||
|      */ | ||||
|     private Integer code; | ||||
|  | ||||
|     /** | ||||
|      * 错误提示 | ||||
|      */ | ||||
|     private String message; | ||||
|  | ||||
|     /** | ||||
|      * 错误明细,内部调试错误 | ||||
|      */ | ||||
|     private String detailMessage; | ||||
|  | ||||
|     public ServiceException(String message) { | ||||
|         this.message = message; | ||||
|     } | ||||
|  | ||||
|     public ServiceException(String message, Integer code) { | ||||
|         this.message = message; | ||||
|         this.code = code; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getMessage() { | ||||
|         return message; | ||||
|     } | ||||
|  | ||||
|     public ServiceException setMessage(String message) { | ||||
|         this.message = message; | ||||
|         return this; | ||||
|     } | ||||
|  | ||||
|     public ServiceException setDetailMessage(String detailMessage) { | ||||
|         this.detailMessage = detailMessage; | ||||
|         return this; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,62 @@ | ||||
| package org.dromara.common.core.exception; | ||||
|  | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.Data; | ||||
| import lombok.EqualsAndHashCode; | ||||
| import lombok.NoArgsConstructor; | ||||
|  | ||||
| import java.io.Serial; | ||||
|  | ||||
| /** | ||||
|  * sse 特制异常 | ||||
|  * | ||||
|  * @author LionLi | ||||
|  */ | ||||
| @Data | ||||
| @EqualsAndHashCode(callSuper = true) | ||||
| @NoArgsConstructor | ||||
| @AllArgsConstructor | ||||
| public final class SseException extends RuntimeException { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 错误码 | ||||
|      */ | ||||
|     private Integer code; | ||||
|  | ||||
|     /** | ||||
|      * 错误提示 | ||||
|      */ | ||||
|     private String message; | ||||
|  | ||||
|     /** | ||||
|      * 错误明细,内部调试错误 | ||||
|      */ | ||||
|     private String detailMessage; | ||||
|  | ||||
|     public SseException(String message) { | ||||
|         this.message = message; | ||||
|     } | ||||
|  | ||||
|     public SseException(String message, Integer code) { | ||||
|         this.message = message; | ||||
|         this.code = code; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getMessage() { | ||||
|         return message; | ||||
|     } | ||||
|  | ||||
|     public SseException setMessage(String message) { | ||||
|         this.message = message; | ||||
|         return this; | ||||
|     } | ||||
|  | ||||
|     public SseException setDetailMessage(String detailMessage) { | ||||
|         this.detailMessage = detailMessage; | ||||
|         return this; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,74 @@ | ||||
| package org.dromara.common.core.exception.base; | ||||
|  | ||||
| import lombok.AllArgsConstructor; | ||||
| import org.dromara.common.core.utils.MessageUtils; | ||||
| import org.dromara.common.core.utils.StringUtils; | ||||
| import lombok.Data; | ||||
| import lombok.EqualsAndHashCode; | ||||
| import lombok.NoArgsConstructor; | ||||
|  | ||||
| import java.io.Serial; | ||||
|  | ||||
| /** | ||||
|  * 基础异常 | ||||
|  * | ||||
|  * @author ruoyi | ||||
|  */ | ||||
| @Data | ||||
| @EqualsAndHashCode(callSuper = true) | ||||
| @NoArgsConstructor | ||||
| @AllArgsConstructor | ||||
| public class BaseException extends RuntimeException { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 所属模块 | ||||
|      */ | ||||
|     private String module; | ||||
|  | ||||
|     /** | ||||
|      * 错误码 | ||||
|      */ | ||||
|     private String code; | ||||
|  | ||||
|     /** | ||||
|      * 错误码对应的参数 | ||||
|      */ | ||||
|     private Object[] args; | ||||
|  | ||||
|     /** | ||||
|      * 错误消息 | ||||
|      */ | ||||
|     private String defaultMessage; | ||||
|  | ||||
|     public BaseException(String module, String code, Object[] args) { | ||||
|         this(module, code, args, null); | ||||
|     } | ||||
|  | ||||
|     public BaseException(String module, String defaultMessage) { | ||||
|         this(module, null, null, defaultMessage); | ||||
|     } | ||||
|  | ||||
|     public BaseException(String code, Object[] args) { | ||||
|         this(null, code, args, null); | ||||
|     } | ||||
|  | ||||
|     public BaseException(String defaultMessage) { | ||||
|         this(null, null, null, defaultMessage); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getMessage() { | ||||
|         String message = null; | ||||
|         if (!StringUtils.isEmpty(code)) { | ||||
|             message = MessageUtils.message(code, args); | ||||
|         } | ||||
|         if (message == null) { | ||||
|             message = defaultMessage; | ||||
|         } | ||||
|         return message; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,21 @@ | ||||
| package org.dromara.common.core.exception.file; | ||||
|  | ||||
| import org.dromara.common.core.exception.base.BaseException; | ||||
|  | ||||
| import java.io.Serial; | ||||
|  | ||||
| /** | ||||
|  * 文件信息异常类 | ||||
|  * | ||||
|  * @author ruoyi | ||||
|  */ | ||||
| public class FileException extends BaseException { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     public FileException(String code, Object[] args) { | ||||
|         super("file", code, args, null); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,18 @@ | ||||
| package org.dromara.common.core.exception.file; | ||||
|  | ||||
| import java.io.Serial; | ||||
|  | ||||
| /** | ||||
|  * 文件名称超长限制异常类 | ||||
|  * | ||||
|  * @author ruoyi | ||||
|  */ | ||||
| public class FileNameLengthLimitExceededException extends FileException { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     public FileNameLengthLimitExceededException(int defaultFileNameLength) { | ||||
|         super("upload.filename.exceed.length", new Object[]{defaultFileNameLength}); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,18 @@ | ||||
| package org.dromara.common.core.exception.file; | ||||
|  | ||||
| import java.io.Serial; | ||||
|  | ||||
| /** | ||||
|  * 文件名大小限制异常类 | ||||
|  * | ||||
|  * @author ruoyi | ||||
|  */ | ||||
| public class FileSizeLimitExceededException extends FileException { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     public FileSizeLimitExceededException(long defaultMaxSize) { | ||||
|         super("upload.exceed.maxSize", new Object[]{defaultMaxSize}); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,18 @@ | ||||
| package org.dromara.common.core.exception.user; | ||||
|  | ||||
| import java.io.Serial; | ||||
|  | ||||
| /** | ||||
|  * 验证码错误异常类 | ||||
|  * | ||||
|  * @author ruoyi | ||||
|  */ | ||||
| public class CaptchaException extends UserException { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     public CaptchaException() { | ||||
|         super("user.jcaptcha.error"); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,18 @@ | ||||
| package org.dromara.common.core.exception.user; | ||||
|  | ||||
| import java.io.Serial; | ||||
|  | ||||
| /** | ||||
|  * 验证码失效异常类 | ||||
|  * | ||||
|  * @author ruoyi | ||||
|  */ | ||||
| public class CaptchaExpireException extends UserException { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     public CaptchaExpireException() { | ||||
|         super("user.jcaptcha.expire"); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,20 @@ | ||||
| package org.dromara.common.core.exception.user; | ||||
|  | ||||
| import org.dromara.common.core.exception.base.BaseException; | ||||
|  | ||||
| import java.io.Serial; | ||||
|  | ||||
| /** | ||||
|  * 用户信息异常类 | ||||
|  * | ||||
|  * @author ruoyi | ||||
|  */ | ||||
| public class UserException extends BaseException { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     public UserException(String code, Object... args) { | ||||
|         super("user", code, args, null); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,52 @@ | ||||
| package org.dromara.common.core.factory; | ||||
|  | ||||
| import cn.hutool.core.lang.PatternPool; | ||||
| import org.dromara.common.core.constant.RegexConstants; | ||||
|  | ||||
| import java.util.regex.Pattern; | ||||
|  | ||||
| /** | ||||
|  * 正则表达式模式池工厂 | ||||
|  * <p>初始化的时候将正则表达式加入缓存池当中</p> | ||||
|  * <p>提高正则表达式的性能,避免重复编译相同的正则表达式</p> | ||||
|  * | ||||
|  * @author 21001 | ||||
|  */ | ||||
| public class RegexPatternPoolFactory extends PatternPool { | ||||
|  | ||||
|     /** | ||||
|      * 字典类型必须以字母开头,且只能为(小写字母,数字,下滑线) | ||||
|      */ | ||||
|     public static final Pattern DICTIONARY_TYPE = get(RegexConstants.DICTIONARY_TYPE); | ||||
|  | ||||
|     /** | ||||
|      * 身份证号码(后6位) | ||||
|      */ | ||||
|     public static final Pattern ID_CARD_LAST_6 = get(RegexConstants.ID_CARD_LAST_6); | ||||
|  | ||||
|     /** | ||||
|      * QQ号码 | ||||
|      */ | ||||
|     public static final Pattern QQ_NUMBER = get(RegexConstants.QQ_NUMBER); | ||||
|  | ||||
|     /** | ||||
|      * 邮政编码 | ||||
|      */ | ||||
|     public static final Pattern POSTAL_CODE = get(RegexConstants.POSTAL_CODE); | ||||
|  | ||||
|     /** | ||||
|      * 注册账号 | ||||
|      */ | ||||
|     public static final Pattern ACCOUNT = get(RegexConstants.ACCOUNT); | ||||
|  | ||||
|     /** | ||||
|      * 密码:包含至少8个字符,包括大写字母、小写字母、数字和特殊字符 | ||||
|      */ | ||||
|     public static final Pattern PASSWORD = get(RegexConstants.PASSWORD); | ||||
|  | ||||
|     /** | ||||
|      * 通用状态(0表示正常,1表示停用) | ||||
|      */ | ||||
|     public static final Pattern STATUS = get(RegexConstants.STATUS); | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,31 @@ | ||||
| package org.dromara.common.core.factory; | ||||
|  | ||||
| import org.dromara.common.core.utils.StringUtils; | ||||
| import org.springframework.beans.factory.config.YamlPropertiesFactoryBean; | ||||
| import org.springframework.core.env.PropertiesPropertySource; | ||||
| import org.springframework.core.env.PropertySource; | ||||
| import org.springframework.core.io.support.DefaultPropertySourceFactory; | ||||
| import org.springframework.core.io.support.EncodedResource; | ||||
|  | ||||
| import java.io.IOException; | ||||
|  | ||||
| /** | ||||
|  * yml 配置源工厂 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| public class YmlPropertySourceFactory extends DefaultPropertySourceFactory { | ||||
|  | ||||
|     @Override | ||||
|     public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException { | ||||
|         String sourceName = resource.getResource().getFilename(); | ||||
|         if (StringUtils.isNotBlank(sourceName) && StringUtils.endsWithAny(sourceName, ".yml", ".yaml")) { | ||||
|             YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); | ||||
|             factory.setResources(resource.getResource()); | ||||
|             factory.afterPropertiesSet(); | ||||
|             return new PropertiesPropertySource(sourceName, factory.getObject()); | ||||
|         } | ||||
|         return super.createPropertySource(name, resource); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,18 @@ | ||||
| package org.dromara.common.core.service; | ||||
|  | ||||
| /** | ||||
|  * 通用 参数配置服务 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| public interface ConfigService { | ||||
|  | ||||
|     /** | ||||
|      * 根据参数 key 获取参数值 | ||||
|      * | ||||
|      * @param configKey 参数 key | ||||
|      * @return 参数值 | ||||
|      */ | ||||
|     String getConfigValue(String configKey); | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,37 @@ | ||||
| package org.dromara.common.core.service; | ||||
|  | ||||
| import org.dromara.common.core.domain.dto.DeptDTO; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| /** | ||||
|  * 通用 部门服务 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| public interface DeptService { | ||||
|  | ||||
|     /** | ||||
|      * 通过部门ID查询部门名称 | ||||
|      * | ||||
|      * @param deptIds 部门ID串逗号分隔 | ||||
|      * @return 部门名称串逗号分隔 | ||||
|      */ | ||||
|     String selectDeptNameByIds(String deptIds); | ||||
|  | ||||
|     /** | ||||
|      * 根据部门ID查询部门负责人 | ||||
|      * | ||||
|      * @param deptId 部门ID,用于指定需要查询的部门 | ||||
|      * @return 返回该部门的负责人ID | ||||
|      */ | ||||
|     Long selectDeptLeaderById(Long deptId); | ||||
|  | ||||
|     /** | ||||
|      * 查询部门 | ||||
|      * | ||||
|      * @return 部门列表 | ||||
|      */ | ||||
|     List<DeptDTO> selectDeptsByList(); | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,67 @@ | ||||
| package org.dromara.common.core.service; | ||||
|  | ||||
| import java.util.Map; | ||||
|  | ||||
| /** | ||||
|  * 通用 字典服务 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| public interface DictService { | ||||
|  | ||||
|     /** | ||||
|      * 分隔符 | ||||
|      */ | ||||
|     String SEPARATOR = ","; | ||||
|  | ||||
|     /** | ||||
|      * 根据字典类型和字典值获取字典标签 | ||||
|      * | ||||
|      * @param dictType  字典类型 | ||||
|      * @param dictValue 字典值 | ||||
|      * @return 字典标签 | ||||
|      */ | ||||
|     default String getDictLabel(String dictType, String dictValue) { | ||||
|         return getDictLabel(dictType, dictValue, SEPARATOR); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 根据字典类型和字典标签获取字典值 | ||||
|      * | ||||
|      * @param dictType  字典类型 | ||||
|      * @param dictLabel 字典标签 | ||||
|      * @return 字典值 | ||||
|      */ | ||||
|     default String getDictValue(String dictType, String dictLabel) { | ||||
|         return getDictValue(dictType, dictLabel, SEPARATOR); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 根据字典类型和字典值获取字典标签 | ||||
|      * | ||||
|      * @param dictType  字典类型 | ||||
|      * @param dictValue 字典值 | ||||
|      * @param separator 分隔符 | ||||
|      * @return 字典标签 | ||||
|      */ | ||||
|     String getDictLabel(String dictType, String dictValue, String separator); | ||||
|  | ||||
|     /** | ||||
|      * 根据字典类型和字典标签获取字典值 | ||||
|      * | ||||
|      * @param dictType  字典类型 | ||||
|      * @param dictLabel 字典标签 | ||||
|      * @param separator 分隔符 | ||||
|      * @return 字典值 | ||||
|      */ | ||||
|     String getDictValue(String dictType, String dictLabel, String separator); | ||||
|  | ||||
|     /** | ||||
|      * 获取字典下所有的字典值与标签 | ||||
|      * | ||||
|      * @param dictType 字典类型 | ||||
|      * @return dictValue为key,dictLabel为值组成的Map | ||||
|      */ | ||||
|     Map<String, String> getAllDictByDictType(String dictType); | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,29 @@ | ||||
| package org.dromara.common.core.service; | ||||
|  | ||||
| import org.dromara.common.core.domain.dto.OssDTO; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| /** | ||||
|  * 通用 OSS服务 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| public interface OssService { | ||||
|  | ||||
|     /** | ||||
|      * 通过ossId查询对应的url | ||||
|      * | ||||
|      * @param ossIds ossId串逗号分隔 | ||||
|      * @return url串逗号分隔 | ||||
|      */ | ||||
|     String selectUrlByIds(String ossIds); | ||||
|  | ||||
|     /** | ||||
|      * 通过ossId查询列表 | ||||
|      * | ||||
|      * @param ossIds ossId串逗号分隔 | ||||
|      * @return 列表 | ||||
|      */ | ||||
|     List<OssDTO> selectByIds(String ossIds); | ||||
| } | ||||
| @ -0,0 +1,10 @@ | ||||
| package org.dromara.common.core.service; | ||||
|  | ||||
| /** | ||||
|  * 通用 岗位服务 | ||||
|  * | ||||
|  * @author AprilWind | ||||
|  */ | ||||
| public interface PostService { | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,10 @@ | ||||
| package org.dromara.common.core.service; | ||||
|  | ||||
| /** | ||||
|  * 通用 角色服务 | ||||
|  * | ||||
|  * @author AprilWind | ||||
|  */ | ||||
| public interface RoleService { | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,45 @@ | ||||
| package org.dromara.common.core.service; | ||||
|  | ||||
| import org.dromara.common.core.domain.dto.TaskAssigneeDTO; | ||||
| import org.dromara.common.core.domain.model.TaskAssigneeBody; | ||||
|  | ||||
| /** | ||||
|  * 工作流设计器获取任务执行人 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| public interface TaskAssigneeService { | ||||
|  | ||||
|     /** | ||||
|      * 查询角色并返回任务指派的列表,支持分页 | ||||
|      * | ||||
|      * @param taskQuery 查询条件 | ||||
|      * @return 办理人 | ||||
|      */ | ||||
|     TaskAssigneeDTO selectRolesByTaskAssigneeList(TaskAssigneeBody taskQuery); | ||||
|  | ||||
|     /** | ||||
|      * 查询岗位并返回任务指派的列表,支持分页 | ||||
|      * | ||||
|      * @param taskQuery 查询条件 | ||||
|      * @return 办理人 | ||||
|      */ | ||||
|     TaskAssigneeDTO selectPostsByTaskAssigneeList(TaskAssigneeBody taskQuery); | ||||
|  | ||||
|     /** | ||||
|      * 查询部门并返回任务指派的列表,支持分页 | ||||
|      * | ||||
|      * @param taskQuery 查询条件 | ||||
|      * @return 办理人 | ||||
|      */ | ||||
|     TaskAssigneeDTO selectDeptsByTaskAssigneeList(TaskAssigneeBody taskQuery); | ||||
|  | ||||
|     /** | ||||
|      * 查询用户并返回任务指派的列表,支持分页 | ||||
|      * | ||||
|      * @param taskQuery 查询条件 | ||||
|      * @return 办理人 | ||||
|      */ | ||||
|     TaskAssigneeDTO selectUsersByTaskAssigneeList(TaskAssigneeBody taskQuery); | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,94 @@ | ||||
| package org.dromara.common.core.service; | ||||
|  | ||||
| import org.dromara.common.core.domain.dto.UserDTO; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| /** | ||||
|  * 通用 用户服务 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| public interface UserService { | ||||
|  | ||||
|     /** | ||||
|      * 通过用户ID查询用户账户 | ||||
|      * | ||||
|      * @param userId 用户ID | ||||
|      * @return 用户账户 | ||||
|      */ | ||||
|     String selectUserNameById(Long userId); | ||||
|  | ||||
|     /** | ||||
|      * 通过用户ID查询用户账户 | ||||
|      * | ||||
|      * @param userId 用户ID | ||||
|      * @return 用户名称 | ||||
|      */ | ||||
|     String selectNicknameById(Long userId); | ||||
|  | ||||
|     /** | ||||
|      * 通过用户ID查询用户账户 | ||||
|      * | ||||
|      * @param userIds 用户ID 多个用逗号隔开 | ||||
|      * @return 用户名称 | ||||
|      */ | ||||
|     String selectNicknameByIds(String userIds); | ||||
|  | ||||
|     /** | ||||
|      * 通过用户ID查询用户手机号 | ||||
|      * | ||||
|      * @param userId 用户id | ||||
|      * @return 用户手机号 | ||||
|      */ | ||||
|     String selectPhonenumberById(Long userId); | ||||
|  | ||||
|     /** | ||||
|      * 通过用户ID查询用户邮箱 | ||||
|      * | ||||
|      * @param userId 用户id | ||||
|      * @return 用户邮箱 | ||||
|      */ | ||||
|     String selectEmailById(Long userId); | ||||
|  | ||||
|     /** | ||||
|      * 通过用户ID查询用户列表 | ||||
|      * | ||||
|      * @param userIds 用户ids | ||||
|      * @return 用户列表 | ||||
|      */ | ||||
|     List<UserDTO> selectListByIds(List<Long> userIds); | ||||
|  | ||||
|     /** | ||||
|      * 通过角色ID查询用户ID | ||||
|      * | ||||
|      * @param roleIds 角色ids | ||||
|      * @return 用户ids | ||||
|      */ | ||||
|     List<Long> selectUserIdsByRoleIds(List<Long> roleIds); | ||||
|  | ||||
|     /** | ||||
|      * 通过角色ID查询用户 | ||||
|      * | ||||
|      * @param roleIds 角色ids | ||||
|      * @return 用户 | ||||
|      */ | ||||
|     List<UserDTO> selectUsersByRoleIds(List<Long> roleIds); | ||||
|  | ||||
|     /** | ||||
|      * 通过部门ID查询用户 | ||||
|      * | ||||
|      * @param deptIds 部门ids | ||||
|      * @return 用户 | ||||
|      */ | ||||
|     List<UserDTO> selectUsersByDeptIds(List<Long> deptIds); | ||||
|  | ||||
|     /** | ||||
|      * 通过岗位ID查询用户 | ||||
|      * | ||||
|      * @param postIds 岗位ids | ||||
|      * @return 用户 | ||||
|      */ | ||||
|     List<UserDTO> selectUsersByPostIds(List<Long> postIds); | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,86 @@ | ||||
| package org.dromara.common.core.service; | ||||
|  | ||||
| import org.dromara.common.core.domain.dto.CompleteTaskDTO; | ||||
| import org.dromara.common.core.domain.dto.StartProcessDTO; | ||||
| import org.dromara.common.core.domain.dto.StartProcessReturnDTO; | ||||
|  | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
|  | ||||
| /** | ||||
|  * 通用 工作流服务 | ||||
|  * | ||||
|  * @author may | ||||
|  */ | ||||
| public interface WorkflowService { | ||||
|  | ||||
|     /** | ||||
|      * 运行中的实例 删除程实例,删除历史记录,删除业务与流程关联信息 | ||||
|      * | ||||
|      * @param businessIds 业务id | ||||
|      * @return 结果 | ||||
|      */ | ||||
|     boolean deleteInstance(List<Long> businessIds); | ||||
|  | ||||
|     /** | ||||
|      * 获取当前流程状态 | ||||
|      * | ||||
|      * @param taskId 任务id | ||||
|      * @return 状态 | ||||
|      */ | ||||
|     String getBusinessStatusByTaskId(Long taskId); | ||||
|  | ||||
|     /** | ||||
|      * 获取当前流程状态 | ||||
|      * | ||||
|      * @param businessId 业务id | ||||
|      * @return 状态 | ||||
|      */ | ||||
|     String getBusinessStatus(String businessId); | ||||
|  | ||||
|     /** | ||||
|      * 设置流程变量 | ||||
|      * | ||||
|      * @param instanceId 流程实例id | ||||
|      * @param variable   流程变量 | ||||
|      */ | ||||
|     void setVariable(Long instanceId, Map<String, Object> variable); | ||||
|  | ||||
|     /** | ||||
|      * 获取流程变量 | ||||
|      * | ||||
|      * @param instanceId 流程实例id | ||||
|      */ | ||||
|     Map<String, Object> instanceVariable(Long instanceId); | ||||
|  | ||||
|     /** | ||||
|      * 按照业务id查询流程实例id | ||||
|      * | ||||
|      * @param businessId 业务id | ||||
|      * @return 结果 | ||||
|      */ | ||||
|     Long getInstanceIdByBusinessId(String businessId); | ||||
|  | ||||
|     /** | ||||
|      * 新增租户流程定义 | ||||
|      * | ||||
|      * @param tenantId 租户id | ||||
|      */ | ||||
|     void syncDef(String tenantId); | ||||
|  | ||||
|     /** | ||||
|      * 启动流程 | ||||
|      * | ||||
|      * @param startProcess 参数 | ||||
|      * @return 结果 | ||||
|      */ | ||||
|     StartProcessReturnDTO startWorkFlow(StartProcessDTO startProcess); | ||||
|  | ||||
|     /** | ||||
|      * 办理任务 | ||||
|      * | ||||
|      * @param completeTask 参数 | ||||
|      * @return 结果 | ||||
|      */ | ||||
|     boolean completeTask(CompleteTaskDTO completeTask); | ||||
| } | ||||
| @ -0,0 +1,287 @@ | ||||
| package org.dromara.common.core.utils; | ||||
|  | ||||
| import org.apache.commons.lang3.time.DateFormatUtils; | ||||
| import org.dromara.common.core.enums.FormatsType; | ||||
| import org.dromara.common.core.exception.ServiceException; | ||||
|  | ||||
| import java.lang.management.ManagementFactory; | ||||
| import java.text.ParseException; | ||||
| import java.text.SimpleDateFormat; | ||||
| import java.time.*; | ||||
| import java.util.Date; | ||||
| import java.util.concurrent.TimeUnit; | ||||
|  | ||||
| /** | ||||
|  * 时间工具类 | ||||
|  * | ||||
|  * @author ruoyi | ||||
|  */ | ||||
| public class DateUtils extends org.apache.commons.lang3.time.DateUtils { | ||||
|     private static final String[] PARSE_PATTERNS = { | ||||
|         "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM", | ||||
|         "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM", | ||||
|         "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"}; | ||||
|  | ||||
|     @Deprecated | ||||
|     private DateUtils() { | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取当前日期和时间 | ||||
|      * | ||||
|      * @return 当前日期和时间的 Date 对象表示 | ||||
|      */ | ||||
|     public static Date getNowDate() { | ||||
|         return new Date(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取当前日期的字符串表示,格式为YYYY-MM-DD | ||||
|      * | ||||
|      * @return 当前日期的字符串表示 | ||||
|      */ | ||||
|     public static String getDate() { | ||||
|         return dateTimeNow(FormatsType.YYYY_MM_DD); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取当前日期的字符串表示,格式为yyyyMMdd | ||||
|      * | ||||
|      * @return 当前日期的字符串表示 | ||||
|      */ | ||||
|     public static String getCurrentDate() { | ||||
|         return DateFormatUtils.format(new Date(), FormatsType.YYYYMMDD.getTimeFormat()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取当前日期的路径格式字符串,格式为"yyyy/MM/dd" | ||||
|      * | ||||
|      * @return 当前日期的路径格式字符串 | ||||
|      */ | ||||
|     public static String datePath() { | ||||
|         Date now = new Date(); | ||||
|         return DateFormatUtils.format(now, FormatsType.YYYY_MM_DD_SLASH.getTimeFormat()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取当前时间的字符串表示,格式为YYYY-MM-DD HH:MM:SS | ||||
|      * | ||||
|      * @return 当前时间的字符串表示 | ||||
|      */ | ||||
|     public static String getTime() { | ||||
|         return dateTimeNow(FormatsType.YYYY_MM_DD_HH_MM_SS); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取当前时间的字符串表示,格式为 "HH:MM:SS" | ||||
|      * | ||||
|      * @return 当前时间的字符串表示,格式为 "HH:MM:SS" | ||||
|      */ | ||||
|     public static String getTimeWithHourMinuteSecond() { | ||||
|         return dateTimeNow(FormatsType.HH_MM_SS); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取当前日期和时间的字符串表示,格式为YYYYMMDDHHMMSS | ||||
|      * | ||||
|      * @return 当前日期和时间的字符串表示 | ||||
|      */ | ||||
|     public static String dateTimeNow() { | ||||
|         return dateTimeNow(FormatsType.YYYYMMDDHHMMSS); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取当前日期和时间的指定格式的字符串表示 | ||||
|      * | ||||
|      * @param format 日期时间格式,例如"YYYY-MM-DD HH:MM:SS" | ||||
|      * @return 当前日期和时间的字符串表示 | ||||
|      */ | ||||
|     public static String dateTimeNow(final FormatsType format) { | ||||
|         return parseDateToStr(format, new Date()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 将指定日期格式化为 YYYY-MM-DD 格式的字符串 | ||||
|      * | ||||
|      * @param date 要格式化的日期对象 | ||||
|      * @return 格式化后的日期字符串 | ||||
|      */ | ||||
|     public static String formatDate(final Date date) { | ||||
|         return parseDateToStr(FormatsType.YYYY_MM_DD, date); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 将指定日期格式化为 YYYY-MM-DD HH:MM:SS 格式的字符串 | ||||
|      * | ||||
|      * @param date 要格式化的日期对象 | ||||
|      * @return 格式化后的日期时间字符串 | ||||
|      */ | ||||
|     public static String formatDateTime(final Date date) { | ||||
|         return parseDateToStr(FormatsType.YYYY_MM_DD_HH_MM_SS, date); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 将指定日期按照指定格式进行格式化 | ||||
|      * | ||||
|      * @param format 要使用的日期时间格式,例如"YYYY-MM-DD HH:MM:SS" | ||||
|      * @param date   要格式化的日期对象 | ||||
|      * @return 格式化后的日期时间字符串 | ||||
|      */ | ||||
|     public static String parseDateToStr(final FormatsType format, final Date date) { | ||||
|         return new SimpleDateFormat(format.getTimeFormat()).format(date); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 将指定格式的日期时间字符串转换为 Date 对象 | ||||
|      * | ||||
|      * @param format 要解析的日期时间格式,例如"YYYY-MM-DD HH:MM:SS" | ||||
|      * @param ts     要解析的日期时间字符串 | ||||
|      * @return 解析后的 Date 对象 | ||||
|      * @throws RuntimeException 如果解析过程中发生异常 | ||||
|      */ | ||||
|     public static Date parseDateTime(final FormatsType format, final String ts) { | ||||
|         try { | ||||
|             return new SimpleDateFormat(format.getTimeFormat()).parse(ts); | ||||
|         } catch (ParseException e) { | ||||
|             throw new RuntimeException(e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 将对象转换为日期对象 | ||||
|      * | ||||
|      * @param str 要转换的对象,通常是字符串 | ||||
|      * @return 转换后的日期对象,如果转换失败或输入为null,则返回null | ||||
|      */ | ||||
|     public static Date parseDate(Object str) { | ||||
|         if (str == null) { | ||||
|             return null; | ||||
|         } | ||||
|         try { | ||||
|             return parseDate(str.toString(), PARSE_PATTERNS); | ||||
|         } catch (ParseException e) { | ||||
|             return null; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取服务器启动时间 | ||||
|      * | ||||
|      * @return 服务器启动时间的 Date 对象表示 | ||||
|      */ | ||||
|     public static Date getServerStartDate() { | ||||
|         long time = ManagementFactory.getRuntimeMXBean().getStartTime(); | ||||
|         return new Date(time); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 计算两个日期之间的天数差(以毫秒为单位) | ||||
|      * | ||||
|      * @param date1 第一个日期 | ||||
|      * @param date2 第二个日期 | ||||
|      * @return 两个日期之间的天数差的绝对值 | ||||
|      */ | ||||
|     public static int differentDaysByMillisecond(Date date1, Date date2) { | ||||
|         return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000 * 3600 * 24))); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 计算两个日期之间的时间差,并以天、小时和分钟的格式返回 | ||||
|      * | ||||
|      * @param endDate 结束日期 | ||||
|      * @param nowDate 当前日期 | ||||
|      * @return 表示时间差的字符串,格式为"天 小时 分钟" | ||||
|      */ | ||||
|     public static String getDatePoor(Date endDate, Date nowDate) { | ||||
|         long diffInMillis = endDate.getTime() - nowDate.getTime(); | ||||
|         long day = TimeUnit.MILLISECONDS.toDays(diffInMillis); | ||||
|         long hour = TimeUnit.MILLISECONDS.toHours(diffInMillis) % 24; | ||||
|         long min = TimeUnit.MILLISECONDS.toMinutes(diffInMillis) % 60; | ||||
|         return String.format("%d天 %d小时 %d分钟", day, hour, min); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 计算两个时间点的差值(天、小时、分钟、秒),当值为0时不显示该单位 | ||||
|      * | ||||
|      * @param endDate 结束时间 | ||||
|      * @param nowDate 当前时间 | ||||
|      * @return 时间差字符串,格式为 "x天 x小时 x分钟 x秒",若为 0 则不显示 | ||||
|      */ | ||||
|     public static String getTimeDifference(Date endDate, Date nowDate) { | ||||
|         long diffInMillis = endDate.getTime() - nowDate.getTime(); | ||||
|         long day = TimeUnit.MILLISECONDS.toDays(diffInMillis); | ||||
|         long hour = TimeUnit.MILLISECONDS.toHours(diffInMillis) % 24; | ||||
|         long min = TimeUnit.MILLISECONDS.toMinutes(diffInMillis) % 60; | ||||
|         long sec = TimeUnit.MILLISECONDS.toSeconds(diffInMillis) % 60; | ||||
|         // 构建时间差字符串,条件是值不为0才显示 | ||||
|         StringBuilder result = new StringBuilder(); | ||||
|         if (day > 0) { | ||||
|             result.append(String.format("%d天 ", day)); | ||||
|         } | ||||
|         if (hour > 0) { | ||||
|             result.append(String.format("%d小时 ", hour)); | ||||
|         } | ||||
|         if (min > 0) { | ||||
|             result.append(String.format("%d分钟 ", min)); | ||||
|         } | ||||
|         if (sec > 0) { | ||||
|             result.append(String.format("%d秒", sec)); | ||||
|         } | ||||
|         return result.length() > 0 ? result.toString().trim() : "0秒"; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 将 LocalDateTime 对象转换为 Date 对象 | ||||
|      * | ||||
|      * @param temporalAccessor 要转换的 LocalDateTime 对象 | ||||
|      * @return 转换后的 Date 对象 | ||||
|      */ | ||||
|     public static Date toDate(LocalDateTime temporalAccessor) { | ||||
|         ZonedDateTime zdt = temporalAccessor.atZone(ZoneId.systemDefault()); | ||||
|         return Date.from(zdt.toInstant()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 将 LocalDate 对象转换为 Date 对象 | ||||
|      * | ||||
|      * @param temporalAccessor 要转换的 LocalDate 对象 | ||||
|      * @return 转换后的 Date 对象 | ||||
|      */ | ||||
|     public static Date toDate(LocalDate temporalAccessor) { | ||||
|         LocalDateTime localDateTime = LocalDateTime.of(temporalAccessor, LocalTime.of(0, 0, 0)); | ||||
|         ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault()); | ||||
|         return Date.from(zdt.toInstant()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 校验日期范围 | ||||
|      * | ||||
|      * @param startDate 开始日期 | ||||
|      * @param endDate   结束日期 | ||||
|      * @param maxValue  最大时间跨度的限制值 | ||||
|      * @param unit      时间跨度的单位,可选择 "DAYS"、"HOURS" 或 "MINUTES" | ||||
|      */ | ||||
|     public static void validateDateRange(Date startDate, Date endDate, int maxValue, TimeUnit unit) { | ||||
|         // 校验结束日期不能早于开始日期 | ||||
|         if (endDate.before(startDate)) { | ||||
|             throw new ServiceException("结束日期不能早于开始日期"); | ||||
|         } | ||||
|  | ||||
|         // 计算时间跨度 | ||||
|         long diffInMillis = endDate.getTime() - startDate.getTime(); | ||||
|  | ||||
|         // 根据单位转换时间跨度 | ||||
|         long diff = switch (unit) { | ||||
|             case DAYS -> TimeUnit.MILLISECONDS.toDays(diffInMillis); | ||||
|             case HOURS -> TimeUnit.MILLISECONDS.toHours(diffInMillis); | ||||
|             case MINUTES -> TimeUnit.MILLISECONDS.toMinutes(diffInMillis); | ||||
|             default -> throw new IllegalArgumentException("不支持的时间单位"); | ||||
|         }; | ||||
|  | ||||
|         // 校验时间跨度不超过最大限制 | ||||
|         if (diff > maxValue) { | ||||
|             throw new ServiceException("最大时间跨度为 " + maxValue + " " + unit.toString().toLowerCase()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,93 @@ | ||||
| package org.dromara.common.core.utils; | ||||
|  | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| import cn.hutool.core.map.MapUtil; | ||||
| import cn.hutool.core.util.ObjectUtil; | ||||
| import io.github.linpeilie.Converter; | ||||
| import lombok.AccessLevel; | ||||
| import lombok.NoArgsConstructor; | ||||
|  | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
|  | ||||
| /** | ||||
|  * Mapstruct 工具类 | ||||
|  * <p>参考文档:<a href="https://mapstruct.plus/introduction/quick-start.html">mapstruct-plus</a></p> | ||||
|  * | ||||
|  * | ||||
|  * @author Michelle.Chung | ||||
|  */ | ||||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||||
| public class MapstructUtils { | ||||
|  | ||||
|     private final static Converter CONVERTER = SpringUtils.getBean(Converter.class); | ||||
|  | ||||
|     /** | ||||
|      * 将 T 类型对象,转换为 desc 类型的对象并返回 | ||||
|      * | ||||
|      * @param source 数据来源实体 | ||||
|      * @param desc   描述对象 转换后的对象 | ||||
|      * @return desc | ||||
|      */ | ||||
|     public static <T, V> V convert(T source, Class<V> desc) { | ||||
|         if (ObjectUtil.isNull(source)) { | ||||
|             return null; | ||||
|         } | ||||
|         if (ObjectUtil.isNull(desc)) { | ||||
|             return null; | ||||
|         } | ||||
|         return CONVERTER.convert(source, desc); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 将 T 类型对象,按照配置的映射字段规则,给 desc 类型的对象赋值并返回 desc 对象 | ||||
|      * | ||||
|      * @param source 数据来源实体 | ||||
|      * @param desc   转换后的对象 | ||||
|      * @return desc | ||||
|      */ | ||||
|     public static <T, V> V convert(T source, V desc) { | ||||
|         if (ObjectUtil.isNull(source)) { | ||||
|             return null; | ||||
|         } | ||||
|         if (ObjectUtil.isNull(desc)) { | ||||
|             return null; | ||||
|         } | ||||
|         return CONVERTER.convert(source, desc); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 将 T 类型的集合,转换为 desc 类型的集合并返回 | ||||
|      * | ||||
|      * @param sourceList 数据来源实体列表 | ||||
|      * @param desc       描述对象 转换后的对象 | ||||
|      * @return desc | ||||
|      */ | ||||
|     public static <T, V> List<V> convert(List<T> sourceList, Class<V> desc) { | ||||
|         if (ObjectUtil.isNull(sourceList)) { | ||||
|             return null; | ||||
|         } | ||||
|         if (CollUtil.isEmpty(sourceList)) { | ||||
|             return CollUtil.newArrayList(); | ||||
|         } | ||||
|         return CONVERTER.convert(sourceList, desc); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 将 Map 转换为 beanClass 类型的集合并返回 | ||||
|      * | ||||
|      * @param map       数据来源 | ||||
|      * @param beanClass bean类 | ||||
|      * @return bean对象 | ||||
|      */ | ||||
|     public static <T> T convert(Map<String, Object> map, Class<T> beanClass) { | ||||
|         if (MapUtil.isEmpty(map)) { | ||||
|             return null; | ||||
|         } | ||||
|         if (ObjectUtil.isNull(beanClass)) { | ||||
|             return null; | ||||
|         } | ||||
|         return CONVERTER.convert(map, beanClass); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,33 @@ | ||||
| package org.dromara.common.core.utils; | ||||
|  | ||||
| import lombok.AccessLevel; | ||||
| import lombok.NoArgsConstructor; | ||||
| import org.springframework.context.MessageSource; | ||||
| import org.springframework.context.NoSuchMessageException; | ||||
| import org.springframework.context.i18n.LocaleContextHolder; | ||||
|  | ||||
| /** | ||||
|  * 获取i18n资源文件 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||||
| public class MessageUtils { | ||||
|  | ||||
|     private static final MessageSource MESSAGE_SOURCE = SpringUtils.getBean(MessageSource.class); | ||||
|  | ||||
|     /** | ||||
|      * 根据消息键和参数 获取消息 委托给spring messageSource | ||||
|      * | ||||
|      * @param code 消息键 | ||||
|      * @param args 参数 | ||||
|      * @return 获取国际化翻译值 | ||||
|      */ | ||||
|     public static String message(String code, Object... args) { | ||||
|         try { | ||||
|             return MESSAGE_SOURCE.getMessage(code, args, LocaleContextHolder.getLocale()); | ||||
|         } catch (NoSuchMessageException e) { | ||||
|             return code; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,60 @@ | ||||
| package org.dromara.common.core.utils; | ||||
|  | ||||
| import cn.hutool.core.util.ObjectUtil; | ||||
| import lombok.AccessLevel; | ||||
| import lombok.NoArgsConstructor; | ||||
|  | ||||
| import java.util.function.Function; | ||||
|  | ||||
| /** | ||||
|  * 对象工具类 | ||||
|  * | ||||
|  * @author 秋辞未寒 | ||||
|  */ | ||||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||||
| public class ObjectUtils extends ObjectUtil { | ||||
|  | ||||
|     /** | ||||
|      * 如果对象不为空,则获取对象中的某个字段 ObjectUtils.notNullGetter(user, User::getName); | ||||
|      * | ||||
|      * @param obj 对象 | ||||
|      * @param func 获取方法 | ||||
|      * @return 对象字段 | ||||
|      */ | ||||
|     public static <T, E> E notNullGetter(T obj, Function<T, E> func) { | ||||
|         if (isNotNull(obj) && isNotNull(func)) { | ||||
|             return func.apply(obj); | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 如果对象不为空,则获取对象中的某个字段,否则返回默认值 | ||||
|      * | ||||
|      * @param obj          对象 | ||||
|      * @param func         获取方法 | ||||
|      * @param defaultValue 默认值 | ||||
|      * @return 对象字段 | ||||
|      */ | ||||
|     public static <T, E> E notNullGetter(T obj, Function<T, E> func, E defaultValue) { | ||||
|         if (isNotNull(obj) && isNotNull(func)) { | ||||
|             return func.apply(obj); | ||||
|         } | ||||
|         return defaultValue; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 如果值不为空,则返回值,否则返回默认值 | ||||
|      * | ||||
|      * @param obj          对象 | ||||
|      * @param defaultValue 默认值 | ||||
|      * @return 对象字段 | ||||
|      */ | ||||
|     public static <T> T notNull(T obj, T defaultValue) { | ||||
|         if (isNotNull(obj)) { | ||||
|             return obj; | ||||
|         } | ||||
|         return defaultValue; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,289 @@ | ||||
| package org.dromara.common.core.utils; | ||||
|  | ||||
| import cn.hutool.core.convert.Convert; | ||||
| import cn.hutool.extra.servlet.JakartaServletUtil; | ||||
| import cn.hutool.http.HttpStatus; | ||||
| import jakarta.servlet.ServletRequest; | ||||
| import jakarta.servlet.http.HttpServletRequest; | ||||
| import jakarta.servlet.http.HttpServletResponse; | ||||
| import jakarta.servlet.http.HttpSession; | ||||
| import lombok.AccessLevel; | ||||
| import lombok.NoArgsConstructor; | ||||
| import org.springframework.http.MediaType; | ||||
| import org.springframework.util.LinkedCaseInsensitiveMap; | ||||
| import org.springframework.web.context.request.RequestAttributes; | ||||
| import org.springframework.web.context.request.RequestContextHolder; | ||||
| import org.springframework.web.context.request.ServletRequestAttributes; | ||||
|  | ||||
| import java.io.IOException; | ||||
| import java.net.URLDecoder; | ||||
| import java.net.URLEncoder; | ||||
| import java.nio.charset.StandardCharsets; | ||||
| import java.util.Collections; | ||||
| import java.util.Enumeration; | ||||
| import java.util.HashMap; | ||||
| import java.util.Map; | ||||
|  | ||||
| /** | ||||
|  * 客户端工具类,提供获取请求参数、响应处理、头部信息等常用操作 | ||||
|  * | ||||
|  * @author ruoyi | ||||
|  */ | ||||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||||
| public class ServletUtils extends JakartaServletUtil { | ||||
|  | ||||
|     /** | ||||
|      * 获取指定名称的 String 类型的请求参数 | ||||
|      * | ||||
|      * @param name 参数名 | ||||
|      * @return 参数值 | ||||
|      */ | ||||
|     public static String getParameter(String name) { | ||||
|         return getRequest().getParameter(name); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取指定名称的 String 类型的请求参数,若参数不存在,则返回默认值 | ||||
|      * | ||||
|      * @param name         参数名 | ||||
|      * @param defaultValue 默认值 | ||||
|      * @return 参数值或默认值 | ||||
|      */ | ||||
|     public static String getParameter(String name, String defaultValue) { | ||||
|         return Convert.toStr(getRequest().getParameter(name), defaultValue); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取指定名称的 Integer 类型的请求参数 | ||||
|      * | ||||
|      * @param name 参数名 | ||||
|      * @return 参数值 | ||||
|      */ | ||||
|     public static Integer getParameterToInt(String name) { | ||||
|         return Convert.toInt(getRequest().getParameter(name)); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取指定名称的 Integer 类型的请求参数,若参数不存在,则返回默认值 | ||||
|      * | ||||
|      * @param name         参数名 | ||||
|      * @param defaultValue 默认值 | ||||
|      * @return 参数值或默认值 | ||||
|      */ | ||||
|     public static Integer getParameterToInt(String name, Integer defaultValue) { | ||||
|         return Convert.toInt(getRequest().getParameter(name), defaultValue); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取指定名称的 Boolean 类型的请求参数 | ||||
|      * | ||||
|      * @param name 参数名 | ||||
|      * @return 参数值 | ||||
|      */ | ||||
|     public static Boolean getParameterToBool(String name) { | ||||
|         return Convert.toBool(getRequest().getParameter(name)); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取指定名称的 Boolean 类型的请求参数,若参数不存在,则返回默认值 | ||||
|      * | ||||
|      * @param name         参数名 | ||||
|      * @param defaultValue 默认值 | ||||
|      * @return 参数值或默认值 | ||||
|      */ | ||||
|     public static Boolean getParameterToBool(String name, Boolean defaultValue) { | ||||
|         return Convert.toBool(getRequest().getParameter(name), defaultValue); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取所有请求参数(以 Map 的形式返回) | ||||
|      * | ||||
|      * @param request 请求对象{@link ServletRequest} | ||||
|      * @return 请求参数的 Map,键为参数名,值为参数值数组 | ||||
|      */ | ||||
|     public static Map<String, String[]> getParams(ServletRequest request) { | ||||
|         final Map<String, String[]> map = request.getParameterMap(); | ||||
|         return Collections.unmodifiableMap(map); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取所有请求参数(以 Map 的形式返回,值为字符串形式的拼接) | ||||
|      * | ||||
|      * @param request 请求对象{@link ServletRequest} | ||||
|      * @return 请求参数的 Map,键为参数名,值为拼接后的字符串 | ||||
|      */ | ||||
|     public static Map<String, String> getParamMap(ServletRequest request) { | ||||
|         Map<String, String> params = new HashMap<>(); | ||||
|         for (Map.Entry<String, String[]> entry : getParams(request).entrySet()) { | ||||
|             params.put(entry.getKey(), StringUtils.join(entry.getValue(), StringUtils.SEPARATOR)); | ||||
|         } | ||||
|         return params; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取当前 HTTP 请求对象 | ||||
|      * | ||||
|      * @return 当前 HTTP 请求对象 | ||||
|      */ | ||||
|     public static HttpServletRequest getRequest() { | ||||
|         try { | ||||
|             return getRequestAttributes().getRequest(); | ||||
|         } catch (Exception e) { | ||||
|             return null; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取当前 HTTP 响应对象 | ||||
|      * | ||||
|      * @return 当前 HTTP 响应对象 | ||||
|      */ | ||||
|     public static HttpServletResponse getResponse() { | ||||
|         try { | ||||
|             return getRequestAttributes().getResponse(); | ||||
|         } catch (Exception e) { | ||||
|             return null; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取当前请求的 HttpSession 对象 | ||||
|      * <p> | ||||
|      * 如果当前请求已经关联了一个会话(即已经存在有效的 session ID), | ||||
|      * 则返回该会话对象;如果没有关联会话,则会创建一个新的会话对象并返回。 | ||||
|      * <p> | ||||
|      * HttpSession 用于存储会话级别的数据,如用户登录信息、购物车内容等, | ||||
|      * 可以在多个请求之间共享会话数据 | ||||
|      * | ||||
|      * @return 当前请求的 HttpSession 对象 | ||||
|      */ | ||||
|     public static HttpSession getSession() { | ||||
|         return getRequest().getSession(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取当前请求的请求属性 | ||||
|      * | ||||
|      * @return {@link ServletRequestAttributes} 请求属性对象 | ||||
|      */ | ||||
|     public static ServletRequestAttributes getRequestAttributes() { | ||||
|         try { | ||||
|             RequestAttributes attributes = RequestContextHolder.getRequestAttributes(); | ||||
|             return (ServletRequestAttributes) attributes; | ||||
|         } catch (Exception e) { | ||||
|             return null; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取指定请求头的值,如果头部为空则返回空字符串 | ||||
|      * | ||||
|      * @param request 请求对象 | ||||
|      * @param name    头部名称 | ||||
|      * @return 头部值 | ||||
|      */ | ||||
|     public static String getHeader(HttpServletRequest request, String name) { | ||||
|         String value = request.getHeader(name); | ||||
|         if (StringUtils.isEmpty(value)) { | ||||
|             return StringUtils.EMPTY; | ||||
|         } | ||||
|         return urlDecode(value); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取所有请求头的 Map,键为头部名称,值为头部值 | ||||
|      * | ||||
|      * @param request 请求对象 | ||||
|      * @return 请求头的 Map | ||||
|      */ | ||||
|     public static Map<String, String> getHeaders(HttpServletRequest request) { | ||||
|         Map<String, String> map = new LinkedCaseInsensitiveMap<>(); | ||||
|         Enumeration<String> enumeration = request.getHeaderNames(); | ||||
|         if (enumeration != null) { | ||||
|             while (enumeration.hasMoreElements()) { | ||||
|                 String key = enumeration.nextElement(); | ||||
|                 String value = request.getHeader(key); | ||||
|                 map.put(key, value); | ||||
|             } | ||||
|         } | ||||
|         return map; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 将字符串渲染到客户端(以 JSON 格式返回) | ||||
|      * | ||||
|      * @param response 渲染对象 | ||||
|      * @param string   待渲染的字符串 | ||||
|      */ | ||||
|     public static void renderString(HttpServletResponse response, String string) { | ||||
|         try { | ||||
|             response.setStatus(HttpStatus.HTTP_OK); | ||||
|             response.setContentType(MediaType.APPLICATION_JSON_VALUE); | ||||
|             response.setCharacterEncoding(StandardCharsets.UTF_8.toString()); | ||||
|             response.getWriter().print(string); | ||||
|         } catch (IOException e) { | ||||
|             e.printStackTrace(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 判断当前请求是否为 Ajax 异步请求 | ||||
|      * | ||||
|      * @param request 请求对象 | ||||
|      * @return 是否为 Ajax 请求 | ||||
|      */ | ||||
|     public static boolean isAjaxRequest(HttpServletRequest request) { | ||||
|  | ||||
|         // 判断 Accept 头部是否包含 application/json | ||||
|         String accept = request.getHeader("accept"); | ||||
|         if (accept != null && accept.contains(MediaType.APPLICATION_JSON_VALUE)) { | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         // 判断 X-Requested-With 头部是否包含 XMLHttpRequest | ||||
|         String xRequestedWith = request.getHeader("X-Requested-With"); | ||||
|         if (xRequestedWith != null && xRequestedWith.contains("XMLHttpRequest")) { | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         // 判断 URI 后缀是否为 .json 或 .xml | ||||
|         String uri = request.getRequestURI(); | ||||
|         if (StringUtils.equalsAnyIgnoreCase(uri, ".json", ".xml")) { | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         // 判断请求参数 __ajax 是否为 json 或 xml | ||||
|         String ajax = request.getParameter("__ajax"); | ||||
|         return StringUtils.equalsAnyIgnoreCase(ajax, "json", "xml"); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取客户端 IP 地址 | ||||
|      * | ||||
|      * @return 客户端 IP 地址 | ||||
|      */ | ||||
|     public static String getClientIP() { | ||||
|         return getClientIP(getRequest()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 对内容进行 URL 编码 | ||||
|      * | ||||
|      * @param str 内容 | ||||
|      * @return 编码后的内容 | ||||
|      */ | ||||
|     public static String urlEncode(String str) { | ||||
|         return URLEncoder.encode(str, StandardCharsets.UTF_8); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 对内容进行 URL 解码 | ||||
|      * | ||||
|      * @param str 内容 | ||||
|      * @return 解码后的内容 | ||||
|      */ | ||||
|     public static String urlDecode(String str) { | ||||
|         return URLDecoder.decode(str, StandardCharsets.UTF_8); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,67 @@ | ||||
| package org.dromara.common.core.utils; | ||||
|  | ||||
| import cn.hutool.extra.spring.SpringUtil; | ||||
| import org.springframework.beans.factory.NoSuchBeanDefinitionException; | ||||
| import org.springframework.boot.autoconfigure.thread.Threading; | ||||
| import org.springframework.context.ApplicationContext; | ||||
| import org.springframework.core.env.Environment; | ||||
| import org.springframework.stereotype.Component; | ||||
|  | ||||
| /** | ||||
|  * spring工具类 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @Component | ||||
| public final class SpringUtils extends SpringUtil { | ||||
|  | ||||
|     /** | ||||
|      * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true | ||||
|      */ | ||||
|     public static boolean containsBean(String name) { | ||||
|         return getBeanFactory().containsBean(name); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 | ||||
|      * 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException) | ||||
|      */ | ||||
|     public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException { | ||||
|         return getBeanFactory().isSingleton(name); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @return Class 注册对象的类型 | ||||
|      */ | ||||
|     public static Class<?> getType(String name) throws NoSuchBeanDefinitionException { | ||||
|         return getBeanFactory().getType(name); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 如果给定的bean名字在bean定义中有别名,则返回这些别名 | ||||
|      */ | ||||
|     public static String[] getAliases(String name) throws NoSuchBeanDefinitionException { | ||||
|         return getBeanFactory().getAliases(name); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取aop代理对象 | ||||
|      */ | ||||
|     @SuppressWarnings("unchecked") | ||||
|     public static <T> T getAopProxy(T invoker) { | ||||
|         return (T) getBean(invoker.getClass()); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * 获取spring上下文 | ||||
|      */ | ||||
|     public static ApplicationContext context() { | ||||
|         return getApplicationContext(); | ||||
|     } | ||||
|  | ||||
|     public static boolean isVirtual() { | ||||
|         return Threading.VIRTUAL.isActive(getBean(Environment.class)); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,283 @@ | ||||
| package org.dromara.common.core.utils; | ||||
|  | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| import cn.hutool.core.map.MapUtil; | ||||
| import lombok.AccessLevel; | ||||
| import lombok.NoArgsConstructor; | ||||
|  | ||||
| import java.util.*; | ||||
| import java.util.function.BiFunction; | ||||
| import java.util.function.Consumer; | ||||
| import java.util.function.Function; | ||||
| import java.util.function.Predicate; | ||||
| import java.util.stream.Collectors; | ||||
|  | ||||
| /** | ||||
|  * stream 流工具类 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||||
| public class StreamUtils { | ||||
|  | ||||
|     /** | ||||
|      * 将collection过滤 | ||||
|      * | ||||
|      * @param collection 需要转化的集合 | ||||
|      * @param function   过滤方法 | ||||
|      * @return 过滤后的list | ||||
|      */ | ||||
|     public static <E> List<E> filter(Collection<E> collection, Predicate<E> function) { | ||||
|         if (CollUtil.isEmpty(collection)) { | ||||
|             return CollUtil.newArrayList(); | ||||
|         } | ||||
|         // 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题 | ||||
|         return collection.stream().filter(function).collect(Collectors.toList()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 找到流中满足条件的第一个元素 | ||||
|      * | ||||
|      * @param collection 需要查询的集合 | ||||
|      * @param function   过滤方法 | ||||
|      * @return 找到符合条件的第一个元素,没有则返回null | ||||
|      */ | ||||
|     public static <E> E findFirst(Collection<E> collection, Predicate<E> function) { | ||||
|         if (CollUtil.isEmpty(collection)) { | ||||
|             return null; | ||||
|         } | ||||
|         return collection.stream().filter(function).findFirst().orElse(null); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 找到流中任意一个满足条件的元素 | ||||
|      * | ||||
|      * @param collection 需要查询的集合 | ||||
|      * @param function   过滤方法 | ||||
|      * @return 找到符合条件的任意一个元素,没有则返回null | ||||
|      */ | ||||
|     public static <E> Optional<E> findAny(Collection<E> collection, Predicate<E> function) { | ||||
|         if (CollUtil.isEmpty(collection)) { | ||||
|             return Optional.empty(); | ||||
|         } | ||||
|         return collection.stream().filter(function).findAny(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 将collection拼接 | ||||
|      * | ||||
|      * @param collection 需要转化的集合 | ||||
|      * @param function   拼接方法 | ||||
|      * @return 拼接后的list | ||||
|      */ | ||||
|     public static <E> String join(Collection<E> collection, Function<E, String> function) { | ||||
|         return join(collection, function, StringUtils.SEPARATOR); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 将collection拼接 | ||||
|      * | ||||
|      * @param collection 需要转化的集合 | ||||
|      * @param function   拼接方法 | ||||
|      * @param delimiter  拼接符 | ||||
|      * @return 拼接后的list | ||||
|      */ | ||||
|     public static <E> String join(Collection<E> collection, Function<E, String> function, CharSequence delimiter) { | ||||
|         if (CollUtil.isEmpty(collection)) { | ||||
|             return StringUtils.EMPTY; | ||||
|         } | ||||
|         return collection.stream().map(function).filter(Objects::nonNull).collect(Collectors.joining(delimiter)); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 将collection排序 | ||||
|      * | ||||
|      * @param collection 需要转化的集合 | ||||
|      * @param comparing  排序方法 | ||||
|      * @return 排序后的list | ||||
|      */ | ||||
|     public static <E> List<E> sorted(Collection<E> collection, Comparator<E> comparing) { | ||||
|         if (CollUtil.isEmpty(collection)) { | ||||
|             return CollUtil.newArrayList(); | ||||
|         } | ||||
|         // 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题 | ||||
|         return collection.stream().filter(Objects::nonNull).sorted(comparing).collect(Collectors.toList()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 将collection转化为类型不变的map<br> | ||||
|      * <B>{@code Collection<V>  ---->  Map<K,V>}</B> | ||||
|      * | ||||
|      * @param collection 需要转化的集合 | ||||
|      * @param key        V类型转化为K类型的lambda方法 | ||||
|      * @param <V>        collection中的泛型 | ||||
|      * @param <K>        map中的key类型 | ||||
|      * @return 转化后的map | ||||
|      */ | ||||
|     public static <V, K> Map<K, V> toIdentityMap(Collection<V> collection, Function<V, K> key) { | ||||
|         if (CollUtil.isEmpty(collection)) { | ||||
|             return MapUtil.newHashMap(); | ||||
|         } | ||||
|         return collection.stream().filter(Objects::nonNull).collect(Collectors.toMap(key, Function.identity(), (l, r) -> l)); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 将Collection转化为map(value类型与collection的泛型不同)<br> | ||||
|      * <B>{@code Collection<E> -----> Map<K,V>  }</B> | ||||
|      * | ||||
|      * @param collection 需要转化的集合 | ||||
|      * @param key        E类型转化为K类型的lambda方法 | ||||
|      * @param value      E类型转化为V类型的lambda方法 | ||||
|      * @param <E>        collection中的泛型 | ||||
|      * @param <K>        map中的key类型 | ||||
|      * @param <V>        map中的value类型 | ||||
|      * @return 转化后的map | ||||
|      */ | ||||
|     public static <E, K, V> Map<K, V> toMap(Collection<E> collection, Function<E, K> key, Function<E, V> value) { | ||||
|         if (CollUtil.isEmpty(collection)) { | ||||
|             return MapUtil.newHashMap(); | ||||
|         } | ||||
|         return collection.stream().filter(Objects::nonNull).collect(Collectors.toMap(key, value, (l, r) -> l)); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 将collection按照规则(比如有相同的班级id)分类成map<br> | ||||
|      * <B>{@code Collection<E> -------> Map<K,List<E>> } </B> | ||||
|      * | ||||
|      * @param collection 需要分类的集合 | ||||
|      * @param key        分类的规则 | ||||
|      * @param <E>        collection中的泛型 | ||||
|      * @param <K>        map中的key类型 | ||||
|      * @return 分类后的map | ||||
|      */ | ||||
|     public static <E, K> Map<K, List<E>> groupByKey(Collection<E> collection, Function<E, K> key) { | ||||
|         if (CollUtil.isEmpty(collection)) { | ||||
|             return MapUtil.newHashMap(); | ||||
|         } | ||||
|         return collection | ||||
|             .stream().filter(Objects::nonNull) | ||||
|             .collect(Collectors.groupingBy(key, LinkedHashMap::new, Collectors.toList())); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 将collection按照两个规则(比如有相同的年级id,班级id)分类成双层map<br> | ||||
|      * <B>{@code Collection<E>  --->  Map<T,Map<U,List<E>>> } </B> | ||||
|      * | ||||
|      * @param collection 需要分类的集合 | ||||
|      * @param key1       第一个分类的规则 | ||||
|      * @param key2       第二个分类的规则 | ||||
|      * @param <E>        集合元素类型 | ||||
|      * @param <K>        第一个map中的key类型 | ||||
|      * @param <U>        第二个map中的key类型 | ||||
|      * @return 分类后的map | ||||
|      */ | ||||
|     public static <E, K, U> Map<K, Map<U, List<E>>> groupBy2Key(Collection<E> collection, Function<E, K> key1, Function<E, U> key2) { | ||||
|         if (CollUtil.isEmpty(collection)) { | ||||
|             return MapUtil.newHashMap(); | ||||
|         } | ||||
|         return collection | ||||
|             .stream().filter(Objects::nonNull) | ||||
|             .collect(Collectors.groupingBy(key1, LinkedHashMap::new, Collectors.groupingBy(key2, LinkedHashMap::new, Collectors.toList()))); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 将collection按照两个规则(比如有相同的年级id,班级id)分类成双层map<br> | ||||
|      * <B>{@code Collection<E>  --->  Map<T,Map<U,E>> } </B> | ||||
|      * | ||||
|      * @param collection 需要分类的集合 | ||||
|      * @param key1       第一个分类的规则 | ||||
|      * @param key2       第二个分类的规则 | ||||
|      * @param <T>        第一个map中的key类型 | ||||
|      * @param <U>        第二个map中的key类型 | ||||
|      * @param <E>        collection中的泛型 | ||||
|      * @return 分类后的map | ||||
|      */ | ||||
|     public static <E, T, U> Map<T, Map<U, E>> group2Map(Collection<E> collection, Function<E, T> key1, Function<E, U> key2) { | ||||
|         if (CollUtil.isEmpty(collection) || key1 == null || key2 == null) { | ||||
|             return MapUtil.newHashMap(); | ||||
|         } | ||||
|         return collection | ||||
|             .stream().filter(Objects::nonNull) | ||||
|             .collect(Collectors.groupingBy(key1, LinkedHashMap::new, Collectors.toMap(key2, Function.identity(), (l, r) -> l))); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 将collection转化为List集合,但是两者的泛型不同<br> | ||||
|      * <B>{@code Collection<E>  ------>  List<T> } </B> | ||||
|      * | ||||
|      * @param collection 需要转化的集合 | ||||
|      * @param function   collection中的泛型转化为list泛型的lambda表达式 | ||||
|      * @param <E>        collection中的泛型 | ||||
|      * @param <T>        List中的泛型 | ||||
|      * @return 转化后的list | ||||
|      */ | ||||
|     public static <E, T> List<T> toList(Collection<E> collection, Function<E, T> function) { | ||||
|         if (CollUtil.isEmpty(collection)) { | ||||
|             return CollUtil.newArrayList(); | ||||
|         } | ||||
|         return collection | ||||
|             .stream() | ||||
|             .map(function) | ||||
|             .filter(Objects::nonNull) | ||||
|             // 注意此处不要使用 .toList() 新语法 因为返回的是不可变List 会导致序列化问题 | ||||
|             .collect(Collectors.toList()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 将collection转化为Set集合,但是两者的泛型不同<br> | ||||
|      * <B>{@code Collection<E>  ------>  Set<T> } </B> | ||||
|      * | ||||
|      * @param collection 需要转化的集合 | ||||
|      * @param function   collection中的泛型转化为set泛型的lambda表达式 | ||||
|      * @param <E>        collection中的泛型 | ||||
|      * @param <T>        Set中的泛型 | ||||
|      * @return 转化后的Set | ||||
|      */ | ||||
|     public static <E, T> Set<T> toSet(Collection<E> collection, Function<E, T> function) { | ||||
|         if (CollUtil.isEmpty(collection) || function == null) { | ||||
|             return CollUtil.newHashSet(); | ||||
|         } | ||||
|         return collection | ||||
|             .stream() | ||||
|             .map(function) | ||||
|             .filter(Objects::nonNull) | ||||
|             .collect(Collectors.toSet()); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * 合并两个相同key类型的map | ||||
|      * | ||||
|      * @param map1  第一个需要合并的 map | ||||
|      * @param map2  第二个需要合并的 map | ||||
|      * @param merge 合并的lambda,将key  value1 value2合并成最终的类型,注意value可能为空的情况 | ||||
|      * @param <K>   map中的key类型 | ||||
|      * @param <X>   第一个 map的value类型 | ||||
|      * @param <Y>   第二个 map的value类型 | ||||
|      * @param <V>   最终map的value类型 | ||||
|      * @return 合并后的map | ||||
|      */ | ||||
|     public static <K, X, Y, V> Map<K, V> merge(Map<K, X> map1, Map<K, Y> map2, BiFunction<X, Y, V> merge) { | ||||
|         if (MapUtil.isEmpty(map1) && MapUtil.isEmpty(map2)) { | ||||
|             return MapUtil.newHashMap(); | ||||
|         } else if (MapUtil.isEmpty(map1)) { | ||||
|             map1 = MapUtil.newHashMap(); | ||||
|         } else if (MapUtil.isEmpty(map2)) { | ||||
|             map2 = MapUtil.newHashMap(); | ||||
|         } | ||||
|         Set<K> key = new HashSet<>(); | ||||
|         key.addAll(map1.keySet()); | ||||
|         key.addAll(map2.keySet()); | ||||
|         Map<K, V> map = new HashMap<>(); | ||||
|         for (K t : key) { | ||||
|             X x = map1.get(t); | ||||
|             Y y = map2.get(t); | ||||
|             V z = merge.apply(x, y); | ||||
|             if (z != null) { | ||||
|                 map.put(t, z); | ||||
|             } | ||||
|         } | ||||
|         return map; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,342 @@ | ||||
| package org.dromara.common.core.utils; | ||||
|  | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| import cn.hutool.core.convert.Convert; | ||||
| import cn.hutool.core.lang.Validator; | ||||
| import cn.hutool.core.util.StrUtil; | ||||
| import org.springframework.util.AntPathMatcher; | ||||
|  | ||||
| import java.util.*; | ||||
| import java.util.function.Function; | ||||
| import java.util.stream.Collectors; | ||||
|  | ||||
| /** | ||||
|  * 字符串工具类 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| public class StringUtils extends org.apache.commons.lang3.StringUtils { | ||||
|  | ||||
|     public static final String SEPARATOR = ","; | ||||
|  | ||||
|     public static final String SLASH = "/"; | ||||
|  | ||||
|     @Deprecated | ||||
|     private StringUtils() { | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取参数不为空值 | ||||
|      * | ||||
|      * @param str defaultValue 要判断的value | ||||
|      * @return value 返回值 | ||||
|      */ | ||||
|     public static String blankToDefault(String str, String defaultValue) { | ||||
|         return StrUtil.blankToDefault(str, defaultValue); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * * 判断一个字符串是否为空串 | ||||
|      * | ||||
|      * @param str String | ||||
|      * @return true:为空 false:非空 | ||||
|      */ | ||||
|     public static boolean isEmpty(String str) { | ||||
|         return StrUtil.isEmpty(str); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * * 判断一个字符串是否为非空串 | ||||
|      * | ||||
|      * @param str String | ||||
|      * @return true:非空串 false:空串 | ||||
|      */ | ||||
|     public static boolean isNotEmpty(String str) { | ||||
|         return !isEmpty(str); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 去空格 | ||||
|      */ | ||||
|     public static String trim(String str) { | ||||
|         return StrUtil.trim(str); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 截取字符串 | ||||
|      * | ||||
|      * @param str   字符串 | ||||
|      * @param start 开始 | ||||
|      * @return 结果 | ||||
|      */ | ||||
|     public static String substring(final String str, int start) { | ||||
|         return substring(str, start, str.length()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 截取字符串 | ||||
|      * | ||||
|      * @param str   字符串 | ||||
|      * @param start 开始 | ||||
|      * @param end   结束 | ||||
|      * @return 结果 | ||||
|      */ | ||||
|     public static String substring(final String str, int start, int end) { | ||||
|         return StrUtil.sub(str, start, end); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 格式化文本, {} 表示占位符<br> | ||||
|      * 此方法只是简单将占位符 {} 按照顺序替换为参数<br> | ||||
|      * 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可<br> | ||||
|      * 例:<br> | ||||
|      * 通常使用:format("this is {} for {}", "a", "b") -> this is a for b<br> | ||||
|      * 转义{}: format("this is \\{} for {}", "a", "b") -> this is {} for a<br> | ||||
|      * 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b<br> | ||||
|      * | ||||
|      * @param template 文本模板,被替换的部分用 {} 表示 | ||||
|      * @param params   参数值 | ||||
|      * @return 格式化后的文本 | ||||
|      */ | ||||
|     public static String format(String template, Object... params) { | ||||
|         return StrUtil.format(template, params); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 是否为http(s)://开头 | ||||
|      * | ||||
|      * @param link 链接 | ||||
|      * @return 结果 | ||||
|      */ | ||||
|     public static boolean ishttp(String link) { | ||||
|         return Validator.isUrl(link); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 字符串转set | ||||
|      * | ||||
|      * @param str 字符串 | ||||
|      * @param sep 分隔符 | ||||
|      * @return set集合 | ||||
|      */ | ||||
|     public static Set<String> str2Set(String str, String sep) { | ||||
|         return new HashSet<>(str2List(str, sep, true, false)); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 字符串转list | ||||
|      * | ||||
|      * @param str         字符串 | ||||
|      * @param sep         分隔符 | ||||
|      * @param filterBlank 过滤纯空白 | ||||
|      * @param trim        去掉首尾空白 | ||||
|      * @return list集合 | ||||
|      */ | ||||
|     public static List<String> str2List(String str, String sep, boolean filterBlank, boolean trim) { | ||||
|         List<String> list = new ArrayList<>(); | ||||
|         if (isEmpty(str)) { | ||||
|             return list; | ||||
|         } | ||||
|  | ||||
|         // 过滤空白字符串 | ||||
|         if (filterBlank && isBlank(str)) { | ||||
|             return list; | ||||
|         } | ||||
|         String[] split = str.split(sep); | ||||
|         for (String string : split) { | ||||
|             if (filterBlank && isBlank(string)) { | ||||
|                 continue; | ||||
|             } | ||||
|             if (trim) { | ||||
|                 string = trim(string); | ||||
|             } | ||||
|             list.add(string); | ||||
|         } | ||||
|  | ||||
|         return list; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 查找指定字符串是否包含指定字符串列表中的任意一个字符串同时串忽略大小写 | ||||
|      * | ||||
|      * @param cs                  指定字符串 | ||||
|      * @param searchCharSequences 需要检查的字符串数组 | ||||
|      * @return 是否包含任意一个字符串 | ||||
|      */ | ||||
|     public static boolean containsAnyIgnoreCase(CharSequence cs, CharSequence... searchCharSequences) { | ||||
|         return StrUtil.containsAnyIgnoreCase(cs, searchCharSequences); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 驼峰转下划线命名 | ||||
|      */ | ||||
|     public static String toUnderScoreCase(String str) { | ||||
|         return StrUtil.toUnderlineCase(str); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 是否包含字符串 | ||||
|      * | ||||
|      * @param str  验证字符串 | ||||
|      * @param strs 字符串组 | ||||
|      * @return 包含返回true | ||||
|      */ | ||||
|     public static boolean inStringIgnoreCase(String str, String... strs) { | ||||
|         return StrUtil.equalsAnyIgnoreCase(str, strs); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 将下划线大写方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。 例如:HELLO_WORLD->HelloWorld | ||||
|      * | ||||
|      * @param name 转换前的下划线大写方式命名的字符串 | ||||
|      * @return 转换后的驼峰式命名的字符串 | ||||
|      */ | ||||
|     public static String convertToCamelCase(String name) { | ||||
|         return StrUtil.upperFirst(StrUtil.toCamelCase(name)); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 驼峰式命名法 例如:user_name->userName | ||||
|      */ | ||||
|     public static String toCamelCase(String s) { | ||||
|         return StrUtil.toCamelCase(s); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 查找指定字符串是否匹配指定字符串列表中的任意一个字符串 | ||||
|      * | ||||
|      * @param str  指定字符串 | ||||
|      * @param strs 需要检查的字符串数组 | ||||
|      * @return 是否匹配 | ||||
|      */ | ||||
|     public static boolean matches(String str, List<String> strs) { | ||||
|         if (isEmpty(str) || CollUtil.isEmpty(strs)) { | ||||
|             return false; | ||||
|         } | ||||
|         for (String pattern : strs) { | ||||
|             if (isMatch(pattern, str)) { | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 判断url是否与规则配置: | ||||
|      * ? 表示单个字符; | ||||
|      * * 表示一层路径内的任意字符串,不可跨层级; | ||||
|      * ** 表示任意层路径; | ||||
|      * | ||||
|      * @param pattern 匹配规则 | ||||
|      * @param url     需要匹配的url | ||||
|      */ | ||||
|     public static boolean isMatch(String pattern, String url) { | ||||
|         AntPathMatcher matcher = new AntPathMatcher(); | ||||
|         return matcher.match(pattern, url); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 数字左边补齐0,使之达到指定长度。注意,如果数字转换为字符串后,长度大于size,则只保留 最后size个字符。 | ||||
|      * | ||||
|      * @param num  数字对象 | ||||
|      * @param size 字符串指定长度 | ||||
|      * @return 返回数字的字符串格式,该字符串为指定长度。 | ||||
|      */ | ||||
|     public static String padl(final Number num, final int size) { | ||||
|         return padl(num.toString(), size, '0'); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 字符串左补齐。如果原始字符串s长度大于size,则只保留最后size个字符。 | ||||
|      * | ||||
|      * @param s    原始字符串 | ||||
|      * @param size 字符串指定长度 | ||||
|      * @param c    用于补齐的字符 | ||||
|      * @return 返回指定长度的字符串,由原字符串左补齐或截取得到。 | ||||
|      */ | ||||
|     public static String padl(final String s, final int size, final char c) { | ||||
|         final StringBuilder sb = new StringBuilder(size); | ||||
|         if (s != null) { | ||||
|             final int len = s.length(); | ||||
|             if (s.length() <= size) { | ||||
|                 sb.append(String.valueOf(c).repeat(size - len)); | ||||
|                 sb.append(s); | ||||
|             } else { | ||||
|                 return s.substring(len - size, len); | ||||
|             } | ||||
|         } else { | ||||
|             sb.append(String.valueOf(c).repeat(Math.max(0, size))); | ||||
|         } | ||||
|         return sb.toString(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 切分字符串(分隔符默认逗号) | ||||
|      * | ||||
|      * @param str 被切分的字符串 | ||||
|      * @return 分割后的数据列表 | ||||
|      */ | ||||
|     public static List<String> splitList(String str) { | ||||
|         return splitTo(str, Convert::toStr); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 切分字符串 | ||||
|      * | ||||
|      * @param str       被切分的字符串 | ||||
|      * @param separator 分隔符 | ||||
|      * @return 分割后的数据列表 | ||||
|      */ | ||||
|     public static List<String> splitList(String str, String separator) { | ||||
|         return splitTo(str, separator, Convert::toStr); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 切分字符串自定义转换(分隔符默认逗号) | ||||
|      * | ||||
|      * @param str    被切分的字符串 | ||||
|      * @param mapper 自定义转换 | ||||
|      * @return 分割后的数据列表 | ||||
|      */ | ||||
|     public static <T> List<T> splitTo(String str, Function<? super Object, T> mapper) { | ||||
|         return splitTo(str, SEPARATOR, mapper); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 切分字符串自定义转换 | ||||
|      * | ||||
|      * @param str       被切分的字符串 | ||||
|      * @param separator 分隔符 | ||||
|      * @param mapper    自定义转换 | ||||
|      * @return 分割后的数据列表 | ||||
|      */ | ||||
|     public static <T> List<T> splitTo(String str, String separator, Function<? super Object, T> mapper) { | ||||
|         if (isBlank(str)) { | ||||
|             return new ArrayList<>(0); | ||||
|         } | ||||
|         return StrUtil.split(str, separator) | ||||
|             .stream() | ||||
|             .filter(Objects::nonNull) | ||||
|             .map(mapper) | ||||
|             .filter(Objects::nonNull) | ||||
|             .collect(Collectors.toList()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 不区分大小写检查 CharSequence 是否以指定的前缀开头。 | ||||
|      * | ||||
|      * @param str     要检查的 CharSequence 可能为 null | ||||
|      * @param prefixs 要查找的前缀可能为 null | ||||
|      * @return 是否包含 | ||||
|      */ | ||||
|     public static boolean startWithAnyIgnoreCase(CharSequence str, CharSequence... prefixs) { | ||||
|         // 判断是否是以指定字符串开头 | ||||
|         for (CharSequence prefix : prefixs) { | ||||
|             if (StringUtils.startsWithIgnoreCase(str, prefix)) { | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,63 @@ | ||||
| package org.dromara.common.core.utils; | ||||
|  | ||||
| import lombok.AccessLevel; | ||||
| import lombok.NoArgsConstructor; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
|  | ||||
| import java.util.concurrent.*; | ||||
|  | ||||
| /** | ||||
|  * 线程相关工具类. | ||||
|  * | ||||
|  * @author ruoyi | ||||
|  */ | ||||
| @Slf4j | ||||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||||
| public class Threads { | ||||
|     /** | ||||
|      * 停止线程池 | ||||
|      * 先使用shutdown, 停止接收新任务并尝试完成所有已存在任务. | ||||
|      * 如果超时, 则调用shutdownNow, 取消在workQueue中Pending的任务,并中断所有阻塞函数. | ||||
|      * 如果仍然超時,則強制退出. | ||||
|      * 另对在shutdown时线程本身被调用中断做了处理. | ||||
|      */ | ||||
|     public static void shutdownAndAwaitTermination(ExecutorService pool) { | ||||
|         if (pool != null && !pool.isShutdown()) { | ||||
|             pool.shutdown(); | ||||
|             try { | ||||
|                 if (!pool.awaitTermination(120, TimeUnit.SECONDS)) { | ||||
|                     pool.shutdownNow(); | ||||
|                     if (!pool.awaitTermination(120, TimeUnit.SECONDS)) { | ||||
|                         log.info("Pool did not terminate"); | ||||
|                     } | ||||
|                 } | ||||
|             } catch (InterruptedException ie) { | ||||
|                 pool.shutdownNow(); | ||||
|                 Thread.currentThread().interrupt(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 打印线程异常信息 | ||||
|      */ | ||||
|     public static void printException(Runnable r, Throwable t) { | ||||
|         if (t == null && r instanceof Future<?>) { | ||||
|             try { | ||||
|                 Future<?> future = (Future<?>) r; | ||||
|                 if (future.isDone()) { | ||||
|                     future.get(); | ||||
|                 } | ||||
|             } catch (CancellationException ce) { | ||||
|                 t = ce; | ||||
|             } catch (ExecutionException ee) { | ||||
|                 t = ee.getCause(); | ||||
|             } catch (InterruptedException ie) { | ||||
|                 Thread.currentThread().interrupt(); | ||||
|             } | ||||
|         } | ||||
|         if (t != null) { | ||||
|             log.error(t.getMessage(), t); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,96 @@ | ||||
| package org.dromara.common.core.utils; | ||||
|  | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| import cn.hutool.core.lang.tree.Tree; | ||||
| import cn.hutool.core.lang.tree.TreeNodeConfig; | ||||
| import cn.hutool.core.lang.tree.TreeUtil; | ||||
| import cn.hutool.core.lang.tree.parser.NodeParser; | ||||
| import lombok.AccessLevel; | ||||
| import lombok.NoArgsConstructor; | ||||
| import org.dromara.common.core.utils.reflect.ReflectUtils; | ||||
|  | ||||
| import java.util.List; | ||||
| import java.util.stream.Collectors; | ||||
| import java.util.stream.Stream; | ||||
|  | ||||
| /** | ||||
|  * 扩展 hutool TreeUtil 封装系统树构建 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||||
| public class TreeBuildUtils extends TreeUtil { | ||||
|  | ||||
|     /** | ||||
|      * 根据前端定制差异化字段 | ||||
|      */ | ||||
|     public static final TreeNodeConfig DEFAULT_CONFIG = TreeNodeConfig.DEFAULT_CONFIG.setNameKey("label"); | ||||
|  | ||||
|     /** | ||||
|      * 构建树形结构 | ||||
|      * | ||||
|      * @param <T>        输入节点的类型 | ||||
|      * @param <K>        节点ID的类型 | ||||
|      * @param list       节点列表,其中包含了要构建树形结构的所有节点 | ||||
|      * @param nodeParser 解析器,用于将输入节点转换为树节点 | ||||
|      * @return 构建好的树形结构列表 | ||||
|      */ | ||||
|     public static <T, K> List<Tree<K>> build(List<T> list, NodeParser<T, K> nodeParser) { | ||||
|         if (CollUtil.isEmpty(list)) { | ||||
|             return CollUtil.newArrayList(); | ||||
|         } | ||||
|         K k = ReflectUtils.invokeGetter(list.get(0), "parentId"); | ||||
|         return TreeUtil.build(list, k, DEFAULT_CONFIG, nodeParser); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 构建树形结构 | ||||
|      * | ||||
|      * @param <T>        输入节点的类型 | ||||
|      * @param <K>        节点ID的类型 | ||||
|      * @param parentId   顶级节点 | ||||
|      * @param list       节点列表,其中包含了要构建树形结构的所有节点 | ||||
|      * @param nodeParser 解析器,用于将输入节点转换为树节点 | ||||
|      * @return 构建好的树形结构列表 | ||||
|      */ | ||||
|     public static <T, K> List<Tree<K>> build(List<T> list, K parentId, NodeParser<T, K> nodeParser) { | ||||
|         if (CollUtil.isEmpty(list)) { | ||||
|             return CollUtil.newArrayList(); | ||||
|         } | ||||
|         return TreeUtil.build(list, parentId, DEFAULT_CONFIG, nodeParser); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取节点列表中所有节点的叶子节点 | ||||
|      * | ||||
|      * @param <K>   节点ID的类型 | ||||
|      * @param nodes 节点列表 | ||||
|      * @return 包含所有叶子节点的列表 | ||||
|      */ | ||||
|     public static <K> List<Tree<K>> getLeafNodes(List<Tree<K>> nodes) { | ||||
|         if (CollUtil.isEmpty(nodes)) { | ||||
|             return CollUtil.newArrayList(); | ||||
|         } | ||||
|         return nodes.stream() | ||||
|             .flatMap(TreeBuildUtils::extractLeafNodes) | ||||
|             .collect(Collectors.toList()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取指定节点下的所有叶子节点 | ||||
|      * | ||||
|      * @param <K>  节点ID的类型 | ||||
|      * @param node 要查找叶子节点的根节点 | ||||
|      * @return 包含所有叶子节点的列表 | ||||
|      */ | ||||
|     private static <K> Stream<Tree<K>> extractLeafNodes(Tree<K> node) { | ||||
|         if (!node.hasChild()) { | ||||
|             return Stream.of(node); | ||||
|         } else { | ||||
|             // 递归调用,获取所有子节点的叶子节点 | ||||
|             return node.getChildren().stream() | ||||
|                 .flatMap(TreeBuildUtils::extractLeafNodes); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,35 @@ | ||||
| package org.dromara.common.core.utils; | ||||
|  | ||||
| import jakarta.validation.ConstraintViolation; | ||||
| import jakarta.validation.ConstraintViolationException; | ||||
| import jakarta.validation.Validator; | ||||
| import lombok.AccessLevel; | ||||
| import lombok.NoArgsConstructor; | ||||
|  | ||||
| import java.util.Set; | ||||
|  | ||||
| /** | ||||
|  * Validator 校验框架工具 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||||
| public class ValidatorUtils { | ||||
|  | ||||
|     private static final Validator VALID = SpringUtils.getBean(Validator.class); | ||||
|  | ||||
|     /** | ||||
|      * 对给定对象进行参数校验,并根据指定的校验组进行校验 | ||||
|      * | ||||
|      * @param object 要进行校验的对象 | ||||
|      * @param groups 校验组 | ||||
|      * @throws ConstraintViolationException 如果校验不通过,则抛出参数校验异常 | ||||
|      */ | ||||
|     public static <T> void validate(T object, Class<?>... groups) { | ||||
|         Set<ConstraintViolation<T>> validate = VALID.validate(object, groups); | ||||
|         if (!validate.isEmpty()) { | ||||
|             throw new ConstraintViolationException("参数校验异常", validate); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,43 @@ | ||||
| package org.dromara.common.core.utils.file; | ||||
|  | ||||
| import cn.hutool.core.io.FileUtil; | ||||
| import jakarta.servlet.http.HttpServletResponse; | ||||
| import lombok.AccessLevel; | ||||
| import lombok.NoArgsConstructor; | ||||
|  | ||||
| import java.net.URLEncoder; | ||||
| import java.nio.charset.StandardCharsets; | ||||
|  | ||||
| /** | ||||
|  * 文件处理工具类 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||||
| public class FileUtils extends FileUtil { | ||||
|  | ||||
|     /** | ||||
|      * 下载文件名重新编码 | ||||
|      * | ||||
|      * @param response     响应对象 | ||||
|      * @param realFileName 真实文件名 | ||||
|      */ | ||||
|     public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) { | ||||
|         String percentEncodedFileName = percentEncode(realFileName); | ||||
|         String contentDispositionValue = "attachment; filename=%s;filename*=utf-8''%s".formatted(percentEncodedFileName, percentEncodedFileName); | ||||
|         response.addHeader("Access-Control-Expose-Headers", "Content-Disposition,download-filename"); | ||||
|         response.setHeader("Content-disposition", contentDispositionValue); | ||||
|         response.setHeader("download-filename", percentEncodedFileName); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 百分号编码工具方法 | ||||
|      * | ||||
|      * @param s 需要百分号编码的字符串 | ||||
|      * @return 百分号编码后的字符串 | ||||
|      */ | ||||
|     public static String percentEncode(String s) { | ||||
|         String encode = URLEncoder.encode(s, StandardCharsets.UTF_8); | ||||
|         return encode.replaceAll("\\+", "%20"); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,40 @@ | ||||
| package org.dromara.common.core.utils.file; | ||||
|  | ||||
| /** | ||||
|  * 媒体类型工具类 | ||||
|  * | ||||
|  * @author ruoyi | ||||
|  */ | ||||
| public class MimeTypeUtils { | ||||
|     public static final String IMAGE_PNG = "image/png"; | ||||
|  | ||||
|     public static final String IMAGE_JPG = "image/jpg"; | ||||
|  | ||||
|     public static final String IMAGE_JPEG = "image/jpeg"; | ||||
|  | ||||
|     public static final String IMAGE_BMP = "image/bmp"; | ||||
|  | ||||
|     public static final String IMAGE_GIF = "image/gif"; | ||||
|  | ||||
|     public static final String[] IMAGE_EXTENSION = {"bmp", "gif", "jpg", "jpeg", "png"}; | ||||
|  | ||||
|     public static final String[] FLASH_EXTENSION = {"swf", "flv"}; | ||||
|  | ||||
|     public static final String[] MEDIA_EXTENSION = {"swf", "flv", "mp3", "wav", "wma", "wmv", "mid", "avi", "mpg", | ||||
|         "asf", "rm", "rmvb"}; | ||||
|  | ||||
|     public static final String[] VIDEO_EXTENSION = {"mp4", "avi", "rmvb"}; | ||||
|  | ||||
|     public static final String[] DEFAULT_ALLOWED_EXTENSION = { | ||||
|         // 图片 | ||||
|         "bmp", "gif", "jpg", "jpeg", "png", | ||||
|         // word excel powerpoint | ||||
|         "doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt", | ||||
|         // 压缩文件 | ||||
|         "rar", "zip", "gz", "bz2", | ||||
|         // 视频格式 | ||||
|         "mp4", "avi", "rmvb", | ||||
|         // pdf | ||||
|         "pdf"}; | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,33 @@ | ||||
| package org.dromara.common.core.utils.ip; | ||||
|  | ||||
| import cn.hutool.core.net.NetUtil; | ||||
| import cn.hutool.http.HtmlUtil; | ||||
| import org.dromara.common.core.utils.StringUtils; | ||||
| import lombok.AccessLevel; | ||||
| import lombok.NoArgsConstructor; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
|  | ||||
| /** | ||||
|  * 获取地址类 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @Slf4j | ||||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||||
| public class AddressUtils { | ||||
|  | ||||
|     // 未知地址 | ||||
|     public static final String UNKNOWN = "XX XX"; | ||||
|  | ||||
|     public static String getRealAddressByIP(String ip) { | ||||
|         if (StringUtils.isBlank(ip)) { | ||||
|             return UNKNOWN; | ||||
|         } | ||||
|         // 内网不查询 | ||||
|         ip = StringUtils.contains(ip, "0:0:0:0:0:0:0:1") ? "127.0.0.1" : HtmlUtil.cleanHtmlTag(ip); | ||||
|         if (NetUtil.isInnerIP(ip)) { | ||||
|             return "内网IP"; | ||||
|         } | ||||
|         return RegionUtils.getCityInfo(ip); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,67 @@ | ||||
| package org.dromara.common.core.utils.ip; | ||||
|  | ||||
| import cn.hutool.core.io.FileUtil; | ||||
| import cn.hutool.core.io.resource.ClassPathResource; | ||||
| import cn.hutool.core.util.ObjectUtil; | ||||
| import org.dromara.common.core.exception.ServiceException; | ||||
| import org.dromara.common.core.utils.file.FileUtils; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.lionsoul.ip2region.xdb.Searcher; | ||||
|  | ||||
| import java.io.File; | ||||
|  | ||||
| /** | ||||
|  * 根据ip地址定位工具类,离线方式 | ||||
|  * 参考地址:<a href="https://gitee.com/lionsoul/ip2region/tree/master/binding/java">集成 ip2region 实现离线IP地址定位库</a> | ||||
|  * | ||||
|  * @author lishuyan | ||||
|  */ | ||||
| @Slf4j | ||||
| public class RegionUtils { | ||||
|  | ||||
|     private static final Searcher SEARCHER; | ||||
|  | ||||
|     static { | ||||
|         String fileName = "/ip2region.xdb"; | ||||
|         File existFile = FileUtils.file(FileUtil.getTmpDir() + FileUtil.FILE_SEPARATOR + fileName); | ||||
|         if (!FileUtils.exist(existFile)) { | ||||
|             ClassPathResource fileStream = new ClassPathResource(fileName); | ||||
|             if (ObjectUtil.isEmpty(fileStream.getStream())) { | ||||
|                 throw new ServiceException("RegionUtils初始化失败,原因:IP地址库数据不存在!"); | ||||
|             } | ||||
|             FileUtils.writeFromStream(fileStream.getStream(), existFile); | ||||
|         } | ||||
|  | ||||
|         String dbPath = existFile.getPath(); | ||||
|  | ||||
|         // 1、从 dbPath 加载整个 xdb 到内存。 | ||||
|         byte[] cBuff; | ||||
|         try { | ||||
|             cBuff = Searcher.loadContentFromFile(dbPath); | ||||
|         } catch (Exception e) { | ||||
|             throw new ServiceException("RegionUtils初始化失败,原因:从ip2region.xdb文件加载内容失败!" + e.getMessage()); | ||||
|         } | ||||
|         // 2、使用上述的 cBuff 创建一个完全基于内存的查询对象。 | ||||
|         try { | ||||
|             SEARCHER = Searcher.newWithBuffer(cBuff); | ||||
|         } catch (Exception e) { | ||||
|             throw new ServiceException("RegionUtils初始化失败,原因:" + e.getMessage()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 根据IP地址离线获取城市 | ||||
|      */ | ||||
|     public static String getCityInfo(String ip) { | ||||
|         try { | ||||
|             ip = ip.trim(); | ||||
|             // 3、执行查询 | ||||
|             String region = SEARCHER.search(ip); | ||||
|             return region.replace("0|", "").replace("|0", ""); | ||||
|         } catch (Exception e) { | ||||
|             log.error("IP地址离线获取城市异常 {}", ip); | ||||
|             return "未知"; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,56 @@ | ||||
| package org.dromara.common.core.utils.reflect; | ||||
|  | ||||
| import cn.hutool.core.util.ReflectUtil; | ||||
| import org.dromara.common.core.utils.StringUtils; | ||||
| import lombok.AccessLevel; | ||||
| import lombok.NoArgsConstructor; | ||||
|  | ||||
| import java.lang.reflect.Method; | ||||
|  | ||||
| /** | ||||
|  * 反射工具类. 提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class, 被AOP过的真实类等工具函数. | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @SuppressWarnings("rawtypes") | ||||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||||
| public class ReflectUtils extends ReflectUtil { | ||||
|  | ||||
|     private static final String SETTER_PREFIX = "set"; | ||||
|  | ||||
|     private static final String GETTER_PREFIX = "get"; | ||||
|  | ||||
|     /** | ||||
|      * 调用Getter方法. | ||||
|      * 支持多级,如:对象名.对象名.方法 | ||||
|      */ | ||||
|     @SuppressWarnings("unchecked") | ||||
|     public static <E> E invokeGetter(Object obj, String propertyName) { | ||||
|         Object object = obj; | ||||
|         for (String name : StringUtils.split(propertyName, ".")) { | ||||
|             String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(name); | ||||
|             object = invoke(object, getterMethodName); | ||||
|         } | ||||
|         return (E) object; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 调用Setter方法, 仅匹配方法名。 | ||||
|      * 支持多级,如:对象名.对象名.方法 | ||||
|      */ | ||||
|     public static <E> void invokeSetter(Object obj, String propertyName, E value) { | ||||
|         Object object = obj; | ||||
|         String[] names = StringUtils.split(propertyName, "."); | ||||
|         for (int i = 0; i < names.length; i++) { | ||||
|             if (i < names.length - 1) { | ||||
|                 String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(names[i]); | ||||
|                 object = invoke(object, getterMethodName); | ||||
|             } else { | ||||
|                 String setterMethodName = SETTER_PREFIX + StringUtils.capitalize(names[i]); | ||||
|                 Method method = getMethodByName(object.getClass(), setterMethodName); | ||||
|                 invoke(object, method, value); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,31 @@ | ||||
| package org.dromara.common.core.utils.regex; | ||||
|  | ||||
|  | ||||
| import cn.hutool.core.util.ReUtil; | ||||
| import org.dromara.common.core.constant.RegexConstants; | ||||
|  | ||||
| /** | ||||
|  * 正则相关工具类 | ||||
|  * | ||||
|  * @author Feng | ||||
|  */ | ||||
| public final class RegexUtils extends ReUtil { | ||||
|  | ||||
|     /** | ||||
|      * 从输入字符串中提取匹配的部分,如果没有匹配则返回默认值 | ||||
|      * | ||||
|      * @param input        要提取的输入字符串 | ||||
|      * @param regex        用于匹配的正则表达式,可以使用 {@link RegexConstants} 中定义的常量 | ||||
|      * @param defaultInput 如果没有匹配时返回的默认值 | ||||
|      * @return 如果找到匹配的部分,则返回匹配的部分,否则返回默认值 | ||||
|      */ | ||||
|     public static String extractFromString(String input, String regex, String defaultInput) { | ||||
|         try { | ||||
|             String str = ReUtil.get(regex, input, 1); | ||||
|             return str == null ? defaultInput : str; | ||||
|         } catch (Exception e) { | ||||
|             return defaultInput; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,105 @@ | ||||
| package org.dromara.common.core.utils.regex; | ||||
|  | ||||
| import cn.hutool.core.exceptions.ValidateException; | ||||
| import cn.hutool.core.lang.Validator; | ||||
| import org.dromara.common.core.factory.RegexPatternPoolFactory; | ||||
|  | ||||
| import java.util.regex.Pattern; | ||||
|  | ||||
| /** | ||||
|  * 正则字段校验器 | ||||
|  * 主要验证字段非空、是否为满足指定格式等 | ||||
|  * | ||||
|  * @author Feng | ||||
|  */ | ||||
| public class RegexValidator extends Validator { | ||||
|  | ||||
|     /** | ||||
|      * 字典类型必须以字母开头,且只能为(小写字母,数字,下滑线) | ||||
|      */ | ||||
|     public static final Pattern DICTIONARY_TYPE = RegexPatternPoolFactory.DICTIONARY_TYPE; | ||||
|  | ||||
|     /** | ||||
|      * 身份证号码(后6位) | ||||
|      */ | ||||
|     public static final Pattern ID_CARD_LAST_6 = RegexPatternPoolFactory.ID_CARD_LAST_6; | ||||
|  | ||||
|     /** | ||||
|      * QQ号码 | ||||
|      */ | ||||
|     public static final Pattern QQ_NUMBER = RegexPatternPoolFactory.QQ_NUMBER; | ||||
|  | ||||
|     /** | ||||
|      * 邮政编码 | ||||
|      */ | ||||
|     public static final Pattern POSTAL_CODE = RegexPatternPoolFactory.POSTAL_CODE; | ||||
|  | ||||
|     /** | ||||
|      * 注册账号 | ||||
|      */ | ||||
|     public static final Pattern ACCOUNT = RegexPatternPoolFactory.ACCOUNT; | ||||
|  | ||||
|     /** | ||||
|      * 密码:包含至少8个字符,包括大写字母、小写字母、数字和特殊字符 | ||||
|      */ | ||||
|     public static final Pattern PASSWORD = RegexPatternPoolFactory.PASSWORD; | ||||
|  | ||||
|     /** | ||||
|      * 通用状态(0表示正常,1表示停用) | ||||
|      */ | ||||
|     public static final Pattern STATUS = RegexPatternPoolFactory.STATUS; | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * 检查输入的账号是否匹配预定义的规则 | ||||
|      * | ||||
|      * @param value 要验证的账号 | ||||
|      * @return 如果账号符合规则,返回 true;否则,返回 false。 | ||||
|      */ | ||||
|     public static boolean isAccount(CharSequence value) { | ||||
|         return isMatchRegex(ACCOUNT, value); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 验证输入的账号是否符合规则,如果不符合,则抛出 ValidateException 异常 | ||||
|      * | ||||
|      * @param value    要验证的账号 | ||||
|      * @param errorMsg 验证失败时抛出的异常消息 | ||||
|      * @param <T>      CharSequence 的子类型 | ||||
|      * @return 如果验证通过,返回输入的账号 | ||||
|      * @throws ValidateException 如果验证失败 | ||||
|      */ | ||||
|     public static <T extends CharSequence> T validateAccount(T value, String errorMsg) throws ValidateException { | ||||
|         if (!isAccount(value)) { | ||||
|             throw new ValidateException(errorMsg); | ||||
|         } | ||||
|         return value; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 检查输入的状态是否匹配预定义的规则 | ||||
|      * | ||||
|      * @param value 要验证的状态 | ||||
|      * @return 如果状态符合规则,返回 true;否则,返回 false。 | ||||
|      */ | ||||
|     public static boolean isStatus(CharSequence value) { | ||||
|         return isMatchRegex(STATUS, value); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 验证输入的状态是否符合规则,如果不符合,则抛出 ValidateException 异常 | ||||
|      * | ||||
|      * @param value    要验证的状态 | ||||
|      * @param errorMsg 验证失败时抛出的异常消息 | ||||
|      * @param <T>      CharSequence 的子类型 | ||||
|      * @return 如果验证通过,返回输入的状态 | ||||
|      * @throws ValidateException 如果验证失败 | ||||
|      */ | ||||
|     public static <T extends CharSequence> T validateStatus(T value, String errorMsg) throws ValidateException { | ||||
|         if (!isStatus(value)) { | ||||
|             throw new ValidateException(errorMsg); | ||||
|         } | ||||
|         return value; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,56 @@ | ||||
| package org.dromara.common.core.utils.sql; | ||||
|  | ||||
| import lombok.AccessLevel; | ||||
| import lombok.NoArgsConstructor; | ||||
| import org.dromara.common.core.utils.StringUtils; | ||||
|  | ||||
| /** | ||||
|  * sql操作工具类 | ||||
|  * | ||||
|  * @author ruoyi | ||||
|  */ | ||||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||||
| public class SqlUtil { | ||||
|  | ||||
|     /** | ||||
|      * 定义常用的 sql关键字 | ||||
|      */ | ||||
|     public static String SQL_REGEX = "\u000B|and |extractvalue|updatexml|sleep|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |or |union |like |+|/*|user()"; | ||||
|  | ||||
|     /** | ||||
|      * 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序) | ||||
|      */ | ||||
|     public static final String SQL_PATTERN = "[a-zA-Z0-9_\\ \\,\\.]+"; | ||||
|  | ||||
|     /** | ||||
|      * 检查字符,防止注入绕过 | ||||
|      */ | ||||
|     public static String escapeOrderBySql(String value) { | ||||
|         if (StringUtils.isNotEmpty(value) && !isValidOrderBySql(value)) { | ||||
|             throw new IllegalArgumentException("参数不符合规范,不能进行查询"); | ||||
|         } | ||||
|         return value; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 验证 order by 语法是否符合规范 | ||||
|      */ | ||||
|     public static boolean isValidOrderBySql(String value) { | ||||
|         return value.matches(SQL_PATTERN); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * SQL关键字检查 | ||||
|      */ | ||||
|     public static void filterKeyword(String value) { | ||||
|         if (StringUtils.isEmpty(value)) { | ||||
|             return; | ||||
|         } | ||||
|         String[] sqlKeywords = StringUtils.split(SQL_REGEX, "\\|"); | ||||
|         for (String sqlKeyword : sqlKeywords) { | ||||
|             if (StringUtils.indexOfIgnoreCase(value, sqlKeyword) > -1) { | ||||
|                 throw new IllegalArgumentException("参数存在SQL注入风险"); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,9 @@ | ||||
| package org.dromara.common.core.validate; | ||||
|  | ||||
| /** | ||||
|  * 校验分组 add | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| public interface AddGroup { | ||||
| } | ||||
| @ -0,0 +1,9 @@ | ||||
| package org.dromara.common.core.validate; | ||||
|  | ||||
| /** | ||||
|  * 校验分组 edit | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| public interface EditGroup { | ||||
| } | ||||
| @ -0,0 +1,9 @@ | ||||
| package org.dromara.common.core.validate; | ||||
|  | ||||
| /** | ||||
|  * 校验分组 query | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| public interface QueryGroup { | ||||
| } | ||||
| @ -0,0 +1,48 @@ | ||||
| package org.dromara.common.core.validate.enumd; | ||||
|  | ||||
| import jakarta.validation.Constraint; | ||||
| import jakarta.validation.Payload; | ||||
|  | ||||
| import java.lang.annotation.*; | ||||
|  | ||||
| import static java.lang.annotation.ElementType.*; | ||||
| import static java.lang.annotation.RetentionPolicy.RUNTIME; | ||||
|  | ||||
| /** | ||||
|  * 自定义枚举校验 | ||||
|  * | ||||
|  * @author 秋辞未寒 | ||||
|  * @date 2024-12-09 | ||||
|  */ | ||||
| @Documented | ||||
| @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE}) | ||||
| @Retention(RUNTIME) | ||||
| @Repeatable(EnumPattern.List.class) // 允许在同一元素上多次使用该注解 | ||||
| @Constraint(validatedBy = {EnumPatternValidator.class}) | ||||
| public @interface EnumPattern { | ||||
|  | ||||
|     /** | ||||
|      * 需要校验的枚举类型 | ||||
|      */ | ||||
|     Class<? extends Enum<?>> type(); | ||||
|  | ||||
|     /** | ||||
|      * 枚举类型校验值字段名称 | ||||
|      * 需确保该字段实现了 getter 方法 | ||||
|      */ | ||||
|     String fieldName(); | ||||
|  | ||||
|     String message() default "输入值不在枚举范围内"; | ||||
|  | ||||
|     Class<?>[] groups() default {}; | ||||
|  | ||||
|     Class<? extends Payload>[] payload() default {}; | ||||
|  | ||||
|     @Documented | ||||
|     @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE}) | ||||
|     @Retention(RUNTIME) | ||||
|     @interface List { | ||||
|         EnumPattern[] value(); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,37 @@ | ||||
| package org.dromara.common.core.validate.enumd; | ||||
|  | ||||
| import jakarta.validation.ConstraintValidator; | ||||
| import jakarta.validation.ConstraintValidatorContext; | ||||
| import org.dromara.common.core.utils.StringUtils; | ||||
| import org.dromara.common.core.utils.reflect.ReflectUtils; | ||||
|  | ||||
| /** | ||||
|  * 自定义枚举校验注解实现 | ||||
|  * | ||||
|  * @author 秋辞未寒 | ||||
|  * @date 2024-12-09 | ||||
|  */ | ||||
| public class EnumPatternValidator implements ConstraintValidator<EnumPattern, String> { | ||||
|  | ||||
|     private EnumPattern annotation;; | ||||
|  | ||||
|     @Override | ||||
|     public void initialize(EnumPattern annotation) { | ||||
|         ConstraintValidator.super.initialize(annotation); | ||||
|         this.annotation = annotation; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) { | ||||
|         if (StringUtils.isNotBlank(value)) { | ||||
|             String fieldName = annotation.fieldName(); | ||||
|             for (Object e : annotation.type().getEnumConstants()) { | ||||
|                 if (value.equals(ReflectUtils.invokeGetter(e, fieldName))) { | ||||
|                     return true; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,26 @@ | ||||
| package org.dromara.common.core.xss; | ||||
|  | ||||
| import jakarta.validation.Constraint; | ||||
| import jakarta.validation.Payload; | ||||
| import java.lang.annotation.ElementType; | ||||
| import java.lang.annotation.Retention; | ||||
| import java.lang.annotation.RetentionPolicy; | ||||
| import java.lang.annotation.Target; | ||||
|  | ||||
| /** | ||||
|  * 自定义xss校验注解 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @Retention(RetentionPolicy.RUNTIME) | ||||
| @Target(value = {ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER}) | ||||
| @Constraint(validatedBy = {XssValidator.class}) | ||||
| public @interface Xss { | ||||
|  | ||||
|     String message() default "不允许任何脚本运行"; | ||||
|  | ||||
|     Class<?>[] groups() default {}; | ||||
|  | ||||
|     Class<? extends Payload>[] payload() default {}; | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,21 @@ | ||||
| package org.dromara.common.core.xss; | ||||
|  | ||||
| import cn.hutool.core.util.ReUtil; | ||||
| import cn.hutool.http.HtmlUtil; | ||||
|  | ||||
| import jakarta.validation.ConstraintValidator; | ||||
| import jakarta.validation.ConstraintValidatorContext; | ||||
|  | ||||
| /** | ||||
|  * 自定义xss校验注解实现 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| public class XssValidator implements ConstraintValidator<Xss, String> { | ||||
|  | ||||
|     @Override | ||||
|     public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) { | ||||
|         return !ReUtil.contains(HtmlUtil.RE_HTML_MARK, value); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,6 @@ | ||||
| org.dromara.common.core.config.ApplicationConfig | ||||
| org.dromara.common.core.config.AsyncConfig | ||||
| org.dromara.common.core.config.RuoYiConfig | ||||
| org.dromara.common.core.config.ThreadPoolConfig | ||||
| org.dromara.common.core.config.ValidatorConfig | ||||
| org.dromara.common.core.utils.SpringUtils | ||||
		Reference in New Issue
	
	Block a user