T cast(Object obj)
+ {
+ return (T) obj;
+ }
+
+ /**
+ * 数字左边补齐0,使之达到指定长度。注意,如果数字转换为字符串后,长度大于size,则只保留 最后size个字符。
+ *
+ * @param num 数字对象
+ * @param size 字符串指定长度
+ * @return 返回数字的字符串格式,该字符串为指定长度。
+ */
+ public static final String padl(final Number num, final int size)
+ {
+ return padl(num.toString(), size, '0');
+ }
+
+ /**
+ * 字符串左补齐。如果原始字符串s长度大于size,则只保留最后size个字符。
+ *
+ * @param s 原始字符串
+ * @param size 字符串指定长度
+ * @param c 用于补齐的字符
+ * @return 返回指定长度的字符串,由原字符串左补齐或截取得到。
+ */
+ public static final String padl(final String s, final int size, final char c)
+ {
+ final StringBuilder sb = new StringBuilder(size);
+ if (s != null)
+ {
+ final int len = s.length();
+ if (s.length() <= size)
+ {
+ for (int i = size - len; i > 0; i--)
+ {
+ sb.append(c);
+ }
+ sb.append(s);
+ }
+ else
+ {
+ return s.substring(len - size, len);
+ }
+ }
+ else
+ {
+ for (int i = size; i > 0; i--)
+ {
+ sb.append(c);
+ }
+ }
+ return sb.toString();
+ }
+}
diff --git a/common/src/main/java/com/yyy/common/utils/common/Threads.java b/common/src/main/java/com/yyy/common/utils/common/Threads.java
new file mode 100644
index 0000000..fbb7014
--- /dev/null
+++ b/common/src/main/java/com/yyy/common/utils/common/Threads.java
@@ -0,0 +1,94 @@
+package com.yyy.common.utils.common;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.*;
+
+/**
+ * 线程相关工具类.
+ */
+public class Threads
+{
+ private static final Logger logger = LoggerFactory.getLogger(Threads.class);
+
+ /**
+ * sleep等待,单位为毫秒
+ */
+ public static void sleep(long milliseconds)
+ {
+ try
+ {
+ Thread.sleep(milliseconds);
+ }
+ catch (InterruptedException e)
+ {
+ return;
+ }
+ }
+
+ /**
+ * 停止线程池
+ * 先使用shutdown, 停止接收新任务并尝试完成所有已存在任务.
+ * 如果超时, 则调用shutdownNow, 取消在workQueue中Pending的任务,并中断所有阻塞函数.
+ * 如果仍人超時,則強制退出.
+ * 另对在shutdown时线程本身被调用中断做了处理.
+ */
+ public static void shutdownAndAwaitTermination(ExecutorService pool)
+ {
+ if (pool != null && !pool.isShutdown())
+ {
+ pool.shutdown();
+ try
+ {
+ if (!pool.awaitTermination(120, TimeUnit.SECONDS))
+ {
+ pool.shutdownNow();
+ if (!pool.awaitTermination(120, TimeUnit.SECONDS))
+ {
+ logger.info("Pool did not terminate");
+ }
+ }
+ }
+ catch (InterruptedException ie)
+ {
+ pool.shutdownNow();
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+
+ /**
+ * 打印线程异常信息
+ */
+ public static void printException(Runnable r, Throwable t)
+ {
+ if (t == null && r instanceof Future>)
+ {
+ try
+ {
+ Future> future = (Future>) r;
+ if (future.isDone())
+ {
+ future.get();
+ }
+ }
+ catch (CancellationException ce)
+ {
+ t = ce;
+ }
+ catch (ExecutionException ee)
+ {
+ t = ee.getCause();
+ }
+ catch (InterruptedException ie)
+ {
+ Thread.currentThread().interrupt();
+ }
+ }
+ if (t != null)
+ {
+ logger.error(t.getMessage(), t);
+ }
+ }
+}
diff --git a/common/src/main/java/com/yyy/common/utils/eam/DocumentsUtil.java b/common/src/main/java/com/yyy/common/utils/eam/DocumentsUtil.java
new file mode 100644
index 0000000..70cdf9c
--- /dev/null
+++ b/common/src/main/java/com/yyy/common/utils/eam/DocumentsUtil.java
@@ -0,0 +1,4 @@
+package com.yyy.common.utils.eam;
+
+public class DocumentsUtil {
+}
diff --git a/common/src/main/java/com/yyy/common/utils/eam/ResidualValueCalculator.java b/common/src/main/java/com/yyy/common/utils/eam/ResidualValueCalculator.java
new file mode 100644
index 0000000..9cd433d
--- /dev/null
+++ b/common/src/main/java/com/yyy/common/utils/eam/ResidualValueCalculator.java
@@ -0,0 +1,71 @@
+package com.yyy.common.utils.eam;
+
+import java.math.BigDecimal;
+
+public class ResidualValueCalculator {
+ // 年限平均法
+ public static BigDecimal calculateResidualValueByAverageLife(BigDecimal originalValue, int usefulLife, int yearsDepreciated) {
+ BigDecimal depreciationPerYear = originalValue.divide(BigDecimal.valueOf(usefulLife), 2, BigDecimal.ROUND_HALF_UP);
+ BigDecimal accumulatedDepreciation = depreciationPerYear.multiply(BigDecimal.valueOf(yearsDepreciated));
+ BigDecimal residualValue = originalValue.subtract(accumulatedDepreciation);
+ return residualValue;
+ }
+
+ // 按月折旧的年限平均法
+ public static BigDecimal calculateMonthlyDepreciationByAverageLife(BigDecimal originalValue, int usefulLifeInMonths, int monthsDepreciated) {
+ BigDecimal depreciationPerMonth = originalValue.divide(BigDecimal.valueOf(usefulLifeInMonths), 2, BigDecimal.ROUND_HALF_UP);
+ BigDecimal accumulatedDepreciation = depreciationPerMonth.multiply(BigDecimal.valueOf(monthsDepreciated));
+ BigDecimal residualValue = originalValue.subtract(accumulatedDepreciation);
+ return residualValue;
+ }
+
+ // 工作量法
+ public static BigDecimal calculateResidualValueByUnitsOfWork(BigDecimal originalValue, BigDecimal totalUnitsOfWork, BigDecimal unitsOfWorkDone) {
+ BigDecimal depreciationPerUnit = originalValue.divide(totalUnitsOfWork, 2, BigDecimal.ROUND_HALF_UP);
+ BigDecimal accumulatedDepreciation = depreciationPerUnit.multiply(unitsOfWorkDone);
+ BigDecimal residualValue = originalValue.subtract(accumulatedDepreciation);
+ return residualValue;
+ }
+
+ // 双倍余额递减法
+ public static BigDecimal calculateResidualValueByDoubleDecliningBalance(BigDecimal originalValue, BigDecimal depreciationRate, int usefulLife, int yearsDepreciated) {
+ BigDecimal bookValue = originalValue;
+ for (int i = 1; i <= yearsDepreciated; i++) {
+ BigDecimal depreciationExpense = bookValue.multiply(depreciationRate).setScale(2, BigDecimal.ROUND_HALF_UP);
+ bookValue = bookValue.subtract(depreciationExpense);
+ }
+ return bookValue;
+ }
+
+ // 年数总和法
+ public static BigDecimal calculateResidualValueBySumOfTheYearsDigits(BigDecimal originalValue, int usefulLife, int yearsDepreciated) {
+ int sumOfYears = usefulLife * (usefulLife + 1) / 2;
+ BigDecimal depreciationPerYear = originalValue.divide(BigDecimal.valueOf(sumOfYears), 2, BigDecimal.ROUND_HALF_UP);
+ BigDecimal accumulatedDepreciation = BigDecimal.ZERO;
+ for (int i = 1; i <= yearsDepreciated; i++) {
+ accumulatedDepreciation = accumulatedDepreciation.add(depreciationPerYear.multiply(BigDecimal.valueOf(usefulLife - i + 1)));
+ }
+ BigDecimal residualValue = originalValue.subtract(accumulatedDepreciation);
+ return residualValue;
+ }
+
+ public static void main(String[] args) {
+ BigDecimal originalValue = new BigDecimal("3000000.00"); // 资产原值
+ int usefulLife = 5; // 使用年限
+ int yearsDepreciated = 3; // 已折旧年限
+ BigDecimal totalUnitsOfWork = new BigDecimal("1000"); // 总工作量
+ BigDecimal unitsOfWorkDone = new BigDecimal("500"); // 完成的工作量
+ BigDecimal depreciationRate = new BigDecimal("0.2"); // 折旧率
+
+ BigDecimal residualValueAverageLife = calculateResidualValueByAverageLife(originalValue, usefulLife, yearsDepreciated);
+ BigDecimal residualValueUnitsOfWork = calculateResidualValueByUnitsOfWork(new BigDecimal("60.00"), new BigDecimal("50.00"), new BigDecimal("0.40"));
+ BigDecimal residualValueDoubleDecliningBalance = calculateResidualValueByDoubleDecliningBalance(originalValue, depreciationRate, usefulLife, yearsDepreciated);
+ BigDecimal residualValueSumOfTheYearsDigits = calculateResidualValueBySumOfTheYearsDigits(originalValue, usefulLife, yearsDepreciated);
+
+ System.out.println("资产残值 - 年限平均法: " + residualValueAverageLife);
+ System.out.println("年限平均法(按月): 资产残值 - " + calculateMonthlyDepreciationByAverageLife(originalValue,5*12,9));
+ System.out.println("资产残值 - 工作量法: " + residualValueUnitsOfWork);
+ System.out.println("资产残值 - 双倍余额递减法: " + residualValueDoubleDecliningBalance);
+ System.out.println("资产残值 - 年数总和法: " + residualValueSumOfTheYearsDigits);
+ }
+}
diff --git a/common/src/main/java/com/yyy/common/utils/file/FileTypeUtils.java b/common/src/main/java/com/yyy/common/utils/file/FileTypeUtils.java
new file mode 100644
index 0000000..8a3d64d
--- /dev/null
+++ b/common/src/main/java/com/yyy/common/utils/file/FileTypeUtils.java
@@ -0,0 +1,77 @@
+package com.yyy.common.utils.file;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.File;
+
+/**
+ * @Author: fy
+ * @Date: 2024/03/22
+ * @Description:文件类型工具类
+ */
+public class FileTypeUtils
+{
+ /**
+ * 获取文件类型
+ *
+ * 例如: ruoyi.txt, 返回: txt
+ *
+ * @param file 文件名
+ * @return 后缀(不含".")
+ */
+ public static String getFileType(File file)
+ {
+ if (null == file)
+ {
+ return StringUtils.EMPTY;
+ }
+ return getFileType(file.getName());
+ }
+
+ /**
+ * 获取文件类型
+ *
+ * 例如: ruoyi.txt, 返回: txt
+ *
+ * @param fileName 文件名
+ * @return 后缀(不含".")
+ */
+ public static String getFileType(String fileName)
+ {
+ int separatorIndex = fileName.lastIndexOf(".");
+ if (separatorIndex < 0)
+ {
+ return "";
+ }
+ return fileName.substring(separatorIndex + 1).toLowerCase();
+ }
+
+ /**
+ * 获取文件类型
+ *
+ * @param photoByte 文件字节码
+ * @return 后缀(不含".")
+ */
+ public static String getFileExtendName(byte[] photoByte)
+ {
+ String strFileExtendName = "JPG";
+ if ((photoByte[0] == 71) && (photoByte[1] == 73) && (photoByte[2] == 70) && (photoByte[3] == 56)
+ && ((photoByte[4] == 55) || (photoByte[4] == 57)) && (photoByte[5] == 97))
+ {
+ strFileExtendName = "GIF";
+ }
+ else if ((photoByte[6] == 74) && (photoByte[7] == 70) && (photoByte[8] == 73) && (photoByte[9] == 70))
+ {
+ strFileExtendName = "JPG";
+ }
+ else if ((photoByte[0] == 66) && (photoByte[1] == 77))
+ {
+ strFileExtendName = "BMP";
+ }
+ else if ((photoByte[1] == 80) && (photoByte[2] == 78) && (photoByte[3] == 71))
+ {
+ strFileExtendName = "PNG";
+ }
+ return strFileExtendName;
+ }
+}
diff --git a/common/src/main/java/com/yyy/common/utils/file/FileUploadUtils.java b/common/src/main/java/com/yyy/common/utils/file/FileUploadUtils.java
new file mode 100644
index 0000000..36129f4
--- /dev/null
+++ b/common/src/main/java/com/yyy/common/utils/file/FileUploadUtils.java
@@ -0,0 +1,231 @@
+package com.yyy.common.utils.file;
+
+import com.yyy.common.config.AppConfig;
+import com.yyy.common.constant.Constants;
+import com.yyy.common.exception.file.FileNameLengthLimitExceededException;
+import com.yyy.common.exception.file.FileSizeLimitExceededException;
+import com.yyy.common.exception.file.InvalidExtensionException;
+import com.yyy.common.utils.common.DateUtils;
+import com.yyy.common.utils.common.StringUtils;
+import com.yyy.common.utils.uuid.Seq;
+import org.apache.commons.io.FilenameUtils;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.Objects;
+
+/**
+ * 文件上传工具类
+ */
+public class FileUploadUtils
+{
+ /**
+ * 默认大小 50M
+ */
+ public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024;
+
+ /**
+ * 默认的文件名最大长度 100
+ */
+ public static final int DEFAULT_FILE_NAME_LENGTH = 100;
+
+ /**
+ * 默认上传的地址
+ */
+ private static String defaultBaseDir = AppConfig.getProfile();
+
+ public static void setDefaultBaseDir(String defaultBaseDir)
+ {
+ FileUploadUtils.defaultBaseDir = defaultBaseDir;
+ }
+
+ public static String getDefaultBaseDir()
+ {
+ return defaultBaseDir;
+ }
+
+ /**
+ * 以默认配置进行文件上传
+ *
+ * @param file 上传的文件
+ * @return 文件名称
+ * @throws Exception
+ */
+ public static final String upload(MultipartFile file) throws IOException
+ {
+ try
+ {
+ return upload(getDefaultBaseDir(), file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
+ }
+ catch (Exception e)
+ {
+ throw new IOException(e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 根据文件路径上传
+ *
+ * @param baseDir 相对应用的基目录
+ * @param file 上传的文件
+ * @return 文件名称
+ * @throws IOException
+ */
+ public static final String upload(String baseDir, MultipartFile file) throws IOException
+ {
+ try
+ {
+ return upload(baseDir, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
+ }
+ catch (Exception e)
+ {
+ throw new IOException(e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 文件上传
+ *
+ * @param baseDir 相对应用的基目录
+ * @param file 上传的文件
+ * @param allowedExtension 上传文件类型
+ * @return 返回上传成功的文件名
+ * @throws FileSizeLimitExceededException 如果超出最大大小
+ * @throws FileNameLengthLimitExceededException 文件名太长
+ * @throws IOException 比如读写文件出错时
+ * @throws InvalidExtensionException 文件校验异常
+ */
+ public static final String upload(String baseDir, MultipartFile file, String[] allowedExtension)
+ throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException,
+ InvalidExtensionException
+ {
+ int fileNameLength = Objects.requireNonNull(file.getOriginalFilename()).length();
+ if (fileNameLength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH)
+ {
+ throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH);
+ }
+
+ assertAllowed(file, allowedExtension);
+
+ String fileName = extractFilename(file);
+
+ String absPath = getAbsoluteFile(baseDir, fileName).getAbsolutePath();
+ file.transferTo(Paths.get(absPath));
+ return getPathFileName(baseDir, fileName);
+ }
+
+ /**
+ * 编码文件名
+ */
+ public static final String extractFilename(MultipartFile file)
+ {
+ return StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(),
+ FilenameUtils.getBaseName(file.getOriginalFilename()), Seq.getId(Seq.uploadSeqType), getExtension(file));
+ }
+
+ public static final File getAbsoluteFile(String uploadDir, String fileName) throws IOException
+ {
+ File desc = new File(uploadDir + File.separator + fileName);
+
+ if (!desc.exists())
+ {
+ if (!desc.getParentFile().exists())
+ {
+ desc.getParentFile().mkdirs();
+ }
+ }
+ return desc;
+ }
+
+ public static final String getPathFileName(String uploadDir, String fileName) throws IOException
+ {
+ int dirLastIndex = AppConfig.getProfile().length() + 1;
+ String currentDir = StringUtils.substring(uploadDir, dirLastIndex);
+ return Constants.RESOURCE_PREFIX + "/" + currentDir + "/" + fileName;
+ }
+
+ /**
+ * 文件大小校验
+ *
+ * @param file 上传的文件
+ * @return
+ * @throws FileSizeLimitExceededException 如果超出最大大小
+ * @throws InvalidExtensionException
+ */
+ public static final void assertAllowed(MultipartFile file, String[] allowedExtension)
+ throws FileSizeLimitExceededException, InvalidExtensionException
+ {
+ long size = file.getSize();
+ if (size > DEFAULT_MAX_SIZE)
+ {
+ throw new FileSizeLimitExceededException(DEFAULT_MAX_SIZE / 1024 / 1024);
+ }
+
+ String fileName = file.getOriginalFilename();
+ String extension = getExtension(file);
+ if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension))
+ {
+ if (allowedExtension == MimeTypeUtils.IMAGE_EXTENSION)
+ {
+ throw new InvalidExtensionException.InvalidImageExtensionException(allowedExtension, extension,
+ fileName);
+ }
+ else if (allowedExtension == MimeTypeUtils.FLASH_EXTENSION)
+ {
+ throw new InvalidExtensionException.InvalidFlashExtensionException(allowedExtension, extension,
+ fileName);
+ }
+ else if (allowedExtension == MimeTypeUtils.MEDIA_EXTENSION)
+ {
+ throw new InvalidExtensionException.InvalidMediaExtensionException(allowedExtension, extension,
+ fileName);
+ }
+ else if (allowedExtension == MimeTypeUtils.VIDEO_EXTENSION)
+ {
+ throw new InvalidExtensionException.InvalidVideoExtensionException(allowedExtension, extension,
+ fileName);
+ }
+ else
+ {
+ throw new InvalidExtensionException(allowedExtension, extension, fileName);
+ }
+ }
+ }
+
+ /**
+ * 判断MIME类型是否是允许的MIME类型
+ *
+ * @param extension
+ * @param allowedExtension
+ * @return
+ */
+ public static final boolean isAllowedExtension(String extension, String[] allowedExtension)
+ {
+ for (String str : allowedExtension)
+ {
+ if (str.equalsIgnoreCase(extension))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * 获取文件名的后缀
+ *
+ * @param file 表单文件
+ * @return 后缀名
+ */
+ public static final String getExtension(MultipartFile file)
+ {
+ String extension = FilenameUtils.getExtension(file.getOriginalFilename());
+ if (StringUtils.isEmpty(extension))
+ {
+ extension = MimeTypeUtils.getExtension(Objects.requireNonNull(file.getContentType()));
+ }
+ return extension;
+ }
+}
\ No newline at end of file
diff --git a/common/src/main/java/com/yyy/common/utils/file/FileUtils.java b/common/src/main/java/com/yyy/common/utils/file/FileUtils.java
new file mode 100644
index 0000000..4d4fd54
--- /dev/null
+++ b/common/src/main/java/com/yyy/common/utils/file/FileUtils.java
@@ -0,0 +1,285 @@
+package com.yyy.common.utils.file;
+
+import com.yyy.common.config.AppConfig;
+import com.yyy.common.utils.common.DateUtils;
+import com.yyy.common.utils.common.StringUtils;
+import com.yyy.common.utils.uuid.IdUtils;
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.ArrayUtils;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.*;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * @Author: fy
+ * @Date: 2024/03/22
+ * @Description:文件处理工具类
+ */
+public class FileUtils
+{
+ public static String FILENAME_PATTERN = "[a-zA-Z0-9_\\-\\|\\.\\u4e00-\\u9fa5]+";
+
+ /**
+ * 输出指定文件的byte数组
+ *
+ * @param filePath 文件路径
+ * @param os 输出流
+ * @return
+ */
+ public static void writeBytes(String filePath, OutputStream os) throws IOException
+ {
+ FileInputStream fis = null;
+ try
+ {
+ File file = new File(filePath);
+ if (!file.exists())
+ {
+ throw new FileNotFoundException(filePath);
+ }
+ fis = new FileInputStream(file);
+ byte[] b = new byte[1024];
+ int length;
+ while ((length = fis.read(b)) > 0)
+ {
+ os.write(b, 0, length);
+ }
+ }
+ catch (IOException e)
+ {
+ throw e;
+ }
+ finally
+ {
+ IOUtils.close(os);
+ IOUtils.close(fis);
+ }
+ }
+
+ /**
+ * 写数据到文件中
+ *
+ * @param data 数据
+ * @return 目标文件
+ * @throws IOException IO异常
+ */
+ public static String writeImportBytes(byte[] data) throws IOException
+ {
+ return writeBytes(data, AppConfig.getImportPath());
+ }
+
+ /**
+ * 写数据到文件中
+ *
+ * @param data 数据
+ * @param uploadDir 目标文件
+ * @return 目标文件
+ * @throws IOException IO异常
+ */
+ public static String writeBytes(byte[] data, String uploadDir) throws IOException
+ {
+ FileOutputStream fos = null;
+ String pathName = "";
+ try
+ {
+ String extension = getFileExtendName(data);
+ pathName = DateUtils.datePath() + "/" + IdUtils.fastUUID() + "." + extension;
+ File file = FileUploadUtils.getAbsoluteFile(uploadDir, pathName);
+ fos = new FileOutputStream(file);
+ fos.write(data);
+ }
+ finally
+ {
+ IOUtils.close(fos);
+ }
+ return FileUploadUtils.getPathFileName(uploadDir, pathName);
+ }
+
+ /**
+ * 删除文件
+ *
+ * @param filePath 文件
+ * @return
+ */
+ public static boolean deleteFile(String filePath)
+ {
+ boolean flag = false;
+ File file = new File(filePath);
+ // 路径为文件且不为空则进行删除
+ if (file.isFile() && file.exists())
+ {
+ flag = file.delete();
+ }
+ return flag;
+ }
+
+ /**
+ * 文件名称验证
+ *
+ * @param filename 文件名称
+ * @return true 正常 false 非法
+ */
+ public static boolean isValidFilename(String filename)
+ {
+ return filename.matches(FILENAME_PATTERN);
+ }
+
+ /**
+ * 检查文件是否可下载
+ *
+ * @param resource 需要下载的文件
+ * @return true 正常 false 非法
+ */
+ public static boolean checkAllowDownload(String resource)
+ {
+ // 禁止目录上跳级别
+ if (StringUtils.contains(resource, ".."))
+ {
+ return false;
+ }
+
+ // 检查允许下载的文件规则
+ if (ArrayUtils.contains(MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION, FileTypeUtils.getFileType(resource)))
+ {
+ return true;
+ }
+
+ // 不在允许下载的文件规则
+ return false;
+ }
+
+ /**
+ * 下载文件名重新编码
+ *
+ * @param request 请求对象
+ * @param fileName 文件名
+ * @return 编码后的文件名
+ */
+ public static String setFileDownloadHeader(HttpServletRequest request, String fileName) throws UnsupportedEncodingException
+ {
+ final String agent = request.getHeader("USER-AGENT");
+ String filename = fileName;
+ if (agent.contains("MSIE"))
+ {
+ // IE浏览器
+ filename = URLEncoder.encode(filename, "utf-8");
+ filename = filename.replace("+", " ");
+ }
+ else if (agent.contains("Firefox"))
+ {
+ // 火狐浏览器
+ filename = new String(fileName.getBytes(), "ISO8859-1");
+ }
+ else if (agent.contains("Chrome"))
+ {
+ // google浏览器
+ filename = URLEncoder.encode(filename, "utf-8");
+ }
+ else
+ {
+ // 其它浏览器
+ filename = URLEncoder.encode(filename, "utf-8");
+ }
+ return filename;
+ }
+
+ /**
+ * 下载文件名重新编码
+ *
+ * @param response 响应对象
+ * @param realFileName 真实文件名
+ * @return
+ */
+ public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) throws UnsupportedEncodingException
+ {
+ String percentEncodedFileName = percentEncode(realFileName);
+
+ StringBuilder contentDispositionValue = new StringBuilder();
+ contentDispositionValue.append("attachment; filename=")
+ .append(percentEncodedFileName)
+ .append(";")
+ .append("filename*=")
+ .append("utf-8''")
+ .append(percentEncodedFileName);
+
+ response.setHeader("Content-disposition", contentDispositionValue.toString());
+ }
+
+ /**
+ * 百分号编码工具方法
+ *
+ * @param s 需要百分号编码的字符串
+ * @return 百分号编码后的字符串
+ */
+ public static String percentEncode(String s) throws UnsupportedEncodingException
+ {
+ String encode = URLEncoder.encode(s, StandardCharsets.UTF_8.toString());
+ return encode.replaceAll("\\+", "%20");
+ }
+
+ /**
+ * 获取图像后缀
+ *
+ * @param photoByte 图像数据
+ * @return 后缀名
+ */
+ public static String getFileExtendName(byte[] photoByte)
+ {
+ String strFileExtendName = "jpg";
+ if ((photoByte[0] == 71) && (photoByte[1] == 73) && (photoByte[2] == 70) && (photoByte[3] == 56)
+ && ((photoByte[4] == 55) || (photoByte[4] == 57)) && (photoByte[5] == 97))
+ {
+ strFileExtendName = "gif";
+ }
+ else if ((photoByte[6] == 74) && (photoByte[7] == 70) && (photoByte[8] == 73) && (photoByte[9] == 70))
+ {
+ strFileExtendName = "jpg";
+ }
+ else if ((photoByte[0] == 66) && (photoByte[1] == 77))
+ {
+ strFileExtendName = "bmp";
+ }
+ else if ((photoByte[1] == 80) && (photoByte[2] == 78) && (photoByte[3] == 71))
+ {
+ strFileExtendName = "png";
+ }
+ return strFileExtendName;
+ }
+
+ /**
+ * 获取文件名称 /profile/upload/2022/04/16/ruoyi.png -- ruoyi.png
+ *
+ * @param fileName 路径名称
+ * @return 没有文件路径的名称
+ */
+ public static String getName(String fileName)
+ {
+ if (fileName == null)
+ {
+ return null;
+ }
+ int lastUnixPos = fileName.lastIndexOf('/');
+ int lastWindowsPos = fileName.lastIndexOf('\\');
+ int index = Math.max(lastUnixPos, lastWindowsPos);
+ return fileName.substring(index + 1);
+ }
+
+ /**
+ * 获取不带后缀文件名称 /profile/upload/2022/04/16/ruoyi.png -- ruoyi
+ *
+ * @param fileName 路径名称
+ * @return 没有文件路径和后缀的名称
+ */
+ public static String getNameNotSuffix(String fileName)
+ {
+ if (fileName == null)
+ {
+ return null;
+ }
+ String baseName = FilenameUtils.getBaseName(fileName);
+ return baseName;
+ }
+}
\ No newline at end of file
diff --git a/common/src/main/java/com/yyy/common/utils/file/ImageUtils.java b/common/src/main/java/com/yyy/common/utils/file/ImageUtils.java
new file mode 100644
index 0000000..7f977db
--- /dev/null
+++ b/common/src/main/java/com/yyy/common/utils/file/ImageUtils.java
@@ -0,0 +1,98 @@
+package com.yyy.common.utils.file;
+
+
+import com.yyy.common.config.AppConfig;
+import com.yyy.common.constant.Constants;
+import com.yyy.common.utils.common.StringUtils;
+import org.apache.commons.io.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayInputStream;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Arrays;
+
+/**
+ * 图片处理工具类
+ */
+public class ImageUtils
+{
+ private static final Logger log = LoggerFactory.getLogger(ImageUtils.class);
+
+ public static byte[] getImage(String imagePath)
+ {
+ InputStream is = getFile(imagePath);
+ try
+ {
+ return IOUtils.toByteArray(is);
+ }
+ catch (Exception e)
+ {
+ log.error("图片加载异常 {}", e);
+ return null;
+ }
+ finally
+ {
+ IOUtils.closeQuietly(is);
+ }
+ }
+
+ public static InputStream getFile(String imagePath)
+ {
+ try
+ {
+ byte[] result = readFile(imagePath);
+ result = Arrays.copyOf(result, result.length);
+ return new ByteArrayInputStream(result);
+ }
+ catch (Exception e)
+ {
+ log.error("获取图片异常 {}", e);
+ }
+ return null;
+ }
+
+ /**
+ * 读取文件为字节数据
+ *
+ * @param url 地址
+ * @return 字节数据
+ */
+ public static byte[] readFile(String url)
+ {
+ InputStream in = null;
+ try
+ {
+ if (url.startsWith("http"))
+ {
+ // 网络地址
+ URL urlObj = new URL(url);
+ URLConnection urlConnection = urlObj.openConnection();
+ urlConnection.setConnectTimeout(30 * 1000);
+ urlConnection.setReadTimeout(60 * 1000);
+ urlConnection.setDoInput(true);
+ in = urlConnection.getInputStream();
+ }
+ else
+ {
+ // 本机地址
+ String localPath = AppConfig.getProfile();
+ String downloadPath = localPath + StringUtils.substringAfter(url, Constants.RESOURCE_PREFIX);
+ in = new FileInputStream(downloadPath);
+ }
+ return IOUtils.toByteArray(in);
+ }
+ catch (Exception e)
+ {
+ log.error("获取文件路径异常 {}", e);
+ return null;
+ }
+ finally
+ {
+ IOUtils.closeQuietly(in);
+ }
+ }
+}
diff --git a/common/src/main/java/com/yyy/common/utils/file/MimeTypeUtils.java b/common/src/main/java/com/yyy/common/utils/file/MimeTypeUtils.java
new file mode 100644
index 0000000..77eb820
--- /dev/null
+++ b/common/src/main/java/com/yyy/common/utils/file/MimeTypeUtils.java
@@ -0,0 +1,59 @@
+package com.yyy.common.utils.file;
+
+/**
+ * @Author: fy
+ * @Date: 2024/03/22
+ * @Description:媒体类型工具类
+ */
+public class MimeTypeUtils
+{
+ public static final String IMAGE_PNG = "image/png";
+
+ public static final String IMAGE_JPG = "image/jpg";
+
+ public static final String IMAGE_JPEG = "image/jpeg";
+
+ public static final String IMAGE_BMP = "image/bmp";
+
+ public static final String IMAGE_GIF = "image/gif";
+
+ public static final String[] IMAGE_EXTENSION = { "bmp", "gif", "jpg", "jpeg", "png" };
+
+ public static final String[] FLASH_EXTENSION = { "swf", "flv" };
+
+ public static final String[] MEDIA_EXTENSION = { "swf", "flv", "mp3", "wav", "wma", "wmv", "mid", "avi", "mpg",
+ "asf", "rm", "rmvb" };
+
+ public static final String[] VIDEO_EXTENSION = { "mp4", "avi", "rmvb" };
+
+ public static final String[] DEFAULT_ALLOWED_EXTENSION = {
+ // 图片
+ "bmp", "gif", "jpg", "jpeg", "png",
+ // word excel powerpoint
+ "doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt",
+ // 压缩文件
+ "rar", "zip", "gz", "bz2",
+ // 视频格式
+ "mp4", "avi", "rmvb",
+ // pdf
+ "pdf" };
+
+ public static String getExtension(String prefix)
+ {
+ switch (prefix)
+ {
+ case IMAGE_PNG:
+ return "png";
+ case IMAGE_JPG:
+ return "jpg";
+ case IMAGE_JPEG:
+ return "jpeg";
+ case IMAGE_BMP:
+ return "bmp";
+ case IMAGE_GIF:
+ return "gif";
+ default:
+ return "";
+ }
+ }
+}
\ No newline at end of file
diff --git a/common/src/main/java/com/yyy/common/utils/poi/ExcelHandlerAdapter.java b/common/src/main/java/com/yyy/common/utils/poi/ExcelHandlerAdapter.java
new file mode 100644
index 0000000..0e01bc7
--- /dev/null
+++ b/common/src/main/java/com/yyy/common/utils/poi/ExcelHandlerAdapter.java
@@ -0,0 +1,25 @@
+package com.yyy.common.utils.poi;
+
+
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.Workbook;
+
+/**
+ * @Author: fy
+ * @Date: 2024/04/08
+ * @Description:Excel数据格式处理适配器
+ */
+public interface ExcelHandlerAdapter
+{
+ /**
+ * 格式化
+ *
+ * @param value 单元格数据值
+ * @param args excel注解args参数组
+ * @param cell 单元格对象
+ * @param wb 工作簿对象
+ *
+ * @return 处理后的值
+ */
+ Object format(Object value, String[] args, Cell cell, Workbook wb);
+}
\ No newline at end of file
diff --git a/common/src/main/java/com/yyy/common/utils/poi/ExcelUtil.java b/common/src/main/java/com/yyy/common/utils/poi/ExcelUtil.java
new file mode 100644
index 0000000..e4b7ecb
--- /dev/null
+++ b/common/src/main/java/com/yyy/common/utils/poi/ExcelUtil.java
@@ -0,0 +1,1714 @@
+package com.yyy.common.utils.poi;
+
+import com.yyy.common.annotation.Excel;
+import com.yyy.common.annotation.Excel.ColumnType;
+import com.yyy.common.annotation.Excel.Type;
+import com.yyy.common.annotation.Excels;
+import com.yyy.common.config.AppConfig;
+import com.yyy.common.core.domain.Result;
+import com.yyy.common.core.domain.ResultCode;
+import com.yyy.common.core.text.Convert;
+import com.yyy.common.exception.UtilException;
+import com.yyy.common.utils.common.DateUtils;
+import com.yyy.common.utils.common.DictUtils;
+import com.yyy.common.utils.common.StringUtils;
+import com.yyy.common.utils.file.FileTypeUtils;
+import com.yyy.common.utils.file.FileUtils;
+import com.yyy.common.utils.file.ImageUtils;
+import com.yyy.common.utils.reflect.ReflectUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.RegExUtils;
+import org.apache.commons.lang3.reflect.FieldUtils;
+import org.apache.poi.hssf.usermodel.*;
+import org.apache.poi.ooxml.POIXMLDocumentPart;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.apache.poi.ss.util.CellRangeAddressList;
+import org.apache.poi.xssf.streaming.SXSSFWorkbook;
+import org.apache.poi.xssf.usermodel.*;
+import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTMarker;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.*;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.math.BigDecimal;
+import java.text.DecimalFormat;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * Excel相关处理
+ *
+ */
+public class ExcelUtil {
+ private static final Logger log = LoggerFactory.getLogger(ExcelUtil.class);
+
+ public static final String FORMULA_REGEX_STR = "=|-|\\+|@";
+
+ public static final String[] FORMULA_STR = { "=", "-", "+", "@" };
+
+ /**
+ * 用于dictType属性数据存储,避免重复查缓存
+ */
+ public Map sysDictMap = new HashMap();
+
+ /**
+ * Excel sheet最大行数,默认65536
+ */
+ public static final int sheetSize = 65536;
+
+ /**
+ * 工作表名称
+ */
+ private String sheetName;
+
+ /**
+ * 导出类型(EXPORT:导出数据;IMPORT:导入模板)
+ */
+ private Excel.Type type;
+
+ /**
+ * 工作薄对象
+ */
+ private Workbook wb;
+
+ /**
+ * 工作表对象
+ */
+ private Sheet sheet;
+
+ /**
+ * 样式列表
+ */
+ private Map styles;
+
+ /**
+ * 导入导出数据列表
+ */
+ private List list;
+
+ /**
+ * 注解列表
+ */
+ private List