cxd 8 månader sedan
förälder
incheckning
cad87364bf

+ 216 - 0
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/AjaxResultCQY.java

@@ -0,0 +1,216 @@
+package com.ruoyi.common.core.domain;
+
+import java.util.HashMap;
+import java.util.Objects;
+import com.ruoyi.common.constant.HttpStatus;
+import com.ruoyi.common.utils.StringUtils;
+
+/**
+ * 操作消息提醒
+ *
+ * @author ruoyi
+ */
+public class AjaxResultCQY extends HashMap<String, Object>
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 状态码 */
+    public static final String CODE_TAG = "status";
+
+    /** 返回内容 */
+    public static final String MSG_TAG = "message";
+
+    /** 数据对象 */
+    public static final String DATA_TAG = "data";
+
+    /**
+     * 初始化一个新创建的 AjaxResultCQY 对象,使其表示一个空消息。
+     */
+    public AjaxResultCQY()
+    {
+    }
+
+    /**
+     * 初始化一个新创建的 AjaxResultCQY 对象
+     *
+     * @param code 状态码
+     * @param msg 返回内容
+     */
+    public AjaxResultCQY(int code, String msg)
+    {
+        super.put(CODE_TAG, code);
+        super.put(MSG_TAG, msg);
+    }
+
+    /**
+     * 初始化一个新创建的 AjaxResultCQY 对象
+     *
+     * @param code 状态码
+     * @param msg 返回内容
+     * @param data 数据对象
+     */
+    public AjaxResultCQY(int code, String msg, Object data)
+    {
+        super.put(CODE_TAG, code);
+        super.put(MSG_TAG, msg);
+        if (StringUtils.isNotNull(data))
+        {
+            super.put(DATA_TAG, data);
+        }
+    }
+
+    /**
+     * 返回成功消息
+     *
+     * @return 成功消息
+     */
+    public static AjaxResultCQY success()
+    {
+        return AjaxResultCQY.success("操作成功");
+    }
+
+    /**
+     * 返回成功数据
+     *
+     * @return 成功消息
+     */
+    public static AjaxResultCQY success(Object data)
+    {
+        return AjaxResultCQY.success("操作成功", data);
+    }
+
+    /**
+     * 返回成功消息
+     *
+     * @param msg 返回内容
+     * @return 成功消息
+     */
+    public static AjaxResultCQY success(String msg)
+    {
+        return AjaxResultCQY.success(msg, null);
+    }
+
+    /**
+     * 返回成功消息
+     *
+     * @param msg 返回内容
+     * @param data 数据对象
+     * @return 成功消息
+     */
+    public static AjaxResultCQY success(String msg, Object data)
+    {
+        return new AjaxResultCQY(HttpStatus.SUCCESS, msg, data);
+    }
+
+    /**
+     * 返回警告消息
+     *
+     * @param msg 返回内容
+     * @return 警告消息
+     */
+    public static AjaxResultCQY warn(String msg)
+    {
+        return AjaxResultCQY.warn(msg, null);
+    }
+
+    /**
+     * 返回警告消息
+     *
+     * @param msg 返回内容
+     * @param data 数据对象
+     * @return 警告消息
+     */
+    public static AjaxResultCQY warn(String msg, Object data)
+    {
+        return new AjaxResultCQY(HttpStatus.WARN, msg, data);
+    }
+
+    /**
+     * 返回错误消息
+     *
+     * @return 错误消息
+     */
+    public static AjaxResultCQY error()
+    {
+        return AjaxResultCQY.error("操作失败");
+    }
+
+    /**
+     * 返回错误消息
+     *
+     * @param msg 返回内容
+     * @return 错误消息
+     */
+    public static AjaxResultCQY error(String msg)
+    {
+        return AjaxResultCQY.error(msg, null);
+    }
+
+    /**
+     * 返回错误消息
+     *
+     * @param msg 返回内容
+     * @param data 数据对象
+     * @return 错误消息
+     */
+    public static AjaxResultCQY error(String msg, Object data)
+    {
+        return new AjaxResultCQY(HttpStatus.ERROR, msg, data);
+    }
+
+    /**
+     * 返回错误消息
+     *
+     * @param code 状态码
+     * @param msg 返回内容
+     * @return 错误消息
+     */
+    public static AjaxResultCQY error(int code, String msg)
+    {
+        return new AjaxResultCQY(code, msg, null);
+    }
+
+    /**
+     * 是否为成功消息
+     *
+     * @return 结果
+     */
+    public boolean isSuccess()
+    {
+        return Objects.equals(HttpStatus.SUCCESS, this.get(CODE_TAG));
+    }
+
+    /**
+     * 是否为警告消息
+     *
+     * @return 结果
+     */
+    public boolean isWarn()
+    {
+        return Objects.equals(HttpStatus.WARN, this.get(CODE_TAG));
+    }
+
+    /**
+     * 是否为错误消息
+     *
+     * @return 结果
+     */
+    public boolean isError()
+    {
+        return Objects.equals(HttpStatus.ERROR, this.get(CODE_TAG));
+    }
+
+    /**
+     * 方便链式调用
+     *
+     * @param key 键
+     * @param value 值
+     * @return 数据对象
+     */
+    @Override
+    public AjaxResultCQY put(String key, Object value)
+    {
+        super.put(key, value);
+        return this;
+    }
+}

