java常用工具类

在Java和Spring Boot项目开发中,常用的公用工具类有以下几种类型:

1. 字符串处理工具类:包含常见的字符串操作,如字符串拼接、分割、替换、转换大小写等。

2. 时间日期工具类:提供日期的格式化、解析、计算、比较等功能,方便处理日期时间相关操作。

3. 文件操作工具类:提供文件的创建、复制、删除、移动、重命名等操作,以及读取、写入文件内容的方法。

4. 数据校验工具类:包含常见的数据校验方法,如判断是否为空、是否为数字、是否为邮箱等。

5. 加密解密工具类:提供加密算法和解密算法的封装,如MD5、SHA1、AES等常用加密算法。

6. 随机数工具类:生成随机数、随机字符串等。

7. 编码转换工具类:提供字符编码转换的方法,如字符串与字节数组、字符串与16进制字符串的相互转换。

8. 网络请求工具类:封装HTTP请求,包括GET请求、POST请求、文件下载等。

9. 日志工具类:用于打印日志信息,方便程序调试和排查问题。

10. JSON处理工具类:用于JSON数据的解析、转换、验证等操作。

11. HTTP请求工具类:用于发送HTTP请求,如GET、POST、PUT、DELETE等。

12. 数据转换工具类:用于不同数据类型之间的转换,如字符串转整数、整数转字符串等。

13. 集合工具类

14. 枚举类

15. 统一异常处理

16.springBoot静态方法中从yml配置文件中获取参数

以上是常见的公用工具类类型,根据项目的需要,可以自定义和扩展相应的工具类。

字符串处理工具类

import java.util.Arrays;

public class StringUtils {

    /**
     * 将字符串反转
     * @param str 要反转的字符串
     * @return 反转后的字符串
     */
    public static String reverseString(String str) {
        return new StringBuilder(str).reverse().toString();
    }

    /**
     * 判断字符串是否为空
     * @param str 要判断的字符串
     * @return 如果字符串为空则返回true,否则返回false
     */
    public static boolean isEmpty(String str) {
        return str == null || str.isEmpty();
    }

    /**
     * 去除字符串两端的空格
     * @param str 要处理的字符串
     * @return 去除两端空格后的字符串
     */
    public static String trim(String str) {
        return str.trim();
    }

    /**
     * 将字符串转换为大写
     * @param str 要转换的字符串
     * @return 转换为大写后的字符串
     */
    public static String toUpperCase(String str) {
        return str.toUpperCase();
    }

    /**
     * 将字符串转换为小写
     * @param str 要转换的字符串
     * @return 转换为小写后的字符串
     */
    public static String toLowerCase(String str) {
        return str.toLowerCase();
    }

    /**
     * 判断字符串是否以指定的前缀开始
     * @param str 要判断的字符串
     * @param prefix 前缀
     * @return 如果字符串以指定的前缀开始则返回true,否则返回false
     */
    public static boolean startsWith(String str, String prefix) {
        return str.startsWith(prefix);
    }

    /**
     * 判断字符串是否以指定的后缀结束
     * @param str 要判断的字符串
     * @param suffix 后缀
     * @return 如果字符串以指定的后缀结束则返回true,否则返回false
     */
    public static boolean endsWith(String str, String suffix) {
        return str.endsWith(suffix);
    }

    /**
     * 判断字符串是否包含指定子字符串
     * @param str 要判断的字符串
     * @param subStr 子字符串
     * @return 如果字符串包含指定子字符串则返回true,否则返回false
     */
    public static boolean contains(String str, String subStr) {
        return str.contains(subStr);
    }

    /**
     * 将字符串按照指定分隔符拆分为数组
     * @param str 要拆分的字符串
     * @param delimiter 分隔符
     * @return 拆分后的字符串数组
     */
    public static String[] split(String str, String delimiter) {
        return str.split(delimiter);
    }

    /**
     * 将字符串按照指定分隔符拆分为列表
     * @param str 要拆分的字符串
     * @param delimiter 分隔符
     * @return 拆分后的字符串列表
     */
    public static List<String> splitToList(String str, String delimiter) {
        return Arrays.asList(str.split(delimiter));
    }

    /**
     * 替换字符串中的指定子字符串
     * @param str 要替换的字符串
     * @param target 要被替换的子字符串
     * @param replacement 替换字符串
     * @return 替换后的字符串
     */
    public static String replace(String str, String target, String replacement) {
        return str.replace(target, replacement);
    }

    /**
     * 将字符串转换为首字母大写
     * @param str 要转换的字符串
     * @return 首字母大写的字符串
     */
    public static String capitalize(String str) {
        if (isEmpty(str)) {
            return str;
        }
        return Character.toUpperCase(str.charAt(0)) + str.substring(1);
    }

    /**
     * 将字符串转换为首字母小写
     * @param str 要转换的字符串
     * @return 首字母小写的字符串
     */
    public static String uncapitalize(String str) {
        if (isEmpty(str)) {
            return str;
        }
        return Character.toLowerCase(str.charAt(0)) + str.substring(1);
    }

    /**
     * 将字符串转换为驼峰命名法(首字母小写)
     * 例如: "hello_world" -> "helloWorld"
     * @param str 要转换的字符串
     * @return 转换后的字符串
     */
    public static String toCamelCase(String str) {
        if (isEmpty(str)) {
            return str;
        }
        StringBuilder sb = new StringBuilder();
        String[] words = str.split("_");
        sb.append(words[0]);
        for (int i = 1; i < words.length; i++) {
            sb.append(capitalize(words[i]));
        }
        return sb.toString();
    }

    /**
     * 将字符串转换为帕斯卡命名法(首字母大写)
     * 例如: "hello_world" -> "HelloWorld"
     * @param str 要转换的字符串
     * @return 转换后的字符串
     */
    public static String toPascalCase(String str) {
        return capitalize(toCamelCase(str));
    }

    /**
     * 将字符串转换为下划线命名法(小写字母,单词间用下划线分隔)
     * 例如: "HelloWorld" -> "hello_world"
     * @param str 要转换的字符串
     * @return 转换后的字符串
     */
    public static String toSnakeCase(String str) {
        if (isEmpty(str)) {
            return str;
        }
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);
            if (Character.isUpperCase(c)) {
                if (i > 0) {
                    sb.append('_');
                }
                sb.append(Character.toLowerCase(c));
            } else {
                sb.append(c);
            }
        }
        return sb.toString();
    }

    /**
     * 获取重复指定次数的字符串
     * @param str 要重复的字符串
     * @param repeatCount 重复次数
     * @return 重复指定次数后的字符串
     */
    public static String repeat(String str, int repeatCount) {
        if (repeatCount <= 0) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < repeatCount; i++) {
            sb.append(str);
        }
        return sb.toString();
    }

    /**
     * 获取字符串的长度(考虑Unicode扩展字符)
     * @param str 要获取长度的字符串
     * @return 字符串的长度
     */
    public static int length(String str) {
        if (isEmpty(str)) {
            return 0;
        }
        return str.codePointCount(0, str.length());
    }

    /**
     * 判断两个字符串是否相等(忽略大小写)
     * @param str1 字符串1
     * @param str2 字符串2
     * @return 如果两个字符串相等则返回true,否则返回false
     */
    public static boolean equalsIgnoreCase(String str1, String str2) {
        return str1.equalsIgnoreCase(str2);
    }

    /**
     * 判断字符串是否为数字
     * @param str 要判断的字符串
     * @return 如果字符串为数字则返回true,否则返回false
     */
    public static boolean isNumeric(String str) {
        if (isEmpty(str)) {
            return false;
        }
        for (char c : str.toCharArray()) {
            if (!Character.isDigit(c)) {
                return false;
            }
        }
        return true;
    }
}
 

时间日期工具类

import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;

public class DateTimeUtils {

    private static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
    private static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";
    private static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
    static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    // 获取当前日期
    public static LocalDate getCurrentDate() {
        return LocalDate.now();
    }

    // 获取当前时间
    public static LocalTime getCurrentTime() {
        return LocalTime.now();
    }

    // 获取当前日期时间
    public static LocalDateTime getCurrentDateTime() {
        return LocalDateTime.now();
    }

    // 获取当前时间戳
    public static long getCurrentTimestamp() {
        return Instant.now().toEpochMilli();
    }

    /**
     *  生成当前时间字符串,默认格式yyyy-MM-dd HH:mm:ss
     * @return java.lang.String
     * @author yinqi
     * @create 2023/11/16 17:19
     **/
    public static String currentDateStr() {
        LocalDateTime now = LocalDateTime.now();
        return now.format(formatter);
    }

    /**
     * 指定日期格式生成当前时间字符串
     * @param formatter
     * @return java.lang.String
     * @author yinqi
     * @create 2023/11/16 17:20
     **/
    public static String currentDateStr(String formatter) {
        LocalDateTime now = LocalDateTime.now();
        DateTimeFormatter pattern = DateTimeFormatter.ofPattern(formatter);
        return now.format(pattern);
    }

    /**
     **时间转字符串,默认格式化:yyyy-MM-dd HH:mm:ss
     * @param dateTime
     * @return java.lang.String
     * @author yinqi
     * @create 2023/11/30 18:12
     **/
    public static String dateToStr(LocalDateTime dateTime) {
        return dateTime.format(formatter);
    }

    /**
     **时间转字符串
     * @param dateTime
     * @param formatter 格式化
     * @return java.lang.String
     * @author yinqi
     * @create 2023/11/30 18:12
     **/
    public static String dateToStr(LocalDateTime dateTime, DateTimeFormatter formatter) {
        return dateTime.format(formatter);
    }

    /**
     **判断bigTime是否大于smallTime
     * @param smallTime
     * @param bigTime
     * @return boolean
     * @author yinqi
     * @create 2023/12/15 18:55
     **/
    public static boolean lessThanTime(String smallTime, String bigTime) {
        LocalDateTime smallDateTime = LocalDateTime.parse(smallTime, formatter);
        LocalDateTime bigDateTime = LocalDateTime.parse(bigTime, formatter);
        return smallDateTime.isBefore(bigDateTime);
    }

    /**
     **获取num天前的日期
     * @param num
     * @return java.lang.String
     * @author yinqi
     * @create 2023/12/20 9:13
     **/
    public static String getPastDate(int num) {
        LocalDate currentDate = LocalDate.now();
        LocalDate pastDate = currentDate.minusDays(num);
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        return pastDate.format(formatter);
    }

    /**
     **获取近n天的日期字符串集合
     * @param n
     * @return java.util.List<java.lang.String>
     * @author yinqi
     * @create 2024/1/22 9:58
     **/
    public static List<String> getRecentNumDays(int n) {
        List<String> dates = new ArrayList<>();
        LocalDate today = LocalDate.now();
        for (int i = 0; i < n; i++) {
            LocalDate date = today.minusDays(i);
            String dateStr = date.format(DateTimeFormatter.ofPattern("MM-dd"));
            dates.add(dateStr);
        }
        return dates;
    }

    // 将日期字符串解析成LocalDate对象
    public static LocalDate parseDate(String date) {
        return LocalDate.parse(date);
    }

    // 将时间字符串解析成LocalTime对象
    public static LocalTime parseTime(String time) {
        return LocalTime.parse(time);
    }

    // 将日期时间字符串解析成LocalDateTime对象
    public static LocalDateTime parseDateTime(String dateTime) {
        return LocalDateTime.parse(dateTime);
    }

    // 将日期时间字符串按照指定格式解析成LocalDateTime对象
    public static LocalDateTime parseDateTime(String dateTime, String format) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
        return LocalDateTime.parse(dateTime, formatter);
    }

    // 将LocalDate对象格式化成日期字符串
    public static String formatDate(LocalDate date) {
        return date.format(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT));
    }

    // 将LocalTime对象格式化成时间字符串
    public static String formatTime(LocalTime time) {
        return time.format(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT));
    }

    // 将LocalDateTime对象格式化成日期时间字符串
    public static String formatDateTime(LocalDateTime dateTime) {
        return dateTime.format(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT));
    }

    // 将LocalDateTime对象按照指定格式格式化成日期时间字符串
    public static String formatDateTime(LocalDateTime dateTime, String format) {
        return dateTime.format(DateTimeFormatter.ofPattern(format));
    }

    // 获取指定日期的星期几(1-7,分别代表周一至周日)
    public static int getDayOfWeek(LocalDate date) {
        return date.getDayOfWeek().getValue();
    }

    // 获取指定日期的年份
    public static int getYear(LocalDate date) {
        return date.getYear();
    }

    // 获取指定日期的月份
    public static int getMonth(LocalDate date) {
        return date.getMonthValue();
    }

    // 获取指定日期的天数
    public static int getDayOfMonth(LocalDate date) {
        return date.getDayOfMonth();
    }

    // 获取指定日期是当年的第几天
    public static int getDayOfYear(LocalDate date) {
        return date.getDayOfYear();
    }

    // 获取指定日期是否为闰年
    public static boolean isLeapYear(LocalDate date) {
        return date.isLeapYear();
    }

    // 获取指定日期之前或之后的几天
    public static LocalDate plusDays(LocalDate date, long days) {
        return date.plusDays(days);
    }

    // 获取指定日期之前或之后的几周
    public static LocalDate plusWeeks(LocalDate date, long weeks) {
        return date.plusWeeks(weeks);
    }

    // 获取指定日期之前或之后的几个月
    public static LocalDate plusMonths(LocalDate date, long months) {
        return date.plusMonths(months);
    }

    // 获取指定日期之前或之后的几年
    public static LocalDate plusYears(LocalDate date, long years) {
        return date.plusYears(years);
    }

    // 获取指定日期之前或之后的几小时
    public static LocalDateTime plusHours(LocalDateTime dateTime, long hours) {
        return dateTime.plusHours(hours);
    }

    // 获取指定日期之前或之后的几分钟
    public static LocalDateTime plusMinutes(LocalDateTime dateTime, long minutes) {
        return dateTime.plusMinutes(minutes);
    }

    // 获取指定日期之前或之后的几秒钟
    public static LocalDateTime plusSeconds(LocalDateTime dateTime, long seconds) {
        return dateTime.plusSeconds(seconds);
    }

    // 获取指定日期之前或之后的几毫秒
    public static LocalDateTime plusMilliseconds(LocalDateTime dateTime, long milliseconds) {
        return dateTime.plus(milliseconds, ChronoUnit.MILLIS);
    }

    // 获取指定日期之前或之后的几纳秒
    public static LocalDateTime plusNanoseconds(LocalDateTime dateTime, long nanoseconds) {
        return dateTime.plus(nanoseconds, ChronoUnit.NANOS);
    }

    // 比较两个日期的先后顺序(返回值小于0表示date1在date2之前,等于0表示两个日期相等,大于0表示date1在date2之后)
    public static int compareDates(LocalDate date1, LocalDate date2) {
        return date1.compareTo(date2);
    }

    // 判断两个日期是否相等
    public static boolean areDatesEqual(LocalDate date1, LocalDate date2) {
        return date1.isEqual(date2);
    }

    // 计算两个日期之间的天数差
    public static long getDaysBetween(LocalDate date1, LocalDate date2) {
        return ChronoUnit.DAYS.between(date1, date2);
    }

    // 计算两个日期之间的周数差
    public static long getWeeksBetween(LocalDate date1, LocalDate date2) {
        return ChronoUnit.WEEKS.between(date1, date2);
    }

    // 计算两个日期之间的月数差
    public static long getMonthsBetween(LocalDate date1, LocalDate date2) {
        return ChronoUnit.MONTHS.between(date1, date2);
    }

    // 计算两个日期之间的年数差
    public static long getYearsBetween(LocalDate date1, LocalDate date2) {
        return ChronoUnit.YEARS.between(date1, date2);
    }

    // 判断指定日期是否在当前日期之前
    public static boolean isBeforeCurrentDate(LocalDate date) {
        return date.isBefore(getCurrentDate());
    }

    // 判断指定日期是否在当前日期之后
    public static boolean isAfterCurrentDate(LocalDate date) {
        return date.isAfter(getCurrentDate());
    }

    // 判断指定时间是否在当前时间之前
    public static boolean isBeforeCurrentTime(LocalTime time) {
        return time.isBefore(getCurrentTime());
    }

    // 判断指定时间是否在当前时间之后
    public static boolean isAfterCurrentTime(LocalTime time) {
        return time.isAfter(getCurrentTime());
    }

    // 判断指定日期时间是否在当前日期时间之前
    public static boolean isBeforeCurrentDateTime(LocalDateTime dateTime) {
        return dateTime.isBefore(getCurrentDateTime());
    }

    // 判断指定日期时间是否在当前日期时间之后
    public static boolean isAfterCurrentDateTime(LocalDateTime dateTime) {
        return dateTime.isAfter(getCurrentDateTime());
    }

    // 判断两个日期时间是否相等
    public static boolean areDateTimesEqual(LocalDateTime dateTime1, LocalDateTime dateTime2) {
        return dateTime1.isEqual(dateTime2);
    }

    // 计算两个日期时间之间的小时数差
    public static long getHoursBetween(LocalDateTime dateTime1, LocalDateTime dateTime2) {
        return ChronoUnit.HOURS.between(dateTime1, dateTime2);
    }

    // 计算两个日期时间之间的分钟数差
    public static long getMinutesBetween(LocalDateTime dateTime1, LocalDateTime dateTime2) {
        return ChronoUnit.MINUTES.between(dateTime1, dateTime2);
    }

    // 计算两个日期时间之间的秒数差
    public static long getSecondsBetween(LocalDateTime dateTime1, LocalDateTime dateTime2) {
        return ChronoUnit.SECONDS.between(dateTime1, dateTime2);
    }

    // 计算两个日期时间之间的毫秒数差
    public static long getMillisecondsBetween(LocalDateTime dateTime1, LocalDateTime dateTime2) {
        return ChronoUnit.MILLIS.between(dateTime1, dateTime2);
    }

    // 计算两个日期时间之间的纳秒数差
    public static long getNanosecondsBetween(LocalDateTime dateTime1, LocalDateTime dateTime2) {
        return ChronoUnit.NANOS.between(dateTime1, dateTime2);
    }

}
 

