This commit is contained in:
张良(004796)
2024-03-04 18:54:18 +08:00
commit 273ee16e8c
585 changed files with 41565 additions and 0 deletions

View File

@@ -0,0 +1,102 @@
package com.ruoyi.common.utils;
import com.google.common.collect.Lists;
import org.springframework.beans.BeanUtils;
import java.util.Collections;
import java.util.List;
import java.util.function.Supplier;
/**
* bean工具
* @Author: 004795
* @Date: 2022/5/24 11:11
*/
public class BeanConvertUtil extends BeanUtils {
private BeanConvertUtil(){}
@FunctionalInterface
public interface CallBack<S, T> {
/**
* 回调方法
*
* @param s 源对象
* @param t 目标对象
*/
void callBack(S s, T t);
}
/**
* 转换list 对象
*
* @param sources 源对象
* @param targetSupplier 目标对象供应方
* @param callBack 回调方法
* @param <S> 源对象类型
* @param <T> 目标对象类型
* @Return 转换对象
*/
public static <S, T> List<T> convertListTo(List<S> sources, Supplier<T> targetSupplier, CallBack<S, T> callBack) {
if (null == sources || null == targetSupplier) {
return Collections.emptyList();
}
List<T> list = Lists.newArrayListWithCapacity(sources.size());
for (S source : sources) {
T target = targetSupplier.get();
copyProperties(source, target);
if (callBack != null) {
callBack.callBack(source, target);
}
list.add(target);
}
return list;
}
/**
* 转换list 对象
*
* @param sources 源对象
* @param targetSupplier 目标对象供应方
* @param <S> 源对象类型
* @param <T> 目标对象类型
*/
public static <S, T> List<T> convertListTo(List<S> sources, Supplier<T> targetSupplier) {
return convertListTo(sources, targetSupplier, null);
}
/**
* 转换对象
*
* @param source 源对象
* @param targetSupplier 目标对象供应方
* @param <S> 源对象类型
* @param <T> 目标对象类型
* @return 目标对象
*/
public static <S, T> T convertTo(S source, Supplier<T> targetSupplier) {
return convertTo(source, targetSupplier, null);
}
/**
* 转换对象
*
* @param source 源对象
* @param targetSupplier 目标对象供应方
* @param callBack 回调方法
* @param <S> 源对象类型
* @param <T> 目标对象类型
* @return 目标对象
*/
public static <S, T> T convertTo(S source, Supplier<T> targetSupplier, CallBack<S, T> callBack) {
if (null == source || null == targetSupplier) {
return null;
}
T target = targetSupplier.get();
copyProperties(source, target);
if (callBack != null) {
callBack.callBack(source, target);
}
return target;
}
}

View File

@@ -0,0 +1,203 @@
package com.ruoyi.common.utils;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.SimpleCache;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.springframework.cglib.beans.BeanCopier;
import org.springframework.cglib.beans.BeanMap;
import org.springframework.cglib.core.Converter;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* bean拷贝工具(基于 cglib 性能优异)
* <p>
* 重点 cglib 不支持 拷贝到链式对象
* 例如: 源对象 拷贝到 目标(链式对象)
*
* @author Lion Li
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class BeanCopyUtils {
/**
* 单对象基于class创建拷贝
*
* @param source 数据来源实体
* @param desc 描述对象 转换后的对象
* @return desc
*/
public static <T, V> V copy(T source, Class<V> desc) {
if (ObjectUtil.isNull(source)) {
return null;
}
if (ObjectUtil.isNull(desc)) {
return null;
}
final V target = ReflectUtil.newInstanceIfPossible(desc);
return copy(source, target);
}
/**
* 单对象基于对象创建拷贝
*
* @param source 数据来源实体
* @param desc 转换后的对象
* @return desc
*/
public static <T, V> V copy(T source, V desc) {
if (ObjectUtil.isNull(source)) {
return null;
}
if (ObjectUtil.isNull(desc)) {
return null;
}
BeanCopier beanCopier = BeanCopierCache.INSTANCE.get(source.getClass(), desc.getClass(), null);
beanCopier.copy(source, desc, null);
return desc;
}
/**
* 列表对象基于class创建拷贝
*
* @param sourceList 数据来源实体列表
* @param desc 描述对象 转换后的对象
* @return desc
*/
public static <T, V> List<V> copyList(List<T> sourceList, Class<V> desc) {
if (ObjectUtil.isNull(sourceList)) {
return null;
}
if (CollUtil.isEmpty(sourceList)) {
return CollUtil.newArrayList();
}
return StreamUtils.toList(sourceList, source -> {
V target = ReflectUtil.newInstanceIfPossible(desc);
copy(source, target);
return target;
});
}
/**
* bean拷贝到map
*
* @param bean 数据来源实体
* @return map对象
*/
@SuppressWarnings("unchecked")
public static <T> Map<String, Object> copyToMap(T bean) {
if (ObjectUtil.isNull(bean)) {
return null;
}
return BeanMap.create(bean);
}
/**
* map拷贝到bean
*
* @param map 数据来源
* @param beanClass bean类
* @return bean对象
*/
public static <T> T mapToBean(Map<String, Object> map, Class<T> beanClass) {
if (MapUtil.isEmpty(map)) {
return null;
}
if (ObjectUtil.isNull(beanClass)) {
return null;
}
T bean = ReflectUtil.newInstanceIfPossible(beanClass);
return mapToBean(map, bean);
}
/**
* map拷贝到bean
*
* @param map 数据来源
* @param bean bean对象
* @return bean对象
*/
public static <T> T mapToBean(Map<String, Object> map, T bean) {
if (MapUtil.isEmpty(map)) {
return null;
}
if (ObjectUtil.isNull(bean)) {
return null;
}
BeanMap.create(bean).putAll(map);
return bean;
}
/**
* map拷贝到map
*
* @param map 数据来源
* @param clazz 返回的对象类型
* @return map对象
*/
public static <T, V> Map<String, V> mapToMap(Map<String, T> map, Class<V> clazz) {
if (MapUtil.isEmpty(map)) {
return null;
}
if (ObjectUtil.isNull(clazz)) {
return null;
}
Map<String, V> copyMap = new LinkedHashMap<>(map.size());
map.forEach((k, v) -> copyMap.put(k, copy(v, clazz)));
return copyMap;
}
/**
* BeanCopier属性缓存<br>
* 缓存用于防止多次反射造成的性能问题
*
* @author Looly
* @since 5.4.1
*/
public enum BeanCopierCache {
/**
* BeanCopier属性缓存单例
*/
INSTANCE;
private final SimpleCache<String, BeanCopier> cache = new SimpleCache<>();
/**
* 获得类与转换器生成的key在{@link BeanCopier}的Map中对应的元素
*
* @param srcClass 源Bean的类
* @param targetClass 目标Bean的类
* @param converter 转换器
* @return Map中对应的BeanCopier
*/
public BeanCopier get(Class<?> srcClass, Class<?> targetClass, Converter converter) {
final String key = genKey(srcClass, targetClass, converter);
return cache.get(key, () -> BeanCopier.create(srcClass, targetClass, converter != null));
}
/**
* 获得类与转换器生成的key
*
* @param srcClass 源Bean的类
* @param targetClass 目标Bean的类
* @param converter 转换器
* @return 属性名和Map映射的key
*/
private String genKey(Class<?> srcClass, Class<?> targetClass, Converter converter) {
final StringBuilder key = StrUtil.builder()
.append(srcClass.getName()).append('#').append(targetClass.getName());
if (null != converter) {
key.append('#').append(converter.getClass().getName());
}
return key.toString();
}
}
}

View File

