初始化
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,74 @@
|
||||
package tech.easyflow.common.util;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class FileUtil {
|
||||
|
||||
public static String calcByte(Long sizeInBytes) {
|
||||
if (sizeInBytes == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
String sizeFormatted;
|
||||
if (sizeInBytes >= 1024 * 1024 * 1024) {
|
||||
// Convert to GB
|
||||
double sizeInGB = sizeInBytes / (1024.0 * 1024.0 * 1024.0);
|
||||
sizeFormatted = String.format("%.2f GB", sizeInGB);
|
||||
} else if (sizeInBytes >= 1024 * 1024) {
|
||||
// Convert to MB
|
||||
double sizeInMB = sizeInBytes / (1024.0 * 1024.0);
|
||||
sizeFormatted = String.format("%.2f MB", sizeInMB);
|
||||
} else if (sizeInBytes >= 1024) {
|
||||
// Convert to KB
|
||||
double sizeInKB = sizeInBytes / 1024.0;
|
||||
sizeFormatted = String.format("%.2f KB", sizeInKB);
|
||||
} else {
|
||||
// Keep in bytes
|
||||
sizeFormatted = sizeInBytes + " bytes";
|
||||
}
|
||||
return sizeFormatted;
|
||||
}
|
||||
|
||||
|
||||
public static String getFileTypeByExtension(String fileName) {
|
||||
if (fileName.endsWith(".txt")) {
|
||||
return "txt";
|
||||
} else if (fileName.endsWith(".pdf")) {
|
||||
return "pdf";
|
||||
} else if (fileName.endsWith(".md")) {
|
||||
return "md";
|
||||
} else if (fileName.endsWith(".docx")) {
|
||||
return "docx";
|
||||
} else if (fileName.endsWith(".xlsx")) {
|
||||
return "xlsx";
|
||||
} else if (fileName.endsWith(".ppt")) {
|
||||
return "ppt";
|
||||
} else if (fileName.endsWith(".pptx")) {
|
||||
return "pptx";
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* url解编码
|
||||
* @param url
|
||||
* @return
|
||||
*/
|
||||
public static String getDecodedUrl(String url) {
|
||||
String encodedUrl = URLEncoder.encode(url, StandardCharsets.UTF_8)
|
||||
.replace("+", "%20") // 空格转 %20
|
||||
.replace("%2F", "/"); // 保留路径分隔符 /
|
||||
|
||||
try {
|
||||
URI validUri = new URI(encodedUrl);
|
||||
return validUri.toString();
|
||||
} catch (URISyntaxException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package tech.easyflow.common.util;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Base64;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public class HashUtil {
|
||||
private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray();
|
||||
private static final char[] CHAR_ARRAY = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
|
||||
|
||||
public static String md5(String srcStr) {
|
||||
return hash("MD5", srcStr);
|
||||
}
|
||||
|
||||
|
||||
public static String sha256(String srcStr) {
|
||||
return hash("SHA-256", srcStr);
|
||||
}
|
||||
|
||||
public static String macHha256(String srcStr, String secret) {
|
||||
try {
|
||||
Mac hmacSHA256 = Mac.getInstance("HmacSHA256");
|
||||
SecretKeySpec secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
|
||||
hmacSHA256.init(secretKey);
|
||||
byte[] bytes = hmacSHA256.doFinal(srcStr.getBytes(StandardCharsets.UTF_8));
|
||||
return Base64.getEncoder().encodeToString(bytes);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static String hash(String algorithm, String srcStr) {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance(algorithm);
|
||||
byte[] bytes = md.digest(srcStr.getBytes(StandardCharsets.UTF_8));
|
||||
return bytesToHex(bytes);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static String bytesToHex(byte[] bytes) {
|
||||
StringBuilder ret = new StringBuilder(bytes.length * 2);
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
ret.append(HEX_DIGITS[(bytes[i] >> 4) & 0x0f]);
|
||||
ret.append(HEX_DIGITS[bytes[i] & 0x0f]);
|
||||
}
|
||||
return ret.toString();
|
||||
}
|
||||
|
||||
|
||||
public static String generateSalt(int saltLength) {
|
||||
StringBuilder salt = new StringBuilder(saltLength);
|
||||
ThreadLocalRandom random = ThreadLocalRandom.current();
|
||||
for (int i = 0; i < saltLength; i++) {
|
||||
salt.append(CHAR_ARRAY[random.nextInt(CHAR_ARRAY.length)]);
|
||||
}
|
||||
return salt.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package tech.easyflow.common.util;
|
||||
|
||||
import okio.BufferedSink;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class IOUtil {
|
||||
private static final int DEFAULT_BUFFER_SIZE = 8192;
|
||||
|
||||
public static void writeBytes(byte[] bytes, File toFile) {
|
||||
try (FileOutputStream stream = new FileOutputStream(toFile)) {
|
||||
stream.write(bytes);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] readBytes(File file) {
|
||||
try (FileInputStream inputStream = new FileInputStream(file)) {
|
||||
return readBytes(inputStream);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] readBytes(InputStream inputStream) {
|
||||
try {
|
||||
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
|
||||
copy(inputStream, outStream);
|
||||
return outStream.toByteArray();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void copy(InputStream inputStream, BufferedSink sink) throws IOException {
|
||||
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
|
||||
for (int len; (len = inputStream.read(buffer)) != -1; ) {
|
||||
sink.write(buffer, 0, len);
|
||||
}
|
||||
}
|
||||
|
||||
public static void copy(InputStream inputStream, OutputStream outStream) throws IOException {
|
||||
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
|
||||
for (int len; (len = inputStream.read(buffer)) != -1; ) {
|
||||
outStream.write(buffer, 0, len);
|
||||
}
|
||||
}
|
||||
|
||||
public static String readUtf8(InputStream inputStream) throws IOException {
|
||||
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
|
||||
copy(inputStream, outStream);
|
||||
return new String(outStream.toByteArray(), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package tech.easyflow.common.util;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class IdUtil {
|
||||
|
||||
public static String generateUUID() {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
return uuid.toString().replace("-", "").toLowerCase();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
package tech.easyflow.common.util;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Map工具类,提供从Map中安全获取指定类型值的方法。
|
||||
*/
|
||||
public class MapUtil {
|
||||
|
||||
/**
|
||||
* 从Map中获取字符串类型的值。
|
||||
*
|
||||
* @param map 包含键值对的Map对象
|
||||
* @param key 要查找的键
|
||||
* @return 对应键的字符串值,如果不存在或转换失败则返回null
|
||||
*/
|
||||
public static String getString(Map<String, Object> map, String key) {
|
||||
if (map == null) return null;
|
||||
return toString(map.get(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* 从Map中获取字符串类型的值,并支持默认值。
|
||||
*
|
||||
* @param map 包含键值对的Map对象
|
||||
* @param key 要查找的键
|
||||
* @param defaultValue 默认返回值
|
||||
* @return 对应键的字符串值,如果不存在或转换失败则返回默认值
|
||||
*/
|
||||
public static String getString(Map<String, Object> map, String key, String defaultValue) {
|
||||
if (map == null) return defaultValue;
|
||||
String value = toString(map.get(key));
|
||||
return value == null ? defaultValue : value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从Map中获取整数类型的值。
|
||||
*
|
||||
* @param map 包含键值对的Map对象
|
||||
* @param key 要查找的键
|
||||
* @return 对应键的整数值,如果不存在或转换失败则返回null
|
||||
*/
|
||||
public static Integer getInteger(Map<String, Object> map, String key) {
|
||||
if (map == null) return null;
|
||||
return toInteger(map.get(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* 从Map中获取整数类型的值,并支持默认值。
|
||||
*
|
||||
* @param map 包含键值对的Map对象
|
||||
* @param key 要查找的键
|
||||
* @param defaultValue 默认返回值
|
||||
* @return 对应键的整数值,如果不存在或转换失败则返回默认值
|
||||
*/
|
||||
public static Integer getInteger(Map<String, Object> map, String key, Integer defaultValue) {
|
||||
if (map == null) return defaultValue;
|
||||
Integer value = toInteger(map.get(key));
|
||||
return value == null ? defaultValue : value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从Map中获取长整数类型的值。
|
||||
*
|
||||
* @param map 包含键值对的Map对象
|
||||
* @param key 要查找的键
|
||||
* @return 对应键的长整数值,如果不存在或转换失败则返回null
|
||||
*/
|
||||
public static Long getLong(Map<String, Object> map, String key) {
|
||||
if (map == null) return null;
|
||||
return toLong(map.get(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* 从Map中获取长整数类型的值,并支持默认值。
|
||||
*
|
||||
* @param map 包含键值对的Map对象
|
||||
* @param key 要查找的键
|
||||
* @param defaultValue 默认返回值
|
||||
* @return 对应键的长整数值,如果不存在或转换失败则返回默认值
|
||||
*/
|
||||
public static Long getLong(Map<String, Object> map, String key, Long defaultValue) {
|
||||
if (map == null) return defaultValue;
|
||||
Long value = toLong(map.get(key));
|
||||
return value == null ? defaultValue : value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从Map中获取双精度浮点数类型的值。
|
||||
*
|
||||
* @param map 包含键值对的Map对象
|
||||
* @param key 要查找的键
|
||||
* @return 对应键的双精度浮点数值,如果不存在或转换失败则返回null
|
||||
*/
|
||||
public static Double getDouble(Map<String, Object> map, String key) {
|
||||
if (map == null) return null;
|
||||
return toDouble(map.get(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* 从Map中获取双精度浮点数类型的值,并支持默认值。
|
||||
*
|
||||
* @param map 包含键值对的Map对象
|
||||
* @param key 要查找的键
|
||||
* @param defaultValue 默认返回值
|
||||
* @return 对应键的双精度浮点数值,如果不存在或转换失败则返回默认值
|
||||
*/
|
||||
public static Double getDouble(Map<String, Object> map, String key, Double defaultValue) {
|
||||
if (map == null) return defaultValue;
|
||||
Double value = toDouble(map.get(key));
|
||||
return value == null ? defaultValue : value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从Map中获取布尔类型的值。
|
||||
*
|
||||
* @param map 包含键值对的Map对象
|
||||
* @param key 要查找的键
|
||||
* @return 对应键的布尔值,如果不存在或转换失败则返回null
|
||||
*/
|
||||
public static Boolean getBoolean(Map<String, Object> map, String key) {
|
||||
if (map == null) return null;
|
||||
return toBoolean(map.get(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* 从Map中获取布尔类型的值,并支持默认值。
|
||||
*
|
||||
* @param map 包含键值对的Map对象
|
||||
* @param key 要查找的键
|
||||
* @param defaultValue 默认返回值
|
||||
* @return 对应键的布尔值,如果不存在或转换失败则返回默认值
|
||||
*/
|
||||
public static Boolean getBoolean(Map<String, Object> map, String key, Boolean defaultValue) {
|
||||
if (map == null) return defaultValue;
|
||||
Boolean value = toBoolean(map.get(key));
|
||||
return value == null ? defaultValue : value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将给定对象转换为字符串表示形式。
|
||||
*
|
||||
* @param obj 待转换的对象
|
||||
* @return 字符串结果,若原对象为null则返回null;如果是String类型直接返回;否则调用toString()
|
||||
*/
|
||||
private static String toString(Object obj) {
|
||||
if (obj == null) return null;
|
||||
if (obj instanceof String) return (String) obj;
|
||||
return obj.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将给定对象转换为整数。
|
||||
*
|
||||
* @param obj 待转换的对象
|
||||
* @return 整数结果,若原对象为null则返回null;如果是Number子类则取其int值;否则尝试解析字符串
|
||||
*/
|
||||
private static Integer toInteger(Object obj) {
|
||||
if (obj == null) return null;
|
||||
if (obj instanceof Number) return ((Number) obj).intValue();
|
||||
return Integer.parseInt(obj.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 将给定对象转换为长整数。
|
||||
*
|
||||
* @param obj 待转换的对象
|
||||
* @return 长整数结果,若原对象为null则返回null;如果是Number子类则取其long值;否则尝试解析字符串
|
||||
*/
|
||||
private static Long toLong(Object obj) {
|
||||
if (obj == null) return null;
|
||||
if (obj instanceof Number) return ((Number) obj).longValue();
|
||||
return Long.parseLong(obj.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 将给定对象转换为双精度浮点数。
|
||||
*
|
||||
* @param obj 待转换的对象
|
||||
* @return 双精度浮点数结果,若原对象为null则返回null;如果是Number子类则取其double值;否则尝试解析字符串
|
||||
*/
|
||||
private static Double toDouble(Object obj) {
|
||||
if (obj == null) return null;
|
||||
if (obj instanceof Number) return ((Number) obj).doubleValue();
|
||||
return Double.parseDouble(obj.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 将给定对象转换为布尔值。
|
||||
*
|
||||
* @param obj 待转换的对象
|
||||
* @return 布尔值结果,若原对象为null则返回null;如果是Boolean类型直接返回;否则尝试解析字符串
|
||||
*/
|
||||
private static Boolean toBoolean(Object obj) {
|
||||
if (obj == null) return null;
|
||||
if (obj instanceof Boolean) return (Boolean) obj;
|
||||
return Boolean.parseBoolean(obj.toString());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package tech.easyflow.common.util;
|
||||
|
||||
import com.mybatisflex.core.BaseMapper;
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
import com.mybatisflex.core.row.Db;
|
||||
import com.mybatisflex.core.table.IdInfo;
|
||||
import com.mybatisflex.core.table.TableInfo;
|
||||
import com.mybatisflex.core.table.TableInfoFactory;
|
||||
import com.mybatisflex.core.util.CollectionUtil;
|
||||
import com.mybatisflex.core.util.FieldWrapper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class MapperUtil {
|
||||
|
||||
|
||||
/**
|
||||
* 同步 List 到数据库
|
||||
*
|
||||
* @param newModels 新的 Models
|
||||
* @param mapper Mapper 查询
|
||||
* @param existQueryWrapper 查询旧的 Wrapper
|
||||
* @param getter 根据什么字段来对比进行同步
|
||||
* @param <T> Entity 类
|
||||
*/
|
||||
public static <T> void syncList(List<T> newModels, BaseMapper<T> mapper, QueryWrapper existQueryWrapper,
|
||||
Function<T, Object> getter) {
|
||||
syncList(newModels, mapper, existQueryWrapper, getter, null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 同步 List 到数据库
|
||||
*
|
||||
* @param newModels 新的 Models
|
||||
* @param mapper Mapper 查询
|
||||
* @param existQueryWrapper 查询旧的 Wrapper
|
||||
* @param getter 根据什么字段来对比进行同步
|
||||
* @param onSyncBefore 在同步到数据库之前,可能需要做的前置操作
|
||||
* @param <T> Entity 类
|
||||
*/
|
||||
public static <T> void syncList(List<T> newModels, BaseMapper<T> mapper, QueryWrapper existQueryWrapper,
|
||||
Function<T, Object> getter,
|
||||
Consumer<T> onSyncBefore) {
|
||||
|
||||
List<T> existModels = mapper.selectListByQuery(existQueryWrapper);
|
||||
|
||||
List<T> needDeletes = new ArrayList<>();
|
||||
List<T> saveOrUpdates = new ArrayList<>();
|
||||
|
||||
if (CollectionUtil.isNotEmpty(newModels)) {
|
||||
if (CollectionUtil.isEmpty(existModels)) {
|
||||
saveOrUpdates.addAll(newModels);
|
||||
} else {
|
||||
for (T existModel : existModels) {
|
||||
boolean removed = true;
|
||||
for (T newModel : newModels) {
|
||||
if (Objects.equals(getter.apply(existModel), getter.apply(newModel))) {
|
||||
removed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (removed) {
|
||||
needDeletes.add(existModel);
|
||||
}
|
||||
}
|
||||
|
||||
TableInfo tableInfo = TableInfoFactory.ofEntityClass(newModels.get(0).getClass());
|
||||
List<IdInfo> primaryKeyList = tableInfo.getPrimaryKeyList();
|
||||
List<FieldWrapper> fieldWrappers = primaryKeyList.stream().map(idInfo -> FieldWrapper.of(tableInfo.getEntityClass(), idInfo.getProperty()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
|
||||
for (T newModel : newModels) {
|
||||
for (T existModel : existModels) {
|
||||
if (Objects.equals(getter.apply(existModel), getter.apply(newModel))) {
|
||||
|
||||
//复制旧数据库的 ID 到新 model
|
||||
for (FieldWrapper fieldWrapper : fieldWrappers) {
|
||||
fieldWrapper.set(fieldWrapper.get(existModel), newModel);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
saveOrUpdates.add(newModel);
|
||||
}
|
||||
}
|
||||
} else if (CollectionUtil.isNotEmpty(existModels)) {
|
||||
needDeletes.addAll(existModels);
|
||||
}
|
||||
|
||||
Db.tx(() -> {
|
||||
for (T needDelete : needDeletes) {
|
||||
mapper.delete(needDelete);
|
||||
}
|
||||
|
||||
for (T saveOrUpdate : saveOrUpdates) {
|
||||
if (onSyncBefore != null) {
|
||||
onSyncBefore.accept(saveOrUpdate);
|
||||
}
|
||||
mapper.insertOrUpdate(saveOrUpdate);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright (c) 2022-2023, Agents-Flex (fuhai999@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package tech.easyflow.common.util;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class Maps extends HashMap<String, Object> {
|
||||
|
||||
public static Maps of() {
|
||||
return new Maps();
|
||||
}
|
||||
|
||||
public static Maps of(String key, Object value) {
|
||||
Maps maps = Maps.of();
|
||||
maps.put(key, value);
|
||||
return maps;
|
||||
}
|
||||
|
||||
public static Maps ofNotNull(String key, Object value) {
|
||||
return new Maps().setIfNotNull(key, value);
|
||||
}
|
||||
|
||||
public static Maps ofNotEmpty(String key, Object value) {
|
||||
return new Maps().setIfNotEmpty(key, value);
|
||||
}
|
||||
|
||||
public static Maps ofNotEmpty(String key, Maps value) {
|
||||
return new Maps().setIfNotEmpty(key, value);
|
||||
}
|
||||
|
||||
|
||||
public Maps set(String key, Object value) {
|
||||
super.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Maps setChild(String key, Object value) {
|
||||
if (key.contains(".")) {
|
||||
String[] keys = key.split("\\.");
|
||||
Map<String, Object> currentMap = this;
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
String currentKey = keys[i].trim();
|
||||
if (currentKey.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
if (i == keys.length - 1) {
|
||||
currentMap.put(currentKey, value);
|
||||
} else {
|
||||
//noinspection unchecked
|
||||
currentMap = (Map<String, Object>) currentMap.computeIfAbsent(currentKey, k -> Maps.of());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
super.put(key, value);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Maps setOrDefault(String key, Object value, Object orDefault) {
|
||||
if (isNullOrEmpty(value)) {
|
||||
return this.set(key, orDefault);
|
||||
} else {
|
||||
return this.set(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
public Maps setIf(boolean condition, String key, Object value) {
|
||||
if (condition) put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Maps setIf(Function<Maps, Boolean> func, String key, Object value) {
|
||||
if (func.apply(this)) put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Maps setIfNotNull(String key, Object value) {
|
||||
if (value != null) put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Maps setIfNotEmpty(String key, Object value) {
|
||||
if (!isNullOrEmpty(value)) {
|
||||
put(key, value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Maps setIfContainsKey(String checkKey, String key, Object value) {
|
||||
if (this.containsKey(checkKey)) {
|
||||
this.put(key, value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Maps setIfNotContainsKey(String checkKey, String key, Object value) {
|
||||
if (!this.containsKey(checkKey)) {
|
||||
this.put(key, value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public String toJSON() {
|
||||
return JSON.toJSONString(this);
|
||||
}
|
||||
|
||||
|
||||
private static boolean isNullOrEmpty(Object value) {
|
||||
if (value == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (value instanceof Collection && ((Collection<?>) value).isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (value instanceof Map && ((Map<?, ?>) value).isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (value.getClass().isArray() && Array.getLength(value) == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return value instanceof String && ((String) value).trim().isEmpty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
package tech.easyflow.common.util;
|
||||
|
||||
import okhttp3.ConnectionPool;
|
||||
import okhttp3.OkHttpClient;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Proxy;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class OkHttpClientUtil {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(OkHttpClientUtil.class);
|
||||
|
||||
// 系统属性前缀
|
||||
private static final String PREFIX = "okhttp.";
|
||||
|
||||
// 环境变量前缀(大写)
|
||||
private static final String ENV_PREFIX = "OKHTTP_";
|
||||
|
||||
private static volatile OkHttpClient defaultClient;
|
||||
private static volatile OkHttpClient.Builder customBuilder;
|
||||
|
||||
public static void setOkHttpClientBuilder(OkHttpClient.Builder builder) {
|
||||
if (defaultClient != null) {
|
||||
throw new IllegalStateException("OkHttpClient has already been initialized. " +
|
||||
"Please set the builder before first usage.");
|
||||
}
|
||||
customBuilder = builder;
|
||||
}
|
||||
|
||||
public static OkHttpClient buildDefaultClient() {
|
||||
if (defaultClient == null) {
|
||||
synchronized (OkHttpClientUtil.class) {
|
||||
if (defaultClient == null) {
|
||||
OkHttpClient.Builder builder = customBuilder != null
|
||||
? customBuilder
|
||||
: createDefaultBuilder();
|
||||
defaultClient = builder.build();
|
||||
log.debug("OkHttpClient initialized with config: connectTimeout={}s, readTimeout={}s, writeTimeout={}s, " +
|
||||
"connectionPool(maxIdle={}, keepAlive={}min)",
|
||||
getConnectTimeout(), getReadTimeout(), getWriteTimeout(),
|
||||
getMaxIdleConnections(), getKeepAliveMinutes());
|
||||
}
|
||||
}
|
||||
}
|
||||
return defaultClient;
|
||||
}
|
||||
|
||||
private static OkHttpClient.Builder createDefaultBuilder() {
|
||||
OkHttpClient.Builder builder = new OkHttpClient.Builder()
|
||||
.connectTimeout(getConnectTimeout(), TimeUnit.SECONDS)
|
||||
.readTimeout(getReadTimeout(), TimeUnit.SECONDS)
|
||||
.writeTimeout(getWriteTimeout(), TimeUnit.SECONDS)
|
||||
.connectionPool(new ConnectionPool(getMaxIdleConnections(), getKeepAliveMinutes(), TimeUnit.MINUTES));
|
||||
|
||||
configureProxy(builder);
|
||||
return builder;
|
||||
}
|
||||
|
||||
// ==================== 配置读取方法 ====================
|
||||
|
||||
private static int getConnectTimeout() {
|
||||
return getIntConfig("connectTimeout", "CONNECT_TIMEOUT", 60);
|
||||
}
|
||||
|
||||
private static int getReadTimeout() {
|
||||
return getIntConfig("readTimeout", "READ_TIMEOUT", 300);
|
||||
}
|
||||
|
||||
private static int getWriteTimeout() {
|
||||
return getIntConfig("writeTimeout", "WRITE_TIMEOUT", 60);
|
||||
}
|
||||
|
||||
private static int getMaxIdleConnections() {
|
||||
return getIntConfig("connectionPool.maxIdleConnections", "CONNECTION_POOL_MAX_IDLE_CONNECTIONS", 5);
|
||||
}
|
||||
|
||||
private static long getKeepAliveMinutes() {
|
||||
return getLongConfig("connectionPool.keepAliveMinutes", "CONNECTION_POOL_KEEP_ALIVE_MINUTES", 10);
|
||||
}
|
||||
|
||||
private static String getProxyHost() {
|
||||
String host = getPropertyOrEnv("proxy.host", "PROXY_HOST", null);
|
||||
if (StringUtil.hasText(host)) return host.trim();
|
||||
|
||||
// 兼容 Java 标准代理属性(作为 fallback)
|
||||
host = System.getProperty("https.proxyHost");
|
||||
if (StringUtil.hasText(host)) return host.trim();
|
||||
|
||||
host = System.getProperty("http.proxyHost");
|
||||
if (StringUtil.hasText(host)) return host.trim();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String getProxyPort() {
|
||||
String port = getPropertyOrEnv("proxy.port", "PROXY_PORT", null);
|
||||
if (StringUtil.hasText(port)) return port.trim();
|
||||
|
||||
// 兼容 Java 标准代理属性
|
||||
port = System.getProperty("https.proxyPort");
|
||||
if (StringUtil.hasText(port)) return port.trim();
|
||||
|
||||
port = System.getProperty("http.proxyPort");
|
||||
if (StringUtil.hasText(port)) return port.trim();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// ==================== 工具方法 ====================
|
||||
|
||||
private static int getIntConfig(String sysPropKey, String envKey, int defaultValue) {
|
||||
String value = getPropertyOrEnv(sysPropKey, envKey, null);
|
||||
if (value == null) return defaultValue;
|
||||
try {
|
||||
return Integer.parseInt(value.trim());
|
||||
} catch (NumberFormatException e) {
|
||||
log.warn("Invalid integer value for '{}': '{}'. Using default: {}", fullSysPropKey(sysPropKey), value, defaultValue);
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
private static long getLongConfig(String sysPropKey, String envKey, long defaultValue) {
|
||||
String value = getPropertyOrEnv(sysPropKey, envKey, null);
|
||||
if (value == null) return defaultValue;
|
||||
try {
|
||||
return Long.parseLong(value.trim());
|
||||
} catch (NumberFormatException e) {
|
||||
log.warn("Invalid long value for '{}': '{}'. Using default: {}", fullSysPropKey(sysPropKey), value, defaultValue);
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
private static String getPropertyOrEnv(String sysPropKey, String envKey, String defaultValue) {
|
||||
// 1. 系统属性优先
|
||||
String value = System.getProperty(fullSysPropKey(sysPropKey));
|
||||
if (value != null) return value;
|
||||
|
||||
// 2. 环境变量
|
||||
value = System.getenv(ENV_PREFIX + envKey);
|
||||
if (value != null) return value;
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
private static String fullSysPropKey(String key) {
|
||||
return PREFIX + key;
|
||||
}
|
||||
|
||||
// ==================== 代理配置 ====================
|
||||
|
||||
private static void configureProxy(OkHttpClient.Builder builder) {
|
||||
String proxyHost = getProxyHost();
|
||||
String proxyPort = getProxyPort();
|
||||
|
||||
if (StringUtil.hasText(proxyHost) && StringUtil.hasText(proxyPort)) {
|
||||
try {
|
||||
int port = Integer.parseInt(proxyPort);
|
||||
InetSocketAddress address = new InetSocketAddress(proxyHost, port);
|
||||
builder.proxy(new Proxy(Proxy.Type.HTTP, address));
|
||||
log.debug("HTTP proxy configured via config: {}:{}", proxyHost, port);
|
||||
} catch (NumberFormatException e) {
|
||||
log.warn("Invalid proxy port '{}'. Proxy will be ignored.", proxyPort, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
package tech.easyflow.common.util;
|
||||
|
||||
import okhttp3.*;
|
||||
import okio.BufferedSink;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.Map;
|
||||
|
||||
public class OkHttpUtil {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(OkHttpUtil.class);
|
||||
private static final MediaType JSON_TYPE = MediaType.parse("application/json; charset=utf-8");
|
||||
|
||||
|
||||
private static OkHttpClient getOkHttpClient() {
|
||||
return OkHttpClientUtil.buildDefaultClient();
|
||||
}
|
||||
|
||||
|
||||
public static String get(String url) {
|
||||
return executeString(url, "GET", null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取远程URL资源的文件大小(字节数)
|
||||
* 支持分块传输(Transfer-Encoding: chunked)的大文件,兼容普通文件
|
||||
* @param url 远程资源URL
|
||||
* @return 资源字节大小,失败/无有效大小返回 0L
|
||||
*/
|
||||
public static long getFileSize(String url) {
|
||||
Request request = new Request.Builder()
|
||||
.url(url)
|
||||
.get()
|
||||
.build();
|
||||
|
||||
Response response = null;
|
||||
InputStream in = null;
|
||||
try {
|
||||
response = getOkHttpClient().newCall(request).execute();
|
||||
|
||||
if (!response.isSuccessful()) {
|
||||
LOG.error("Failed to get file size, HTTP response code: {} for url: {}",
|
||||
response.code(), url);
|
||||
return 0L;
|
||||
}
|
||||
|
||||
ResponseBody body = response.body();
|
||||
if (body == null) {
|
||||
LOG.warn("Response body is null for url: {}", url);
|
||||
return 0L;
|
||||
}
|
||||
in = body.byteStream();
|
||||
|
||||
byte[] buffer = new byte[1024 * 8];
|
||||
long totalBytes = 0L;
|
||||
int len;
|
||||
while ((len = in.read(buffer)) != -1) {
|
||||
totalBytes += len;
|
||||
}
|
||||
|
||||
LOG.info("Success to get file size for url: {}, size: {} bytes (≈ {} M)",
|
||||
url, totalBytes, String.format("%.2f", totalBytes / 1024.0 / 1024.0));
|
||||
return totalBytes;
|
||||
|
||||
} catch (IOException e) {
|
||||
LOG.error("IO exception when getting file size for url: {}", url, e);
|
||||
return 0L;
|
||||
} finally {
|
||||
if (in != null) {
|
||||
try {
|
||||
in.close();
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Failed to close InputStream when getting file size", e);
|
||||
}
|
||||
}
|
||||
if (response != null) {
|
||||
response.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] getBytes(String url) {
|
||||
return executeBytes(url, "GET", null, null);
|
||||
}
|
||||
|
||||
public static String get(String url, Map<String, String> headers) {
|
||||
return executeString(url, "GET", headers, null);
|
||||
}
|
||||
|
||||
public static InputStream getInputStream(String url) {
|
||||
try (Response response = getOkHttpClient().newCall(new Request.Builder().url(url).build()).execute();
|
||||
ResponseBody body = response.body();
|
||||
InputStream in = body != null ? body.byteStream() : null;
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream()) {
|
||||
|
||||
if (!response.isSuccessful() || in == null) {
|
||||
LOG.error("HTTP request failed with code: {} for url: {}", response.code(), url);
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] buffer = new byte[1024 * 4];
|
||||
int len;
|
||||
while ((len = in.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, len);
|
||||
}
|
||||
out.flush();
|
||||
|
||||
return new ByteArrayInputStream(out.toByteArray());
|
||||
|
||||
} catch (IOException ioe) {
|
||||
LOG.error("HTTP getInputStream failed: " + url, ioe);
|
||||
} catch (Exception e) {
|
||||
LOG.error(e.toString(), e);
|
||||
throw e;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String post(String url, Map<String, String> headers, String payload) {
|
||||
return executeString(url, "POST", headers, payload);
|
||||
}
|
||||
|
||||
public static byte[] postBytes(String url, Map<String, String> headers, String payload) {
|
||||
return executeBytes(url, "POST", headers, payload);
|
||||
}
|
||||
|
||||
public static String put(String url, Map<String, String> headers, String payload) {
|
||||
return executeString(url, "PUT", headers, payload);
|
||||
}
|
||||
|
||||
public static String delete(String url, Map<String, String> headers, String payload) {
|
||||
return executeString(url, "DELETE", headers, payload);
|
||||
}
|
||||
|
||||
public static String multipartString(String url, Map<String, String> headers, Map<String, Object> payload) {
|
||||
try (Response response = multipart(url, headers, payload);
|
||||
ResponseBody body = response.body()) {
|
||||
if (body != null) {
|
||||
return body.string();
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
LOG.error("HTTP multipartString failed: " + url, ioe);
|
||||
} catch (Exception e) {
|
||||
LOG.error(e.toString(), e);
|
||||
throw e;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static byte[] multipartBytes(String url, Map<String, String> headers, Map<String, Object> payload) {
|
||||
try (Response response = multipart(url, headers, payload);
|
||||
ResponseBody body = response.body()) {
|
||||
if (body != null) {
|
||||
return body.bytes();
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
LOG.error("HTTP multipartBytes failed: " + url, ioe);
|
||||
} catch (Exception e) {
|
||||
LOG.error(e.toString(), e);
|
||||
throw e;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public static String executeString(String url, String method, Map<String, String> headers, Object payload) {
|
||||
try (Response response = execute0(url, method, headers, payload);
|
||||
ResponseBody body = response.body()) {
|
||||
if (body != null) {
|
||||
return body.string();
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
LOG.error("HTTP executeString failed: " + url, ioe);
|
||||
} catch (Exception e) {
|
||||
LOG.error(e.toString(), e);
|
||||
throw e;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static byte[] executeBytes(String url, String method, Map<String, String> headers, Object payload) {
|
||||
try (Response response = execute0(url, method, headers, payload);
|
||||
ResponseBody body = response.body()) {
|
||||
if (body != null) {
|
||||
return body.bytes();
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
LOG.error("HTTP executeBytes failed: " + url, ioe);
|
||||
} catch (Exception e) {
|
||||
LOG.error(e.toString(), e);
|
||||
throw e;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Response execute0(String url, String method, Map<String, String> headers, Object payload) throws IOException {
|
||||
Request.Builder builder = new Request.Builder().url(url);
|
||||
if (headers != null && !headers.isEmpty()) {
|
||||
headers.forEach(builder::addHeader);
|
||||
}
|
||||
|
||||
Request request;
|
||||
if ("GET".equalsIgnoreCase(method)) {
|
||||
request = builder.build();
|
||||
} else {
|
||||
RequestBody body = RequestBody.create(payload == null ? "" : payload.toString(), JSON_TYPE);
|
||||
request = builder.method(method, body).build();
|
||||
}
|
||||
|
||||
return getOkHttpClient().newCall(request).execute();
|
||||
}
|
||||
|
||||
public static Response multipart(String url, Map<String, String> headers, Map<String, Object> payload) throws IOException {
|
||||
Request.Builder builder = new Request.Builder().url(url);
|
||||
if (headers != null && !headers.isEmpty()) {
|
||||
headers.forEach(builder::addHeader);
|
||||
}
|
||||
|
||||
MultipartBody.Builder mbBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM);
|
||||
payload.forEach((key, value) -> {
|
||||
if (value instanceof File) {
|
||||
File file = (File) value;
|
||||
RequestBody body = RequestBody.create(file, MediaType.parse("application/octet-stream"));
|
||||
mbBuilder.addFormDataPart(key, file.getName(), body);
|
||||
} else if (value instanceof InputStream) {
|
||||
RequestBody body = new InputStreamRequestBody(MediaType.parse("application/octet-stream"), (InputStream) value);
|
||||
mbBuilder.addFormDataPart(key, key, body);
|
||||
} else if (value instanceof byte[]) {
|
||||
mbBuilder.addFormDataPart(key, key, RequestBody.create((byte[]) value));
|
||||
} else {
|
||||
mbBuilder.addFormDataPart(key, String.valueOf(value));
|
||||
}
|
||||
});
|
||||
|
||||
MultipartBody multipartBody = mbBuilder.build();
|
||||
Request request = builder.post(multipartBody).build();
|
||||
|
||||
return getOkHttpClient().newCall(request).execute();
|
||||
}
|
||||
|
||||
|
||||
public static class InputStreamRequestBody extends RequestBody {
|
||||
private final InputStream inputStream;
|
||||
private final MediaType contentType;
|
||||
|
||||
public InputStreamRequestBody(MediaType contentType, InputStream inputStream) {
|
||||
if (inputStream == null) throw new NullPointerException("inputStream == null");
|
||||
this.contentType = contentType;
|
||||
this.inputStream = inputStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaType contentType() {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long contentLength() throws IOException {
|
||||
return inputStream.available() == 0 ? -1 : inputStream.available();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(BufferedSink sink) throws IOException {
|
||||
IOUtil.copy(inputStream, sink);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package tech.easyflow.common.util;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Properties;
|
||||
|
||||
public class PropertiesUtil {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(PropertiesUtil.class);
|
||||
|
||||
public static Properties textToProperties(String text) {
|
||||
Properties prop = new Properties();
|
||||
try (StringReader reader = new StringReader(text)) {
|
||||
prop.load(reader);
|
||||
} catch (Exception e) {
|
||||
logger.error(e.toString(), e);
|
||||
}
|
||||
return prop;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 将Properties对象转换为指定类型的实体对象。
|
||||
*
|
||||
* @param properties 包含配置信息的Properties对象
|
||||
* @param entityClass 目标实体类的Class对象
|
||||
* @param <T> 目标实体类的泛型类型
|
||||
* @return 转换后的实体对象
|
||||
*/
|
||||
public static <T> T propertiesToEntity(Properties properties, Class<T> entityClass) {
|
||||
try {
|
||||
T entity = entityClass.getDeclaredConstructor().newInstance();
|
||||
for (Field field : entityClass.getDeclaredFields()) {
|
||||
String fieldName = field.getName();
|
||||
String propertyValue = properties.getProperty(fieldName);
|
||||
|
||||
if (propertyValue != null) {
|
||||
field.setAccessible(true);
|
||||
Class<?> fieldType = field.getType();
|
||||
|
||||
if (fieldType.equals(String.class)) {
|
||||
field.set(entity, propertyValue);
|
||||
} else if (fieldType.equals(int.class) || fieldType.equals(Integer.class)) {
|
||||
field.set(entity, Integer.parseInt(propertyValue));
|
||||
} else if (fieldType.equals(long.class) || fieldType.equals(Long.class)) {
|
||||
field.set(entity, Long.parseLong(propertyValue));
|
||||
} else if (fieldType.equals(boolean.class) || fieldType.equals(Boolean.class)) {
|
||||
field.set(entity, Boolean.parseBoolean(propertyValue));
|
||||
} else if (fieldType.equals(double.class) || fieldType.equals(Double.class)) {
|
||||
field.set(entity, Double.parseDouble(propertyValue));
|
||||
} else if (fieldType.equals(float.class) || fieldType.equals(Float.class)) {
|
||||
field.set(entity, Float.parseFloat(propertyValue));
|
||||
} else {
|
||||
// 处理其他类型,例如自定义对象
|
||||
// 这里可以根据需要扩展
|
||||
}
|
||||
}
|
||||
}
|
||||
return entity;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to convert properties to entity", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> T propertiesTextToEntity(String propertiesText, Class<T> entityClass) {
|
||||
Properties properties = textToProperties(propertiesText);
|
||||
return propertiesToEntity(properties, entityClass);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
package tech.easyflow.common.util;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import org.springframework.web.util.ContentCachingRequestWrapper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Map;
|
||||
|
||||
public class RequestUtil {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RequestUtil.class);
|
||||
|
||||
private static final String jsonCacheKey = "__$JSONObjectOrArray";
|
||||
|
||||
public static Object readJsonObjectOrArray(HttpServletRequest request) {
|
||||
Object jsonObjectOrArray = request.getAttribute(jsonCacheKey);
|
||||
if (jsonObjectOrArray == null) {
|
||||
String body = readBodyString(request);
|
||||
jsonObjectOrArray = JSON.parse(body);
|
||||
request.setAttribute(jsonCacheKey, jsonObjectOrArray);
|
||||
}
|
||||
return jsonObjectOrArray;
|
||||
}
|
||||
|
||||
|
||||
public static String readBodyString(HttpServletRequest request) {
|
||||
String ce = request.getCharacterEncoding();
|
||||
if (request instanceof ContentCachingRequestWrapper) {
|
||||
ContentCachingRequestWrapper wrapper = (ContentCachingRequestWrapper) request;
|
||||
byte[] contentAsByteArray = wrapper.getContentAsByteArray();
|
||||
if (contentAsByteArray.length != 0) {
|
||||
try {
|
||||
return new String(contentAsByteArray, ce != null ? ce : "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
InputStreamReader reader = new InputStreamReader(request.getInputStream(), ce != null ? ce : "UTF-8");
|
||||
StringBuilder sb = new StringBuilder();
|
||||
char[] buf = new char[1024];
|
||||
for (int num; (num = reader.read(buf, 0, buf.length)) != -1; ) {
|
||||
sb.append(buf, 0, num);
|
||||
}
|
||||
return sb.toString();
|
||||
} catch (IOException e) {
|
||||
LOG.error(e.toString(), e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public static String getIpAddress(HttpServletRequest request) {
|
||||
String ip = request.getHeader("X-requested-For");
|
||||
if (!StringUtils.hasText(ip) || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("X-Forwarded-For");
|
||||
}
|
||||
if (!StringUtils.hasText(ip) || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("Proxy-Client-IP");
|
||||
}
|
||||
if (!StringUtils.hasText(ip) || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("WL-Proxy-Client-IP");
|
||||
}
|
||||
if (!StringUtils.hasText(ip) || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("HTTP_CLIENT_IP");
|
||||
}
|
||||
if (!StringUtils.hasText(ip) || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
|
||||
}
|
||||
if (!StringUtils.hasText(ip) || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getRemoteAddr();
|
||||
}
|
||||
|
||||
if (ip != null && ip.contains(",")) {
|
||||
String[] ips = ip.split(",");
|
||||
for (String strIp : ips) {
|
||||
if (!("unknown".equalsIgnoreCase(strIp))) {
|
||||
ip = strIp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ip;
|
||||
}
|
||||
|
||||
public static String getUserAgent(HttpServletRequest request) {
|
||||
return request.getHeader("User-Agent");
|
||||
}
|
||||
|
||||
|
||||
public static String getReferer(HttpServletRequest request) {
|
||||
return request.getHeader("Referer");
|
||||
}
|
||||
|
||||
|
||||
public static Boolean getParamAsBoolean(Map<String, String[]> parameters, String key) {
|
||||
if (parameters == null || parameters.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
String[] strings = parameters.get(key);
|
||||
if (strings == null || strings.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return "true".equalsIgnoreCase(strings[0]);
|
||||
}
|
||||
|
||||
|
||||
public static String getParamAsString(String key) {
|
||||
return getParamAsString(getRequest().getParameterMap(), key);
|
||||
}
|
||||
|
||||
|
||||
public static String getParamAsString(Map<String, String[]> parameters, String key) {
|
||||
if (parameters == null || parameters.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
String[] strings = parameters.get(key);
|
||||
if (strings == null || strings.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String trimmed = strings[0].trim();
|
||||
if (trimmed.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
|
||||
public static BigInteger getParamAsBigInteger(Map<String, String[]> parameters, String key) {
|
||||
if (parameters == null || parameters.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
String[] strings = parameters.get(key);
|
||||
if (strings == null || strings.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new BigInteger(strings[0]);
|
||||
}
|
||||
|
||||
public static BigInteger getParamAsBigInteger(String key) {
|
||||
return getParamAsBigInteger(getRequest().getParameterMap(), key);
|
||||
}
|
||||
|
||||
|
||||
public static ServletRequestAttributes getRequestAttributes() {
|
||||
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
|
||||
return (ServletRequestAttributes) attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 HttpServletRequest
|
||||
*/
|
||||
public static HttpServletRequest getRequest() {
|
||||
return getRequestAttributes().getRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 HttpServletResponse
|
||||
*/
|
||||
public static HttpServletResponse getResponse() {
|
||||
return getRequestAttributes().getResponse();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package tech.easyflow.common.util;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class ResponseUtil {
|
||||
|
||||
public static void renderJson(HttpServletResponse response, Object object) {
|
||||
String json = JSON.toJSONString(object);
|
||||
renderJson(response, json);
|
||||
}
|
||||
|
||||
public static void renderJson(HttpServletResponse response, String jsonString) {
|
||||
response.setContentType("application/json; charset=utf-8");
|
||||
try {
|
||||
response.getWriter().write(jsonString);
|
||||
} catch (IOException e) {
|
||||
//ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package tech.easyflow.common.util;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class SSEUtil {
|
||||
|
||||
public static SseEmitter sseEmitterForContent(String content) {
|
||||
SseEmitter emitter = new SseEmitter((long) (1000 * 60 * 2));
|
||||
try {
|
||||
String jsonString = JSON.toJSONString(Maps.of("content", content));
|
||||
emitter.send(jsonString);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}finally {
|
||||
emitter.complete();
|
||||
}
|
||||
return emitter;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package tech.easyflow.common.util;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
public class SpringContextUtil implements BeanFactoryPostProcessor, ApplicationContextAware {
|
||||
|
||||
private static ConfigurableListableBeanFactory beanFactory;
|
||||
private static ApplicationContext applicationContext;
|
||||
|
||||
@Override
|
||||
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
|
||||
SpringContextUtil.beanFactory = beanFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
SpringContextUtil.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
public static ListableBeanFactory getBeanFactory() {
|
||||
return null == beanFactory ? applicationContext : beanFactory;
|
||||
}
|
||||
|
||||
public static ApplicationContext getApplicationContext() {
|
||||
return applicationContext;
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T getBean(String name) {
|
||||
return (T) getBeanFactory().getBean(name);
|
||||
}
|
||||
|
||||
public static <T> T getBean(Class<T> clazz) {
|
||||
return getBeanFactory().getBean(clazz);
|
||||
}
|
||||
|
||||
public static <T> T getBean(String name, Class<T> clazz) {
|
||||
return getBeanFactory().getBean(name, clazz);
|
||||
}
|
||||
|
||||
public static Map<String, Object> getBeansWithAnnotation(Class<? extends Annotation> annotationType) {
|
||||
return getBeanFactory().getBeansWithAnnotation(annotationType);
|
||||
}
|
||||
|
||||
public static Resource getResource(String location){
|
||||
return getApplicationContext().getResource(location);
|
||||
}
|
||||
|
||||
|
||||
public static String getProperty(String key) {
|
||||
if (null == applicationContext) {
|
||||
return null;
|
||||
}
|
||||
return applicationContext.getEnvironment().getProperty(key);
|
||||
}
|
||||
|
||||
public static String getProperty(String key, String defaultValue) {
|
||||
if (null == applicationContext) {
|
||||
return null;
|
||||
}
|
||||
return applicationContext.getEnvironment().getProperty(key, defaultValue);
|
||||
}
|
||||
|
||||
public static <T> T getProperty(String key, Class<T> targetType, T defaultValue) {
|
||||
if (null == applicationContext) {
|
||||
return null;
|
||||
}
|
||||
return applicationContext.getEnvironment().getProperty(key, targetType, defaultValue);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package tech.easyflow.common.util;
|
||||
|
||||
import com.mybatisflex.annotation.Column;
|
||||
import com.mybatisflex.core.constant.SqlOperator;
|
||||
import com.mybatisflex.core.query.SqlOperators;
|
||||
import com.mybatisflex.core.util.ClassUtil;
|
||||
import org.apache.ibatis.util.MapUtil;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class SqlOperatorsUtil {
|
||||
|
||||
private static Map<Class<?>, SqlOperators> sqlOperatorsMap = new ConcurrentHashMap<>();
|
||||
|
||||
public static SqlOperators build(Class<?> entityClass) {
|
||||
return new SqlOperators(MapUtil.computeIfAbsent(sqlOperatorsMap, entityClass, aClass -> {
|
||||
SqlOperators sqlOperators = new SqlOperators();
|
||||
List<Field> allFields = ClassUtil.getAllFields(entityClass);
|
||||
allFields.forEach(field -> {
|
||||
if (field.getType() == String.class) {
|
||||
Column column = field.getAnnotation(Column.class);
|
||||
if (column != null && column.ignore()) {
|
||||
return;
|
||||
}
|
||||
sqlOperators.set(field.getName(), SqlOperator.LIKE);
|
||||
}
|
||||
});
|
||||
return sqlOperators;
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package tech.easyflow.common.util;
|
||||
|
||||
public class SqlUtil {
|
||||
|
||||
public static String buildOrderBy(String sortKey, String sortType) {
|
||||
return buildOrderBy(sortKey, sortType, "");
|
||||
}
|
||||
|
||||
public static String buildOrderBy(String sortKey, String sortType, String defaultOrderBy) {
|
||||
if (StringUtil.noText(sortKey)) {
|
||||
return defaultOrderBy;
|
||||
}
|
||||
|
||||
sortKey = sortKey.trim();
|
||||
if (StringUtil.noText(sortType)) {
|
||||
return sortKey;
|
||||
}
|
||||
|
||||
sortType = sortType.toLowerCase().trim();
|
||||
if (!"asc".equals(sortType) && !"desc".equals(sortType)) {
|
||||
throw new IllegalArgumentException("sortType only support asc or desc");
|
||||
}
|
||||
|
||||
com.mybatisflex.core.util.SqlUtil.keepOrderBySqlSafely(sortKey);
|
||||
|
||||
return sortKey + " " + sortType;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package tech.easyflow.common.util;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class StringUtil extends StringUtils {
|
||||
|
||||
public static boolean areHasText(String... strings) {
|
||||
if (strings == null || strings.length == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (String string : strings) {
|
||||
if (!hasText(string)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean noText(String string) {
|
||||
return !hasText(string);
|
||||
}
|
||||
|
||||
public static boolean isEmail(String email) {
|
||||
return StringUtils.hasText(email)
|
||||
&& email.matches("^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$");
|
||||
}
|
||||
|
||||
public static boolean isMobileNumber(String str) {
|
||||
return hasText(str) && str.length() == 11 && str.startsWith("1") && isNumeric(str);
|
||||
}
|
||||
|
||||
public static boolean isNumeric(String str) {
|
||||
if (noText(str)) {
|
||||
return false;
|
||||
}
|
||||
for (int i = str.length(); --i >= 0; ) {
|
||||
int chr = str.charAt(i);
|
||||
if (chr < 48 || chr > 57) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static String getHasTextOne(String... strings) {
|
||||
for (String string : strings) {
|
||||
if (hasText(string)) {
|
||||
return string;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public static Set<String> splitToSet(String src, String regex) {
|
||||
if (src == null) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
String[] strings = src.split(regex);
|
||||
Set<String> set = new LinkedHashSet<>();
|
||||
for (String s : strings) {
|
||||
if (hasText(s)) {
|
||||
set.add(s.trim());
|
||||
}
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
public static Set<String> splitToSetByComma(String src) {
|
||||
return splitToSet(src, ",");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 删除文件名的后缀。
|
||||
*
|
||||
* @param fileName 完整的文件名,包括后缀
|
||||
* @return 不带后缀的文件名
|
||||
*/
|
||||
public static String removeFileExtension(String fileName) {
|
||||
if (fileName == null || fileName.isEmpty()) {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
int dotIndex = fileName.lastIndexOf('.');
|
||||
if (dotIndex == -1) {
|
||||
return fileName; // 没有后缀
|
||||
}
|
||||
|
||||
return fileName.substring(0, dotIndex);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package tech.easyflow.common.util;
|
||||
|
||||
import java.net.IDN;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class UrlEncoderUtil {
|
||||
|
||||
// 匹配URL的「协议+域名」部分(如 http://localhost:8080 或 https://www.baidu.商店)
|
||||
private static final Pattern URL_DOMAIN_PATTERN = Pattern.compile("^((http|https)://[^/]+)(/.*)?$");
|
||||
// 匹配连续的斜杠(用于清理多余/)
|
||||
private static final Pattern MULTIPLE_SLASH_PATTERN = Pattern.compile("/+");
|
||||
|
||||
/**
|
||||
* 完整URL编码:兼容IDN域名 + 路径/文件名URL编码 + 自动清理多余斜杠
|
||||
* @param url 完整URL(如:http://localhost:8080//attachment/host 副本.txt)
|
||||
* @return 编码后URL(如:http://localhost:8080/attachment/host%20%E5%89%AF%E6%9C%AC.txt)
|
||||
*/
|
||||
public static String getEncodedUrl(String url) {
|
||||
if (url == null || url.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// 第一步:先清理所有连续的斜杠(// → /),保留协议后的//(如 http://)
|
||||
String cleanUrl = cleanMultipleSlashes(url);
|
||||
|
||||
Matcher matcher = URL_DOMAIN_PATTERN.matcher(cleanUrl);
|
||||
String domainPart = ""; // 协议+域名部分(如 http://localhost:8080)
|
||||
String pathPart = ""; // 路径+文件名部分(如 /attachment/host 副本.txt)
|
||||
|
||||
// 1. 拆分URL为「域名部分」和「路径部分」
|
||||
if (matcher.matches()) {
|
||||
domainPart = matcher.group(1);
|
||||
pathPart = matcher.group(3) == null ? "" : matcher.group(3);
|
||||
} else {
|
||||
// 无路径的纯域名(如 http://www.baidu.商店)
|
||||
domainPart = cleanUrl;
|
||||
}
|
||||
|
||||
// 2. 处理域名部分:IDN域名转Punycode编码(如 商店 → xn--3ds443g)
|
||||
String encodedDomain = encodeDomain(domainPart);
|
||||
|
||||
// 3. 处理路径部分:URL编码(保留/,编码空格/中文)
|
||||
String encodedPath = encodePath(pathPart);
|
||||
|
||||
// 4. 拼接完整URL(再次清理可能的多余斜杠)
|
||||
String finalUrl = encodedDomain + encodedPath;
|
||||
return cleanMultipleSlashes(finalUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理URL中多余的连续斜杠(保留协议后的//,如 http://)
|
||||
*/
|
||||
private static String cleanMultipleSlashes(String url) {
|
||||
if (url.startsWith("http://")) {
|
||||
return "http://" + MULTIPLE_SLASH_PATTERN.matcher(url.substring(7)).replaceAll("/");
|
||||
} else if (url.startsWith("https://")) {
|
||||
return "https://" + MULTIPLE_SLASH_PATTERN.matcher(url.substring(8)).replaceAll("/");
|
||||
} else {
|
||||
// 非HTTP/HTTPS URL,直接替换所有连续斜杠
|
||||
return MULTIPLE_SLASH_PATTERN.matcher(url).replaceAll("/");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 编码域名:IDN域名转Punycode(处理中文/特殊字符域名)
|
||||
*/
|
||||
private static String encodeDomain(String domain) {
|
||||
if (domain.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
// 拆分协议和域名(如 http:// + www.baidu.商店)
|
||||
String protocol = "";
|
||||
String pureDomain = domain;
|
||||
if (domain.startsWith("http://")) {
|
||||
protocol = "http://";
|
||||
pureDomain = domain.substring(7);
|
||||
} else if (domain.startsWith("https://")) {
|
||||
protocol = "https://";
|
||||
pureDomain = domain.substring(8);
|
||||
}
|
||||
|
||||
// IDN域名转Punycode(核心:处理中文后缀如「商店」)
|
||||
String punycodeDomain = IDN.toASCII(pureDomain);
|
||||
return protocol + punycodeDomain;
|
||||
}
|
||||
|
||||
/**
|
||||
* 编码路径:仅编码路径/文件名中的特殊字符,保留/
|
||||
*/
|
||||
private static String encodePath(String path) {
|
||||
if (path.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
// 按/拆分路径段,逐个编码后拼接(避免/被编码)
|
||||
String[] pathSegments = path.split("/");
|
||||
StringBuilder encodedPath = new StringBuilder();
|
||||
for (String segment : pathSegments) {
|
||||
if (!segment.isEmpty()) {
|
||||
String encodedSegment = URLEncoder.encode(segment, StandardCharsets.UTF_8)
|
||||
.replace("+", "%20") // 空格转%20
|
||||
.replace("%2F", "/"); // 保留段内的/(如有)
|
||||
encodedPath.append("/").append(encodedSegment);
|
||||
} else {
|
||||
encodedPath.append("/"); // 保留空段(如开头的/)
|
||||
}
|
||||
}
|
||||
// 处理末尾的/(避免多拼接)
|
||||
String result = encodedPath.length() > 0 ? encodedPath.toString() : path;
|
||||
// 清理路径中的多余斜杠
|
||||
return MULTIPLE_SLASH_PATTERN.matcher(result).replaceAll("/");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user