文件操作工具类

	/**
     * *根据文件路径获取文件列表
     * @param directoryPath 文件路径
     * @return java.util.List<com.daasan.modules.spider.core.model.SpiderFile>
     * @create 2024/10/30 11:18
     **/
    @Override
    public List<SpiderFile> readFilesFromDirectory(String directoryPath, String keyWord) {
        directoryPath = FileUtils.formatPath(directoryPath);
        List<SpiderFile> fileInfoList = new ArrayList<>();
        File directory = new File(directoryPath);

        // 校验directory是否为存在的目录;及目录中是否存在文件
        if (!directory.exists() || !directory.isDirectory()) {
            return fileInfoList;
        }
        File[] files = directory.listFiles();
        if (files == null) {
            return fileInfoList;
        }

        for (File file : files) {
            if(file.isHidden()){
                continue;// 不显示隐藏的文件
            }
            SpiderFile fileInfo = new SpiderFile();
            fileInfo.setFileName(file.getName());
            fileInfo.setFilePath(FileUtils.formatPath(file.getAbsolutePath()));
            long length = file.length();
            if(length != 0){
                length = length/1024 + 1;
            }
            fileInfo.setFileSize(length + " KB");
            fileInfo.setLastModifiedTime(new Date(file.lastModified()));

            // 获取文件类型
            String fileName = file.getName();
            if (fileName.contains(".")) {
                fileInfo.setFileType(fileName.substring(fileName.lastIndexOf(".") + 1));
            } else {
                if(file.isDirectory()){
                    fileInfo.setFileType("文件夹");
                    fileInfo.setFileSize("");// 文件夹不显示文件大小
                }else {
                    fileInfo.setFileType("文件");
                }
            }

            // 关键词不为空时只返回文件名称能模糊匹配到的文件,关键词为空时返回所有
            if(StrUtil.isNotEmpty(keyWord)){
                if(fileName.toLowerCase().contains(keyWord.toLowerCase())){
                    fileInfoList.add(fileInfo);
                }
            }else{
                fileInfoList.add(fileInfo);
            }
        }

        return fileInfoList;
    }
	
	/**
     **下载爬虫文件
     * @param filePath
     * @return org.springframework.http.ResponseEntity<org.springframework.core.io.Resource>
     * @create 2024/10/31 9:27
     **/
    @Override
    public ResponseEntity<Resource> downloadFile(String filePath) {
        try {
            // 判断文件路径是否存在
            File fileOrDir = new File(filePath);
            if (!fileOrDir.exists()) {
                return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
            }

            File zipFile;
            if (fileOrDir.isDirectory()) {
                // 如果是文件夹,则创建一个临时ZIP文件
                zipFile = createZipFromDirectory(fileOrDir);
            } else {
                // 如果是文件,则直接使用该文件
                zipFile = fileOrDir;
            }

            // 读取ZIP文件内容到字节数组
            byte[] fileContent = Files.readAllBytes(zipFile.toPath());

            // 设置响应头
            String fileName = zipFile.getName();
            HttpHeaders headers = new HttpHeaders();
            headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileName + "\"");
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);

            // 创建字节数组资源并返回
            ByteArrayResource resource = new ByteArrayResource(fileContent);
            return ResponseEntity.ok().headers(headers).body(resource);
        } catch (Exception e) {
            e.printStackTrace();
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
        }
    }
	
	/**
     **将目录打包成ZIP文件
     * @param directory
     * @return java.io.File
     * @create 2024/10/31 9:20
     **/
    public File createZipFromDirectory(File directory) throws IOException {
        File zipFile = File.createTempFile("download", ".zip");
        zipFile.deleteOnExit(); // JVM退出时删除临时文件

        try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) {
            Files.walk(directory.toPath())
                    .filter(path -> !Files.isDirectory(path))
                    .forEach(path -> {
                        ZipEntry zipEntry = new ZipEntry(directory.getName() + "/" + directory.toPath().relativize(path));
                        try {
                            zos.putNextEntry(zipEntry);
                            Files.copy(path, zos);
                            zos.closeEntry();
                        } catch (IOException e) {
                            throw new RuntimeException(e);
                        }
                    });
        }
        return zipFile;
    }
	
	@Data
	public class SpiderFile {
		private String fileName;
		private String filePath;
		private String fileSize;
		private Date lastModifiedTime;
		private String fileType;
	}
import cn.hutool.core.collection.CollectionUtil;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @author yinqi
 * @description
 * @create 2023/11/21 17:29
 **/
public class FileUtil {
    
    /***
     * @Description:将文件路径统一转换成”C:/Users/Daasan/Desktop“格式
     * @data:[filePath]
     * @return: java.lang.String
     * @Author:yinqi
     * @Date:2023/11/13 17:55
     */
    public static String formatPath(String filePath) {
        if (StringUtils.isEmpty(filePath)){
            return "";
        }
        // 将反斜杠转为正斜杠
        filePath = filePath.replaceAll("\\\\", "/");
        // 将连续的斜杠替换为单个斜杠
        filePath = filePath.replaceAll("/{2,}", "/");
        // 如果路径以斜杠结尾,则去掉结尾的斜杠
        if (filePath.endsWith("/")) {
            filePath = filePath.substring(0, filePath.length() - 1);
        }
        return filePath;
    }

    /**
     **获取文件路径中的最后一级(目录或文件名称)
     * \aa\bb\cc\dd  -->  dd
     * \aa\bb\cc\dd.txt  -->  dd.txt
     * @param filePath
     * @return java.lang.String
     * @author yinqi
     * @create 2023/11/25 10:37
     **/
    public static String getPathLastLevel(String filePath) {
        //判空
        if (StringUtils.isEmpty(filePath)){
            return "";
        }
        // 格式化文件路径
        filePath = formatPath(filePath);

        int index = filePath.lastIndexOf("/");
        if (index != -1) {
            return filePath.substring(index + 1);
        }
        return filePath;
    }

    /**
     **获取文件的后缀:  test.ppt  -->  ppt
     * @param fileName 文件名称
     * @return java.lang.String
     * @author yinqi
     * @create 2023/11/23 11:20
     **/
    public static String getFileSuffix(String fileName) {
        if (fileName == null) {
            return null;
        }
        int index = fileName.lastIndexOf(".");
        if (index == -1) {
            return "";
        }
        return fileName.substring(index + 1);//不包含”.“
    }

    /**
     **获取文件的名称(去掉后缀)
     * @param fileName
     * @return java.lang.String
     * @author yinqi
     * @create 2023/11/23 11:21
     **/
    public static String getFileNameWithoutSuffix(String fileName) {
        if (fileName == null) {
            return null;
        }
        int index = fileName.lastIndexOf(".");
        if (index == -1) {
            return fileName;
        }
        return fileName.substring(0, index);
    }

    /**
     **创建空文件
     * @param file 需创建的文件
     * @return void
     * @author yinqi
     * @create 2023/11/23 11:25
     **/
    public static void createEmptyFile(File file) throws IOException {
        if (file.exists()) {
            return;
        }
        File parent = file.getParentFile();
        if (parent != null && !parent.exists()) {
            parent.mkdirs();
        }
        file.createNewFile();
    }

    /**
     **批量移动磁盘中的文件
     * @param rootPath 根目录
     * @param absolutePathList 存放源文件和目标文件的绝对路径
     * @return void
     * @author yinqi
     * @create 2023/11/25 11:29
     **/
    public static void moveFiles(String rootPath, List<Map<String, String>> absolutePathList) throws IOException {
        for (Map<String, String> pathMap : absolutePathList) {
            String oldFilePath = pathMap.get("oldFilePath");
            String newFilePath = pathMap.get("newFilePath");
            Path sourcePath = Paths.get(formatPath(rootPath + oldFilePath));
            Path targetPath = Paths.get(formatPath(rootPath + newFilePath));
            Files.move(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING);
        }
    }

    /**
     **从网络路径中获取文件的字节数组
     * @param urlStr 文件的网络路径
     * @return byte[] 文件的字节数组
     * @author yinqi
     * @create 2023/11/27 17:17
     **/
    public static byte[] getByteFromUrl(String urlStr) throws IOException {
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            HttpGet httpGet = new HttpGet(urlStr);
            try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
                return EntityUtils.toByteArray(response.getEntity());
            }
        }
    }

    /**
     **递归获取文件夹下的所有文件和文件夹
     * @param folder
     * @return java.util.List<java.io.File>
     * @author yinqi
     * @create 2023/12/6 16:17
     **/
    public static List<File> getFilesRecursively(File folder) {
        List<File> fileList = new ArrayList<>();
        File[] files = folder.listFiles();
        if (files != null) {
            for (File file : files) {
                fileList.add(file);
                if (file.isDirectory()) {
                    fileList.addAll(getFilesRecursively(file));
                }
            }
        }
        return fileList;
    }

    /**
     **去掉路径中的特殊字符
     * @param path
     * @return java.nio.file.Path
     * @author yinqi
     * @create 2024/1/19 10:22
     **/
    public static Path sanitizePath(Path path) {

        String fileName = path.getFileName().toString();
        String parentPathString = path.getParent().toString();

        // 定义特殊字符的正则表达式
        Pattern pattern = Pattern.compile("[\\\\/:*?\"<>|&'+-=]");

        // 使用空字符串替换特殊字符
        String sanitizedFileName = pattern.matcher(fileName).replaceAll("");

        Path path1 = Paths.get(parentPathString, sanitizedFileName);

        // 返回路径的父路径与处理后的文件名拼接成的完整路径
        return path1;
    }

    /**
     **判断文件名称中是否包含特殊字符
     * @param fileName
     * @return boolean
     * @author yinqi
     * @create 2024/1/19 11:21
     **/
    public static boolean hasSpecialCharacters(String fileName) {
        String SPECIAL_CHARACTERS_REGEX = "[!@#$%^&*()_+\\-=\\[\\]{};':\"\\\\|,<>\\/?]";
        Pattern pattern = Pattern.compile(SPECIAL_CHARACTERS_REGEX);
        return pattern.matcher(fileName).find();
    }

    /**
     **去掉文件名称中包含的特殊字符
     * @param fileName
     * @return java.lang.String
     * @author yinqi
     * @create 2024/1/22 11:11
     **/
    public static String removeSpecialCharacters(String fileName) {
        String SPECIAL_CHARACTERS_REGEX = "[!@#$%^&*()_+\\-=\\[\\]{};':\"\\\\|,<>\\/?]";
        return fileName.replaceAll(SPECIAL_CHARACTERS_REGEX, "");
    }

    /**
     * 获取文件的绝对路径
     * 
     * @param filePath 文件路径
     * @return 文件的绝对路径
     */
    public static String getAbsolutePath(String filePath) {
        File file = new File(filePath);
        return file.getAbsolutePath();
    }


    /**
     * 复制文件
     * 
     * @param sourceFilePath      源文件路径
     * @param destinationFilePath 目标文件路径
     * @throws IOException 如果复制过程中出现错误,抛出 IOException
     */
    public static void copyFile(String sourceFilePath, String destinationFilePath) throws IOException {
        File sourceFile = new File(sourceFilePath);
        File destinationFile = new File(destinationFilePath);
        
        if (!sourceFile.exists()) {
            throw new FileNotFoundException("Source file does not exist: " + sourceFilePath);
        }
        
        try (InputStream inputStream = new FileInputStream(sourceFile);
             OutputStream outputStream = new FileOutputStream(destinationFile)) {
            byte[] buffer = new byte[4096];
            int length;
            while ((length = inputStream.read(buffer)) > 0) {
                outputStream.write(buffer, 0, length);
            }
        }
    }

    /**
     **修改文件的名称
     * @param filePath
     * @param newFileName
     * @return void
     * @author yinqi
     * @create 2024/3/25 16:16
     **/
    public static void renameFile(String filePath, String newFileName) {
        File file = new File(filePath);
        if (file.exists()) {
            String parentPath = file.getParent();
            String newFilePath = parentPath + File.separator + newFileName;
            File newFile = new File(newFilePath);
            if (file.renameTo(newFile)) {
                System.out.println("文件重命名成功!");
            } else {
                System.out.println("文件重命名失败!");
            }
        } else {
            System.out.println("文件不存在!");
        }
    }

    /**
     **递归获取文件夹下所有文件的路径后,将路径放到filePaths中
     * @param folderPath 文件夹路径
     * @param filePaths 存放文件路径的集合
     * @return void
     * @author yinqi
     * @create 2024/3/25 15:09
     **/
    public static void getAllFilePaths(String folderPath, List<String> filePaths) {
        File folder = new File(folderPath);
        // 获取文件夹下所有文件和子文件夹
        File[] files = folder.listFiles();

        if (files != null) {
            for (File file : files) {
                if (file.isFile()) {
                    // 如果是文件,将文件路径添加到列表中
                    filePaths.add(file.getAbsolutePath());
                } else if (file.isDirectory()) {
                    // 如果是文件夹,则递归调用该方法获取文件夹下所有文件路径
                    getAllFilePaths(file.getPath(), filePaths);
                }
            }
        }
    }

    /**
     **将源文件夹下的所有文件复制到目标文件夹下
     * @param sourceFolderPath
     * @param targetFolderPath
     * @return void
     * @author yinqi
     * @create 2024/3/25 11:41
     **/
    public static void copyFolder(String sourceFolderPath, String targetFolderPath) throws IOException {
        File sourceFolder = new File(sourceFolderPath);
        File targetFolder = new File(targetFolderPath);

        // 检查源文件夹是否存在
        if (!sourceFolder.exists()) {
            throw new IllegalArgumentException("源文件夹不存在");
        }

        // 检查目标文件夹是否存在,如果不存在则创建
        if (!targetFolder.exists()) {
            targetFolder.mkdirs();
        }

        // 获取源文件夹下的所有文件和文件夹
        File[] files = sourceFolder.listFiles();

        if (files != null) {
            for (File file : files) {
                if (file.isDirectory()) {
                    // 如果是文件夹,则递归调用copyFolder方法复制文件夹
                    copyFolder(file.getAbsolutePath(), targetFolderPath + File.separator + file.getName());
                } else {
                    // 如果是文件,则使用Files.copy方法复制文件
                    Path sourcePath = file.toPath();
                    Path targetPath = new File(targetFolderPath + File.separator + file.getName()).toPath();

                    Files.copy(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING);
                }
            }
        }
    }



}
通用文件强制下载