@@ -0,0 +1,168 @@
package com.ruoyi.common.utils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.time.DateFormatUtils;
import java.lang.management.ManagementFactory;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Date;
/**
* 时间工具类
*
* @author ruoyi
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
public static final String YYYY = "yyyy";
public static final String YYYY_MM = "yyyy-MM";
public static final String YYYY_MM_DD = "yyyy-MM-dd";
public static final String YYYYMMDDHHMMSS = "yyyyMMddHHmmss";
public static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
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"};
/**
* 获取当前Date型日期
*
* @return Date() 当前日期
*/
public static Date getNowDate() {
return new Date();
}
/**
* 获取当前日期, 默认格式为yyyy-MM-dd
*
* @return String
*/
public static String getDate() {
return dateTimeNow(YYYY_MM_DD);
}
public static String getTime() {
return dateTimeNow(YYYY_MM_DD_HH_MM_SS);
}
public static String dateTimeNow() {
return dateTimeNow(YYYYMMDDHHMMSS);
}
public static String dateTimeNow(final String format) {
return parseDateToStr(format, new Date());
}
public static String dateTime(final Date date) {
return parseDateToStr(YYYY_MM_DD, date);
}
public static String parseDateToStr(final String format, final Date date) {
return new SimpleDateFormat(format).format(date);
}
public static Date dateTime(final String format, final String ts) {
try {
return new SimpleDateFormat(format).parse(ts);
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
/**
* 日期路径 即年/月/日 如2018/08/08
*/
public static String datePath() {
Date now = new Date();
return DateFormatUtils.format(now, "yyyy/MM/dd");
}
/**
* 日期路径 即年/月/日 如20180808
*/
public static String dateTime() {
Date now = new Date();
return DateFormatUtils.format(now, "yyyyMMdd");
}
/**
* 日期型字符串转化为日期 格式
*/
public static Date parseDate(Object str) {
if (str == null) {
return null;
}
try {
return parseDate(str.toString(), PARSE_PATTERNS);
} catch (ParseException e) {
return null;
}
}
/**
* 获取服务器启动时间
*/
public static Date getServerStartDate() {
long time = ManagementFactory.getRuntimeMXBean().getStartTime();
return new Date(time);
}
/**
* 计算相差天数
*/
public static int differentDaysByMillisecond(Date date1, Date date2) {
return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000 * 3600 * 24)));
}
/**
* 计算两个时间差
*/
public static String getDatePoor(Date endDate, Date nowDate) {
long nd = 1000 * 24 * 60 * 60;
long nh = 1000 * 60 * 60;
long nm = 1000 * 60;
// long ns = 1000;
// 获得两个时间的毫秒时间差异
long diff = endDate.getTime() - nowDate.getTime();
// 计算差多少天
long day = diff / nd;
// 计算差多少小时
long hour = diff % nd / nh;
// 计算差多少分钟
long min = diff % nd % nh / nm;
// 计算差多少秒//输出结果
// long sec = diff % nd % nh % nm / ns;
return day + "" + hour + "小时" + min + "分钟";
}
/**
* 增加 LocalDateTime ==> Date
*/
public static Date toDate(LocalDateTime temporalAccessor) {
ZonedDateTime zdt = temporalAccessor.atZone(ZoneId.systemDefault());
return Date.from(zdt.toInstant());
}
/**
* 增加 LocalDate ==> 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());
}
}

View File

@@ -0,0 +1,311 @@
package com.ruoyi.common.utils;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import cn.hutool.crypto.asymmetric.SM2;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
/**
* 安全相关工具类
*
* @author 老马
*/
public class EncryptUtils {
/**
* 公钥
*/
public static final String PUBLIC_KEY = "publicKey";
/**
* 私钥
*/
public static final String PRIVATE_KEY = "privateKey";
/**
* Base64加密
*
* @param data 待加密数据
* @return 加密后字符串
*/
public static String encryptByBase64(String data) {
return Base64.encode(data, StandardCharsets.UTF_8);
}
/**
* Base64解密
*
* @param data 待解密数据
* @return 解密后字符串
*/
public static String decryptByBase64(String data) {
return Base64.decodeStr(data, StandardCharsets.UTF_8);
}
/**
* AES加密
*
* @param data 待解密数据
* @param password 秘钥字符串
* @return 加密后字符串, 采用Base64编码
*/
public static String encryptByAes(String data, String password) {
if (StrUtil.isBlank(password)) {
throw new IllegalArgumentException("AES需要传入秘钥信息");
}
// aes算法的秘钥要求是16位、24位、32位
int[] array = {16, 24, 32};
if (!ArrayUtil.contains(array, password.length())) {
throw new IllegalArgumentException("AES秘钥长度要求为16位、24位、32位");
}
return SecureUtil.aes(password.getBytes(StandardCharsets.UTF_8)).encryptBase64(data, StandardCharsets.UTF_8);
}
/**
* AES加密
*
* @param data 待解密数据
* @param password 秘钥字符串
* @return 加密后字符串, 采用Hex编码
*/
public static String encryptByAesHex(String data, String password) {
if (StrUtil.isBlank(password)) {
throw new IllegalArgumentException("AES需要传入秘钥信息");
}
// aes算法的秘钥要求是16位、24位、32位
int[] array = {16, 24, 32};
if (!ArrayUtil.contains(array, password.length())) {
throw new IllegalArgumentException("AES秘钥长度要求为16位、24位、32位");
}
return SecureUtil.aes(password.getBytes(StandardCharsets.UTF_8)).encryptHex(data, StandardCharsets.UTF_8);
}
/**
* AES解密
*
* @param data 待解密数据
* @param password 秘钥字符串
* @return 解密后字符串
*/
public static String decryptByAes(String data, String password) {
if (StrUtil.isBlank(password)) {
throw new IllegalArgumentException("AES需要传入秘钥信息");
}
// aes算法的秘钥要求是16位、24位、32位
int[] array = {16, 24, 32};
if (!ArrayUtil.contains(array, password.length())) {
throw new IllegalArgumentException("AES秘钥长度要求为16位、24位、32位");
}
return SecureUtil.aes(password.getBytes(StandardCharsets.UTF_8)).decryptStr(data, StandardCharsets.UTF_8);
}
/**
* sm4加密
*
* @param data 待加密数据
* @param password 秘钥字符串
* @return 加密后字符串, 采用Base64编码
*/
public static String encryptBySm4(String data, String password) {
if (StrUtil.isBlank(password)) {
throw new IllegalArgumentException("SM4需要传入秘钥信息");
}
// sm4算法的秘钥要求是16位长度
int sm4PasswordLength = 16;
if (sm4PasswordLength != password.length()) {
throw new IllegalArgumentException("SM4秘钥长度要求为16位");
}
return SmUtil.sm4(password.getBytes(StandardCharsets.UTF_8)).encryptBase64(data, StandardCharsets.UTF_8);
}
/**
* sm4加密
*
* @param data 待加密数据
* @param password 秘钥字符串
* @return 加密后字符串, 采用Base64编码
*/
public static String encryptBySm4Hex(String data, String password) {
if (StrUtil.isBlank(password)) {
throw new IllegalArgumentException("SM4需要传入秘钥信息");
}
// sm4算法的秘钥要求是16位长度
int sm4PasswordLength = 16;
if (sm4PasswordLength != password.length()) {
throw new IllegalArgumentException("SM4秘钥长度要求为16位");
}
return SmUtil.sm4(password.getBytes(StandardCharsets.UTF_8)).encryptHex(data, StandardCharsets.UTF_8);
}
/**
* sm4解密
*
* @param data 待解密数据
* @param password 秘钥字符串
* @return 解密后字符串
*/
public static String decryptBySm4(String data, String password) {
if (StrUtil.isBlank(password)) {
throw new IllegalArgumentException("SM4需要传入秘钥信息");
}
// sm4算法的秘钥要求是16位长度
int sm4PasswordLength = 16;
if (sm4PasswordLength != password.length()) {
throw new IllegalArgumentException("SM4秘钥长度要求为16位");
}
return SmUtil.sm4(password.getBytes(StandardCharsets.UTF_8)).decryptStr(data, StandardCharsets.UTF_8);
}
/**
* 产生sm2加解密需要的公钥和私钥
*
* @return 公私钥Map
*/
public static Map<String, String> generateSm2Key() {
Map<String, String> keyMap = new HashMap<>(2);
SM2 sm2 = SmUtil.sm2();
keyMap.put(PRIVATE_KEY, sm2.getPrivateKeyBase64());
keyMap.put(PUBLIC_KEY, sm2.getPublicKeyBase64());
return keyMap;
}
/**
* sm2公钥加密
*
* @param data 待加密数据
* @param publicKey 公钥
* @return 加密后字符串, 采用Base64编码
*/
public static String encryptBySm2(String data, String publicKey) {
if (StrUtil.isBlank(publicKey)) {
throw new IllegalArgumentException("SM2需要传入公钥进行加密");
}
SM2 sm2 = SmUtil.sm2(null, publicKey);
return sm2.encryptBase64(data, StandardCharsets.UTF_8, KeyType.PublicKey);
}
/**
* sm2公钥加密
*
* @param data 待加密数据
* @param publicKey 公钥
* @return 加密后字符串, 采用Hex编码
*/
public static String encryptBySm2Hex(String data, String publicKey) {
if (StrUtil.isBlank(publicKey)) {
throw new IllegalArgumentException("SM2需要传入公钥进行加密");
}
SM2 sm2 = SmUtil.sm2(null, publicKey);
return sm2.encryptHex(data, StandardCharsets.UTF_8, KeyType.PublicKey);
}
/**
* sm2私钥解密
*
* @param data 待加密数据
* @param privateKey 私钥
* @return 解密后字符串
*/
public static String decryptBySm2(String data, String privateKey) {
if (StrUtil.isBlank(privateKey)) {
throw new IllegalArgumentException("SM2需要传入私钥进行解密");
}
SM2 sm2 = SmUtil.sm2(privateKey, null);
return sm2.decryptStr(data, KeyType.PrivateKey, StandardCharsets.UTF_8);
}
/**
* 产生RSA加解密需要的公钥和私钥
*
* @return 公私钥Map
*/
public static Map<String, String> generateRsaKey() {
Map<String, String> keyMap = new HashMap<>(2);
RSA rsa = SecureUtil.rsa();
keyMap.put(PRIVATE_KEY, rsa.getPrivateKeyBase64());
keyMap.put(PUBLIC_KEY, rsa.getPublicKeyBase64());
return keyMap;
}
/**
* rsa公钥加密
*
* @param data 待加密数据
* @param publicKey 公钥
* @return 加密后字符串, 采用Base64编码
*/
public static String encryptByRsa(String data, String publicKey) {
if (StrUtil.isBlank(publicKey)) {
throw new IllegalArgumentException("RSA需要传入公钥进行加密");
}
RSA rsa = SecureUtil.rsa(null, publicKey);
return rsa.encryptBase64(data, StandardCharsets.UTF_8, KeyType.PublicKey);
}
/**
* rsa公钥加密
*
* @param data 待加密数据
* @param publicKey 公钥
* @return 加密后字符串, 采用Hex编码
*/
public static String encryptByRsaHex(String data, String publicKey) {
if (StrUtil.isBlank(publicKey)) {
throw new IllegalArgumentException("RSA需要传入公钥进行加密");
}
RSA rsa = SecureUtil.rsa(null, publicKey);
return rsa.encryptHex(data, StandardCharsets.UTF_8, KeyType.PublicKey);
}
/**
* rsa私钥解密
*
* @param data 待加密数据
* @param privateKey 私钥
* @return 解密后字符串
*/
public static String decryptByRsa(String data, String privateKey) {
if (StrUtil.isBlank(privateKey)) {
throw new IllegalArgumentException("RSA需要传入私钥进行解密");
}
RSA rsa = SecureUtil.rsa(privateKey, null);
return rsa.decryptStr(data, KeyType.PrivateKey, StandardCharsets.UTF_8);
}
/**
* md5加密
*
* @param data 待加密数据
* @return 加密后字符串, 采用Hex编码
*/
public static String encryptByMd5(String data) {
return SecureUtil.md5(data);
}
/**
* sha256加密
*
* @param data 待加密数据
* @return 加密后字符串, 采用Hex编码
*/
public static String encryptBySha256(String data) {
return SecureUtil.sha256(data);
}
/**
* sm3加密
*
* @param data 待加密数据
* @return 加密后字符串, 采用Hex编码
*/
public static String encryptBySm3(String data) {
return SmUtil.sm3(data);
}
}