+ 228 - 0
ruoyi-common/src/main/java/com/ruoyi/common/utils/FileTypeUtils.java

@@ -0,0 +1,228 @@
+package com.ruoyi.common.utils;
+
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FileTypeUtils {
+
+    private static final Logger log = LoggerFactory.getLogger(FileTypeUtils.class);
+
+    /** 文件类型映射 */
+    private static final Map<String, String> FILE_TYPE_MAP = new HashMap<>();
+
+    static {
+        // 图片
+        FILE_TYPE_MAP.put("FFD8FFE0", "jpg");
+        FILE_TYPE_MAP.put("FFD8FFE1", "jpg");
+        FILE_TYPE_MAP.put("FFD8FFE8", "jpg");
+        FILE_TYPE_MAP.put("89504E47", "png");
+        FILE_TYPE_MAP.put("47494638", "gif");
+        FILE_TYPE_MAP.put("424D", "bmp");
+        // 文档
+        FILE_TYPE_MAP.put("504B0304", "docx");  // 也可能是xlsx、pptx、zip
+        FILE_TYPE_MAP.put("D0CF11E0", "doc");   // 也可能是xls、ppt
+        FILE_TYPE_MAP.put("25504446", "pdf");
+        // 压缩文件
+        FILE_TYPE_MAP.put("504B0304", "zip");
+        FILE_TYPE_MAP.put("526172", "rar");
+        FILE_TYPE_MAP.put("377ABCAF", "7z");
+        // 视频
+        FILE_TYPE_MAP.put("000001BA", "mpg");
+        FILE_TYPE_MAP.put("000001B3", "mpg");
+        FILE_TYPE_MAP.put("6674706D", "mp4");
+        FILE_TYPE_MAP.put("41564920", "avi");
+        // 音频
+        FILE_TYPE_MAP.put("49443303", "mp3");
+        FILE_TYPE_MAP.put("57415645", "wav");
+        // 其他
+        FILE_TYPE_MAP.put("3C3F786D", "xml");
+        FILE_TYPE_MAP.put("7B5C7274", "rtf");
+    }
+
+    /** 文本类型 */
+    private static final String[] TEXT_TYPES = {
+            "txt", "md", "json", "xml", "html", "sql", "js", "css", "java", "py", "c", "cpp", "php",
+            "log", "properties", "yml", "yaml", "ini", "conf"
+    };
+
+    /**
+     * 获取文件类型
+     *
+     * @param file MultipartFile
+     * @return 文件类型
+     */
+    public static String getFileType(MultipartFile file) {
+        String fileType = getFileTypeByMagicNumber(file);
+        if (StringUtils.isNotEmpty(fileType)) {
+            return fileType;
+        }
+        return getExtension(file);
+    }
+
+    /**
+     * 通过魔数获取文件类型
+     */
+    private static String getFileTypeByMagicNumber(MultipartFile file) {
+        if (file == null || file.isEmpty()) {
+            return StringUtils.EMPTY;
+        }
+
+        try (InputStream is = file.getInputStream()) {
+            byte[] bytes = new byte[28];
+            if (is.read(bytes) < 28) {
+                return StringUtils.EMPTY;
+            }
+
+            String magicNumberHex = bytesToHexString(bytes);
+
+            // 判断文件类型
+            for (Map.Entry<String, String> entry : FILE_TYPE_MAP.entrySet()) {
+                String magicNumber = entry.getKey();
+                if (magicNumberHex.startsWith(magicNumber)) {
+                    String type = entry.getValue();
+                    // 处理ZIP类型的特殊情况
+                    if ("zip".equals(type)) {
+                        type = checkZipType(file.getOriginalFilename(), type);
+                    }
+                    // 处理MS Office旧版本的特殊情况
+                    else if ("doc".equals(type)) {
+                        type = checkOfficeType(file.getOriginalFilename(), type);
+                    }
+                    return type;
+                }
+            }
+
+            // 检查是否为文本文件
+            if (isTextFile(bytes)) {
+                return "txt";
+            }
+        } catch (IOException e) {
+            log.error("获取文件类型失败", e);
+        }
+
+        return StringUtils.EMPTY;
+    }
+
+    /**
+     * 检查ZIP类型文件的具体类型
+     */
+    private static String checkZipType(String fileName, String defaultType) {
+        if (StringUtils.isEmpty(fileName)) {
+            return defaultType;
+        }
+        String extension = getExtension(fileName).toLowerCase();
+        switch (extension) {
+            case "docx":
+            case "xlsx":
+            case "pptx":
+                return extension;
+            default:
+                return "zip";
+        }
+    }
+
+    /**
+     * 检查MS Office旧版本文件的具体类型
+     */
+    private static String checkOfficeType(String fileName, String defaultType) {
+        if (StringUtils.isEmpty(fileName)) {
+            return defaultType;
+        }
+        String extension = getExtension(fileName).toLowerCase();
+        switch (extension) {
+            case "xls":
+            case "ppt":
+                return extension;
+            default:
+                return "doc";
+        }
+    }
+
+    /**
+     * 检查是否为文本文件
+     */
+    private static boolean isTextFile(byte[] bytes) {
+        int nullCount = 0;
+        for (byte b : bytes) {
+            if (b == 0) {
+                nullCount++;
+            }
+        }
+        // 文本文件通常不会包含太多的空字节
+        return nullCount < bytes.length * 0.1;
+    }
+
+    /**
+     * 字节数组转16进制字符串
+     */
+    private static String bytesToHexString(byte[] bytes) {
+        StringBuilder stringBuilder = new StringBuilder();
+        if (bytes == null || bytes.length <= 0) {
+            return StringUtils.EMPTY;
+        }
+        for (byte b : bytes) {
+            int v = b & 0xFF;
+            String hv = Integer.toHexString(v);
+            if (hv.length() < 2) {
+                stringBuilder.append(0);
+            }
+            stringBuilder.append(hv);
+        }
+        return stringBuilder.toString().toUpperCase();
+    }
+
+    /**
+     * 获取文件扩展名
+     */
+    public static String getExtension(String fileName) {
+        if (StringUtils.isEmpty(fileName)) {
+            return StringUtils.EMPTY;
+        }
+        String extension = StringUtils.EMPTY;
+        int lastIndex = fileName.lastIndexOf(".");
+        if (lastIndex > 0) {
+            extension = fileName.substring(lastIndex + 1).toLowerCase();
+        }
+        return extension;
+    }
+
+    /**
+     * 获取文件扩展名
+     */
+    public static String getExtension(MultipartFile file) {
+        if (file == null) {
+            return StringUtils.EMPTY;
+        }
+        return getExtension(file.getOriginalFilename());
+    }
+
+    /**
+     * 是否是文本文件
+     */
+    public static boolean isText(String fileName) {
+        String extension = getExtension(fileName);
+        return isInArray(extension, TEXT_TYPES);
+    }
+
+    /**
+     * 判断是否在指定数组中
+     */
+    private static boolean isInArray(String value, String[] array) {
+        if (StringUtils.isEmpty(value)) {
+            return false;
+        }
+        for (String item : array) {
+            if (item.equalsIgnoreCase(value)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+}

+ 172 - 0
ruoyi-common/src/main/java/com/ruoyi/common/utils/FileUtils.java

@@ -0,0 +1,172 @@
+package com.ruoyi.common.utils;
+
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+import org.apache.commons.io.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * 文件处理工具类
+ */
+public class FileUtils {
+    private static final Logger log = LoggerFactory.getLogger(FileUtils.class);
+
+    /**
+     * 读取文件内容
+     *
+     * @param file 上传的文件
+     * @return 文件内容
+     */
+    public static String readString(MultipartFile file) throws IOException {
+        return readString(file, StandardCharsets.UTF_8);
+    }
+
+    /**
+     * 读取文件内容
+     *
+     * @param file 上传的文件
+     * @param charset 字符集
+     * @return 文件内容
+     */
+    public static String readString(MultipartFile file, Charset charset) throws IOException {
+        try (InputStream inputStream = file.getInputStream()) {
+            return IOUtils.toString(inputStream, charset);
+        } catch (IOException e) {
+            log.error("读取文件内容失败", e);
+            throw new IOException("读取文件内容失败", e);
+        }
+    }
+
+    /**
+     * 逐行读取文件内容
+     *
+     * @param file 上传的文件
+     * @return 文件内容
+     */
+    public static String readLines(MultipartFile file) throws IOException {
+        return readLines(file, StandardCharsets.UTF_8);
+    }
+
+    /**
+     * 逐行读取文件内容
+     *
+     * @param file 上传的文件
+     * @param charset 字符集
+     * @return 文件内容
+     */
+    public static String readLines(MultipartFile file, Charset charset) throws IOException {
+        StringBuilder content = new StringBuilder();
+        try (BufferedReader reader = new BufferedReader(new InputStreamReader(file.getInputStream(), charset))) {
+            String line;
+            while ((line = reader.readLine()) != null) {
+                content.append(line).append(System.lineSeparator());
+            }
+            return content.toString();
+        } catch (IOException e) {
+            log.error("逐行读取文件内容失败", e);
+            throw new IOException("逐行读取文件内容失败", e);
+        }
+    }
+
+    /**
+     * 检测文件是否为文本文件
+     *
+     * @param file 上传的文件
+     * @return 是否为文本文件
+     */
+    public static boolean isTextFile(MultipartFile file) {
+        String filename = file.getOriginalFilename();
+        if (StringUtils.isEmpty(filename)) {
+            return false;
+        }
+
+        // 检查文件扩展名
+        String extension = getExtension(filename);
+        if (isTextExtension(extension)) {
+            return true;
+        }
+
+        // 检查文件内容
+        try {
+            return isTextContent(file);
+        } catch (IOException e) {
+            log.error("检测文件类型失败", e);
+            return false;
+        }
+    }
+
+    /**
+     * 获取文件扩展名
+     */
+    public static String getExtension(String filename) {
+        if (StringUtils.isEmpty(filename)) {
+            return "";
+        }
+        int dotIndex = filename.lastIndexOf(".");
+        return (dotIndex == -1) ? "" : filename.substring(dotIndex + 1).toLowerCase();
+    }
+
+    /**
+     * 检查扩展名是否为文本文件
+     */
+    private static boolean isTextExtension(String extension) {
+        String[] textExtensions = {
+                "txt", "text", "json", "xml", "html", "htm", "css", "js", "md", "markdown",
+                "ini", "conf", "config", "properties", "prop", "yaml", "yml",
+                "sql", "java", "py", "c", "cpp", "h", "hpp", "cs", "php", "rb", "pl", "sh"
+        };
+        return Arrays.asList(textExtensions).contains(extension.toLowerCase());
+    }
+
+    /**
+     * 通过检查文件内容来判断是否为文本文件
+     */
+    private static boolean isTextContent(MultipartFile file) throws IOException {
+        try (InputStream is = file.getInputStream()) {
+            byte[] bytes = new byte[1024];
+            int read = is.read(bytes);
+            if (read == -1) {
+                return false;
+            }
+
+            // 检查是否包含空字节,文本文件通常不包含空字节
+            for (int i = 0; i < read; i++) {
+                if (bytes[i] == 0) {
+                    return false;
+                }
+            }
+            return true;
+        }
+    }
+
+    /**
+     * 安全的读取文件内容
+     *
+     * @param file 上传的文件
+     * @param maxSize 最大允许大小(字节)
+     * @return 文件内容
+     */
+    public static String readStringSafely(MultipartFile file, long maxSize) throws IOException {
+        // 检查文件大小
+        if (file.getSize() > maxSize) {
+            throw new IOException("文件大小超过限制");
+        }
+
+        // 检查是否为文本文件
+        if (!isTextFile(file)) {
+            throw new IOException("不支持的文件类型");
+        }
+
+        // 读取文件内容
+        return readString(file);
+    }
+}