后端:

    @ApiOperation(
        value = "通用文件下载",
        notes = "通用文件下载"
    )
    @GetMapping({"/view/{id}"})
    public void viewImage(HttpServletResponse response, @PathVariable String id) {
        if (Str.isNotEmpty(id)) {
            SysFile sysFile = (SysFile)this.sysFileService.getById(id);
            if (sysFile != null && Str.isNotEmpty(sysFile.getPath())) {
                String filePath = this.uploadPath + sysFile.getPath();
                this.outputImage(response, filePath, sysFile.getOriginalName());
            }
        }

    }

    private void outputImage(HttpServletResponse response, String filePath, String fileName) {
        InputStream inputStream = null;
        ServletOutputStream outputStream = null;

        try {
            File file = new File(filePath);
            response.setContentType("application/force-download");
            response.addHeader("Content-Disposition", "attachment;fileName=" + new String((Str.isNotEmpty(fileName) ? fileName : file.getName()).getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1));
            inputStream = new BufferedInputStream(new FileInputStream(filePath));
            outputStream = response.getOutputStream();
            byte[] buf = new byte[1024];

            int len;
            while((len = inputStream.read(buf)) > 0) {
                outputStream.write(buf, 0, len);
            }

            response.flushBuffer();
        } catch (IOException var21) {
            log.error("预览文件失败" + var21.getMessage());
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException var20) {
                    log.error(var20.getMessage(), var20);
                }
            }

            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException var19) {
                    log.error(var19.getMessage(), var19);
                }
            }

        }

    }

前端:

/**
 * 下载文件
 * @param {*} url 
 */
export function XHRLoadFileTwo(url) {
  const token = getToken();
  let xhr = new XMLHttpRequest()
  if (url.charAt(0) !== "/") {
    url = "/" + url;
  }

  xhr.open('get', process.env.BASE_API + url)
  //如果需要请求头中这是token信息可以在这设置
  xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8')
  xhr.setRequestHeader('X-Access-Token', token)
  xhr.responseType = 'blob'
  xhr.send();
  // 此处引入了mock需要这样写,mock改写了XMLHttpRequest
  if (window._XMLHttpRequest) {
    XHRLoadFileTwo_1(xhr)
  } else {
    xhr.onreadystatechange = function () {
      XHRLoadFileTwo_1(xhr)
    }
  }
}

export function XHRLoadFileTwo_1(xhr) {
  if (xhr.readyState === 4 && xhr.status === 200) {
    const blob = xhr.response;
    const filename = getFilename(xhr.getResponseHeader('content-disposition'));
    
    // 创建一个a标签元素,设置下载属性,点击下载,最后移除该元素
    const link = document.createElement('a');
    link.href = window.URL.createObjectURL(blob);
    link.download = filename;
    
    // 下载
    link.click();
    
    // 移除a标签元素
    document.body.removeChild(link);
  }
}

export function getFilename(header) {
  const match = header.match(/fileName=(.*?)(;|$)/);
  if (match && match.length > 1) {
    return match[1];
  } else {
    return 'download';
  }
}
Excel文件上传
	@BsLog(value = "爬虫-导入爬虫")
	@PostMapping(value = "/importSpider")
	@ApiOperation(value = "爬虫-导入爬虫", notes = "爬虫-导入爬虫")
	public R<?> importSpider(@RequestParam("files") MultipartFile file) {
		return R.data(spiderFlowService.importSpider(file));
	}


/**
     * *导入爬虫,支持xls、xlsx格式
     * @param file
     * @return com.daasan.common.core.api.Record
     * @author yinqi
     * @create 2024/10/31 16:39
     **/
    @SneakyThrows
    @Override
    public Record importSpider(MultipartFile file) {
        Record result = new Record();
        try (InputStream inputStream = file.getInputStream();
             Workbook workbook = WorkbookFactory.create(inputStream)) {

            Sheet sheet = workbook.getSheetAt(0); // 读取第一个sheet
            int n = 0;
            for (Row row : sheet){
                if(n > 0){
                    int id = getIntValue(row.getCell(0));
                    String name = getStringValue(row.getCell(1));
                    int age = getIntValue(row.getCell(2));
                    String address = getStringValue(row.getCell(3));

                    System.out.println(id);
                    System.out.println(name);
                    System.out.println(age);
                    System.out.println(address);
                    System.out.println("-------------------");
                }
                n++;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return null;
    }

    public static Object getCellValue(Cell cell){
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy/MM/dd");
        Object result = null;
        CellType cellType = cell.getCellTypeEnum();//得到单元格的数据类型
        switch (cellType){
            case STRING: //字符串类型
                result = cell.getStringCellValue();
                break;
            case BOOLEAN: //boolean类型
                result = cell.getBooleanCellValue();
                break;
            case NUMERIC: //数字类型和日期类型都会走这里
                if(DateUtil.isCellDateFormatted(cell)){//表示的是日期
                    result = cell.getDateCellValue();
                    result = simpleDateFormat.format(result);
                }else{//表示的普通数字
                    result = cell.getNumericCellValue();
                }
                break;
            case FORMULA: //表示的公式
                result = cell.getCellFormula();
                break;
            default:
                break;
        }
        return result;
    }

    private int getIntValue(Cell cell) {
        if (cell == null) return 0; // 或者抛出异常
        if (cell.getCellTypeEnum() == CellType.NUMERIC) {
            return (int) cell.getNumericCellValue();
        } else if (cell.getCellTypeEnum() == CellType.STRING) {
            try {
                return Integer.parseInt(cell.getStringCellValue());
            } catch (NumberFormatException e) {
                throw new IllegalStateException("单元格中的整数值无效", e);
            }
        } else {
            throw new IllegalStateException("整数值的单元格类型异常");
        }
    }

    private String getStringValue(Cell cell) {
        if (cell == null) return ""; // 或者抛出异常
        switch (cell.getCellTypeEnum()) {
            case STRING:
                return cell.getStringCellValue();
            case NUMERIC:
                if (DateUtil.isCellDateFormatted(cell)) {
                    Date date = cell.getDateCellValue();
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
                    return sdf.format(date);
                } else {
                    return String.valueOf(cell.getNumericCellValue());
                }
            case BOOLEAN:
                return String.valueOf(cell.getBooleanCellValue());
            case FORMULA:
                return cell.getCellFormula();
            default:
                return ""; // 或者抛出异常
        }
    }
            <a-upload :showUploadList="false" :loading="file.uploading" accept=".xls,.xlsx" :before-upload="fileObj => handleImport(fileObj)">
              <a-button type="dashed" icon="import">导入</a-button>
            </a-upload>


// 导入
    handleImport(file) {
      if (!file || !file.name) {
        this.$message.warning({
          key: this.messageKey,
          content: '该文件有误,请检查后上传!'
        })
        return false
      }
      let fileType = file.name.lastIndexOf('.') > 0 ? file.name.substr(file.name.lastIndexOf('.') + 1) : ''
      if (!fileType || fileType.length > 10 || (fileType !== 'xls' && fileType !== 'xlsx')) {
        this.$message.warning({
          key: this.messageKey,
          content: '文件类型有误,请检查后上传!'
        })
        return false
      }
      const isLt1024M = file.size < 1024 * 1024 * 50
      if (!isLt1024M) {
        this.$message.warning({
          key: this.messageKey,
          content: '文件大于50M,上传失败!'
        })
        return false
      }

      this.importSpider(file);
      return false;
    },
    // 导入
    importSpider(file) {
      console.log('importSpider')

      let that = this;
      that.file.uploading = true;
      let formParams = new FormData()
      formParams.append('files', file)

      that.$http.post(that.url.importSpider, formParams,
        { contentType: false, processData: false, headers: { 'Content-Type': 'application/x-www-form-urlencoded' } })
        .then(res => {
          if (res.success) {
             alert('上传成功!')
          } else {
            that.$message.error({
              key: this.messageKey,
              content: '上传失败,请重试' + (res.msg ? `【${res.msg}】` : '') + '!'
            })
          }
        }).catch(() => {
          that.$message.error({
            key: this.messageKey,
            content: '上传失败,请重试!'
          })
        }).finally(() => {
          that.file.uploading = false;
        })

      
    },
Excel文件上传的同时传递其他参数
后端:
    @BsLog(value = "表单-单文件导入表单")
    @PostMapping("/importForm")
    @ApiOperation(value = "表单-单文件导入表单", notes = "表单-单文件导入表单")
    public R<?> importForm(@ModelAttribute ImportFormVO importFormVO) {
        boolean flag = formBaseService.importForm(importFormVO);
        return R.status(flag);
    }

    @BsLog(value = "表单-多文件导入表单")
    @PostMapping("/batchImportForm")
    @ApiOperation(value = "表单-多文件导入表单", notes = "表单-多文件导入表单")
    public R<?> batchImportForm(@RequestParam("typeId") String typeId,
                            @RequestParam("linkId") String linkId,
                            @RequestParam("headRow") Integer headRow,
                            @RequestParam("files") MultipartFile[] files,
                            @RequestParam("tableNames") String[] tableNames,
                            @RequestParam("tableNotes") String[] tableNotes,
                            @RequestParam("tableExits") boolean[] tableExists) {
        boolean flag = formBaseService.batchImportForm(typeId, linkId, headRow, files, tableNames, tableNotes, tableExists);
        return R.status(flag);
    }
	
	@Data
	@Accessors(chain = true)
	@ApiModel(value = "导入表单",description = "导入表单")
	public class ImportFormVO implements Serializable {
		/**
		 * 序列化对象验证版本一致性的唯一版本号
		 */
		private static final long serialVersionUID = -4872975987140332502L;

		@ApiModelProperty(value = "表头所在行")
		private Integer headRow;

		@ApiModelProperty(value = "表单分类")
		@NotBlank(message = "表单分类不能为空!")
		private String typeId;

		@ApiModelProperty(value = "数据源id")
		private String linkId;

		@ApiModelProperty(value = "表名称")
		private String tableName;

		@ApiModelProperty(value = "表备注")
		private String tableNote;

		@ApiModelProperty(value = "表是否存在")
		private boolean tableExist;

		@ApiModelProperty(value = "上传的文件")
		private MultipartFile file;
	}
	

前端单文件上传:
	<a-row>
		<a-col :span="11">
			<a-form-item label="选择文件">
				<a-upload :action="uploadAction" :beforeUpload="beforeUpload" :fileList="fileList"
					:headers="headers" :multiple="false"
					v-decorator="['fileId', validatorRules.fileId]"
					accept='.dmp,.sql,.bak,.xls,.xlsx,.doc,.docx,.xml,.ofd,.pdf,.png,.jpg,.jpeg,.zip,.rar'
					name='file' @change="handleChangeFile" >
					<a-button>
						<a-icon type="upload" />
						文件上传
					</a-button>
				</a-upload>
			</a-form-item>
		</a-col>
		<a-col :span="11" :offset="2">
			<a-form-item label="表头所在行">
				<a-input style="width: 100%;" key="key" placeholder="请输入表头所在行" @blur='change'
					:min="1" :max='maxValue'
					v-decorator="['headRow', validatorRules.headRow]" />
			</a-form-item>
		</a-col>
	</a-row>	
	
	
	handleSubmit(e) {
		console.log(this.fileList[0])
		this.loading = true;
		let that = this
		// 触发表单验证
		this.form.validateFields((err, formData) => {
			if (!err) {
				// 合并model与表单数据 
				formData.tableName = formData.tableName[0];//表名
				formData.tableExist = this.isAddTable; //是否新增的表
				delete formData.fileId;
				let formParams = new FormData()
				Object.keys(formData).forEach(key => {
					formParams.append(key, formData[key]); // 添加其他字段
				});
				formParams.append('file', this.filesBary)
				this.$http.post(api.importForm, formParams, { contentType: false, processData: false, headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }).then(res => {
					if (res.code == 400) {
						that.$message.error(res.msg)
					} else {
						that.$emit('handleSubmit');
					}
					that.loading = false
					that.close()
				}).finally(() => {
					that.loading = false;
				})
			} else {
				that.loading = false;

			}
		})
	},	
	
	beforeUpload(file) {
		this.filesBary = file;
		this.excelFlag = false
		let _this = this
		_this.fileList = []
		if (!file || !file.name) {
			this.$message.error({
				key: this.messageKey,
				content: '该文件有误,请检查后上传!'
			})
			return false
		}
		let fileName = file.name
		if (fileName.length > 100) {
			this.$message.error('文件名称不可超过100个字符')
			_this.$nextTick(() => {
				_this.model.fileId = ''
				_this.fileList = []
				_this.form.setFieldsValue({ 'fileId': undefined })
			})
			return false
		}
		const isLt32M = file.size / 1024 / 1024 <= 2048
		if (!isLt32M) {
			this.$message.error({
				key: this.messageKey,
				content: '文件大于2048M,上传失败!'
			})
			_this.$nextTick(() => {
				_this.model.fileId = ''
				_this.fileList = []
				_this.form.setFieldsValue({ 'fileId': undefined })
			})
			return false
		}
		let uid = file.uid
		let pos = fileName.lastIndexOf('.')
		let lastName = fileName.substring(pos, fileName.length)
		if (lastName.toLowerCase() === '.xlsx' || lastName.toLowerCase() === '.xls') {
			this.excelFlag = true
			this.validatorRules.headRow.rules.push({ required: true, message: '请输入表头所在行!' })
			// this.validatorRules.start.rules.push({ required: true, message: '请输入数据起始行!' })
			// this.validatorRules.end.rules.push({ required: true, message: '请输入数据截止行!' })
		}
		this.lastName = lastName.toLowerCase()
		//判断文件类型.dmp,.sql,.bak,.xls,.xlsx
		if (
			lastName.toLowerCase() !== '.dmp' &&
			lastName.toLowerCase() !== '.sql' &&
			lastName.toLowerCase() !== '.bak' &&
			lastName.toLowerCase() !== '.xls' &&
			lastName.toLowerCase() !== '.xlsx' &&
			lastName.toLowerCase() !== '.doc' &&
			lastName.toLowerCase() !== '.docx' &&
			lastName.toLowerCase() !== '.xml' &&
			lastName.toLowerCase() !== '.ofd' &&
			lastName.toLowerCase() !== '.pdf' &&
			lastName.toLowerCase() !== '.png' &&
			lastName.toLowerCase() !== '.jpg' &&
			lastName.toLowerCase() !== '.jpeg' &&
			lastName.toLowerCase() !== '.zip' &&
			lastName.toLowerCase() !== '.rar'
		) {
			this.$message.error('文件必须为.dmp .sql .bak .xls .xlsx .doc .docx .xml .ofd .pdf .png .jpg .jpeg .zip .rar类型')
			_this.$nextTick(() => {
				_this.model.fileId = ''
				_this.fileList = []
				_this.form.setFieldsValue({ 'fileId': undefined })
			})
			return false
		}
		this.fileList = [file]
		return true
	},	


前端多文件上传:
		<a-col :span="11" :offset="2">
			<a-form-item label="选择文件" style="margin-bottom: 10px;">
				<a-upload :action="uploadAction" :beforeUpload="beforeUpload" :fileList="fileList"
					:headers="headers" :multiple="true" v-decorator="['fileId', validatorRules.fileId]"
					accept='.dmp,.sql,.bak,.xls,.xlsx,.doc,.docx,.xml,.ofd,.pdf,.png,.jpg,.jpeg,.zip,.rar'
					name='file' @change="handleChangeFile" :showUploadList="false">
					<a-button>
						<a-icon type="upload" />
						文件上传
					</a-button>
				</a-upload>
			</a-form-item>
		</a-col>

        handleSubmit(e) {
            // this.loading = true;
            let that = this
            // 触发表单验证
            this.form.validateFields((err, formData) => {
                if (!err) {
                    const files = this.fileListJson;
                    let params = {
                        // files: this.filesBary,
                        headRow: formData.headRow,
                        linkId: formData.linkId,
                        typeId: formData.typeId,
                        tableExits: files.map(item => !item.isAddTable),
                        tableNames: files.map(item => item.tableName[0]),
                        tableNotes: files.map(item => item.tableNote),
                    }
                    let formParams = new FormData()
                    Object.keys(params).forEach(key => {
                        formParams.append(key, params[key]); // 添加其他字段
                    });
                    this.filesBary.forEach(item => {
                        formParams.append('files', item)
                    })
                    formParams.tableExits = params.tableExits;
                    formParams.tableNames = params.tableNames;
                    formParams.tableNotes = params.tableNotes;
                    this.$http.post(api.batchImportForm, formParams, { contentType: false, processData: false, headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }).then(res => {
                        if (res.code == 400) {
                            that.$message.error(res.msg)
                            return
                        }
                        that.$emit('handleSubmit');
                        that.loading = false
                        that.close()

                    }).finally(() => {
                        that.loading = false;
                    })
                } else {
                    that.loading = false;
                }
            })
        },
		
		
        beforeUpload(file) {
            // 优选选择数据源
            if (!this.form.getFieldValue('linkId')) {
                this.form.validateFields(['linkId'])
                return;
            };
            this.excelFlag = false
            let _this = this
            if (!file || !file.name) {
                this.$message.error({
                    key: this.messageKey,
                    content: '该文件有误,请检查后上传!'
                })
                return false
            }
            let fileName = file.name
            if (fileName.length > 100) {
                this.$message.error('文件名称不可超过100个字符')
                _this.$nextTick(() => {
                    _this.model.fileId = ''
                    _this.form.setFieldsValue({ 'fileId': undefined })
                })
                return false
            }
            const isLt32M = file.size / 1024 / 1024 <= 2048
            if (!isLt32M) {
                this.$message.error({
                    key: this.messageKey,
                    content: '文件大于2048M,上传失败!'
                })
                _this.$nextTick(() => {
                    _this.model.fileId = ''
                    _this.form.setFieldsValue({ 'fileId': undefined })
                })
                return false
            }
            let uid = file.uid
            let pos = fileName.lastIndexOf('.')
            let lastName = fileName.substring(pos, fileName.length)
            if (lastName.toLowerCase() === '.xlsx' || lastName.toLowerCase() === '.xls') {
                this.excelFlag = true
                this.validatorRules.headRow.rules.push({ required: true, message: '请输入表头所在行!' })
            }
            this.lastName = lastName.toLowerCase()
            //判断文件类型.dmp,.sql,.bak,.xls,.xlsx
            if (
                lastName.toLowerCase() !== '.dmp' &&
                lastName.toLowerCase() !== '.sql' &&
                lastName.toLowerCase() !== '.bak' &&
                lastName.toLowerCase() !== '.xls' &&
                lastName.toLowerCase() !== '.xlsx' &&
                lastName.toLowerCase() !== '.doc' &&
                lastName.toLowerCase() !== '.docx' &&
                lastName.toLowerCase() !== '.xml' &&
                lastName.toLowerCase() !== '.ofd' &&
                lastName.toLowerCase() !== '.pdf' &&
                lastName.toLowerCase() !== '.png' &&
                lastName.toLowerCase() !== '.jpg' &&
                lastName.toLowerCase() !== '.jpeg' &&
                lastName.toLowerCase() !== '.zip' &&
                lastName.toLowerCase() !== '.rar'
            ) {
                this.$message.error('文件必须为.dmp .sql .bak .xls .xlsx .doc .docx .xml .ofd .pdf .png .jpg .jpeg .zip .rar类型')
                _this.$nextTick(() => {
                    _this.model.fileId = ''
                    // _this.fileList = []
                    _this.form.setFieldsValue({ 'fileId': undefined })
                })
                return false
            }
            this.fileList.push(file)
            this.filesBary.push(file)
            return true
        },		
Excel文件下载
	@GetMapping(value = "/downloadTemplate")
	@ApiOperation(value = "爬虫-下载导入模板", notes = "爬虫-下载导入模板")
	public void downloadTemplate(HttpServletResponse response, HttpServletRequest request) {
		spiderFlowService.downloadTemplate(response, request);
	}
	
	public void downloadTemplate(HttpServletResponse response, HttpServletRequest request) {
        List<SpiderFlow> spiderFlows = new ArrayList<>(8);
        spiderFlows.add(new SpiderFlow().setName("爬虫名称A").setXml("<mxGraphModel><root><mxCell id=\"0\"></mxCell></root></mxGraphModel>").setRemark("备注A").setSort(1));
        spiderFlows.add(new SpiderFlow().setName("爬虫名称B").setXml("<mxGraphModel><root><mxCell id=\"0\"></mxCell></root></mxGraphModel>").setRemark("备注B").setSort(2));
        spiderFlows.add(new SpiderFlow().setName("爬虫名称C").setXml("<mxGraphModel><root><mxCell id=\"0\"></mxCell></root></mxGraphModel>").setRemark("备注C").setSort(3));

        // 导出
        exportExcel(response, request, spiderFlows);
    }
	
	@SneakyThrows
    public void exportExcel(HttpServletResponse response, HttpServletRequest request, List<SpiderFlow> spiderFlows){
        // 创建workbook
        Workbook workbook = new XSSFWorkbook();
        Sheet sheet = workbook.createSheet("导出爬虫");
        // 创建表头
        Row headerRow = sheet.createRow(0);
        headerRow.createCell(0).setCellValue("爬虫名称");
        headerRow.createCell(1).setCellValue("xml配置");
        headerRow.createCell(2).setCellValue("备注");
        headerRow.createCell(3).setCellValue("排序");

        /**设置表头的样式*/
        //设置列的宽度
        sheet.setColumnWidth(0, 9000);
        sheet.setColumnWidth(1, 15000);
        sheet.setColumnWidth(2, 6000);
        sheet.setColumnWidth(3, 3000);
        // 设置表头的行高度为30个点
        headerRow.setHeightInPoints(30);
        //设置表头单元格的样式
        for (Cell cell : headerRow){
            // 创建表头样式
            CellStyle headerStyle = workbook.createCellStyle();
            Font headerFont = workbook.createFont();
            headerFont.setBold(true); // 设置字体为粗体
            headerStyle.setFont(headerFont);
            headerStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex()); // 设置背景颜色为灰色
            headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
            headerStyle.setBorderLeft(BorderStyle.THIN); // 设置边框为实线
            headerStyle.setBorderRight(BorderStyle.THIN);
            headerStyle.setBorderTop(BorderStyle.THIN);
            headerStyle.setBorderBottom(BorderStyle.THIN);
            cell.setCellStyle(headerStyle);
        }

        // 填充数据
        for (int i = 0; i < spiderFlows.size(); i++) {
            Row row = sheet.createRow(i + 1);
            SpiderFlow spiderFlow = spiderFlows.get(i);//第几行的数据
            /**导出的基础字段*/
            row.createCell(0).setCellValue(spiderFlow.getName());
            row.createCell(1).setCellValue(spiderFlow.getXml());
            row.createCell(2).setCellValue(spiderFlow.getRemark());
            row.createCell(3).setCellValue(spiderFlow.getSort());
        }

        // 设置response头信息
        response.setContentType("application/vnd.ms-excel");
        response.setHeader("Content-Disposition", "attachment; filename=导出爬虫.xlsx");

        // 将workbook写入response输出流
        OutputStream outputStream = response.getOutputStream();
        workbook.write(outputStream);
        workbook.close();
        outputStream.close();
    }
    <a-button @click="downloadTemplate()" icon="export">下载模板</a-button>  
  
    // 下载导入模板
    downloadTemplate(){
      this.$message.loading({ content: '正在下载...' })
      downloadFile(this.url.downloadTemplate, "爬虫导入模板.xlsx");
    }
	
	/**
	 * 下载文件
	 * @param url 文件路径
	 * @param fileName 文件名
	 * @param parameter
	 * @returns {*}
	 */
	export function downloadFile(url, fileName, parameter) {
	  return downFile(url, parameter).then((data) => {
		if (!data || data.size === 0) {
		  Vue.prototype['$message'].warning('文件下载失败!')
		  return
		}
		const blob = new Blob([data], { type: 'application/octet-stream' })
		if (typeof window.navigator.msSaveBlob !== 'undefined') {
		  window.navigator.msSaveBlob(blob, fileName)
		} else {
		  let url = window.URL.createObjectURL(blob)
		  let link = document.createElement('a')
		  link.style.display = 'none'
		  link.href = url
		  link.setAttribute('download', fileName)
		  document.body.appendChild(link)
		  link.click()
		  document.body.removeChild(link) //下载完成移除元素
		  window.URL.revokeObjectURL(url) //释放掉blob对象
		}
	  })
	}

数据校验工具类

加密解密工具类

package com.daasan.common.util;

import lombok.SneakyThrows;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

/**
 * 加密解密工具类
 * @author 25129
 * @Date  2025/3/21 14:10
 **/
public class EncryptionUtil {
    private static final String ALGORITHM = "AES";
    private static final String SECRET_KEY = "elad&UJM6yhn-TRM";

    /**
     * 将String类型数据加密
     * @Date  2025/3/21 14:08
     * @Param [data]
     * @Return java.lang.String
     **/
    public static String encrypt(String data) {
        try {
            SecretKeySpec secretKeySpec = new SecretKeySpec(SECRET_KEY.getBytes(), ALGORITHM);
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
            byte[] encryptedBytes = cipher.doFinal(data.getBytes());
            return Base64.getEncoder().encodeToString(encryptedBytes);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("加密失败!");
        }
    }

    /**
     * 将double类型数据加密
     * @Date  2025/3/21 14:08
     * @Param [data]
     * @Return java.lang.String
     **/
    public static String encrypt(double data) {
        String dataStr = String.valueOf(data);
        try {
            SecretKeySpec secretKeySpec = new SecretKeySpec(SECRET_KEY.getBytes(), ALGORITHM);
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
            byte[] encryptedBytes = cipher.doFinal(dataStr.getBytes());
            return Base64.getEncoder().encodeToString(encryptedBytes);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("加密失败!");
        }
    }

    /**
     * 将字符串解密,得到String数据
     * @Date  2025/3/21 14:08
     * @Param [encryptedData]
     * @Return java.lang.String
     **/
    public static String decrypt(String encryptedData) {
        try {
            SecretKeySpec secretKeySpec = new SecretKeySpec(SECRET_KEY.getBytes(), ALGORITHM);
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
            byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedData));
            return new String(decryptedBytes);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("解密失败!");
        }
    }

    /**
     * 将字符串解密,得到double数据
     * @Date  2025/3/21 14:09
     * @Param [encryptedData]
     * @Return double
     **/
    public static double decryptDouble(String encryptedData) {
        try {
            SecretKeySpec secretKeySpec = new SecretKeySpec(SECRET_KEY.getBytes(), ALGORITHM);
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
            byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedData));
            String dataStr = new String(decryptedBytes);
            return Double.valueOf(dataStr);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("解密失败!");
        }
    }

    /**
     * 原数据加密后是否等于key
     * @param encryptedDataKey
     * @param originalData
     * @return
     * @throws Exception
     */
    public static boolean encryptAndCompare(String encryptedDataKey, String originalData) {
        if(StrUtil.isEmpty(encryptedDataKey) || StrUtil.isEmpty(originalData)){
            return false;
        }
        String originalDataKey = encrypt(originalData);
        return encryptedDataKey.equals(originalDataKey);
    }

    /**
     * 原数据加密后是否等于key
     * @param encryptedDataKey
     * @param originalData
     * @return
     * @throws Exception
     */
    public static boolean encryptAndCompare(String encryptedDataKey, Double originalData) {
        if(StrUtil.isEmpty(encryptedDataKey) || originalData == null){
            return false;
        }
        String originalDataKey = encrypt(originalData);
        return encryptedDataKey.equals(originalDataKey);
    }

    /**
     * key解密后是否等于原数据
     * @param encryptedDataKey
     * @param originalData
     * @return
     * @throws Exception
     */
    public static boolean decryptAndCompare(String encryptedDataKey, String originalData) throws Exception {
        if(StrUtil.isEmpty(encryptedDataKey) || StrUtil.isEmpty(originalData)){
            return false;
        }
        String decryptedData = decrypt(encryptedDataKey);
        return decryptedData.equals(originalData);
    }

    // 生成一个示例密钥(实际使用中应该从安全存储中获取)
    public static String generateSecretKey() throws Exception {
        KeyGenerator keyGen = KeyGenerator.getInstance(ALGORITHM);
        keyGen.init(128); // 对于AES可以是128, 192, 或 256位
        SecretKey secretKey = keyGen.generateKey();
        return Base64.getEncoder().encodeToString(secretKey.getEncoded());
    }

    @SneakyThrows
    public static void main(String[] args) {
        double price = 108.85;

        String priceSecret = encrypt(price);
        System.out.println("加密:"+priceSecret);

        double data = decryptDouble("rQnktIxxKk3fLBS0tXlFyQ==");
        System.out.println("解密:"+data);

        boolean flag = encryptAndCompare("rQnktIxxKk3fLBS0tXlFyQ==", price);
        System.out.println("验证通过:" + flag);
    }
}
import CryptoJS from 'crypto-js'