View File

@@ -0,0 +1,112 @@
package com.ruoyi.common.utils;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
import com.ruoyi.common.utils.spring.SpringUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* JSON 工具类
*
* @author 芋道源码
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class JsonUtils {
private static final ObjectMapper OBJECT_MAPPER = SpringUtils.getBean(ObjectMapper.class);
public static ObjectMapper getObjectMapper() {
return OBJECT_MAPPER;
}
public static String toJsonString(Object object) {
if (ObjectUtil.isNull(object)) {
return null;
}
try {
return OBJECT_MAPPER.writeValueAsString(object);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
public static <T> T parseObject(String text, Class<T> clazz) {
if (StringUtils.isEmpty(text)) {
return null;
}
try {
return OBJECT_MAPPER.readValue(text, clazz);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static <T> T parseObject(byte[] bytes, Class<T> clazz) {
if (ArrayUtil.isEmpty(bytes)) {
return null;
}
try {
return OBJECT_MAPPER.readValue(bytes, clazz);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static <T> T parseObject(String text, TypeReference<T> typeReference) {
if (StringUtils.isBlank(text)) {
return null;
}
try {
return OBJECT_MAPPER.readValue(text, typeReference);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static Dict parseMap(String text) {
if (StringUtils.isBlank(text)) {
return null;
}
try {
return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructType(Dict.class));
} catch (MismatchedInputException e) {
// 类型不匹配说明不是json
return null;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static List<Dict> parseArrayMap(String text) {
if (StringUtils.isBlank(text)) {
return null;
}
try {
return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, Dict.class));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static <T> List<T> parseArray(String text, Class<T> clazz) {
if (StringUtils.isEmpty(text)) {
return new ArrayList<>();
}
try {
return OBJECT_MAPPER.readValue(text, OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, clazz));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,51 @@
package com.ruoyi.common.utils;
import java.math.BigDecimal;
public class MapGetUtil {
public static Long getLong(Object obj){
return getLong(obj,null);
}
public static Long getLong(Object obj, Long defaultValue){
if(obj == null){
return defaultValue;
}
return Long.valueOf(obj.toString());
}
public static String getString(Object obj){
if(obj == null){
return null;
}
return String.valueOf(obj);
}
public static Integer getInt(Object obj){
if(obj == null){
return null;
}
return Integer.valueOf(obj.toString());
}
public static BigDecimal getBigDecimal(Object obj){
if(obj == null){
return null;
}
return new BigDecimal(obj.toString());
}
public static Boolean getBoolean(Object obj) {
if(obj == null){
return null;
}
if(obj instanceof Boolean){
return (Boolean)obj;
}
String str = String.valueOf(obj);
return str.equals("true");
}
}

View File

@@ -0,0 +1,29 @@
package com.ruoyi.common.utils;
import com.ruoyi.common.utils.spring.SpringUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.springframework.context.MessageSource;
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) {
return MESSAGE_SOURCE.getMessage(code, args, LocaleContextHolder.getLocale());
}
}

View File

@@ -0,0 +1,211 @@
package com.ruoyi.common.utils;
import cn.hutool.core.convert.Convert;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.HttpStatus;
import com.ruoyi.common.constant.Constants;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.attribute.FileTime;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* 客户端工具类
*
* @author ruoyi
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ServletUtils extends ServletUtil {
private final static String HEAD_UUID = "uuid";
/**
* 获取String参数
*/
public static String getParameter(String name) {
return getRequest().getParameter(name);
}
/**
* 获取String参数
*/
public static String getParameter(String name, String defaultValue) {
return Convert.toStr(getRequest().getParameter(name), defaultValue);
}
/**
* 获取Integer参数
*/
public static Integer getParameterToInt(String name) {
return Convert.toInt(getRequest().getParameter(name));
}
/**
* 获取Integer参数
*/
public static Integer getParameterToInt(String name, Integer defaultValue) {
return Convert.toInt(getRequest().getParameter(name), defaultValue);
}
/**
* 获取Boolean参数
*/
public static Boolean getParameterToBool(String name) {
return Convert.toBool(getRequest().getParameter(name));
}
/**
* 获取Boolean参数
*/
public static Boolean getParameterToBool(String name, Boolean defaultValue) {
return Convert.toBool(getRequest().getParameter(name), defaultValue);
}
/**
* 获得所有请求参数
*
* @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);
}
/**
* 获得所有请求参数
*
* @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;
}
/**
* 获取request
*/
public static HttpServletRequest getRequest() {
return getRequestAttributes().getRequest();
}
/**
* 获取response
*/
public static HttpServletResponse getResponse() {
return getRequestAttributes().getResponse();
}
/**
* 获取session
*/
public static HttpSession getSession() {
return getRequest().getSession();
}
public static ServletRequestAttributes getRequestAttributes() {
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
return (ServletRequestAttributes) attributes;
}
/**
* 将字符串渲染到客户端
*
* @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
*/
public static boolean isAjaxRequest(HttpServletRequest request) {
String accept = request.getHeader("accept");
if (accept != null && accept.contains(MediaType.APPLICATION_JSON_VALUE)) {
return true;
}
String xRequestedWith = request.getHeader("X-Requested-With");
if (xRequestedWith != null && xRequestedWith.contains("XMLHttpRequest")) {
return true;
}
String uri = request.getRequestURI();
if (StringUtils.equalsAnyIgnoreCase(uri, ".json", ".xml")) {
return true;
}
String ajax = request.getParameter("__ajax");
return StringUtils.equalsAnyIgnoreCase(ajax, "json", "xml");
}
public static String getClientIP() {
return getClientIP(getRequest());
}
public static String getImei(){
return getRequest().getHeader(HEAD_UUID);
}
/**
* 内容编码
*
* @param str 内容
* @return 编码后的内容
*/
public static String urlEncode(String str) {
try {
return URLEncoder.encode(str, Constants.UTF8);
} catch (UnsupportedEncodingException e) {
return StringUtils.EMPTY;
}
}
/**
* 内容解码
*
* @param str 内容
* @return 解码后的内容
*/
public static String urlDecode(String str) {
try {
return URLDecoder.decode(str, Constants.UTF8);
} catch (UnsupportedEncodingException e) {
return StringUtils.EMPTY;
}
}
}

View File

@@ -0,0 +1,251 @@
package com.ruoyi.common.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.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();
}
return collection.stream().filter(function).collect(Collectors.toList());
}
/**
* 将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();
}
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)
.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;
}
}

View File

@@ -0,0 +1,325 @@
package com.ruoyi.common.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 lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.springframework.util.AntPathMatcher;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 字符串工具类
*
* @author Lion Li
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class StringUtils extends org.apache.commons.lang3.StringUtils {
public static final String SEPARATOR = ",";
/**
* 获取参数不为空值
*
* @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) {
for (int i = size - len; i > 0; i--) {
sb.append(c);
}
sb.append(s);
} else {
return s.substring(len - size, len);
}
} else {
for (int i = size; i > 0; i--) {
sb.append(c);
}
}
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)
.collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,75 @@
package com.ruoyi.common.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 {
/**
* sleep等待,单位为毫秒
*/
public static void sleep(long milliseconds) {
try {
Thread.sleep(milliseconds);
} catch (InterruptedException e) {
return;
}
}
/**
* 停止线程池
* 先使用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);
}
}
}

View File

@@ -0,0 +1,35 @@
package com.ruoyi.common.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 com.ruoyi.common.utils.reflect.ReflectUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* 扩展 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");
public static <T, K> List<Tree<K>> build(List<T> list, NodeParser<T, K> nodeParser) {
if (CollUtil.isEmpty(list)) {
return null;
}
K k = ReflectUtils.invokeGetter(list.get(0), "parentId");
return TreeUtil.build(list, k, DEFAULT_CONFIG, nodeParser);
}
}

View File

@@ -0,0 +1,29 @@
package com.ruoyi.common.utils;
import com.ruoyi.common.utils.spring.SpringUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Validator;
import java.util.Set;
/**
* Validator 校验框架工具
*
* @author Lion Li
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ValidatorUtils {
private static final Validator VALID = SpringUtils.getBean(Validator.class);
public static <T> void validate(T object, Class<?>... groups) {
Set<ConstraintViolation<T>> validate = VALID.validate(object, groups);
if (!validate.isEmpty()) {
throw new ConstraintViolationException("参数校验异常", validate);
}
}
}

View File

@@ -0,0 +1,468 @@
package com.ruoyi.common.utils.email;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.mail.*;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import javax.mail.Authenticator;
import javax.mail.Session;
import java.io.File;
import java.io.InputStream;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* 邮件工具类
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class MailUtils {
private static final MailAccount ACCOUNT = SpringUtils.getBean(MailAccount.class);
/**
* 获取邮件发送实例
*/
public static MailAccount getMailAccount() {
return ACCOUNT;
}
/**
* 获取邮件发送实例 (自定义发送人以及授权码)
*
* @param user 发送人
* @param pass 授权码
*/
public static MailAccount getMailAccount(String from, String user, String pass) {
ACCOUNT.setFrom(StringUtils.blankToDefault(from, ACCOUNT.getFrom()));
ACCOUNT.setUser(StringUtils.blankToDefault(user, ACCOUNT.getUser()));
ACCOUNT.setPass(StringUtils.blankToDefault(pass, ACCOUNT.getPass()));
return ACCOUNT;
}
/**
* 使用配置文件中设置的账户发送文本邮件,发送给单个或多个收件人<br>
* 多个收件人可以使用逗号“,”分隔,也可以通过分号“;”分隔
*
* @param to 收件人
* @param subject 标题
* @param content 正文
* @param files 附件列表
* @return message-id
* @since 3.2.0
*/
public static String sendText(String to, String subject, String content, File... files) {
return send(to, subject, content, false, files);
}
/**
* 使用配置文件中设置的账户发送HTML邮件发送给单个或多个收件人<br>
* 多个收件人可以使用逗号“,”分隔,也可以通过分号“;”分隔
*
* @param to 收件人
* @param subject 标题
* @param content 正文
* @param files 附件列表
* @return message-id
* @since 3.2.0
*/
public static String sendHtml(String to, String subject, String content, File... files) {
return send(to, subject, content, true, files);
}
/**
* 使用配置文件中设置的账户发送邮件,发送单个或多个收件人<br>
* 多个收件人可以使用逗号“,”分隔,也可以通过分号“;”分隔
*
* @param to 收件人
* @param subject 标题
* @param content 正文
* @param isHtml 是否为HTML
* @param files 附件列表
* @return message-id
*/
public static String send(String to, String subject, String content, boolean isHtml, File... files) {
return send(splitAddress(to), subject, content, isHtml, files);
}
/**
* 使用配置文件中设置的账户发送邮件,发送单个或多个收件人<br>
* 多个收件人、抄送人、密送人可以使用逗号“,”分隔,也可以通过分号“;”分隔
*
* @param to 收件人,可以使用逗号“,”分隔,也可以通过分号“;”分隔
* @param cc 抄送人,可以使用逗号“,”分隔,也可以通过分号“;”分隔
* @param bcc 密送人,可以使用逗号“,”分隔,也可以通过分号“;”分隔
* @param subject 标题
* @param content 正文
* @param isHtml 是否为HTML
* @param files 附件列表
* @return message-id
* @since 4.0.3
*/
public static String send(String to, String cc, String bcc, String subject, String content, boolean isHtml, File... files) {
return send(splitAddress(to), splitAddress(cc), splitAddress(bcc), subject, content, isHtml, files);
}
/**
* 使用配置文件中设置的账户发送文本邮件,发送给多人
*
* @param tos 收件人列表
* @param subject 标题
* @param content 正文
* @param files 附件列表
* @return message-id
*/
public static String sendText(Collection<String> tos, String subject, String content, File... files) {
return send(tos, subject, content, false, files);
}
/**
* 使用配置文件中设置的账户发送HTML邮件发送给多人
*
* @param tos 收件人列表
* @param subject 标题
* @param content 正文
* @param files 附件列表
* @return message-id
* @since 3.2.0
*/
public static String sendHtml(Collection<String> tos, String subject, String content, File... files) {
return send(tos, subject, content, true, files);
}
/**
* 使用配置文件中设置的账户发送邮件,发送给多人
*
* @param tos 收件人列表
* @param subject 标题
* @param content 正文
* @param isHtml 是否为HTML
* @param files 附件列表
* @return message-id
*/
public static String send(Collection<String> tos, String subject, String content, boolean isHtml, File... files) {
return send(tos, null, null, subject, content, isHtml, files);
}
/**
* 使用配置文件中设置的账户发送邮件,发送给多人
*
* @param tos 收件人列表
* @param ccs 抄送人列表可以为null或空
* @param bccs 密送人列表可以为null或空
* @param subject 标题
* @param content 正文
* @param isHtml 是否为HTML
* @param files 附件列表
* @return message-id
* @since 4.0.3
*/
public static String send(Collection<String> tos, Collection<String> ccs, Collection<String> bccs, String subject, String content, boolean isHtml, File... files) {
return send(getMailAccount(), true, tos, ccs, bccs, subject, content, null, isHtml, files);
}
// ------------------------------------------------------------------------------------------------------------------------------- Custom MailAccount
/**
* 发送邮件给多人
*
* @param mailAccount 邮件认证对象
* @param to 收件人,多个收件人逗号或者分号隔开
* @param subject 标题
* @param content 正文
* @param isHtml 是否为HTML格式
* @param files 附件列表
* @return message-id
* @since 3.2.0
*/
public static String send(MailAccount mailAccount, String to, String subject, String content, boolean isHtml, File... files) {
return send(mailAccount, splitAddress(to), subject, content, isHtml, files);
}
/**
* 发送邮件给多人
*
* @param mailAccount 邮件帐户信息
* @param tos 收件人列表
* @param subject 标题
* @param content 正文
* @param isHtml 是否为HTML格式
* @param files 附件列表
* @return message-id
*/
public static String send(MailAccount mailAccount, Collection<String> tos, String subject, String content, boolean isHtml, File... files) {
return send(mailAccount, tos, null, null, subject, content, isHtml, files);
}
/**
* 发送邮件给多人
*
* @param mailAccount 邮件帐户信息
* @param tos 收件人列表
* @param ccs 抄送人列表可以为null或空
* @param bccs 密送人列表可以为null或空
* @param subject 标题
* @param content 正文
* @param isHtml 是否为HTML格式
* @param files 附件列表
* @return message-id
* @since 4.0.3
*/
public static String send(MailAccount mailAccount, Collection<String> tos, Collection<String> ccs, Collection<String> bccs, String subject, String content, boolean isHtml, File... files) {
return send(mailAccount, false, tos, ccs, bccs, subject, content, null, isHtml, files);
}
/**
* 使用配置文件中设置的账户发送HTML邮件发送给单个或多个收件人<br>
* 多个收件人可以使用逗号“,”分隔,也可以通过分号“;”分隔
*
* @param to 收件人
* @param subject 标题
* @param content 正文
* @param imageMap 图片与占位符占位符格式为cid:$IMAGE_PLACEHOLDER
* @param files 附件列表
* @return message-id
* @since 3.2.0
*/
public static String sendHtml(String to, String subject, String content, Map<String, InputStream> imageMap, File... files) {
return send(to, subject, content, imageMap, true, files);
}
/**
* 使用配置文件中设置的账户发送邮件,发送单个或多个收件人<br>
* 多个收件人可以使用逗号“,”分隔,也可以通过分号“;”分隔
*
* @param to 收件人
* @param subject 标题
* @param content 正文
* @param imageMap 图片与占位符占位符格式为cid:$IMAGE_PLACEHOLDER
* @param isHtml 是否为HTML
* @param files 附件列表
* @return message-id
*/
public static String send(String to, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) {
return send(splitAddress(to), subject, content, imageMap, isHtml, files);
}
/**
* 使用配置文件中设置的账户发送邮件,发送单个或多个收件人<br>
* 多个收件人、抄送人、密送人可以使用逗号“,”分隔,也可以通过分号“;”分隔
*
* @param to 收件人,可以使用逗号“,”分隔,也可以通过分号“;”分隔
* @param cc 抄送人,可以使用逗号“,”分隔,也可以通过分号“;”分隔
* @param bcc 密送人,可以使用逗号“,”分隔,也可以通过分号“;”分隔
* @param subject 标题
* @param content 正文
* @param imageMap 图片与占位符占位符格式为cid:$IMAGE_PLACEHOLDER
* @param isHtml 是否为HTML
* @param files 附件列表
* @return message-id
* @since 4.0.3
*/
public static String send(String to, String cc, String bcc, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) {
return send(splitAddress(to), splitAddress(cc), splitAddress(bcc), subject, content, imageMap, isHtml, files);
}
/**
* 使用配置文件中设置的账户发送HTML邮件发送给多人
*
* @param tos 收件人列表
* @param subject 标题
* @param content 正文
* @param imageMap 图片与占位符占位符格式为cid:$IMAGE_PLACEHOLDER
* @param files 附件列表
* @return message-id
* @since 3.2.0
*/
public static String sendHtml(Collection<String> tos, String subject, String content, Map<String, InputStream> imageMap, File... files) {
return send(tos, subject, content, imageMap, true, files);
}
/**
* 使用配置文件中设置的账户发送邮件,发送给多人
*
* @param tos 收件人列表
* @param subject 标题
* @param content 正文
* @param imageMap 图片与占位符占位符格式为cid:$IMAGE_PLACEHOLDER
* @param isHtml 是否为HTML
* @param files 附件列表
* @return message-id
*/
public static String send(Collection<String> tos, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) {
return send(tos, null, null, subject, content, imageMap, isHtml, files);
}
/**
* 使用配置文件中设置的账户发送邮件,发送给多人
*
* @param tos 收件人列表
* @param ccs 抄送人列表可以为null或空
* @param bccs 密送人列表可以为null或空
* @param subject 标题
* @param content 正文
* @param imageMap 图片与占位符占位符格式为cid:$IMAGE_PLACEHOLDER
* @param isHtml 是否为HTML
* @param files 附件列表
* @return message-id
* @since 4.0.3
*/
public static String send(Collection<String> tos, Collection<String> ccs, Collection<String> bccs, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) {
return send(getMailAccount(), true, tos, ccs, bccs, subject, content, imageMap, isHtml, files);
}
// ------------------------------------------------------------------------------------------------------------------------------- Custom MailAccount
/**
* 发送邮件给多人
*
* @param mailAccount 邮件认证对象
* @param to 收件人,多个收件人逗号或者分号隔开
* @param subject 标题
* @param content 正文
* @param imageMap 图片与占位符占位符格式为cid:$IMAGE_PLACEHOLDER
* @param isHtml 是否为HTML格式
* @param files 附件列表
* @return message-id
* @since 3.2.0
*/
public static String send(MailAccount mailAccount, String to, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) {
return send(mailAccount, splitAddress(to), subject, content, imageMap, isHtml, files);
}
/**
* 发送邮件给多人
*
* @param mailAccount 邮件帐户信息
* @param tos 收件人列表
* @param subject 标题
* @param content 正文
* @param imageMap 图片与占位符占位符格式为cid:$IMAGE_PLACEHOLDER
* @param isHtml 是否为HTML格式
* @param files 附件列表
* @return message-id
* @since 4.6.3
*/
public static String send(MailAccount mailAccount, Collection<String> tos, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) {
return send(mailAccount, tos, null, null, subject, content, imageMap, isHtml, files);
}
/**
* 发送邮件给多人
*
* @param mailAccount 邮件帐户信息
* @param tos 收件人列表
* @param ccs 抄送人列表可以为null或空
* @param bccs 密送人列表可以为null或空
* @param subject 标题
* @param content 正文
* @param imageMap 图片与占位符占位符格式为cid:$IMAGE_PLACEHOLDER
* @param isHtml 是否为HTML格式
* @param files 附件列表
* @return message-id
* @since 4.6.3
*/
public static String send(MailAccount mailAccount, Collection<String> tos, Collection<String> ccs, Collection<String> bccs, String subject, String content, Map<String, InputStream> imageMap,
boolean isHtml, File... files) {
return send(mailAccount, false, tos, ccs, bccs, subject, content, imageMap, isHtml, files);
}
/**
* 根据配置文件,获取邮件客户端会话
*
* @param mailAccount 邮件账户配置
* @param isSingleton 是否单例(全局共享会话)
* @return {@link Session}
* @since 5.5.7
*/
public static Session getSession(MailAccount mailAccount, boolean isSingleton) {
Authenticator authenticator = null;
if (mailAccount.isAuth()) {
authenticator = new UserPassAuthenticator(mailAccount.getUser(), mailAccount.getPass());
}
return isSingleton ? Session.getDefaultInstance(mailAccount.getSmtpProps(), authenticator) //
: Session.getInstance(mailAccount.getSmtpProps(), authenticator);
}
// ------------------------------------------------------------------------------------------------------------------------ Private method start
/**
* 发送邮件给多人
*
* @param mailAccount 邮件帐户信息
* @param useGlobalSession 是否全局共享Session
* @param tos 收件人列表
* @param ccs 抄送人列表可以为null或空
* @param bccs 密送人列表可以为null或空
* @param subject 标题
* @param content 正文
* @param imageMap 图片与占位符占位符格式为cid:${cid}
* @param isHtml 是否为HTML格式
* @param files 附件列表
* @return message-id
* @since 4.6.3
*/
private static String send(MailAccount mailAccount, boolean useGlobalSession, Collection<String> tos, Collection<String> ccs, Collection<String> bccs, String subject, String content,
Map<String, InputStream> imageMap, boolean isHtml, File... files) {
final Mail mail = Mail.create(mailAccount).setUseGlobalSession(useGlobalSession);
// 可选抄送人
if (CollUtil.isNotEmpty(ccs)) {
mail.setCcs(ccs.toArray(new String[0]));
}
// 可选密送人
if (CollUtil.isNotEmpty(bccs)) {
mail.setBccs(bccs.toArray(new String[0]));
}
mail.setTos(tos.toArray(new String[0]));
mail.setTitle(subject);
mail.setContent(content);
mail.setHtml(isHtml);
mail.setFiles(files);
// 图片
if (MapUtil.isNotEmpty(imageMap)) {
for (Map.Entry<String, InputStream> entry : imageMap.entrySet()) {
mail.addImage(entry.getKey(), entry.getValue());
// 关闭流
IoUtil.close(entry.getValue());
}
}
return mail.send();
}
/**
* 将多个联系人转为列表,分隔符为逗号或者分号
*
* @param addresses 多个联系人如果为空返回null
* @return 联系人列表
*/
private static List<String> splitAddress(String addresses) {
if (StrUtil.isBlank(addresses)) {
return null;
}
List<String> result;
if (StrUtil.contains(addresses, CharUtil.COMMA)) {
result = StrUtil.splitTrim(addresses, CharUtil.COMMA);
} else if (StrUtil.contains(addresses, ';')) {
result = StrUtil.splitTrim(addresses, ';');
} else {
result = CollUtil.newArrayList(addresses);
}
return result;
}
// ------------------------------------------------------------------------------------------------------------------------ Private method end
}

View File

@@ -0,0 +1,52 @@
package com.ruoyi.common.utils.file;
import cn.hutool.core.io.FileUtil;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
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) throws UnsupportedEncodingException {
String percentEncodedFileName = percentEncode(realFileName);
StringBuilder contentDispositionValue = new StringBuilder();
contentDispositionValue.append("attachment; filename=")
.append(percentEncodedFileName)
.append(";")
.append("filename*=")
.append("utf-8''")
.append(percentEncodedFileName);
response.addHeader("Access-Control-Expose-Headers", "Content-Disposition,download-filename");
response.setHeader("Content-disposition", contentDispositionValue.toString());
response.setHeader("download-filename", percentEncodedFileName);
}
/**
* 百分号编码工具方法
*
* @param s 需要百分号编码的字符串
* @return 百分号编码后的字符串
*/
public static String percentEncode(String s) throws UnsupportedEncodingException {
String encode = URLEncoder.encode(s, StandardCharsets.UTF_8.toString());
return encode.replaceAll("\\+", "%20");
}
}

View File

@@ -0,0 +1,40 @@
package com.ruoyi.common.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"};
}

View File

@@ -0,0 +1,33 @@
package com.ruoyi.common.utils.ip;
import cn.hutool.core.net.NetUtil;
import cn.hutool.http.HtmlUtil;
import com.ruoyi.common.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);
}
}

View File

@@ -0,0 +1,66 @@
package com.ruoyi.common.utils.ip;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.resource.ClassPathResource;
import cn.hutool.core.util.ObjectUtil;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.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 "未知";
}
}
}

View File

@@ -0,0 +1,380 @@
package com.ruoyi.common.utils.poi;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.resource.ClassPathResource;
import cn.hutool.core.util.IdUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.fill.FillConfig;
import com.alibaba.excel.write.metadata.fill.FillWrapper;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.ruoyi.common.convert.ExcelBigNumberConvert;
import com.ruoyi.common.excel.*;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.file.FileUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* Excel相关处理
*
* @author Lion Li
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ExcelUtil {
/**
* 同步导入(适用于小数据量)
*
* @param is 输入流
* @return 转换后集合
*/
public static <T> List<T> importExcel(InputStream is, Class<T> clazz) {
return EasyExcel.read(is).head(clazz).autoCloseStream(false).sheet().doReadSync();
}
/**
* 使用校验监听器 异步导入 同步返回
*
* @param is 输入流
* @param clazz 对象类型
* @param isValidate 是否 Validator 检验 默认为是
* @return 转换后集合
*/
public static <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, boolean isValidate) {
DefaultExcelListener<T> listener = new DefaultExcelListener<>(isValidate);
EasyExcel.read(is, clazz, listener).sheet().doRead();
return listener.getExcelResult();
}
/**
* 使用自定义监听器 异步导入 自定义返回
*
* @param is 输入流
* @param clazz 对象类型
* @param listener 自定义监听器
* @return 转换后集合
*/
public static <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, ExcelListener<T> listener) {
EasyExcel.read(is, clazz, listener).sheet().doRead();
return listener.getExcelResult();
}
/**
* 导出excel
*
* @param list 导出数据集合
* @param sheetName 工作表的名称
* @param clazz 实体类
* @param response 响应体
*/
public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, HttpServletResponse response) {
try {
resetResponse(sheetName, response);
ServletOutputStream os = response.getOutputStream();
exportExcel(list, sheetName, clazz, false, os, null);
} catch (IOException e) {
throw new RuntimeException("导出Excel异常");
}
}
/**
* 导出excel
*
* @param list 导出数据集合
* @param sheetName 工作表的名称
* @param clazz 实体类
* @param response 响应体
* @param options 级联下拉选
*/
public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, HttpServletResponse response, List<DropDownOptions> options) {
try {
resetResponse(sheetName, response);
ServletOutputStream os = response.getOutputStream();
exportExcel(list, sheetName, clazz, false, os, options);
} catch (IOException e) {
throw new RuntimeException("导出Excel异常");
}
}
/**
* 导出excel
*
* @param list 导出数据集合
* @param sheetName 工作表的名称
* @param clazz 实体类
* @param merge 是否合并单元格
* @param response 响应体
*/
public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, boolean merge, HttpServletResponse response) {
try {
resetResponse(sheetName, response);
ServletOutputStream os = response.getOutputStream();
exportExcel(list, sheetName, clazz, merge, os, null);
} catch (IOException e) {
throw new RuntimeException("导出Excel异常");
}
}
/**
* 导出excel
*
* @param list 导出数据集合
* @param sheetName 工作表的名称
* @param clazz 实体类
* @param merge 是否合并单元格
* @param response 响应体
* @param options 级联下拉选
*/
public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, boolean merge, HttpServletResponse response, List<DropDownOptions> options) {
try {
resetResponse(sheetName, response);
ServletOutputStream os = response.getOutputStream();
exportExcel(list, sheetName, clazz, merge, os, options);
} catch (IOException e) {
throw new RuntimeException("导出Excel异常");
}
}
/**
* 导出excel
*
* @param list 导出数据集合
* @param sheetName 工作表的名称
* @param clazz 实体类
* @param os 输出流
*/
public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, OutputStream os) {
exportExcel(list, sheetName, clazz, false, os, null);
}
/**
* 导出excel
*
* @param list 导出数据集合
* @param sheetName 工作表的名称
* @param clazz 实体类
* @param os 输出流
* @param options 级联下拉选内容
*/
public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, OutputStream os, List<DropDownOptions> options) {
exportExcel(list, sheetName, clazz, false, os, options);
}
/**
* 导出excel
*
* @param list 导出数据集合
* @param sheetName 工作表的名称
* @param clazz 实体类
* @param merge 是否合并单元格
* @param os 输出流
*/
public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, boolean merge,
OutputStream os, List<DropDownOptions> options) {
ExcelWriterSheetBuilder builder = EasyExcel.write(os, clazz)
.autoCloseStream(false)
// 自动适配
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
// 大数值自动转换 防止失真
.registerConverter(new ExcelBigNumberConvert())
.sheet(sheetName);
if (merge) {
// 合并处理器
builder.registerWriteHandler(new CellMergeStrategy(list, true));
}
// 添加下拉框操作
builder.registerWriteHandler(new ExcelDownHandler(options));
builder.doWrite(list);
}
/**
* 单表多数据模板导出 模板格式为 {.属性}
*
* @param filename 文件名
* @param templatePath 模板路径 resource 目录下的路径包括模板文件名
* 例如: excel/temp.xlsx
* 重点: 模板文件必须放置到启动类对应的 resource 目录下
* @param data 模板需要的数据
* @param response 响应体
*/
public static void exportTemplate(List<Object> data, String filename, String templatePath, HttpServletResponse response) {
try {
resetResponse(filename, response);
ServletOutputStream os = response.getOutputStream();
exportTemplate(data, templatePath, os);
} catch (IOException e) {
throw new RuntimeException("导出Excel异常");
}
}
/**
* 单表多数据模板导出 模板格式为 {.属性}
*
* @param templatePath 模板路径 resource 目录下的路径包括模板文件名
* 例如: excel/temp.xlsx
* 重点: 模板文件必须放置到启动类对应的 resource 目录下
* @param data 模板需要的数据
* @param os 输出流
*/
public static void exportTemplate(List<Object> data, String templatePath, OutputStream os) {
ClassPathResource templateResource = new ClassPathResource(templatePath);
ExcelWriter excelWriter = EasyExcel.write(os)
.withTemplate(templateResource.getStream())
.autoCloseStream(false)
// 大数值自动转换 防止失真
.registerConverter(new ExcelBigNumberConvert())
.build();
WriteSheet writeSheet = EasyExcel.writerSheet().build();
if (CollUtil.isEmpty(data)) {
throw new IllegalArgumentException("数据为空");
}
// 单表多数据导出 模板格式为 {.属性}
for (Object d : data) {
excelWriter.fill(d, writeSheet);
}
excelWriter.finish();
}
/**
* 多表多数据模板导出 模板格式为 {key.属性}
*
* @param filename 文件名
* @param templatePath 模板路径 resource 目录下的路径包括模板文件名
* 例如: excel/temp.xlsx
* 重点: 模板文件必须放置到启动类对应的 resource 目录下
* @param data 模板需要的数据
* @param response 响应体
*/
public static void exportTemplateMultiList(Map<String, Object> data, String filename, String templatePath, HttpServletResponse response) {
try {
resetResponse(filename, response);
ServletOutputStream os = response.getOutputStream();
exportTemplateMultiList(data, templatePath, os);
} catch (IOException e) {
throw new RuntimeException("导出Excel异常");
}
}
/**
* 多表多数据模板导出 模板格式为 {key.属性}
*
* @param templatePath 模板路径 resource 目录下的路径包括模板文件名
* 例如: excel/temp.xlsx
* 重点: 模板文件必须放置到启动类对应的 resource 目录下
* @param data 模板需要的数据
* @param os 输出流
*/
public static void exportTemplateMultiList(Map<String, Object> data, String templatePath, OutputStream os) {
ClassPathResource templateResource = new ClassPathResource(templatePath);
ExcelWriter excelWriter = EasyExcel.write(os)
.withTemplate(templateResource.getStream())
.autoCloseStream(false)
// 大数值自动转换 防止失真
.registerConverter(new ExcelBigNumberConvert())
.build();
WriteSheet writeSheet = EasyExcel.writerSheet().build();
if (CollUtil.isEmpty(data)) {
throw new IllegalArgumentException("数据为空");
}
for (Map.Entry<String, Object> map : data.entrySet()) {
// 设置列表后续还有数据
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
if (map.getValue() instanceof Collection) {
// 多表导出必须使用 FillWrapper
excelWriter.fill(new FillWrapper(map.getKey(), (Collection<?>) map.getValue()), fillConfig, writeSheet);
} else {
excelWriter.fill(map.getValue(), writeSheet);
}
}
excelWriter.finish();
}
/**
* 重置响应体
*/
private static void resetResponse(String sheetName, HttpServletResponse response) throws UnsupportedEncodingException {
String filename = encodingFilename(sheetName);
FileUtils.setAttachmentResponseHeader(response, filename);
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8");
}
/**
* 解析导出值 0=男,1=女,2=未知
*
* @param propertyValue 参数值
* @param converterExp 翻译注解
* @param separator 分隔符
* @return 解析后值
*/
public static String convertByExp(String propertyValue, String converterExp, String separator) {
StringBuilder propertyString = new StringBuilder();
String[] convertSource = converterExp.split(StringUtils.SEPARATOR);
for (String item : convertSource) {
String[] itemArray = item.split("=");
if (StringUtils.containsAny(propertyValue, separator)) {
for (String value : propertyValue.split(separator)) {
if (itemArray[0].equals(value)) {
propertyString.append(itemArray[1] + separator);
break;
}
}
} else {
if (itemArray[0].equals(propertyValue)) {
return itemArray[1];
}
}
}
return StringUtils.stripEnd(propertyString.toString(), separator);
}
/**
* 反向解析值 男=0,女=1,未知=2
*
* @param propertyValue 参数值
* @param converterExp 翻译注解
* @param separator 分隔符
* @return 解析后值
*/
public static String reverseByExp(String propertyValue, String converterExp, String separator) {
StringBuilder propertyString = new StringBuilder();
String[] convertSource = converterExp.split(StringUtils.SEPARATOR);
for (String item : convertSource) {
String[] itemArray = item.split("=");
if (StringUtils.containsAny(propertyValue, separator)) {
for (String value : propertyValue.split(separator)) {
if (itemArray[1].equals(value)) {
propertyString.append(itemArray[0] + separator);
break;
}
}
} else {
if (itemArray[1].equals(propertyValue)) {
return itemArray[0];
}
}
}
return StringUtils.stripEnd(propertyString.toString(), separator);
}
/**
* 编码文件名
*/
public static String encodingFilename(String filename) {
return IdUtil.fastSimpleUUID() + "_" + filename + ".xlsx";
}
}