// 默认的 KEY 与 iv
const key = CryptoJS.enc.Utf8.parse('elad&UJM6yhn-TRM')
const iv = CryptoJS.enc.Utf8.parse('AES')

/**
 * URL编码
 * @param word
 * @returns {string}
 */
export function urlEncrypt(word) {
    return encodeURIComponent(encrypt(word))
}

/**
 * AES加密
 * @param {*} word
 * @returns
 */
export function encrypt(word) {
    let encrypted = CryptoJS.AES.encrypt(CryptoJS.enc.Utf8.parse(word), key, {
        iv: iv,
        mode: CryptoJS.mode.ECB,
        padding: CryptoJS.pad.Pkcs7,
    })
    return CryptoJS.enc.Base64.stringify(encrypted.ciphertext)
}

/**
 * AES解密
 * @param {*} encryptedWord
 * @returns
 */
export function decrypt(encryptedWord) {
    const iv = CryptoJS.enc.Utf8.parse('AES') // 需与加密时的IV一致
    // 解析Base64密文
    const encryptedData = CryptoJS.enc.Base64.parse(encryptedWord)
    const cipherParams = CryptoJS.lib.CipherParams.create({
        ciphertext: encryptedData,
    })
    // 执行解密
    const decrypted = CryptoJS.AES.decrypt(cipherParams, key, {
        iv: iv,
        mode: CryptoJS.mode.ECB,
        padding: CryptoJS.pad.Pkcs7,
    })
    // 转换为UTF-8字符串
    return decrypted.toString(CryptoJS.enc.Utf8)
}

随机数工具类

package com.daasan.common.util;

import java.util.concurrent.atomic.AtomicLong;

public class IdUtil {

    // 起始时间戳:2023-10-01 00:00:00
    private static final long START_TIMESTAMP = 1696108800000L;

    // 每个ID占用的位数
    private static final int TIMESTAMP_BITS = 14; // 2023-10-01 ~ 2090-10-01 (约67年)
    private static final int MACHINE_ID_BITS = 5; // 最支持32台机器
    private static final int SEQUENCE_BITS = 9;  // 每毫秒支持512次请求

    // 每部分的偏移量
    private static final long MACHINE_ID_SHIFT = SEQUENCE_BITS;
    private static final long TIMESTAMP_SHIFT = MACHINE_ID_BITS + SEQUENCE_BITS;

    // 最大值
    private static final long MAX_MACHINE_ID = (1L << MACHINE_ID_BITS) - 1;
    private static final long MAX_SEQUENCE = (1L << SEQUENCE_BITS) - 1;

    private final long machineId;
    private final AtomicLong sequence = new AtomicLong(0);
    private long lastTimestamp = -1L;

    public IdUtil(long machineId) {
        if (machineId > MAX_MACHINE_ID || machineId < 0) {
            throw new IllegalArgumentException("Machine ID超出范围[0, 31]");
        }
        this.machineId = machineId;
    }

    public synchronized long nextId() {
        long currentTimestamp = System.currentTimeMillis() - START_TIMESTAMP;

        if (currentTimestamp < lastTimestamp) {
            throw new RuntimeException("时间戳回拨,当前时间早于生成器初始化时间");
        }

        if (currentTimestamp == lastTimestamp) {
            long seq = sequence.incrementAndGet();
            if (seq > MAX_SEQUENCE) {
                throw new RuntimeException("序列号溢出,等待下一毫秒");
            }
            return ((currentTimestamp << TIMESTAMP_SHIFT) |
                    (machineId << MACHINE_ID_SHIFT) |
                    seq);
        } else {
            sequence.set(0);
            lastTimestamp = currentTimestamp;
            return ((currentTimestamp << TIMESTAMP_SHIFT) |
                    (machineId << MACHINE_ID_SHIFT) |
                    0);
        }
    }

    public static long getId() {
        IdUtil generator = new IdUtil(1); // 这里的1是machineId,可以根据需要调整
        return generator.nextId();
    }

    public static void main(String[] args) {
        System.out.println(getId());
    }
}
import java.util.Random;

/**
 * 随机数工具类
 */
public class RandomUtils {

    private static Random random = new Random();