View File

@@ -0,0 +1,75 @@
package com.ruoyi.common.utils.redis;
import com.ruoyi.common.utils.spring.SpringUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.redisson.api.RMap;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import java.util.Set;
/**
* 缓存操作工具类 {@link }
*
* @author Michelle.Chung
* @date 2022/8/13
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@SuppressWarnings(value = {"unchecked"})
public class CacheUtils {
private static final CacheManager CACHE_MANAGER = SpringUtils.getBean(CacheManager.class);
/**
* 获取缓存组内所有的KEY
*
* @param cacheNames 缓存组名称
*/
public static Set<Object> keys(String cacheNames) {
RMap<Object, Object> rmap = (RMap<Object, Object>) CACHE_MANAGER.getCache(cacheNames).getNativeCache();
return rmap.keySet();
}
/**
* 获取缓存值
*
* @param cacheNames 缓存组名称
* @param key 缓存key
*/
public static <T> T get(String cacheNames, Object key) {
Cache.ValueWrapper wrapper = CACHE_MANAGER.getCache(cacheNames).get(key);
return wrapper != null ? (T) wrapper.get() : null;
}
/**
* 保存缓存值
*
* @param cacheNames 缓存组名称
* @param key 缓存key
* @param value 缓存值
*/
public static void put(String cacheNames, Object key, Object value) {
CACHE_MANAGER.getCache(cacheNames).put(key, value);
}
/**
* 删除缓存值
*
* @param cacheNames 缓存组名称
* @param key 缓存key
*/
public static void evict(String cacheNames, Object key) {
CACHE_MANAGER.getCache(cacheNames).evict(key);
}
/**
* 清空缓存值
*
* @param cacheNames 缓存组名称
*/
public static void clear(String cacheNames) {
CACHE_MANAGER.getCache(cacheNames).clear();
}
}

View File

@@ -0,0 +1,236 @@
package com.ruoyi.common.utils.redis;
import com.ruoyi.common.utils.spring.SpringUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.redisson.api.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
/**
* 分布式队列工具
* 轻量级队列 重量级数据量 请使用 MQ
* 要求 redis 5.X 以上
*
* @author Lion Li
* @version 3.6.0 新增
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class QueueUtils {
private static final RedissonClient CLIENT = SpringUtils.getBean(RedissonClient.class);
/**
* 获取客户端实例
*/
public static RedissonClient getClient() {
return CLIENT;
}
/**
* 添加普通队列数据
*
* @param queueName 队列名
* @param data 数据
*/
public static <T> boolean addQueueObject(String queueName, T data) {
RBlockingQueue<T> queue = CLIENT.getBlockingQueue(queueName);
return queue.offer(data);
}
/**
* 通用获取一个队列数据 没有数据返回 null(不支持延迟队列)
*
* @param queueName 队列名
*/
public static <T> T getQueueObject(String queueName) {
RBlockingQueue<T> queue = CLIENT.getBlockingQueue(queueName);
return queue.poll();
}
/**
* 通用删除队列数据(不支持延迟队列)
*/
public static <T> boolean removeQueueObject(String queueName, T data) {
RBlockingQueue<T> queue = CLIENT.getBlockingQueue(queueName);
return queue.remove(data);
}
/**
* 通用销毁队列 所有阻塞监听 报错(不支持延迟队列)
*/
public static <T> boolean destroyQueue(String queueName) {
RBlockingQueue<T> queue = CLIENT.getBlockingQueue(queueName);
return queue.delete();
}
/**
* 添加延迟队列数据 默认毫秒
*
* @param queueName 队列名
* @param data 数据
* @param time 延迟时间
*/
public static <T> void addDelayedQueueObject(String queueName, T data, long time) {
addDelayedQueueObject(queueName, data, time, TimeUnit.MILLISECONDS);
}
/**
* 添加延迟队列数据
*
* @param queueName 队列名
* @param data 数据
* @param time 延迟时间
* @param timeUnit 单位
*/
public static <T> void addDelayedQueueObject(String queueName, T data, long time, TimeUnit timeUnit) {
RBlockingQueue<T> queue = CLIENT.getBlockingQueue(queueName);
RDelayedQueue<T> delayedQueue = CLIENT.getDelayedQueue(queue);
delayedQueue.offer(data, time, timeUnit);
}
/**
* 获取一个延迟队列数据 没有数据返回 null
*
* @param queueName 队列名
*/
public static <T> T getDelayedQueueObject(String queueName) {
RBlockingQueue<T> queue = CLIENT.getBlockingQueue(queueName);
RDelayedQueue<T> delayedQueue = CLIENT.getDelayedQueue(queue);
return delayedQueue.poll();
}
/**
* 删除延迟队列数据
*/
public static <T> boolean removeDelayedQueueObject(String queueName, T data) {
RBlockingQueue<T> queue = CLIENT.getBlockingQueue(queueName);
RDelayedQueue<T> delayedQueue = CLIENT.getDelayedQueue(queue);
return delayedQueue.remove(data);
}
/**
* 销毁延迟队列 所有阻塞监听 报错
*/
public static <T> void destroyDelayedQueue(String queueName) {
RBlockingQueue<T> queue = CLIENT.getBlockingQueue(queueName);
RDelayedQueue<T> delayedQueue = CLIENT.getDelayedQueue(queue);
delayedQueue.destroy();
}
/**
* 添加优先队列数据
*
* @param queueName 队列名
* @param data 数据
*/
public static <T> boolean addPriorityQueueObject(String queueName, T data) {
RPriorityBlockingQueue<T> priorityBlockingQueue = CLIENT.getPriorityBlockingQueue(queueName);
return priorityBlockingQueue.offer(data);
}
/**
* 优先队列获取一个队列数据 没有数据返回 null(不支持延迟队列)
*
* @param queueName 队列名
*/
public static <T> T getPriorityQueueObject(String queueName) {
RPriorityBlockingQueue<T> queue = CLIENT.getPriorityBlockingQueue(queueName);
return queue.poll();
}
/**
* 优先队列删除队列数据(不支持延迟队列)
*/
public static <T> boolean removePriorityQueueObject(String queueName, T data) {
RPriorityBlockingQueue<T> queue = CLIENT.getPriorityBlockingQueue(queueName);
return queue.remove(data);
}
/**
* 优先队列销毁队列 所有阻塞监听 报错(不支持延迟队列)
*/
public static <T> boolean destroyPriorityQueue(String queueName) {
RPriorityBlockingQueue<T> queue = CLIENT.getPriorityBlockingQueue(queueName);
return queue.delete();
}
/**
* 尝试设置 有界队列 容量 用于限制数量
*
* @param queueName 队列名
* @param capacity 容量
*/
public static <T> boolean trySetBoundedQueueCapacity(String queueName, int capacity) {
RBoundedBlockingQueue<T> boundedBlockingQueue = CLIENT.getBoundedBlockingQueue(queueName);
return boundedBlockingQueue.trySetCapacity(capacity);
}
/**
* 尝试设置 有界队列 容量 用于限制数量
*
* @param queueName 队列名
* @param capacity 容量
* @param destroy 已存在是否销毁
*/
public static <T> boolean trySetBoundedQueueCapacity(String queueName, int capacity, boolean destroy) {
RBoundedBlockingQueue<T> boundedBlockingQueue = CLIENT.getBoundedBlockingQueue(queueName);
if (boundedBlockingQueue.isExists() && destroy) {
destroyQueue(queueName);
}
return boundedBlockingQueue.trySetCapacity(capacity);
}
/**
* 添加有界队列数据
*
* @param queueName 队列名
* @param data 数据
* @return 添加成功 true 已达到界限 false
*/
public static <T> boolean addBoundedQueueObject(String queueName, T data) {
RBoundedBlockingQueue<T> boundedBlockingQueue = CLIENT.getBoundedBlockingQueue(queueName);
return boundedBlockingQueue.offer(data);
}
/**
* 有界队列获取一个队列数据 没有数据返回 null(不支持延迟队列)
*
* @param queueName 队列名
*/
public static <T> T getBoundedQueueObject(String queueName) {
RBoundedBlockingQueue<T> queue = CLIENT.getBoundedBlockingQueue(queueName);
return queue.poll();
}
/**
* 有界队列删除队列数据(不支持延迟队列)
*/
public static <T> boolean removeBoundedQueueObject(String queueName, T data) {
RBoundedBlockingQueue<T> queue = CLIENT.getBoundedBlockingQueue(queueName);
return queue.remove(data);
}
/**
* 有界队列销毁队列 所有阻塞监听 报错(不支持延迟队列)
*/
public static <T> boolean destroyBoundedQueue(String queueName) {
RBoundedBlockingQueue<T> queue = CLIENT.getBoundedBlockingQueue(queueName);
return queue.delete();
}
/**
* 订阅阻塞队列(可订阅所有实现类 例如: 延迟 优先 有界 等)
*/
public static <T> void subscribeBlockingQueue(String queueName, Consumer<T> consumer, boolean isDelayed) {
RBlockingQueue<T> queue = CLIENT.getBlockingQueue(queueName);
if (isDelayed) {
// 订阅延迟队列
CLIENT.getDelayedQueue(queue);
}
queue.subscribeOnElements(consumer);
}
}