    /**
     * 生成指定范围内的随机整数
     * @param min 最小值(包含)
     * @param max 最大值(包含)
     * @return 随机整数
     */
    public static int getRandomInt(int min, int max) {
        return random.nextInt(max - min + 1) + min;
    }

    /**
     * 生成指定范围内的随机浮点数
     * @param min 最小值(包含)
     * @param max 最大值(不包含)
     * @return 随机浮点数
     */
    public static float getRandomFloat(float min, float max) {
        return random.nextFloat() * (max - min) + min;
    }

    /**
     * 生成指定长度的随机字符串
     * @param length 字符串长度
     * @return 随机字符串
     */
    public static String getRandomString(int length) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < length; i++) {
            char c = (char) getRandomInt(97, 122); // 生成a-z之间的随机字符
            sb.append(c);
        }
        return sb.toString();
    }

    /**
     * 生成指定位数的随机数字串
     * @param length 数字串长度
     * @return 随机数字串
     */
    public static String getRandomNumber(int length) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < length; i++) {
            int num = getRandomInt(0, 9);
            sb.append(num);
        }
        return sb.toString();
    }

    /**
     * 生成指定长度和字符范围的随机字符串
     * @param length 字符串长度
     * @param chars 字符范围
     * @return 随机字符串
     */
    public static String getRandomString(int length, char[] chars) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < length; i++) {
            int index = getRandomInt(0, chars.length - 1);
            char c = chars[index];
            sb.append(c);
        }
        return sb.toString();
    }

    /**
     * 生成指定长度和字符范围的随机数字串
     * @param length 数字串长度
     * @param digits 数字范围
     * @return 随机数字串
     */
    public static String getRandomNumber(int length, int[] digits) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < length; i++) {
            int index = getRandomInt(0, digits.length - 1);
            int num = digits[index];
            sb.append(num);
        }
        return sb.toString();
    }
    
    // 其他方法...
}
 

编码转换工具类

HTTP请求工具类

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

public class HttpUtils {

    /**
     * 发送GET请求并获取响应结果
     * 
     * @param url 请求的URL
     * @return 响应结果,如果请求失败则返回null
     */
    public static String sendGetRequest(String url) {
        HttpURLConnection connection = null;
        BufferedReader reader = null;
        StringBuilder result = new StringBuilder();
        
        try {
            URL requestUrl = new URL(url);
            connection = (HttpURLConnection) requestUrl.openConnection();
            connection.setRequestMethod("GET");
            connection.setConnectTimeout(5000);
            connection.setReadTimeout(5000);

            int responseCode = connection.getResponseCode();
            if (responseCode == HttpURLConnection.HTTP_OK) {
                reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
                String line;
                while ((line = reader.readLine()) != null) {
                    result.append(line);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (connection != null) {
                connection.disconnect();
            }
        }

        return result.toString();
    }

    /**
     * 发送POST请求并获取响应结果
     * 
     * @param url    请求的URL
     * @param params 请求参数,格式为key=value,多个参数之间用&连接
     * @return 响应结果,如果请求失败则返回null
     */
    public static String sendPostRequest(String url, String params) {
        HttpURLConnection connection = null;
        BufferedReader reader = null;
        StringBuilder result = new StringBuilder();
        
        try {
            URL requestUrl = new URL(url);
            connection = (HttpURLConnection) requestUrl.openConnection();
            connection.setRequestMethod("POST");
            connection.setConnectTimeout(5000);
            connection.setReadTimeout(5000);
            connection.setDoOutput(true);

            connection.getOutputStream().write(params.getBytes("UTF-8"));

            int responseCode = connection.getResponseCode();
            if (responseCode == HttpURLConnection.HTTP_OK) {
                reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
                String line;
                while ((line = reader.readLine()) != null) {
                    result.append(line);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (connection != null) {
                connection.disconnect();
            }
        }

        return result.toString();
    }

    /**
     * 发送HTTP请求
     * 
     * @param method 请求方法,如GET、POST等
     * @param url    请求的URL
     * @param params 请求参数,格式为key=value,多个参数之间用&连接
     * @return 响应结果,如果请求失败则返回null
     */
    public static String sendHttpRequest(String method, String url, String params) {
        if ("GET".equalsIgnoreCase(method)) {
            return sendGetRequest(url);
        } else if ("POST".equalsIgnoreCase(method)) {
            return sendPostRequest(url, params);
        }
        return null;
    }

    /**
     * *调用接口
     * @param url,method,json字符串参数
     * @return com.daasan.common.core.api.R<?>
     * @author yinqi
     * @create 2024/2/1 10:00
     **/
    @Override
    public R<?> callApi(String url, String method, String paramsStr) {
        //校验必填参数
        if (StringUtils.isBlank(url)){
            throw new BsException("请求路径不能为空!");
        }
        if (StringUtils.isBlank(method)){
            throw new BsException("请求方式不能为空!");
        }

        // 根据请求方法构建不同的请求对象
        HttpMethod httpMethod = HttpMethod.resolve(method.toUpperCase());
        ResponseEntity<String> response;

        //将json字符串参数转为map类型的参数
        Map<String, Object> params = ScreenConfigUtil.jsonToMap(paramsStr);

        //调用接口
        if (httpMethod == HttpMethod.GET) {
            // GET请求
            String queryString = ScreenConfigUtil.buildQueryString(params);
            if (StringUtils.isNotBlank(queryString)) {
                url = url + "?" + queryString;
            }
            response = restTemplate.getForEntity(url, String.class);
        } else {
            // POST、PUT等请求
            HttpHeaders headers = new HttpHeaders();
            headers.set("Content-Type", "application/json");
            HttpEntity<Object> requestEntity = new HttpEntity<>(params, headers);
            response = restTemplate.exchange(url, httpMethod, requestEntity, String.class);
        }

        // 获取接口调用结果(json格式字符串)
        String body = response.getBody();

        //如果接口没有返回值,则返回调用成功的消息提示
        if (StringUtils.isBlank(body)){
            return R.success("接口调用成功!");
        }

        //如果接口返回结果以“[”开头,则转换为List<Map<String, Object>>
        if (body.startsWith("[")){
            //将结果由json格式字符串转换为List<Map<String, Object>>
            List<Map<String, Object>> result = ScreenConfigUtil.jsonToList(body);
            if (CollectionUtil.isNotEmpty(result)){
                return R.data(result);
            }
        }

        //如果接口返回结果以“{”开头,则转化为Map<String, Object>
        if (body.startsWith("{")){
            Map<String, Object> result = ScreenConfigUtil.jsonToMap(body);
            if (result != null && !result.isEmpty()){
                return R.data(result);
            }
        }

        return R.data(body);
    }

}
 
import lombok.SneakyThrows;
import org.apache.http.HttpEntity;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;

public class HttpClientUtil {
    @SneakyThrows
    public static void callPost() {
        String url = "https://api.so-gov.cn/query/s";
        String[] params = {
                "siteCode", "ynzcwjk",
                "tab", "all",
                "qt", "云南省人民政府办公厅关于印发云南省人民政府常务会议议定事项督查落实办法(试行)的通知",
                "tabType", "0",
                "sort", "relevance",
                "keyPlace", "1",
                "timeOption", "0",
                "page", "1",
                "pageSize", "20",
                "ie", "91ba2106-b493-4628-9ef2-8ff357840137"
        };
        for (int i = 0; i < 5; i++) {
            int randomNumber = (int) (Math.random() * (1500 - 500 + 1)) + 500; // 延迟时间为500到1500之间的随机数
            Thread.sleep(randomNumber);
            String response = sendPostRequestWithFormData(url, params);
            System.out.println("Response: " + response);
            System.out.println("第" + i + "次调用接口结束-------------------------------------------------");
        }
    }

    /**
     * *调用post请求的接口
     *
     * @param url
     * @param params
     * @return java.lang.String
     * @create 2024/10/30 15:50
     **/
    public static String sendPostRequestWithFormData(String url, String... params) throws IOException {
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            HttpPost postRequest = new HttpPost(url);

            List<BasicNameValuePair> formParams = new ArrayList<>();
            for (int i = 0; i < params.length; i += 2) {
                if (i + 1 < params.length) {
                    formParams.add(new BasicNameValuePair(params[i], params[i + 1]));
                }
            }

            UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formParams, StandardCharsets.UTF_8);
            postRequest.setEntity(entity);

            try (CloseableHttpResponse response = httpClient.execute(postRequest)) {
                return EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
            }
        }
    }

    @SneakyThrows
    public static void callGet() {
        String url = "https://www.zjk.gov.cn/content/guizhang/193741.html";
//        String url = "https://www.yn.gov.cn/zwgk/zcwj/yzf/202206/t20220628_243663.html";
        for (int i = 0; i < 1; i++) {
            int randomNumber = (int) (Math.random() * (1500 - 500 + 1)) + 500; // 延迟时间为500到1500之间的随机数
            Thread.sleep(randomNumber);
            String response = sendGetRequest(url);
            System.out.println("Response: " + response);
            System.out.println("第" + i + "次调用接口结束-------------------------------------------------");
        }
    }

    /**
     * *调用get请求接口,绕过https证书;使用TLSv1.3协议时jdk版本要大于1.8(1.8及以下只支持TLSv1.2及以下协议);
     *
     * @param url
     * @return java.lang.String
     * @create 2024/10/30 15:51
     **/
    public static String sendGetRequest(String url) throws IOException {
        // 创建一个信任所有证书的 TrustManager(仅用于测试环境)
        final TrustManager[] trustAllCerts = new TrustManager[]{
                new X509TrustManager() {
                    @Override
                    public void checkClientTrusted(X509Certificate[] chain, String authType) {
                    }

                    @Override
                    public void checkServerTrusted(X509Certificate[] chain, String authType) {
                    }

                    @Override
                    public X509Certificate[] getAcceptedIssuers() {
                        return new X509Certificate[]{};
                    }
                }
        };
        // 安装信任所有证书的 SSLContext
        try {
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, trustAllCerts, new java.security.SecureRandom());

            // 创建一个 SSLSocketFactory,并尝试设置 TLS 版本(这种方法可能不适用)
            SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(
                    sslContext,
                    new String[]{"SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3"},
                    null, // 默认的 cipher suites
                    new DefaultHostnameVerifier() // 默认的主机名验证器
            );

            // 创建 HttpClient 并配置 SSLSocketFactory
            try (CloseableHttpClient httpClient = HttpClients.custom()
                    .setSSLSocketFactory(sslSocketFactory)
                    .build()) {

                HttpGet request = new HttpGet(url);
                try (CloseableHttpResponse response = httpClient.execute(request)) {
                    HttpEntity entity = response.getEntity();
                    if (entity != null) {
                        return EntityUtils.toString(entity);
                    } else {
                        throw new IOException("No response entity received from the server");
                    }
                }
            }
        } catch (Exception e) {
            throw new IOException("Failed to create SSL context or HTTP client", e);
        }
    }

    public static void main(String[] args) {
        callGet();
    }
}

日志工具类

import java.io.IOException;
import java.util.logging.*;

// 日志级别枚举类
enum LogLevel {
    INFO,
    WARNING,
    ERROR
}

public class LogUtil {
    private static final Logger logger = Logger.getLogger(LogUtil.class.getName());

    // 设置日志格式
    static {
        LogManager.getLogManager().reset();
        ConsoleHandler consoleHandler = new ConsoleHandler();
        consoleHandler.setFormatter(new SimpleFormatter());
        logger.addHandler(consoleHandler);
    }

    /**
     * 记录信息级别的日志
     * @param message 日志信息
     */
    public static void info(String message) {
        logger.log(Level.INFO, message);
    }

    /**
     * 记录警告级别的日志
     * @param message 日志信息
     */
    public static void warning(String message) {
        logger.log(Level.WARNING, message);
    }

    /**
     * 记录错误级别的日志
     * @param message 日志信息
     */
    public static void error(String message) {
        logger.log(Level.SEVERE, message);
    }

    /**
     * 动态设置日志级别
     * @param logLevel 日志级别
     */
    public static void setLogLevel(LogLevel logLevel) {
        switch (logLevel) {
            case INFO:
                logger.setLevel(Level.INFO);
                break;
            case WARNING:
                logger.setLevel(Level.WARNING);
                break;
            case ERROR:
                logger.setLevel(Level.SEVERE);
                break;
        }
    }

    /**
     * 将日志输出到文件
     * @param filePath 文件路径
     */
    public static void setLogFile(String filePath) {
        try {
            FileHandler fileHandler = new FileHandler(filePath);
            fileHandler.setFormatter(new SimpleFormatter());
            logger.addHandler(fileHandler);
        } catch (IOException e) {
            logger.log(Level.SEVERE, "Failed to set log file.", e);
        }
    }
    
    /**
     * 将日志输出到指定的日志处理器
     * @param logHandler 日志处理器
     */
    public static void setLogHandler(Handler logHandler) {
        logger.addHandler(logHandler);
    }
    
    /**
     * 移除指定的日志处理器
     * @param logHandler 日志处理器
     */
    public static void removeLogHandler(Handler logHandler) {
        logger.removeHandler(logHandler);
    }

    /**
     * 清除所有的日志处理器
     */
    public static void clearLogHandlers() {
        Handler[] handlers = logger.getHandlers();
        for (Handler handler : handlers) {
            logger.removeHandler(handler);
        }
    }
}
 

JSON处理工具类

import org.json.JSONArray;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.List;

/**
 * JSON处理工具类
 */
public class JsonUtils {

    /**
     **将json字符串转为map
     * @param json
     * @return java.util.Map<java.lang.String, java.lang.Object>
     * @author yinqi
     * @create 2024/1/31 10:05
     **/
    public static Map<String, Object> jsonToMap(String json) {
        if (StringUtils.isBlank(json)){
            return null;
        }

        ObjectMapper objectMapper = new ObjectMapper();

        Map<String, Object> map = null;
        try {
            map = objectMapper.readValue(json, new TypeReference<Map<String, Object>>() {});
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }

        return map;
    }

    /**
     **json格式字符串转换为List<Map<String, Object>>
     * @param json
     * @return java.util.List<java.util.Map < java.lang.String, java.lang.Object>>
     * @author yinqi
     * @create 2024/2/1 9:36
     **/
    public static List<Map<String, Object>> jsonToList(String json) {
        if (StringUtils.isBlank(json)){
            return null;
        }

        ObjectMapper objectMapper = new ObjectMapper();
        List<Map<String, Object>> list = null;
        try {
            list = objectMapper.readValue(json, new TypeReference<List<Map<String, Object>>>() {
            });
        } catch (JsonProcessingException e) {
            System.err.println(e.getMessage());
        }
        return list;
    }

    /**
     * 将JSON字符串转换为JSONObject对象
     *
     * @param json JSON字符串
     * @return 转换后的JSONObject对象
     */
    public static JSONObject stringToJson(String json) {
        return new JSONObject(json);
    }

    /**
     * 将JSONObject对象转换为JSON字符串
     *
     * @param jsonObject JSONObject对象
     * @return 转换后的JSON字符串
     */
    public static String jsonToString(JSONObject jsonObject) {
        return jsonObject.toString();
    }

    /**
     * 获取JSONObject中指定字段的字符串值
     *
     * @param jsonObject JSONObject对象
     * @param key        字段名
     * @return 字段对应的字符串值,若字段不存在则返回空字符串
     */
    public static String getString(JSONObject jsonObject, String key) {
        return jsonObject.optString(key, "");
    }

    /**
     * 获取JSONObject中指定字段的整数值
     *
     * @param jsonObject JSONObject对象
     * @param key        字段名
     * @return 字段对应的整数值,若字段不存在或无法转换为整数时返回0
     */
    public static int getInt(JSONObject jsonObject, String key) {
        return jsonObject.optInt(key, 0);
    }

    /**
     * 获取JSONObject中指定字段的布尔值
     *
     * @param jsonObject JSONObject对象
     * @param key        字段名
     * @return 字段对应的布尔值,若字段不存在或无法转换为布尔值时返回false
     */
    public static boolean getBoolean(JSONObject jsonObject, String key) {
        return jsonObject.optBoolean(key, false);
    }

    /**
     * 获取JSONObject中指定字段的JSONObject对象
     *
     * @param jsonObject JSONObject对象
     * @param key        字段名
     * @return 字段对应的JSONObject对象,若字段不存在或无法转换为JSONObject对象时返回null
     */
    public static JSONObject getJSONObject(JSONObject jsonObject, String key) {
        return jsonObject.optJSONObject(key);
    }