View File

@@ -0,0 +1,489 @@
package com.ruoyi.common.utils.redis;
import com.ruoyi.common.utils.spring.SpringUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.redisson.api.*;
import java.time.Duration;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* redis 工具类
*
* @author Lion Li
* @version 3.1.0 新增
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@SuppressWarnings(value = {"unchecked", "rawtypes"})
public class RedisUtils {
private static final RedissonClient CLIENT = SpringUtils.getBean(RedissonClient.class);
/**
* 限流
*
* @param key 限流key
* @param rateType 限流类型
* @param rate 速率
* @param rateInterval 速率间隔
* @return -1 表示失败
*/
public static long rateLimiter(String key, RateType rateType, int rate, int rateInterval) {
RRateLimiter rateLimiter = CLIENT.getRateLimiter(key);
rateLimiter.trySetRate(rateType, rate, rateInterval, RateIntervalUnit.SECONDS);
if (rateLimiter.tryAcquire()) {
return rateLimiter.availablePermits();
} else {
return -1L;
}
}
/**
* 获取客户端实例
*/
public static RedissonClient getClient() {
return CLIENT;
}
/**
* 发布通道消息
*
* @param channelKey 通道key
* @param msg 发送数据
* @param consumer 自定义处理
*/
public static <T> void publish(String channelKey, T msg, Consumer<T> consumer) {
RTopic topic = CLIENT.getTopic(channelKey);
topic.publish(msg);
consumer.accept(msg);
}
public static <T> void publish(String channelKey, T msg) {
RTopic topic = CLIENT.getTopic(channelKey);
topic.publish(msg);
}
/**
* 订阅通道接收消息
*
* @param channelKey 通道key
* @param clazz 消息类型
* @param consumer 自定义处理
*/
public static <T> void subscribe(String channelKey, Class<T> clazz, Consumer<T> consumer) {
RTopic topic = CLIENT.getTopic(channelKey);
topic.addListener(clazz, (channel, msg) -> consumer.accept(msg));
}
/**
* 缓存基本的对象Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
*/
public static <T> void setCacheObject(final String key, final T value) {
setCacheObject(key, value, false);
}
/**
* 缓存基本的对象,保留当前对象 TTL 有效期
*
* @param key 缓存的键值
* @param value 缓存的值
* @param isSaveTtl 是否保留TTL有效期(例如: set之前ttl剩余90 set之后还是为90)
* @since Redis 6.X 以上使用 setAndKeepTTL 兼容 5.X 方案
*/
public static <T> void setCacheObject(final String key, final T value, final boolean isSaveTtl) {
RBucket<T> bucket = CLIENT.getBucket(key);
if (isSaveTtl) {
try {
bucket.setAndKeepTTL(value);
} catch (Exception e) {
long timeToLive = bucket.remainTimeToLive();
setCacheObject(key, value, Duration.ofMillis(timeToLive));
}
} else {
bucket.set(value);
}
}
/**
* 缓存基本的对象Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
* @param duration 时间
*/
public static <T> void setCacheObject(final String key, final T value, final Duration duration) {
RBatch batch = CLIENT.createBatch();
RBucketAsync<T> bucket = batch.getBucket(key);
bucket.setAsync(value);
bucket.expireAsync(duration);
batch.execute();
}
/**
* 如果不存在则设置 并返回 true 如果存在则返回 false
*
* @param key 缓存的键值
* @param value 缓存的值
* @return set成功或失败
*/
public static <T> boolean setObjectIfAbsent(final String key, final T value, final Duration duration) {
RBucket<T> bucket = CLIENT.getBucket(key);
return bucket.setIfAbsent(value, duration);
}
/**
* 注册对象监听器
* <p>
* key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置
*
* @param key 缓存的键值
* @param listener 监听器配置
*/
public static <T> void addObjectListener(final String key, final ObjectListener listener) {
RBucket<T> result = CLIENT.getBucket(key);
result.addListener(listener);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @return true=设置成功false=设置失败
*/
public static boolean expire(final String key, final long timeout) {
return expire(key, Duration.ofSeconds(timeout));
}
/**
* 设置有效时间
*
* @param key Redis键
* @param duration 超时时间
* @return true=设置成功false=设置失败
*/
public static boolean expire(final String key, final Duration duration) {
RBucket rBucket = CLIENT.getBucket(key);
return rBucket.expire(duration);
}
/**
* 获得缓存的基本对象。
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public static <T> T getCacheObject(final String key) {
RBucket<T> rBucket = CLIENT.getBucket(key);
return rBucket.get();
}
/**
* 获得key剩余存活时间
*
* @param key 缓存键值
* @return 剩余存活时间
*/
public static <T> long getTimeToLive(final String key) {
RBucket<T> rBucket = CLIENT.getBucket(key);
return rBucket.remainTimeToLive();
}
/**
* 删除单个对象
*
* @param key 缓存的键值
*/
public static boolean deleteObject(final String key) {
return CLIENT.getBucket(key).delete();
}
/**
* 删除集合对象
*
* @param collection 多个对象
*/
public static void deleteObject(final Collection collection) {
RBatch batch = CLIENT.createBatch();
collection.forEach(t -> {
batch.getBucket(t.toString()).deleteAsync();
});
batch.execute();
}
/**
* 检查缓存对象是否存在
*
* @param key 缓存的键值
*/
public static boolean isExistsObject(final String key) {
return CLIENT.getBucket(key).isExists();
}
/**
* 缓存List数据
*
* @param key 缓存的键值
* @param dataList 待缓存的List数据
* @return 缓存的对象
*/
public static <T> boolean setCacheList(final String key, final List<T> dataList) {
RList<T> rList = CLIENT.getList(key);
return rList.addAll(dataList);
}
/**
* 注册List监听器
* <p>
* key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置
*
* @param key 缓存的键值
* @param listener 监听器配置
*/
public static <T> void addListListener(final String key, final ObjectListener listener) {
RList<T> rList = CLIENT.getList(key);
rList.addListener(listener);
}
/**
* 获得缓存的list对象
*
* @param key 缓存的键值
* @return 缓存键值对应的数据
*/
public static <T> List<T> getCacheList(final String key) {
RList<T> rList = CLIENT.getList(key);
return rList.readAll();
}
/**
* 缓存Set
*
* @param key 缓存键值
* @param dataSet 缓存的数据
* @return 缓存数据的对象
*/
public static <T> boolean setCacheSet(final String key, final Set<T> dataSet) {
RSet<T> rSet = CLIENT.getSet(key);
return rSet.addAll(dataSet);
}
/**
* 注册Set监听器
* <p>
* key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置
*
* @param key 缓存的键值
* @param listener 监听器配置
*/
public static <T> void addSetListener(final String key, final ObjectListener listener) {
RSet<T> rSet = CLIENT.getSet(key);
rSet.addListener(listener);
}
/**
* 获得缓存的set
*
* @param key 缓存的key
* @return set对象
*/
public static <T> Set<T> getCacheSet(final String key) {
RSet<T> rSet = CLIENT.getSet(key);
return rSet.readAll();
}
/**
* 缓存Map
*
* @param key 缓存的键值
* @param dataMap 缓存的数据
*/
public static <T> void setCacheMap(final String key, final Map<String, T> dataMap) {
if (dataMap != null) {
RMap<String, T> rMap = CLIENT.getMap(key);
rMap.putAll(dataMap);
}
}
/**
* 注册Map监听器
* <p>
* key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置
*
* @param key 缓存的键值
* @param listener 监听器配置
*/
public static <T> void addMapListener(final String key, final ObjectListener listener) {
RMap<String, T> rMap = CLIENT.getMap(key);
rMap.addListener(listener);
}
/**
* 获得缓存的Map
*
* @param key 缓存的键值
* @return map对象
*/
public static <T> Map<String, T> getCacheMap(final String key) {
RMap<String, T> rMap = CLIENT.getMap(key);
return rMap.getAll(rMap.keySet());
}
/**
* 获得缓存Map的key列表
*
* @param key 缓存的键值
* @return key列表
*/
public static <T> Set<String> getCacheMapKeySet(final String key) {
RMap<String, T> rMap = CLIENT.getMap(key);
return rMap.keySet();
}
/**
* 往Hash中存入数据
*
* @param key Redis键
* @param hKey Hash键
* @param value 值
*/
public static <T> void setCacheMapValue(final String key, final String hKey, final T value) {
RMap<String, T> rMap = CLIENT.getMap(key);
rMap.put(hKey, value);
}
/**
* 获取Hash中的数据
*
* @param key Redis键
* @param hKey Hash键
* @return Hash中的对象
*/
public static <T> T getCacheMapValue(final String key, final String hKey) {
RMap<String, T> rMap = CLIENT.getMap(key);
return rMap.get(hKey);
}
/**
* 删除Hash中的数据
*
* @param key Redis键
* @param hKey Hash键
* @return Hash中的对象
*/
public static <T> T delCacheMapValue(final String key, final String hKey) {
RMap<String, T> rMap = CLIENT.getMap(key);
return rMap.remove(hKey);
}
/**
* 删除Hash中的数据
*
* @param key Redis键
* @param hKeys Hash键
*/
public static <T> void delMultiCacheMapValue(final String key, final Set<String> hKeys) {
RBatch batch = CLIENT.createBatch();
RMapAsync<String, T> rMap = batch.getMap(key);
for (String hKey : hKeys) {
rMap.removeAsync(hKey);
}
batch.execute();
}
/**
* 获取多个Hash中的数据
*
* @param key Redis键
* @param hKeys Hash键集合
* @return Hash对象集合
*/
public static <K, V> Map<K, V> getMultiCacheMapValue(final String key, final Set<K> hKeys) {
RMap<K, V> rMap = CLIENT.getMap(key);
return rMap.getAll(hKeys);
}
/**
* 设置原子值
*
* @param key Redis键
* @param value 值
*/
public static void setAtomicValue(String key, long value) {
RAtomicLong atomic = CLIENT.getAtomicLong(key);
atomic.set(value);
}
/**
* 获取原子值
*
* @param key Redis键
* @return 当前值
*/
public static long getAtomicValue(String key) {
RAtomicLong atomic = CLIENT.getAtomicLong(key);
return atomic.get();
}
/**
* 递增原子值
*
* @param key Redis键
* @return 当前值
*/
public static long incrAtomicValue(String key) {
RAtomicLong atomic = CLIENT.getAtomicLong(key);
return atomic.incrementAndGet();
}
/**
* 递减原子值
*
* @param key Redis键
* @return 当前值
*/
public static long decrAtomicValue(String key) {
RAtomicLong atomic = CLIENT.getAtomicLong(key);
return atomic.decrementAndGet();
}
/**
* 获得缓存的基本对象列表
*
* @param pattern 字符串前缀
* @return 对象列表
*/
public static Collection<String> keys(final String pattern) {
Stream<String> stream = CLIENT.getKeys().getKeysStreamByPattern(pattern);
return stream.collect(Collectors.toList());
}
/**
* 删除缓存的基本对象列表
*
* @param pattern 字符串前缀
*/
public static void deleteKeys(final String pattern) {
CLIENT.getKeys().deleteByPattern(pattern);
}
/**
* 检查redis中是否存在key
*
* @param key 键
*/
public static Boolean hasKey(String key) {
RKeys rKeys = CLIENT.getKeys();
return rKeys.countExists(key) > 0;
}
}

View File

@@ -0,0 +1,56 @@
package com.ruoyi.common.utils.reflect;
import cn.hutool.core.util.ReflectUtil;
import com.ruoyi.common.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);
}
}
}
}

View File

@@ -0,0 +1,74 @@
package com.ruoyi.common.utils.spring;
import cn.hutool.extra.spring.SpringUtil;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
/**
* spring工具类
*
* @author Lion Li
*/
@Component
public final class SpringUtils extends SpringUtil {
/**
* 如果BeanFactory包含一个与所给名称匹配的bean定义则返回true
*
* @param name
* @return boolean
*/
public static boolean containsBean(String name) {
return getBeanFactory().containsBean(name);
}
/**
* 判断以给定名字注册的bean定义是一个singleton还是一个prototype。
* 如果与给定名字相应的bean定义没有被找到将会抛出一个异常NoSuchBeanDefinitionException
*
* @param name
* @return boolean
*/
public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException {
return getBeanFactory().isSingleton(name);
}
/**
* @param name
* @return Class 注册对象的类型
*/
public static Class<?> getType(String name) throws NoSuchBeanDefinitionException {
return getBeanFactory().getType(name);
}
/**
* 如果给定的bean名字在bean定义中有别名则返回这些别名
*
* @param name
*/
public static String[] getAliases(String name) throws NoSuchBeanDefinitionException {
return getBeanFactory().getAliases(name);
}
/**
* 获取aop代理对象
*
* @param invoker
* @return
*/
@SuppressWarnings("unchecked")
public static <T> T getAopProxy(T invoker) {
return (T) AopContext.currentProxy();
}
/**
* 获取spring上下文
*/
public static ApplicationContext context() {
return getApplicationContext();
}
}

View File

@@ -0,0 +1,57 @@
package com.ruoyi.common.utils.sql;
import com.ruoyi.common.exception.UtilException;
import com.ruoyi.common.utils.StringUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
/**
* sql操作工具类
*
* @author ruoyi
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class SqlUtil {
/**
* 定义常用的 sql关键字
*/
public static final String SQL_REGEX = "select |insert |delete |update |drop |count |exec |chr |mid |master |truncate |char |and |declare ";
/**
* 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序)
*/
public static final String SQL_PATTERN = "[a-zA-Z0-9_\\ \\,\\.]+";
/**
* 检查字符,防止注入绕过
*/
public static String escapeOrderBySql(String value) {
if (StringUtils.isNotEmpty(value) && !isValidOrderBySql(value)) {
throw new UtilException("参数不符合规范,不能进行查询");
}
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 UtilException("参数存在SQL注入风险");
}
}
}
}