    /**
     * 获取JSONObject中指定字段的JSONArray对象
     *
     * @param jsonObject JSONObject对象
     * @param key        字段名
     * @return 字段对应的JSONArray对象,若字段不存在或无法转换为JSONArray对象时返回null
     */
    public static JSONArray getJSONArray(JSONObject jsonObject, String key) {
        return jsonObject.optJSONArray(key);
    }

    /**
     * 将对象转换为JSONObject对象
     *
     * @param obj 对象
     * @return 转换后的JSONObject对象
     */
    public static JSONObject objectToJson(Object obj) {
        return new JSONObject(obj);
    }

    /**
     * 将JSON字符串转换为JSONArray对象
     *
     * @param json JSON字符串
     * @return 转换后的JSONArray对象,若转换失败则返回null
     */
    public static JSONArray stringToJsonArray(String json) {
        try {
            return new JSONArray(json);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 将JSONArray对象转换为JSON字符串
     *
     * @param jsonArray JSONArray对象
     * @return 转换后的JSON字符串
     */
    public static String jsonArrayToString(JSONArray jsonArray) {
        return jsonArray.toString();
    }

    /**
     * 将JSONArray对象转换为指定类型的List集合
     *
     * @param jsonArray JSONArray对象
     * @param clazz     List集合的元素类型
     * @return 转换后的List集合
     */
    public static <T> List<T> jsonArrayToList(JSONArray jsonArray, Class<T> clazz) {
        List<T> list = new ArrayList<>();
        for (int i = 0; i < jsonArray.length(); i++) {
            T item = null;
            try {
                item = clazz.newInstance();
                if (item instanceof JSONObject) {
                    list.add((T) jsonArray.getJSONObject(i));
                } else {
                    list.add((T) jsonArray.get(i));
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return list;
    }

    /**
     * 将List集合转换为JSONArray对象
     *
     * @param list List集合
     * @return 转换后的JSONArray对象
     */
    public static JSONArray listToJsonArray(List<?> list) {
        JSONArray jsonArray = new JSONArray();
        for (Object obj : list) {
            jsonArray.put(objectToJson(obj));
        }
        return jsonArray;
    }

        /**
     **将json字符串转换为对象集合
     * @param jsonString
     * @param valueType
     * @return java.util.List<T>
     * @author yinqi
     * @create 2023/12/16 15:49
     **/
    public static <T> List<T> jsonToList(String jsonString, Class<T> valueType) throws IOException {
        if (StringUtils.isBlank(jsonString)){
            return null;
        }
        ObjectMapper objectMapper = new ObjectMapper();
        return objectMapper.readValue(jsonString, objectMapper.getTypeFactory().constructCollectionType(List.class, valueType));
    }

    /**
     **将json字符串中的旧字符串替换成新字符串
     * @param jsonString json字符串
     * @param oldString 旧字符串
     * @param newString 新字符串
     * @return java.lang.String
     * @author yinqi
     * @create 2024/3/26 14:41
     **/
    public static String replaceStringInJson(String jsonString, String oldString, String newString) {
        try {
            // 将json字符串转换为JSONObject对象
            JSONObject json = new JSONObject(jsonString);

            // 使用递归函数进行替换
            replaceStringInJson(json, oldString, newString);

            // 返回更新后的json字符串
            return json.toString();
        } catch (JSONException e) {
            e.printStackTrace();
        }

        // 如果发生异常,则返回原始的json字符串
        return jsonString;
    }

    /**
     **将json对象中的旧字符串替换成新字符串
     * @param object JSONObject或JSONArray
     * @param oldString 旧字符串
     * @param newString 新字符串
     * @return void
     * @author yinqi
     * @create 2024/3/26 14:21
     **/
    public static void replaceStringInJson(Object object, String oldString, String newString) {
        if (object instanceof JSONObject) {
            JSONObject json = (JSONObject) object;
            // 遍历JSONObject的所有键
            for (String key : json.keySet()) {
                Object value = json.get(key);
                // 如果键对应的值是字符串类型,则尝试替换旧字符串为新字符串
                if (value instanceof String) {
                    String oldValue = (String) value;
                    String newValue = oldValue.replaceAll(oldString, newString);
                    // 更新键对应的值为新字符串
                    json.putOpt(key, newValue);
                }else {
                    replaceStringInJson(value, oldString, newString);
                }
            }
        } else if (object instanceof JSONArray) {
            JSONArray jsonArray = (JSONArray) object;
            for (int i = 0; i < jsonArray.size(); i++) {
                Object value = jsonArray.get(i);
                replaceStringInJson(value, oldString, newString);
            }
        }
    }
}
 

数据转换工具类

package com.daasan.common.util;

import cn.hutool.core.collection.CollectionUtil;
import org.apache.commons.beanutils.BeanUtils;
import org.springframework.cglib.beans.BeanMap;
import org.springframework.util.Assert;

import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 实体工具类
 *
 * @author yinqi
 */
public class BeanUtil extends org.springframework.beans.BeanUtils {

    /**
     * 实例化对象
     *
     * @param clazz 类
     * @param <T>   泛型标记
     * @return 对象
     */
    public static <T> T newInstance(Class<T> clazz) {
        return instantiateClass(clazz);
    }

    /**
     * 将单个源对象转换为目标类对象
     * @param source 源对象
     * @param targetClass 目标类Class
     * @return 转换后的目标类对象
     */
    public static <S, T> T convert(S source, Class<T> targetClass) {
        if (source == null) {
            return null;
        }
        try {
            T target = targetClass.getDeclaredConstructor().newInstance();
            if (source instanceof Map) { // 处理 JSONObject 等 Map 类型
                BeanUtils.populate(target, (Map<String, ?>) source);
            } else {
                org.springframework.beans.BeanUtils.copyProperties(source, target);
            }
            return target;
        } catch (Exception e) {
            throw new RuntimeException("转换失败", e);
        }
    }

    /**
     * 将源列表转换为目标类列表
     * @param sourceList 源列表
     * @param targetClass 目标类Class
     * @return 转换后的目标类列表
     */
    public static <S, T> List<T> convertToList(List<S> sourceList, Class<T> targetClass) {
        if(CollectionUtil.isEmpty(sourceList)){
            return Collections.emptyList();
        }
        return sourceList.stream()
                .map(source -> convert(source, targetClass))
                .collect(Collectors.toList());
    }

    /**
     * @Description 将对象转换为Map,并去除值为null的属性
     * @Date  2025/3/5 19:15
     * @Param obj
     * @Return java.util.Map<java.lang.String,java.lang.Object>
     **/
    public static Map<String, Object> beanToMap(Object obj) {
        if (obj == null) {
            return null;
        }

        Map<String, Object> resultMap = new HashMap<>();
        Field[] fields = obj.getClass().getDeclaredFields();

        for (Field field : fields) {
            field.setAccessible(true);

            try {
                Object value = field.get(obj);
                if (value != null) {
                    resultMap.put(field.getName(), value);
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }

        return resultMap;
    }

    /**
     * 检查是否为数组或List类型
     * @param val
     * @return
     */
    public static Boolean checkListOrArr(Object val) {
        if (val == null) return false;

        // 增强数组检测:支持原始类型数组和对象数组
        boolean isArray = val.getClass().isArray();

        // 增强List检测:支持所有List子类/实现类
        boolean isList = List.class.isInstance(val);

        return isArray || isList;
    }

    /**
     * 实例化对象
     *
     * @param clazzStr 类名
     * @param <T>      泛型标记
     * @return 对象
     */
    public static <T> T newInstance(String clazzStr) {
        try {
            Class<?> clazz = Class.forName(clazzStr);
            return (T) newInstance(clazz);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取Bean的属性
     *
     * @param bean         bean
     * @param propertyName 属性名
     * @return 属性值
     */
    public static Object getProperty(Object bean, String propertyName) {
        Assert.notNull(bean, "bean is null...");
        return BeanMap.create(bean).get(propertyName);
    }

    /**
     * 设置Bean属性
     *
     * @param bean         bean
     * @param propertyName 属性名
     * @param value        属性值
     */
    public static void setProperty(Object bean, String propertyName, Object value) {
        Assert.notNull(bean, "bean is null...");
        BeanMap.create(bean).put(propertyName, value);
    }


    /**
     * 将对象装成map形式
     *
     * @param bean 源对象
     * @return {Map}
     */
    public static Map<String, Object> toMap(Object bean) {
        return BeanMap.create(bean);
    }

    /**
     * 将map 转为 bean
     *
     * @param beanMap   map
     * @param valueType 对象类型
     * @param <T>       泛型标记
     * @return {T}
     */
    public static <T> T toBean(Map<String, Object> beanMap, Class<T> valueType) {
        T bean = BeanUtil.newInstance(valueType);
        BeanMap.create(bean).putAll(beanMap);
        return bean;
    }

    public static Field[] getAllFields(Object object) {
        Class clazz = object.getClass();
        List<Field> fieldList = new ArrayList<>();
        while (clazz != null) {
            fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));
            clazz = clazz.getSuperclass();
        }
        Field[] fields = new Field[fieldList.size()];
        fieldList.toArray(fields);
        return fields;
    }

}
package com.daasan.common.util;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 *         List<SystemLogs> systemLogsList = ...;
 *         List<SystemLogs> tree = TreeUtils.buildTree(
 *                 systemLogsList,
 *                 SystemLogs::getId,
 *                 SystemLogs::getParentId,
 *                 (parent, children) -> parent.setChildren(children),
 *                 node -> node.getParentId() == null
 *         );
 *
 * @Description 构建树结构
 * @Date  2025/3/7 17:07
 **/
public class TreeUtils {
    
    /**
     * 构建树形结构(支持多根节点)
     * @param list 原始数据列表
     * @param idExtractor 获取ID的方法引用(如SystemLogs::getId)
     * @param parentIdExtractor 获取父ID的方法引用(如SystemLogs::getParentId)
     * @param childrenSetter 设置子节点的方法引用(如SystemLogs::setChildren)
     * @param isRoot 判断根节点的条件(如parentId为null)
     * @return 树形结构列表
     */
    public static <T> List<T> buildTree(List<T> list,
                                      Function<T, ?> idExtractor,
                                      Function<T, ?> parentIdExtractor,
                                      ChildrenSetter<T> childrenSetter,
                                      Function<T, Boolean> isRoot) {
        
        // 分组优化:按父ID分组,使用 HashMap 来允许 null 键值
        Map<Object, List<T>> parentGroup = list.stream()
                .collect(HashMap::new,
                        (map, item) -> {
                            Object parentId = parentIdExtractor.apply(item);
                            map.computeIfAbsent(parentId, k -> new ArrayList<>()).add(item);
                        },
                        HashMap::putAll);
        
        return list.stream()
                .filter(isRoot::apply) // 过滤根节点
                .peek(root -> buildChildren(root, parentGroup, idExtractor, childrenSetter))
                .collect(Collectors.toList());
    }

    private static <T> void buildChildren(T node,
                                         Map<Object, List<T>> parentGroup,
                                         Function<T, ?> idExtractor,
                                         ChildrenSetter<T> childrenSetter) {
        Object nodeId = idExtractor.apply(node);
        List<T> children = parentGroup.getOrDefault(nodeId, Collections.emptyList());
        childrenSetter.setChildren(node, children); // 设置子节点(网页6的BiConsumer思想)
        children.forEach(child -> buildChildren(child, parentGroup, idExtractor, childrenSetter));
    }

    @FunctionalInterface
    public interface ChildrenSetter<T> {
        void setChildren(T parent, List<T> children);
    }
}
package com.daasan.common.util;

import com.daasan.common.base.model.AntTreeModel;
import com.daasan.common.base.model.TreeNode;
import lombok.experimental.UtilityClass;

import java.util.ArrayList;
import java.util.List;

/**
 * 树模型构建工具类
 *
 * @author Daasan
 */
@UtilityClass
public class TreeUtil {

    /**
     * 两层循环实现建树
     *
     * @param treeNodes 节点集合
     * @param root      根节点
     * @return List
     */
    public <T extends TreeNode> List<T> buildByLoop(List<T> treeNodes, String root) {
        List<T> trees = new ArrayList<>();
        for (T treeNode : treeNodes) {
            if (root.equals(treeNode.getParentId())) {
                trees.add(treeNode);
            }

            for (T it : treeNodes) {
                if (it.getParentId().equals(treeNode.getId())) {
                    if (treeNode.getChildren() == null) {
                        treeNode.setChildren(new ArrayList<>());
                    }
                    treeNode.add(it);
                }
            }
        }
        return trees;
    }

    /**
     * 使用递归方法建树
     *
     * @param treeNodes 节点集合
     * @param root      根节点
     * @return List
     */
    public <T extends TreeNode> List<T> buildByRecursive(List<T> treeNodes, String root) {
        List<T> trees = new ArrayList<>();
        for (T treeNode : treeNodes) {
            if (root.equals(treeNode.getParentId())) {
                trees.add(findChildren(treeNode, treeNodes));
            }
        }
        return trees;
    }

    /**
     * 递归查找子节点
     *
     * @param treeNode  父节点
     * @param treeNodes 节点结合
     * @return TreeNode
     */
    private <T extends TreeNode> T findChildren(T treeNode, List<T> treeNodes) {
        for (T it : treeNodes) {
            if (treeNode.getId().equals(it.getParentId())) {
                if (treeNode.getChildren() == null) {
                    treeNode.setChildren(new ArrayList<>());
                }
                treeNode.add(findChildren(it, treeNodes));
            }
        }
        return treeNode;
    }

    /**
     * AntDesign Tree搜索后台实现
     *
     * @param key   搜索关键字
     * @param roots 根节点集合
     */
    public List<AntTreeModel> searchNode(String key, List<AntTreeModel> roots) {
        List<AntTreeModel> outs = new ArrayList<>();
        for (AntTreeModel root : roots) {
            AntTreeModel out = searchNode(key, root);
            if (out != null) {
                outs.add(out);
            }
        }
        return outs;
    }

    /**
     * AntDesign Tree搜索后台实现
     *
     * @param key  搜索关键字
     * @param root 根节点
     */
    public AntTreeModel searchNode(String key, AntTreeModel root) {
        AntTreeModel out = null;
        boolean isSetFatherNodeValue = false;
        // 节点包含key中的信息,就赋值
        String title = root.getTitle();
        if (title != null && !title.isEmpty() && title.toLowerCase().contains(key.toLowerCase())) {
            out = new AntTreeModel();
            out.setTitle(title);
            out.setId(root.getId());
            isSetFatherNodeValue = true;
        }

        List<TreeNode> nodes = root.getChildren();
        if (nodes != null && nodes.size() > 0) {
            List<AntTreeModel> ns = new ArrayList<>();
            for (TreeNode child : nodes) {
                AntTreeModel node = searchNode(key, (AntTreeModel) child);
                if (node != null) {
                    ns.add(node);
                    if (!isSetFatherNodeValue) {
                        out = new AntTreeModel();
                        out.setTitle(root.getTitle());
                        out.setId(root.getId());
                    }
                }
            }
            if (ns.size() > 0) {
                out.addAll(ns);
            }
        }

        return out;
    }

    /**
     * AntDesign Tree搜索后台实现
     *
     * @param key  搜索关键字
     * @param root 根节点
     */
    public AntTreeModel searchNodeAllChild(String key, AntTreeModel root) {
        AntTreeModel out = null;
        boolean isSetFatherNodeValue = false;
        // 节点包含key中的信息,就赋值
        String title = root.getTitle();
        if (title != null && !title.isEmpty() && title.toLowerCase().contains(key.toLowerCase())) {
            out = new AntTreeModel();
            out.setTitle(title);
            out.setId(root.getId());
            isSetFatherNodeValue = true;
        }

        List<TreeNode> nodes = root.getChildren();
        if (nodes != null && nodes.size() > 0) {
            List<AntTreeModel> ns = new ArrayList<>();
            for (TreeNode child : nodes) {
                if(isSetFatherNodeValue) {
                    // 添加所有子节点
                    ns.add((AntTreeModel) child);
                } else {
                    AntTreeModel node = searchNodeAllChild(key, (AntTreeModel) child);
                    if (node != null) {
                        ns.add(node);
                        out = new AntTreeModel();
                        out.setTitle(root.getTitle());
                        out.setId(root.getId());
                    }
                }
            }
            if (ns.size() > 0) {
                out.addAll(ns);
            }
        }

        return out;
    }
}

集合工具类

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * 集合工具类
 */
public class CollectionUtils {

    /**
     **取两个集合的交集
     * @param listOne 集合1
     * @param listTwo 集合2
     * @return java.util.List<java.lang.String>
     * @author yinqi
     * @create 2023/12/5 15:02
     **/
    public static List<String> getIntersection(List<String> listOne, List<String> listTwo) {
        List<String> intersectionList = new ArrayList<>();
        if (CollectionUtil.isNotEmpty(listOne) && CollectionUtil.isNotEmpty(listTwo)) {
            for (String fileId : listOne) {
                if (listTwo.contains(fileId)) {
                    intersectionList.add(fileId);
                }
            }
        }
        return intersectionList;
    }

    /**
     **合并两个集合的元素
     * @param listOne
     * @param listTwo
     * @return java.util.List<T>
     * @author yinqi
     * @create 2023/12/5 16:09
     **/
    public static <T> List<T> mergeLists(List<T> listOne, List<T> listTwo) {
        List<T> result = new ArrayList<>();
        if (CollectionUtil.isNotEmpty(listOne)) {
            result.addAll(listOne);
        }
        if (CollectionUtil.isNotEmpty(listTwo)) {
            result.addAll(listTwo);
        }
        return result;
    }

    /**
     **去重复
     * @param list
     * @return java.util.List<java.lang.String>
     * @author yinqi
     * @create 2023/12/20 19:44
     **/
    public static List<String> removeDuplicates(List<String> list) {
        Set<String> set = new HashSet<>(list);
        return new ArrayList<>(set);
    }

    /**
     * 将allList剔除掉existList后返回
     * @param allList
     * @param existList
     * @return java.util.List<java.lang.String>
     * @author yinqi
     * @create 2023/12/21 10:16
     **/
    public static List<String> removeExistingElements(List<String> allList, List<String> existList) {
        List<String> resultList = new ArrayList<>(allList);
        resultList.removeAll(existList);
        return resultList;
    }

    /**
     * 获取两个List的并集
     *
     * @param list1 第一个List
     * @param list2 第二个List
     * @return 两个List的并集
     */
    public static <T> List<T> union(List<T> list1, List<T> list2) {
        Set<T> set = new HashSet<>();
        set.addAll(list1);
        set.addAll(list2);
        return new ArrayList<>(set);
    }

    /**
     * 获取两个List的交集
     *
     * @param list1 第一个List
     * @param list2 第二个List
     * @return 两个List的交集
     */
    public static <T> List<T> intersection(List<T> list1, List<T> list2) {
        List<T> result = new ArrayList<>();
        for (T element : list1) {
            if (list2.contains(element)) {
                result.add(element);
            }
        }
        return result;
    }

    /**
     * 去除List中的重复元素
     *
     * @param list 原始List
     * @return 去除重复元素后的List
     */
    public static <T> List<T> removeDuplicates(List<T> list) {
        Set<T> set = new HashSet<>();
        List<T> result = new ArrayList<>();
        for (T element : list) {
            if (set.add(element)) {
                result.add(element);
            }
        }
        return result;
    }

    /**
     * 实体转Map
     * @param entity 实体
     * @return map
     */
    public static Map<String, Object> entityToMap(Object entity) {
        Map<String, Object> map = new HashMap<>(20);
        try {
            // 获取实体对象的class
            Class<?> clazz = entity.getClass();
            // 遍历实体对象的所有字段
            for (Field field : clazz.getDeclaredFields()) {
                // 设置字段可访问
                field.setAccessible(true);
                // 获取字段名和字段值,并放入map中
                String fieldName = field.getName();
                Object fieldValue = field.get(entity);
                if ("sex".equalsIgnoreCase(fieldName)) {
                    map.put(fieldName, fieldValue != null && Str.isNotEmpty(fieldValue.toString()) ? ("0".equalsIgnoreCase(fieldValue.toString()) ? "男" : "女") : "未知");
                } else {
                    map.put(fieldName, fieldValue);
                }
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return map;
    }

    /**
     **将List<String>集合转为以逗号隔开的字符串,如:
     * '张三', '李四', '王五'
     * @param list
     * @return java.lang.String
     * @author yinqi
     * @create 2023/11/23 15:19
     **/
    public static String convertListToString(List<String> list) {
        if (CollectionUtil.isEmpty(list)){
            return "";
        }
        return list.stream().map(s -> "'" + s + "'").collect(Collectors.joining(", "));
    }


}
 

枚举类

package com.daasan.modules.trm.enums;

import com.daasan.modules.trm.vo.EnumKeyValVo;
import lombok.Getter;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 流程操作日志 枚举类: 节点名称
 * @author yinqi
 * @Date  2025/3/15 16:49
 **/
@Getter
public enum ProcessLogNodeEnum {
    AWAIT_SUBMIT("await_submit", "待提交"),
    DEMAND_SUBMIT("demand_submit", "需求提交"),
    DEMAND_REVIEW("demand_review", "需求审核"),
    DEMAND_REVIEW_TRANSFER("demand_review_transfer", "需求审核转办"),
    FIXTURE_SCHEDULING("fixture_scheduling", "治具排程"),
    TASK_ASSIGNMENT("task_assignment", "任务分配"),
    TASK_ASSIGNMENT_TRANSFER("task_assignment_transfer", "任务分配转办"),
    TASK_EXECUTION("task_execution", "任务执行"),
    TASK_EXECUTION_TRANSFER("task_execution_transfer", "任务执行转办"),
    FIXTURE_PURCHASE_REVIEW("fixture_purchase_review", "治具请购需求审核"),
    FIXTURE_PURCHASE_REVIEW_TRANSFER("fixture_purchase_review_transfer", "治具请购需求审核转办"),
    FIXTURE_PURCHASE("fixture_purchase", "治具采购"),
    FIXTURE_PURCHASE_TRANSFER("fixture_purchase_transfer", "治具采购转办"),
    FIXTURE_PROCESSING("fixture_processing", "治具加工"),
    PART_INSPECTION("part_inspection", "零件入库检验"),
    FIXTURE_PART_STORAGE("fixture_part_inbound", "治具零件入库"),
    FIXTURE_INSPECTION("fixture_inspection", "治具检验"),
    FIXTURE_INSPECTION_TRANSFER("fixture_inspection_transfer", "治具检验转办"),
    FIXTURE_STORAGE("fixture_inbound", "治具入库"),
    PROCESS_COMPLETE("process_complete", "流程结束");

    private final String value;
    private final String desc;

    ProcessLogNodeEnum(String value, String desc) {
        this.value = value;
        this.desc = desc;
    }

    /**
     * 根据value获取枚举值
     * @Date 2025/3/15 16:45
     * @Param [value]
     * @Return com.daasan.modules.trm.enums.ProcessNodeEnum
     **/
    public static ProcessLogNodeEnum fromValue(String value) {
        for (ProcessLogNodeEnum node : ProcessLogNodeEnum.values()) {
            if (node.getValue().equalsIgnoreCase(value)) {
                return node;
            }
        }
        throw new IllegalArgumentException("无效的value: " + value);
    }

    /**
     * 根据desc获取枚举值
     * @Date 2025/3/15 16:46
     * @Param [desc]
     * @Return com.daasan.modules.trm.enums.ProcessNodeEnum
     **/
    public static ProcessLogNodeEnum fromDesc(String desc) {
        for (ProcessLogNodeEnum node : ProcessLogNodeEnum.values()) {
            if (node.getDesc().equals(desc)) {
                return node;
            }
        }
        throw new IllegalArgumentException("无效的desc: " + desc);
    }

    /**
     * 返回所有的枚举key-val
     * @Date 2025/3/18 15:36
     * @Param []
     * @Return java.util.Map<java.lang.String, java.lang.String>
     **/
    public static Map<String, String> getAllProcessLogNode() {
        Map<String, String> dataMap = new HashMap<>();
        for (ProcessLogNodeEnum status : values()) {
            dataMap.put(status.getValue(), status.getDesc());
        }
        return dataMap;
    }

    /**
     * 返回所有的枚举key-val
     * @Date 2025/3/18 15:36
     * @Param []
     * @Return java.util.Map<java.lang.String, java.lang.String>
     **/
    public static List<EnumKeyValVo> getAllProcessLogNodeList() {
        return Arrays.stream(values())
                .map(status -> new EnumKeyValVo().setCode(status.getValue()).setName(status.getDesc()))
                .collect(Collectors.toList());
    }
}
import lombok.Getter;

/**
 * @author yinqi
 * @description
 * @create 2023/11/28 8:53
 **/
@Getter
public enum OrgFolderEnum {

    org("org", "机构"),
    user("user", "用户"),
    folder("folder", "文件夹"),
    tag("tag", "标签"),
    tags("tags", "标签目录")
    ;


    private String value;
    private String desc;

    OrgFolderEnum(String value, String desc) {
        this.value = value;
        this.desc = desc;
    }

    public String getValue() {
        return value;
    }

    public String getDesc() {
        return desc;
    }
}
import com.daasan.common.util.Str;
import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * 关键词布控状态
 * @author 25129
 * @date 2023/09/26 11:00:06
 * @description OA审批状态
 */
@Getter
@AllArgsConstructor
public enum KeywordStatus {

    /** 未开始*/
    NOSTART("NOSTART", "未开始"),
    /** 正在执行*/
    EXECUTING("EXECUTING", "正在执行"),
    /** 执行失败*/
    FAILURE("FAILURE", "执行失败"),
    /** 已完成*/
    COMPLETED("COMPLETED", "已完成"),
    ;

    /**
     * code编码
     */
    private final String code;

    /**
     * 描述
     */
    private final String msg;

    /**
     * 根据Code获取Text
     * @param code 编码
     * @return String
     */
    public String getText(String code) {
        if (Str.isEmpty(code)) {
            return null;
        }
        for (KeywordStatus commonality : KeywordStatus.values()) {
            if (code.equalsIgnoreCase(commonality.getCode())) {
                return commonality.getMsg();
            }
        }
        return null;
    }

}

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 **文件操作类型
 * @author yinqi
 * @create 2023/11/24 16:38
 **/
@Getter
@AllArgsConstructor
public enum FileOperateTypeEnum {

    UPLOAD("1", "上传", "通过上传创建该文件"),
	UPLOAD_DIRECTORY("2", "上传文件夹", "通过文件夹上传创建该文件"),
    CREATE_EMPTY("3", "创建空文件", "创建一个空的文件"),
    CREATE_DIRECTORY("4", "新建文件夹", "创建一个文件夹"),
    REMOVE("5", "移动文件", "移动文件"),
    REMOVE_DIRECTORY("6", "移动目录", "移动目录"),
    COPY("7", "复制文件", "复制文件"),
    COPY_DIRECTORY("8", "复制文件夹", "复制文件夹"),
    OPEN("9", "打开查看", "打开查看文件解析、打开在线文档"),
    EDIT("10", "编辑", "编辑了文件"),
    DOWNLOAD("11", "下载", "下载了文件"),
	DELETE("12", "删除", "删除该文件"),
	DELETE_DIRECTORY("13", "删除目录", "删除该目录"),
	RENAME("14", "重命名", "重命名"),
    OFFLINE_DOWNLOAD("15", "离线下载", "离线下载"),
    DISK_UPLOAD_DIRECTORY("16", "硬盘导入文件夹", "硬盘导入文件夹"),
    DISK_UPLOAD_FILE("17", "硬盘导入文件", "硬盘导入文件"),
    UPDATE_NOTE("18", "修改描述说明", "修改描述说明"),
    UPDATE_TRANSLATE_TEXT("19", "修改译文", "修改译文"),
    RESETALL_TRANSLATE_TEXT("27", "重置全部译文", "重置全部译文"),
    UPDATE_TAG("20", "设置标签", "设置了标签"),
    DELETE_ANALYZE_INFO("21", "删除解析信息", "删除解析信息"),
    UPDATE_ANALYZE_INFO("25", "解析信息调整", "解析信息调整"),
    UPDATE_BATCH("22", "修改批次号", "修改批次号"),
    ADD_COMMENT("23", "添加评论", "添加评论"),
    DEL_COMMENT("24", "删除评论", "删除评论"),
    UPDATE_LANGUAGE("26", "语种纠错", "语种纠错"),
    GET_KEYWORD("28", "提取关键词", "提取关键词"),
    GET_USER_PROPERTY("29", "提取人员属性", "提取人员属性"),
    ADD_COMMONALITY("30", "加入中转站", "加入中转站"),
    UPLOAD_VERSION("31", "上传新版本", "上传新版本"),
    ADD_COLLECT("32", "添加收藏", "添加到收藏夹"),
    REMOVE_COLLECT("33", "取消收藏", "从收藏夹移出"),
    ADD_GROUP("34", "添加组", "添加组"),
    ;

    /**
     * code编码
     */
    private final String code;

    /**
     * 描述
     */
    private final String msg;

    /**
     * 描述
     */
    private final String note;

    /**
     * 根据Code获取Text
     * @param code 编码
     * @return String
     */
    public String getText(String code) {
        if (code == null) {
            return null;
        }
        for (FileOperateTypeEnum fileOperateTypeEnum : FileOperateTypeEnum.values()) {
            if (code.equals(fileOperateTypeEnum.getCode())) {
                return fileOperateTypeEnum.getMsg();
            }
        }
        return null;
    }

    public String getNote() {
        return this.note;
    }

    public String getNote(String sourceItem, String targetItem) {

        switch (this.code) {
            case "5":
                // 移动文件
            case "6":
                // 移动目录
                return "从【" + sourceItem + "】移动到了【" + targetItem + "】";
            case "7":
                // 复制文件
            case "8":
                // 复制文件夹
                return "从【" + sourceItem + "】复制到了【" + targetItem + "】";
            case "14":
                // 重命名
                return "从【" + sourceItem + "】重命名操作成【" + targetItem + "】";
            case "15":
                // 离线下载
                return "通过链接【" + sourceItem + "】创建文件";
            case "16":
                // 硬盘导入文件夹
                return "通过硬盘路径【" + sourceItem + "】创建文件夹";
            case "17":
                // 硬盘导入文件
                return "通过硬盘路径【" + sourceItem + "】创建文件";
            case "18":
                // 修改描述说明
                return "将描述说明从【" + sourceItem + "】修改成了【" + targetItem + "】";
            case "19":
                // 修改译文
                return "将译文从【" + sourceItem + "】修改成了【" + targetItem + "】";
            case "22":
                // 修改批次号
                return "将批次号从【" + sourceItem + "】修改成了【" + targetItem + "】";
        }
        return "";
    }

}

Sql工具类

package com.daasan.common.util;

import com.daasan.common.core.exception.BsException;
import lombok.extern.slf4j.Slf4j;

/**
 * sql注入处理工具类
 *
 * @author Daasan
 */
@Slf4j
public class SqlUtil {


    /**
     * 所有关键字过滤
     */
    final static String XSS_STR = "'|and |exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |;|or |+|,";

    /**
     * 条件关键字过滤
     */
    final static String CONDITION_XSS_STR = "exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |;|+|";

    /**
     * 报表部分的关键字
     */
    final static String REPORT_XSS_STR = "exec |insert |delete |update |drop |chr |mid |master |truncate |char |declare |;|+" +
            "|exec\n|insert\n|delete\n|update\n|drop\n|chr\n|mid\n|master\n|truncate\n|char\n|declare\n|";

    /**
     * sql注入过滤处理,遇到注入关键字抛异常
     *
     * @param value String
     */
    public static void filterContent(String value) {
        String[] xssArr = XSS_STR.split("\\|");
        doFilter(value, xssArr);
    }

    /**
     * sql注入过滤处理,遇到注入关键字抛异常
     *
     * @param values String Array
     */
    public static void filterContent(String... values) {
        String[] xssArr = XSS_STR.split("\\|");
        for (String value : values) {
            doFilter(value, xssArr);
        }
    }

    /**
     * 用于条件SQL参数,注入过滤
     * 去除and or 等条件参数过滤
     *
     * @param value String
     */
    public static void conditionFilterContent(String value) {
        String[] xssArr = CONDITION_XSS_STR.split("\\|");
        doFilter(value, xssArr);
    }

    /**
     * 执行过滤
     *
     * @param value  字符串
     * @param xssArr 关键字数组
     */
    private static void doFilter(String value, String[] xssArr) {
        if (value == null || "".equals(value)) {
            return;
        }
        // 统一转为小写
        value = value.toLowerCase();
        for (String s : xssArr) {
            if (value.contains(s)) {
                log.error("存在SQL注入关键词---> {}", s);
                log.error("可能存在SQL注入风险!---> {}", value);
                throw new BsException("可能存在SQL注入风险!--->" + value);
            }
        }
    }

    /**
     * 报表部分的SQL注入方法
     * @param value 字符串
     */
    public static void conditionFilterReport(String value) {
        String[] xssArr = REPORT_XSS_STR.split("\\|");
        doFilter(value, xssArr);
    }

}

统一异常处理

@ControllerAdvice 是一个 Spring 框架的注解,用于定义一个控制器增强类(controller advice class),它提供了一种方式来为所有控制器提供通用功能。这个注解通常用于全局处理异常、日志记录、权限验证等通用操作。

用途
以下是 @ControllerAdvice 注解的一些主要用途:
1.全局异常处理:你可以使用 @ExceptionHandler 注解来定义异常处理方法,这些方法将应用于所有控制器。
2.日志记录:你可以在 @ControllerAdvice 类中添加日志记录代码,用于记录所有请求的详细信息,如请求参数、返回值等。
3.权限验证:你可以添加权限验证逻辑,确保只有具有相应权限的用户才能访问特定的控制器或方法。
4.请求和响应处理:你可以在 @ControllerAdvice 类中修改请求数据或响应数据,例如,添加默认值、格式化数据等。
5.跨域处理:你可以在 @ControllerAdvice 类中配置跨域资源共享(CORS)的相关设置。
6.性能监控:你可以添加代码来监控所有请求的性能,如计算请求处理时间等。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ModelAttribute;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@ControllerAdvice
public class GlobalControllerAdvice {

    private static final Logger logger = LoggerFactory.getLogger(GlobalControllerAdvice.class);

    // 全局异常处理
    @ExceptionHandler(Exception.class)
    public String handleException(HttpServletRequest request, HttpServletResponse response, Exception ex) {
        logger.error("Exception occurred in request to " + request.getRequestURI(), ex);
        // 返回错误页面或者其他处理逻辑
        return "error";
    }

    // 全局请求日志记录
    @ModelAttribute
    public void logRequest(HttpServletRequest request) {
        logger.info("Request: " + request.getMethod() + " " + request.getRequestURI());
    }

    // 记录请求处理时间
    @ModelAttribute
    public void logPerformance(HttpServletRequest request, HttpServletResponse response) {
        long startTime = System.currentTimeMillis();
        request.setAttribute("startTime", startTime);

        response.addHeader("Request-Received-Time", String.valueOf(startTime));
    }

    // 计算请求处理时间并记录
    @ModelAttribute
    public void logPerformance(HttpServletRequest request, HttpServletResponse response) {
        long startTime = (Long) request.getAttribute("startTime");
        long endTime = System.currentTimeMillis();
        long elapsedTime = endTime - startTime;
        logger.info("Request processed in " + elapsedTime + " milliseconds");
    }
}

在Spring Boot中,可以通过使用@ControllerAdvice和@ExceptionHandler注解来实现统一的异常处理。下面是两个常用的示例:

需要注意的是,如果在Controller层面上已经使用了@ExceptionHandler注解处理了某些特定的异常情况,那么全局异常处理类中的处理方法将不会被调用。

  1. 创建一个全局异常处理类,使用@ControllerAdvice注解标记它。
  2. 在handleException方法中,可以根据具体的需求定义处理异常的逻辑,比如返回自定义的错误信息和特定的HTTP状态码。

  3. 当系统中发生任何未处理的Exception级别的异常时,Spring Boot会自动调用这个全局异常处理类中的handleException方法进行处理。

import com.common.core.api.R;
import com.common.core.exception.BsException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;

/**
 * 全局拦截异常处理器
 */
@RestControllerAdvice
public class GlobalExceptionHandler {

    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    /**
     * 拦截抛出的 bsException 异常
     * @param bsException
     * @param request
     * @return
     */
    @ExceptionHandler(BsException.class)
    public R<?> bsException(BsException bsException, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        bsException.printStackTrace();
        log.error("请求地址'{}',发生BsException异常:'{}'",requestURI,bsException.getMessage());
//        return R.failure("系统异常");
        return R.failure(bsException.getMessage());
    }

    /**
     * 拦截抛出的 runtimeException 异常
     * @param runtimeException
     * @param request
     * @return
     */
    @ExceptionHandler(RuntimeException.class)
    public R<?> runtimeException(RuntimeException runtimeException, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        runtimeException.printStackTrace();
        log.error("请求地址'{}',发生RuntimeException异常:'{}'",requestURI,runtimeException.getMessage());
        return R.failure("系统异常");
    }

    /**
     * 兜底方法,拦截抛出的所有异常
     * @param exception
     * @param request
     * @return
     */
    @ExceptionHandler(Exception.class)
    public R<?> exception(Exception exception, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        exception.printStackTrace();
        log.error("请求地址'{}',拦截到兜底异常:'{}'",requestURI,exception.getMessage());
        return R.failure("系统异常");
    }
}
@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleException(Exception ex) {
        // 处理异常的逻辑
        return new ResponseEntity<>("发生了异常:" + ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
    }
}
import com.demo.common.core.api.R;
import com.demo.common.core.exception.BsException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.io.IOException;
import java.util.regex.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 全局拦截异常处理器
 * @date 2024/3/7
 */
@RestControllerAdvice
public class GlobalExceptionHandler {

    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    /**
     * 定义正则表达式,匹配所有连续的汉字
     */
    Pattern pattern = Pattern.compile("[\\u4e00-\\u9fa5]+");

    /**
     * 拦截抛出的 bsException 异常
     * @param bsException
     * @param request
     * @return
     */
    @ExceptionHandler(BsException.class)
    public R<?> bsException(BsException bsException, HttpServletRequest request, HttpServletResponse response) {
        String requestURI = request.getRequestURI();
        bsException.printStackTrace();
        log.error("请求地址'{}',发生BsException异常:'{}'",requestURI,bsException.getMessage());
        // TODO 匹配 bsException.getMessage() 是否为汉字,如果是,那么返回汉字。不是汉字返回 系统异常
        String res = bsException.getMessage();
        Matcher matcher = pattern.matcher(res);
        if( !matcher.find() ) {
            res = "系统异常";
        }
        try {
            response.sendError(500);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return R.failure(res);
    }

    /**
     * 拦截抛出的 runtimeException 异常
     * @param runtimeException
     * @param request
     * @return
     */
    @ExceptionHandler(RuntimeException.class)
    public R<?> runtimeException(RuntimeException runtimeException, HttpServletRequest request, HttpServletResponse response) {
        String requestURI = request.getRequestURI();
        runtimeException.printStackTrace();
        log.error("请求地址'{}',发生RuntimeException异常:'{}'",requestURI,runtimeException.getMessage());

        try {
            response.sendError(400);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return R.failure("系统异常");
    }

    /**
     * 兜底方法,拦截抛出的所有异常
     * @param exception
     * @param request
     * @return
     */
    @ExceptionHandler(Exception.class)
    public R<?> exception(Exception exception, HttpServletRequest request, HttpServletResponse response) {
        String requestURI = request.getRequestURI();
        exception.printStackTrace();
        log.error("请求地址'{}',拦截到兜底异常:'{}'",requestURI,exception.getMessage());

        try {
            response.sendError(400);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return R.failure("系统异常");
    }

}

springBoot静态方法中从yml配置文件中获取参数

1.application-dev.yml中配置的参数如下:

chatmodel:
  #  是否关闭大语言模型。 false:调用大语言模型;  true:不调用大语言模型
  closeModel: true

2.添加配置类获取配置文件中的参数:

package com.modules.analyze.util;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "chatmodel")
public class TextConfig {
    // 项目名称
    private static boolean closeModel;

    public static boolean getCloseModel() {
        return closeModel;
    }

    /**
     * 但是发布到 linux 服务器 (k8s)后,发现其中有个值始终获取值为null。发现此值的get方法在set之前。原因目前未知。
     **如果遇到此问题或者获取值时为空,在set方法加入注解即可。
     * @param closeModel
     * @return void
     * @author yinqi
     * @create 2024/3/8 11:14
     **/
    @Value("${chatmodel.closeModel:false}")
    public void setCloseModel(boolean closeModel) {
        TextConfig.closeModel = closeModel;
    }
}

 3.在静态方法中使用该参数

package com.modules.analyze.util;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.daasan.common.util.Str;
import java.util.HashMap;
import java.util.Map;

/**
 * 调用大语言模型
 * @description
 **/
public class SendChatModelUtil {

    /**
     **向模型提示问题,返回模型给的回复
     * @param info 问题
     * @return java.lang.String 回复
     **/
    public static String sendChat(String info) {
        boolean closeModel = TextConfig.getCloseModel();
        System.out.println("测试---------------"+closeModel);
        System.out.println("调用大语言模型:"+info);
        if (closeModel){
            return "【调用大语言模型失败,请重试】";
        }
        String requestUrl = "http://127.0.0.1:8899";
        Map<String, String> headers = new HashMap<>(2);
        headers.put("Content-Type", "application/json");
        Map<String, Object> params = new HashMap<>(2);
        params.put("prompt", info);
        String result = "";
        try {
            result = HttpUtil.doPost(requestUrl, headers, params);
            JSONObject jsonObj = JSON.parseObject(result);
            result = jsonObj.get("response").toString();
        } catch (Exception e) {
            e.printStackTrace();
            System.err.println(result);
        }
        return Str.isNotEmpty(result) ? result : "【调用大语言模型失败,请重试】";
    }
}

java调用dos命令

引入依赖

    <dependency>  
        <groupId>com.jcraft</groupId>  
        <artifactId>jsch</artifactId>  
        <version>0.1.55</version> <!-- 使用时请检查是否有更新的版本 -->  
    </dependency> 
import com.jcraft.jsch.*;

import java.io.IOException;
import java.io.InputStream;

public class RemoteSSH {  
  
    private String host;  
    private String user;  
    private String password;  
  
    public RemoteSSH(String host, String user, String password) {
        this.host = host;  
        this.user = user;  
        this.password = password;  
    }  
  
    public void executeCommands(String... commands) throws JSchException, SftpException, IOException {
        JSch jsch = new JSch();  
        Session session = jsch.getSession(user, host, 22);  
        session.setPassword(password);  
  
        // 避免询问yes/no  
        java.util.Properties config = new java.util.Properties();  
        config.put("StrictHostKeyChecking", "no");  
        session.setConfig(config);  
  
        session.connect();  
  
        for (String command : commands) {  
            ChannelExec channelExec = (ChannelExec) session.openChannel("exec");  
            ((ChannelExec) channelExec).setCommand(command);  
  
            channelExec.setInputStream(null);  
            ((ChannelExec) channelExec).setErrStream(System.err);  
  
            InputStream in = channelExec.getInputStream();
            channelExec.connect();  
  
            byte[] tmp = new byte[1024];  
            while (true) {  
                while (in.available() > 0) {  
                    int i = in.read(tmp, 0, 1024);  
                    if (i < 0) break;  
                    System.out.print(new String(tmp, 0, i));  
                }
                if (channelExec.isClosed()) {  
                    if (in.available() > 0) continue;  
                    System.out.println("exit-status: " + channelExec.getExitStatus());  
                    break;  
                }  
                try {  
                    Thread.sleep(1000);  
                } catch (Exception ee) {  
                }  
            }
            channelExec.disconnect();
        }  
  
        session.disconnect();  
    }  
  
    public static void main(String[] args) {
        RemoteSSH remoteSSH = new RemoteSSH("127.1.1.1", "root", "root"); // 使用你的IP、用户名和密码

        String[] commands = {
                "pwd",
                "ifconfig"
        };
  
        try {  
            remoteSSH.executeCommands(commands);
        } catch (JSchException | SftpException e) {  
            e.printStackTrace();  
        } catch (IOException e) {
            e.printStackTrace();
        }
    }  
}

SpringBoot整合swagger使用doc.html接口文档

引入依赖:

        <!--添加Swagger3依赖-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-boot-starter</artifactId>
            <version>3.0.0</version>
        </dependency>
        <!--knife4j(接口文档)-->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <version>3.0.3</version>
        </dependency>

增加配置类:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
public class SwaggerConfig {

    @Bean
    public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2)
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.example.modules"))
                .paths(PathSelectors.any())
                .build()
                .apiInfo(apiInfo());
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("API Documentation")
                .description("REST API Documentation")
                .version("1.0.0")
                .build();
    }
}

给接口增加名称:

使用以下注解修改接口名称,不使用注解时默认以类名和方法名作为接口名称:

@Api(tags = "生产者")
@ApiOperation(value = "发送邮件", notes = "发送邮件")

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;

@Api(tags = "生产者")
@RestController
@RequestMapping("/producer")
public class ProducerTopic {
    @Resource
    private RabbitTemplate rabbitTemplate;

    @ApiOperation(value = "发送邮件", notes = "发送邮件")
    @GetMapping("/testSendEmail")
    public void testSendEmail() {
        for(int i = 0 ; i < 5 ; i ++) {
            String message = "email inform to user" + i;
            rabbitTemplate.convertAndSend(RabbitConfig.EXCHANGE_TOPIC_INFORM,"inform.email",message);
            System.out.println("Send Message is:'" + message + "'");
        }
    }
}

安全证书校验

使用truelicense-core工具和AES加密解密实现

Maven: de.schlichtherle.truelicense:truelicense-core:1.332

truelicense-core参考http://t.csdnimg.cn/dJNdq

加密解密工具类:

package com.example.modules.util;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;  
import javax.crypto.SecretKey;  
import javax.crypto.spec.SecretKeySpec;  
import java.security.SecureRandom;  
import java.util.Base64;  
  
public class EncryptionUtil {  
  
    // DES加密解密  
    public static String desEncrypt(String data, String key) throws Exception {  
        SecretKey secretKey = generateDESKey(key);  
        Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");  
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);  
        byte[] encrypted = cipher.doFinal(data.getBytes());  
        return Base64.getEncoder().encodeToString(encrypted);  
    }  
  
    public static String desDecrypt(String encryptedData, String key) throws Exception {  
        SecretKey secretKey = generateDESKey(key);  
        Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");  
        cipher.init(Cipher.DECRYPT_MODE, secretKey);  
        byte[] original = cipher.doFinal(Base64.getDecoder().decode(encryptedData));  
        return new String(original);  
    }  
  
    private static SecretKey generateDESKey(String key) throws Exception {  
        byte[] keyBytes = key.getBytes();  
        SecretKeySpec keySpec = new SecretKeySpec(keyBytes, 0, 8, "DES");  
        return keySpec;  
    }  
  
    // AES加密解密  
    public static String aesEncrypt(String data, String key) throws Exception {  
        SecretKey secretKey = generateAESKey(key);  
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");  
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);  
        byte[] encrypted = cipher.doFinal(data.getBytes());  
        return Base64.getEncoder().encodeToString(encrypted);  
    }  
  
    public static String aesDecrypt(String encryptedData, String key) throws Exception {  
        SecretKey secretKey = generateAESKey(key);  
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");  
        cipher.init(Cipher.DECRYPT_MODE, secretKey);  
        byte[] original = cipher.doFinal(Base64.getDecoder().decode(encryptedData));  
        return new String(original);  
    }  
  
    private static SecretKey generateAESKey(String key) throws Exception {  
        byte[] keyBytes = key.getBytes();  
        SecretKeySpec keySpec = new SecretKeySpec(keyBytes, 0, 16, "AES");  
        return keySpec;  
    }  
  
    public static void main(String[] args) {  
        try {  
            String originalText = "Hello, World!";  
            String desKey = "12345678"; // DES key must be 8 bytes  
            String aesKey = "1234567890123456"; // AES key must be 16 bytes  
  
            String desEncrypted = desEncrypt(originalText, desKey);  
            String desDecrypted = desDecrypt(desEncrypted, desKey);  
  
            String aesEncrypted = aesEncrypt(originalText, aesKey);  
            String aesDecrypted = aesDecrypt(aesEncrypted, aesKey);  
  
            System.out.println("DES Original: " + originalText);  
            System.out.println("DES Encrypted: " + desEncrypted);  
            System.out.println("DES Decrypted: " + desDecrypted);  
  
            System.out.println("AES Original: " + originalText);  
            System.out.println("AES Encrypted: " + aesEncrypted);  
            System.out.println("AES Decrypted: " + aesDecrypted);  
  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值