9b4433fddf5b401edb0aace8a404ac733b122702..17925215d37dd97d744c9296b185aeb16d3e44fb
2025-11-18 Ludc
URL请求路径安全校验
179252 对比 | 目录
2025-11-18 Ludc
所有文件上传接口增加文件安全校验逻辑。
447005 对比 | 目录
已添加2个文件
已修改10个文件
835 ■■■■■ 文件已修改
Source/UBCS/pom.xml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Source/UBCS/ubcs-common/src/main/java/com/vci/ubcs/common/constant/LauncherConstant.java 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Source/UBCS/ubcs-common/src/main/java/com/vci/ubcs/common/validator/ComprehensiveFileValidator.java 477 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Source/UBCS/ubcs-gateway/src/main/java/com/vci/ubcs/gateway/filter/GlobalUrlSecurityFilter.java 142 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Source/UBCS/ubcs-ops/ubcs-flow/src/main/java/com/vci/ubcs/flow/engine/controller/FlowManagerController.java 31 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Source/UBCS/ubcs-ops/ubcs-resource/src/main/java/com/vci/ubcs/resource/controller/FileController.java 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Source/UBCS/ubcs-ops/ubcs-resource/src/main/java/com/vci/ubcs/resource/endpoint/OssEndpoint.java 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Source/UBCS/ubcs-service/ubcs-code/src/main/java/com/vci/ubcs/code/controller/CodeClassifyController.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Source/UBCS/ubcs-service/ubcs-code/src/main/java/com/vci/ubcs/code/controller/MdmEngineController.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Source/UBCS/ubcs-service/ubcs-deploy/src/main/java/com/vci/ubcs/deploy/controller/DeployAppsController.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Source/UBCS/ubcs-service/ubcs-system/src/main/java/com/vci/ubcs/system/controller/RegionController.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Source/UBCS/ubcs-service/ubcs-user/src/main/java/com/vci/ubcs/system/user/controller/UserController.java 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Source/UBCS/pom.xml
@@ -108,11 +108,11 @@
            <resource>
                <directory>src/main/resources</directory>
                <excludes>
                    <!--<exclude>bootstrap.yml</exclude>
                    <exclude>bootstrap.yml</exclude>
                    <exclude>application.yml</exclude>
                    <exclude>application-dev.yml</exclude>
                    <exclude>application-test.yml</exclude>
                    <exclude>application-prop.yml</exclude>-->
                    <exclude>application-prop.yml</exclude>
                </excludes>
            </resource>
            <resource>
@@ -205,6 +205,7 @@
                        <excludes>
                            <exclude>application-dev.yml</exclude>
                            <exclude>application-prod.yml</exclude>
                            <exclude>application-test.yml</exclude>
                            <exclude>application.yml</exclude>
                            <exclude>lib/*</exclude>
                        </excludes>
Source/UBCS/ubcs-common/src/main/java/com/vci/ubcs/common/constant/LauncherConstant.java
@@ -40,20 +40,20 @@
    /**
     * nacos dev åœ°å€
     */
    //String NACOS_DEV_ADDR = "dev.vci-tech.com:38848";
    String NACOS_DEV_ADDR = "127.0.0.1:8848";
    String NACOS_DEV_ADDR = "dev.vci-tech.com:38848";
    //String NACOS_DEV_ADDR = "127.0.0.1:8848";
    /**
     * nacos prod åœ°å€
     */
    //String NACOS_PROD_ADDR = "dev.vci-tech.com:38848";
    String NACOS_PROD_ADDR = "127.0.0.1:8848";
    String NACOS_PROD_ADDR = "dev.vci-tech.com:38848";
    //String NACOS_PROD_ADDR = "127.0.0.1:8848";
    /**
     * nacos test åœ°å€
     */
    //String NACOS_TEST_ADDR = "dev.vci-tech.com:38848";
    String NACOS_TEST_ADDR = "127.0.0.1:8848";
    String NACOS_TEST_ADDR = "dev.vci-tech.com:38848";
    //String NACOS_TEST_ADDR = "127.0.0.1:8848";
    /**
     * sentinel dev åœ°å€
Source/UBCS/ubcs-common/src/main/java/com/vci/ubcs/common/validator/ComprehensiveFileValidator.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,477 @@
package com.vci.ubcs.common.validator;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.PostConstruct;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
/**
 * æ–‡ä»¶å®‰å…¨éªŒè¯å™¨
 */
@Component
@Slf4j
public class ComprehensiveFileValidator {
    /**
     * æ–‡ä»¶ç™½åå•
     */
    @Value("${app.upload.security.allowed-extensions:jpg,jpeg,png,pdf}")
    private String allowedExtensionsConfig;
    /**
     * å¤šé‡æ‰©å±•名文件禁止
     */
    @Value("${app.upload.security.prevent-multiple-extensions:true}")
    private boolean preventMultipleExtensions;
    /**
     * é™åˆ¶çš„危险文件类型
     */
    @Value("${app.upload.security.dangerous-primary-extensions:jsp,jspx,php,asp,aspx,war,exe,sh,bat}")
    private String dangerousExtensionsConfig;
    /**
     * æ–‡ä»¶å†…容类型是否匹配校验
     */
    @Value("${app.upload.security.validate-content-type:true}")
    private boolean validateContentType;
    /**
     * æ–‡ä»¶å¤´éªŒè¯
     */
    @Value("${app.upload.security.validate-file-header:true}")
    private boolean validateFileHeader;
    /**
     * ä¸¥æ ¼æ¨¡å¼
     */
    @Value("${app.upload.security.strict-mode:false}")
    private boolean strictMode;
    /**
     * å…è®¸ä¸Šä¼ çš„后缀
     */
    private Set<String> allowedExtensions;
    /**
     * å±é™©çš„æ–‡ä»¶åŽç¼€
     */
    private Set<String> dangerousPrimaryExtensions;
    @PostConstruct
    public void init() {
        // è§£æžé€—号分隔的配置
        this.allowedExtensions = parseCommaSeparatedConfig(allowedExtensionsConfig);
        this.dangerousPrimaryExtensions = parseCommaSeparatedConfig(dangerousExtensionsConfig);
        log.info("文件上传验证器初始化完成");
        log.info("允许的扩展名: {}", allowedExtensions);
        log.info("危险扩展名: {}", dangerousPrimaryExtensions);
    }
    private Set<String> parseCommaSeparatedConfig(String config) {
        if (config == null || config.trim().isEmpty()) {
            return new HashSet<>();
        }
        return Arrays.stream(config.split(","))
            .map(String::trim)
            .map(String::toLowerCase)
            .collect(Collectors.toSet());
    }
    /**
     * éªŒè¯å•个文件
     */
    public UploadValidationResult validateFile(MultipartFile file) {
        UploadValidationResult result = new UploadValidationResult();
        try {
            // åŸºç¡€æ£€æŸ¥
            if (!basicValidation(file, result)) {
                return result;
            }
            String filename = file.getOriginalFilename();
            // æ–‡ä»¶åå®‰å…¨éªŒè¯
            if (!filenameSecurityValidation(filename, result)) {
                return result;
            }
            // å†…容安全验证
            if (!contentSecurityValidation(file, result)) {
                return result;
            }
            result.setValid(true);
            result.setMessage("文件验证通过");
        } catch (Exception e) {
            log.error("文件验证异常", e);
            result.setValid(false);
            result.setMessage("验证过程发生异常");
        }
        return result;
    }
    /**
     * éªŒè¯å¤šä¸ªæ–‡ä»¶
     * @param files æ–‡ä»¶åˆ—表
     * @return å¤šä¸ªæ–‡ä»¶çš„验证结果
     */
    public MultiUploadValidationResult validateFiles(List<MultipartFile> files) {
        return validateFiles(files, false);
    }
    /**
     * éªŒè¯å¤šä¸ªæ–‡ä»¶
     * @param files æ–‡ä»¶åˆ—表
     * @param stopOnFirstError é‡åˆ°ç¬¬ä¸€ä¸ªé”™è¯¯æ˜¯å¦åœæ­¢éªŒè¯
     * @return å¤šä¸ªæ–‡ä»¶çš„验证结果
     */
    public MultiUploadValidationResult validateFiles(List<MultipartFile> files, boolean stopOnFirstError) {
        MultiUploadValidationResult result = new MultiUploadValidationResult();
        if (files == null || files.isEmpty()) {
            result.setValid(false);
            result.setMessage("文件列表为空");
            return result;
        }
        List<FileValidationDetail> details = new ArrayList<>();
        boolean allValid = true;
        for (int i = 0; i < files.size(); i++) {
            MultipartFile file = files.get(i);
            FileValidationDetail detail = new FileValidationDetail();
            detail.setFileName(file.getOriginalFilename());
            detail.setFileIndex(i);
            detail.setFileSize(file.getSize());
            // éªŒè¯å•个文件
            UploadValidationResult singleResult = validateFile(file);
            detail.setValid(singleResult.isValid());
            detail.setMessage(singleResult.getMessage());
            detail.setDetectedType(singleResult.getDetectedType());
            details.add(detail);
            if (!singleResult.isValid()) {
                allValid = false;
                if (stopOnFirstError) {
                    // é‡åˆ°é”™è¯¯ä¸”设置为快速失败,立即返回
                    result.setValid(false);
                    result.setMessage("第" + (i + 1) + "个文件验证失败: " + file.getOriginalFilename());
                    result.setDetails(details);
                    result.setFailedIndex(i);
                    return result;
                }
            }
        }
        result.setValid(allValid);
        result.setMessage(allValid ? "所有文件验证通过" : "部分文件验证失败");
        result.setDetails(details);
        result.setTotalFiles(files.size());
        result.setValidFiles((int) details.stream().filter(FileValidationDetail::isValid).count());
        result.setInvalidFiles((int) details.stream().filter(d -> !d.isValid()).count());
        return result;
    }
    /**
     * éªŒè¯å¤šä¸ªæ–‡ä»¶ï¼ˆæ•°ç»„版本)
     */
    public MultiUploadValidationResult validateFiles(MultipartFile[] files) {
        return validateFiles(Arrays.asList(files));
    }
    /**
     * éªŒè¯å¤šä¸ªæ–‡ä»¶ï¼ˆæ•°ç»„版本,可设置是否快速失败)
     */
    public MultiUploadValidationResult validateFiles(MultipartFile[] files, boolean stopOnFirstError) {
        return validateFiles(Arrays.asList(files), stopOnFirstError);
    }
    /**
     * æ‰¹é‡éªŒè¯æ–‡ä»¶å¹¶è¿”回有效的文件列表
     */
    public List<MultipartFile> getValidFiles(List<MultipartFile> files) {
        MultiUploadValidationResult result = validateFiles(files);
        List<MultipartFile> validFiles = new ArrayList<>();
        for (int i = 0; i < files.size(); i++) {
            if (result.getDetails().get(i).isValid()) {
                validFiles.add(files.get(i));
            }
        }
        return validFiles;
    }
    /**
     * æ£€æŸ¥æ˜¯å¦æ‰€æœ‰æ–‡ä»¶éƒ½æœ‰æ•ˆ
     */
    public boolean areAllFilesValid(List<MultipartFile> files) {
        MultiUploadValidationResult result = validateFiles(files);
        return result.isValid();
    }
    // åŽŸæœ‰çš„ç§æœ‰æ–¹æ³•ä¿æŒä¸å˜
    private boolean basicValidation(MultipartFile file, UploadValidationResult result) {
        if (file == null || file.isEmpty()) {
            result.setMessage("文件为空");
            return false;
        }
        String filename = file.getOriginalFilename();
        if (filename == null || filename.trim().isEmpty()) {
            result.setMessage("文件名为空");
            return false;
        }
        return true;
    }
    private boolean filenameSecurityValidation(String filename, UploadValidationResult result) {
        // è·¯å¾„遍历检查
        if (filename.contains("..") || filename.contains("/") || filename.contains("\\")) {
            result.setMessage("文件名包含危险字符");
            return false;
        }
        // æ‰©å±•名检查
        String finalExtension = getFinalExtension(filename);
        if (finalExtension.isEmpty() || !allowedExtensions.contains(finalExtension.toLowerCase())) {
            result.setMessage("不支持的文件类型: " + finalExtension);
            return false;
        }
        // å¤šé‡æ‰©å±•名检查
        if (preventMultipleExtensions && hasMultipleExtensions(filename)) {
            if (strictMode) {
                // ä¸¥æ ¼æ¨¡å¼ï¼šæ‹¦æˆªæ‰€æœ‰å¤šé‡æ‰©å±•名
                result.setMessage("多重扩展名文件被禁止");
                return false;
            } else {
                // æ™®é€šæ¨¡å¼ï¼šåªæ‹¦æˆªåŒ…含危险扩展名的多重扩展名
                if (containsDangerousExtension(filename)) {
                    result.setMessage("检测到伪装Webshell文件: " + filename);
                    return false;
                }
            }
        }
        return true;
    }
    private boolean contentSecurityValidation(MultipartFile file, UploadValidationResult result) {
        // å†…容类型验证
        if (validateContentType && !validateContentType(file)) {
            result.setMessage("文件内容类型不匹配");
            return false;
        }
        // æ–‡ä»¶å¤´éªŒè¯
        if (validateFileHeader && !validateFileHeader(file)) {
            result.setMessage("文件头验证失败");
            return false;
        }
        return true;
    }
    private boolean hasMultipleExtensions(String filename) {
        String name = getFileNameWithoutPath(filename);
        return name.chars().filter(ch -> ch == '.').count() > 1;
    }
    private boolean containsDangerousExtension(String filename) {
        String name = getFileNameWithoutPath(filename);
        String[] parts = name.split("\\.");
        // æ£€æŸ¥é™¤æœ€åŽä¸€ä¸ªæ‰©å±•名之外的所有部分
        for (int i = 0; i < parts.length - 1; i++) {
            String part = parts[i].toLowerCase();
            if (dangerousPrimaryExtensions.contains(part)) {
                return true;
            }
        }
        return false;
    }
    private boolean validateContentType(MultipartFile file) {
        try {
            String declaredType = file.getContentType();
            if (declaredType == null) {
                return true; // æ²¡æœ‰å£°æ˜Žç±»åž‹ï¼Œæ”¾è¿‡
            }
            // ç®€å•的类型匹配检查
            String finalExtension = getFinalExtension(file.getOriginalFilename()).toLowerCase();
            return isContentTypeConsistent(declaredType, finalExtension);
        } catch (Exception e) {
            log.error("内容类型验证失败", e);
            return false;
        }
    }
    /**
     * éªŒè¯æ–‡ä»¶çš„内容类型(Content-Type)是否与文件扩展名一致
     * @param contentType
     * @param extension
     * @return
     */
    private boolean isContentTypeConsistent(String contentType, String extension) {
        // æ‰©å±•更全面的类型映射
        Map<String, String> expectedTypes = new HashMap<>();
        // å›¾ç‰‡ç±»åž‹
        expectedTypes.put("jpg", "image/jpeg");
        expectedTypes.put("jpeg", "image/jpeg");
        expectedTypes.put("png", "image/png");
        expectedTypes.put("gif", "image/gif");
        expectedTypes.put("bmp", "image/bmp");
        expectedTypes.put("webp", "image/webp");
        expectedTypes.put("svg", "image/svg+xml");
        // æ–‡æ¡£ç±»åž‹
        expectedTypes.put("pdf", "application/pdf");
        expectedTypes.put("doc", "application/msword");
        expectedTypes.put("docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document");
        expectedTypes.put("xls", "application/vnd.ms-excel");
        expectedTypes.put("xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        expectedTypes.put("ppt", "application/vnd.ms-powerpoint");
        expectedTypes.put("pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation");
        expectedTypes.put("txt", "text/plain");
        // åŽ‹ç¼©æ–‡ä»¶
        expectedTypes.put("zip", "application/zip");
        expectedTypes.put("rar", "application/x-rar-compressed");
        expectedTypes.put("7z", "application/x-7z-compressed");
        String expectedType = expectedTypes.get(extension);
        return expectedType == null || expectedType.equalsIgnoreCase(contentType);
    }
    private boolean validateFileHeader(MultipartFile file) {
        try {
            byte[] header = new byte[8];
            int bytesRead = file.getInputStream().read(header);
            if (bytesRead < 4) {
                return false;
            }
            String finalExtension = getFinalExtension(file.getOriginalFilename()).toLowerCase();
            // åŸºç¡€çš„æ–‡ä»¶å¤´éªŒè¯
            switch (finalExtension) {
                case "jpg":
                case "jpeg":
                    return isJpeg(header);
                case "png":
                    return isPng(header);
                case "pdf":
                    return isPdf(header);
                case "gif":
                    return isGif(header);
                default:
                    return true; // å…¶ä»–类型不验证文件头
            }
        } catch (IOException e) {
            log.error("文件头验证失败", e);
            return false;
        }
    }
    /**
     * æ–‡ä»¶å¤´éªŒè¯æ–¹æ³•
     * @param header
     * @return
     */
    private boolean isJpeg(byte[] header) {
        return (header[0] & 0xFF) == 0xFF && (header[1] & 0xFF) == 0xD8;
    }
    private boolean isPng(byte[] header) {
        return header[0] == (byte) 0x89 && header[1] == 0x50 &&
            header[2] == 0x4E && header[3] == 0x47;
    }
    private boolean isPdf(byte[] header) {
        return header[0] == 0x25 && header[1] == 0x50 &&
            header[2] == 0x44 && header[3] == 0x46;
    }
    private boolean isGif(byte[] header) {
        return header[0] == 'G' && header[1] == 'I' &&
            header[2] == 'F' && header[3] == '8';
    }
    // è¾…助方法
    private String getFinalExtension(String filename) {
        if (filename == null || !filename.contains(".")) return "";
        String[] parts = filename.split("\\.");
        return parts[parts.length - 1];
    }
    private String getFileNameWithoutPath(String filename) {
        if (filename == null) return "";
        filename = filename.replace('\\', '/');
        int lastSlash = filename.lastIndexOf('/');
        return lastSlash >= 0 ? filename.substring(lastSlash + 1) : filename;
    }
    @Data
    public static class UploadValidationResult {
        private boolean valid;
        private String message;
        private String detectedType;
        public UploadValidationResult() {
            this.valid = false;
            this.message = "";
        }
    }
    /**
     * å¤šæ–‡ä»¶éªŒè¯ç»“æžœ
     */
    @Data
    public static class MultiUploadValidationResult {
        private boolean valid;
        private String message;
        private int totalFiles;
        private int validFiles;
        private int invalidFiles;
        private int failedIndex = -1; // ç¬¬ä¸€ä¸ªå¤±è´¥çš„æ–‡ä»¶ç´¢å¼•
        private List<FileValidationDetail> details;
        public MultiUploadValidationResult() {
            this.valid = false;
            this.message = "";
            this.details = new ArrayList<>();
        }
    }
    /**
     * å•个文件验证详情
     */
    @Data
    public static class FileValidationDetail {
        private String fileName;
        private int fileIndex;
        private long fileSize;
        private boolean valid;
        private String message;
        private String detectedType;
    }
}
Source/UBCS/ubcs-gateway/src/main/java/com/vci/ubcs/gateway/filter/GlobalUrlSecurityFilter.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,142 @@
package com.vci.ubcs.gateway.filter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import javax.annotation.PostConstruct;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;
/**
 * å…¨å±€URL安全过滤器 - å¯¹æ‰€æœ‰è¯·æ±‚生效
 * ç›´æŽ¥ä½¿ç”¨@Value注解读取配置
 */
@Component
public class GlobalUrlSecurityFilter implements GlobalFilter, Ordered {
    private static final Logger log = LoggerFactory.getLogger(GlobalUrlSecurityFilter.class);
    // æ˜¯å¦å¯ç”¨URL安全过滤器
    @Value("${gateway.security.url.enabled:true}")
    private boolean enabled;
    // å±é™©å­—符正则表达式
    @Value("${gateway.security.url.dangerous-pattern:<script|</script|javascript:|onload|onerror|onclick|union.*select|select.*from|[\"';<>\\x00-\\x1F\\x7F]|%3C|%3E|%27|%22|%00|\\.\\./|\\.\\.\\\\}")
    private String dangerousPattern;
    // ç™½åå•路径
    @Value("${gateway.security.url.whitelist-paths:/health,/actuator/health,/actuator/info,/favicon.ico}")
    private String[] whitelistPaths;
    private Pattern compiledPattern;
    private Set<String> whitelistSet;
    /**
     * åˆå§‹åŒ–方法
     */
    @PostConstruct
    public void init() {
        try {
            // ç¼–译危险字符正则表达式
            this.compiledPattern = Pattern.compile(dangerousPattern, Pattern.CASE_INSENSITIVE);
            // åˆå§‹åŒ–白名单集合
            this.whitelistSet = new HashSet<>(Arrays.asList(whitelistPaths));
            log.info("全局URL安全过滤器初始化完成");
            log.info("过滤器状态: {}", enabled ? "已启用" : "已禁用");
            log.info("白名单路径: {}", whitelistSet);
        } catch (Exception e) {
            log.error("初始化过滤器失败", e);
            // ä½¿ç”¨é»˜è®¤é…ç½®ä½œä¸ºåŽå¤‡
            this.compiledPattern = Pattern.compile(
                "<script|</script|javascript:|onload|onerror|onclick|union.*select|select.*from|[\"';<>\\x00-\\x1F\\x7F]|%3C|%3E|%27|%22|%00|\\.\\./|\\.\\.\\\\",
                Pattern.CASE_INSENSITIVE
            );
            this.whitelistSet = new HashSet<>(Arrays.asList(
                "/health", "/actuator/health", "/actuator/info", "/favicon.ico"
            ));
        }
    }
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // æ£€æŸ¥æ˜¯å¦å¯ç”¨è¿‡æ»¤å™¨
        if (!enabled) {
            return chain.filter(exchange);
        }
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();
        String method = request.getMethod().name();
        // æ£€æŸ¥ç™½åå•路径
        if (isWhitelistedPath(path)) {
            return chain.filter(exchange);
        }
        // éªŒè¯è·¯å¾„安全性
        if (!isPathSafe(path)) {
            log.warn("拦截危险请求: {} {}", method, request.getURI());
            exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }
    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }
    /**
     * éªŒè¯è·¯å¾„安全性
     */
    private boolean isPathSafe(String path) {
        if (path == null || path.isEmpty()) {
            return true;
        }
        // æ£€æŸ¥è·¯å¾„遍历
        if (path.contains("../") || path.contains("..\\")) {
            return false;
        }
        // æ£€æŸ¥å±é™©å­—符
        if (compiledPattern.matcher(path).find()) {
            return false;
        }
        return true;
    }
    /**
     * æ£€æŸ¥è·¯å¾„是否在白名单中
     */
    private boolean isWhitelistedPath(String path) {
        if (path == null) {
            return false;
        }
        for (String whitelistPath : whitelistSet) {
            if (path.startsWith(whitelistPath) || path.equals(whitelistPath)) {
                return true;
            }
        }
        return false;
    }
}
Source/UBCS/ubcs-ops/ubcs-flow/src/main/java/com/vci/ubcs/flow/engine/controller/FlowManagerController.java
@@ -18,6 +18,7 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.vci.ubcs.common.validator.ComprehensiveFileValidator;
import com.vci.ubcs.flow.engine.entity.FlowProcess;
import com.vci.ubcs.flow.engine.service.FlowEngineService;
import io.swagger.annotations.Api;
@@ -31,6 +32,7 @@
import org.springblade.core.tool.support.Kv;
import org.springblade.core.tool.utils.Func;
import com.vci.ubcs.flow.engine.constant.FlowEngineConstant;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
@@ -44,18 +46,23 @@
 */
@NonDS
@RestController
@RequestMapping("manager")
@RequestMapping("/manager")
@AllArgsConstructor
@Api(value = "流程管理接口", tags = "流程管理接口")
//@PreAuth(RoleConstant.HAS_ROLE_ADMINISTRATOR)
public class FlowManagerController {
    private final FlowEngineService flowEngineService;
    /**
     * æ–‡ä»¶å®‰å…¨æ£€æŸ¥
     */
    @Autowired
    private ComprehensiveFileValidator fileValidator;
    /**
     * åˆ†é¡µ
     */
    @GetMapping("list")
    @GetMapping("/list")
    @ApiOperationSupport(order = 1)
    @ApiOperation(value = "分页", notes = "传入流程类型")
    public R<IPage<FlowProcess>> list(@ApiParam("流程类型") String category, Query query, @RequestParam(required = false, defaultValue = "1") Integer mode) {
@@ -69,7 +76,7 @@
     * @param state     çŠ¶æ€
     * @param processId æµç¨‹id
     */
    @PostMapping("change-state")
    @PostMapping("/change-state")
    @ApiOperationSupport(order = 2)
    @ApiOperation(value = "变更流程状态", notes = "传入state,processId")
    public R changeState(@RequestParam String state, @RequestParam String processId) {
@@ -82,7 +89,7 @@
     *
     * @param deploymentIds éƒ¨ç½²æµç¨‹id集合
     */
    @PostMapping("delete-deployment")
    @PostMapping("/delete-deployment")
    @ApiOperationSupport(order = 3)
    @ApiOperation(value = "删除部署流程", notes = "部署流程id集合")
    public R deleteDeployment(String deploymentIds) {
@@ -94,10 +101,15 @@
     *
     * @param file æµç¨‹æ–‡ä»¶
     */
    @PostMapping("check-upload")
    @PostMapping("/check-upload")
    @ApiOperationSupport(order = 4)
    @ApiOperation(value = "上传部署流程文件", notes = "传入文件")
    public R checkUpload(@RequestParam MultipartFile file) {
        // ä½¿ç”¨æ–‡ä»¶å®‰å…¨éªŒè¯å™¨
        ComprehensiveFileValidator.UploadValidationResult result = fileValidator.validateFile(file);
        if (!result.isValid()) {
            return R.fail(result.getMessage());
        }
        boolean temp = Objects.requireNonNull(file.getOriginalFilename()).endsWith(FlowEngineConstant.SUFFIX);
        return R.data(Kv.create().set("name", file.getOriginalFilename()).set("success", temp));
    }
@@ -108,12 +120,17 @@
     * @param files    æµç¨‹æ–‡ä»¶
     * @param category ç±»åž‹
     */
    @PostMapping("deploy-upload")
    @PostMapping("/deploy-upload")
    @ApiOperationSupport(order = 5)
    @ApiOperation(value = "上传部署流程文件", notes = "传入文件")
    public R deployUpload(@RequestParam List<MultipartFile> files,
                          @RequestParam String category,
                          @RequestParam(required = false, defaultValue = "") String tenantIds) {
        // ä½¿ç”¨æ–‡ä»¶å®‰å…¨éªŒè¯å™¨
        ComprehensiveFileValidator.MultiUploadValidationResult result = fileValidator.validateFiles(files,true);
        if (!result.isValid()) {
            return R.fail(result.getMessage());
        }
        return R.status(flowEngineService.deployUpload(files, category, Func.toStrList(tenantIds)));
    }
Source/UBCS/ubcs-ops/ubcs-resource/src/main/java/com/vci/ubcs/resource/controller/FileController.java
@@ -1,8 +1,8 @@
package com.vci.ubcs.resource.controller;
import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.common.utils.StringUtils;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.vci.ubcs.common.validator.ComprehensiveFileValidator;
import com.vci.ubcs.resource.dto.FileObjectDTO;
import com.vci.ubcs.resource.dto.FileReleaseDTO;
import com.vci.ubcs.resource.dto.FileShareDTO;
@@ -10,14 +10,11 @@
import com.vci.ubcs.resource.service.IFileService;
import com.vci.ubcs.resource.utils.FileDownloadUtil;
import com.vci.ubcs.resource.vo.FileObjectVO;
import com.vci.ubcs.starter.exception.VciBaseException;
import com.vci.ubcs.starter.web.util.ControllerUtil;
import com.vci.ubcs.starter.web.util.LangBaseUtil;
import com.vci.ubcs.starter.web.util.VciBaseUtil;
import lombok.extern.java.Log;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.mp.support.Query;
import org.springblade.core.oss.MinioTemplate;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
@@ -45,6 +42,12 @@
     */
    @Autowired
    private IFileService fileService;
    /**
     * æ–‡ä»¶å®‰å…¨æ£€æŸ¥
     */
    @Autowired
    private ComprehensiveFileValidator fileValidator;
    /**
     * æ ¹æ®æ–‡ä»¶ä¸»é”®ä¸‹è½½æ–‡ä»¶
@@ -82,7 +85,7 @@
            if(StringUtil.isBlank(msg)){
                msg = "未知错误";
            }
            log.debug(msg);
            log.error(msg);
            return R.fail(msg);
        }
        return R.success("删除成功");
@@ -97,6 +100,12 @@
    @PostMapping("/uploadFile")
    public R<FileObjectVO> uploadFile(MultipartFile file, FileObjectDTO fileObjectDTO){
        if (file != null ) {
            // ä½¿ç”¨æ–‡ä»¶å®‰å…¨éªŒè¯å™¨
            ComprehensiveFileValidator.UploadValidationResult result = fileValidator.validateFile(file);
            if (!result.isValid()) {
                return R.fail(result.getMessage());
            }
            //上传文件
            return fileService.uploadFile(file, fileObjectDTO);
        } else {
            return R.fail("无上传的文件");
Source/UBCS/ubcs-ops/ubcs-resource/src/main/java/com/vci/ubcs/resource/endpoint/OssEndpoint.java
@@ -17,6 +17,7 @@
package com.vci.ubcs.resource.endpoint;
import com.vci.ubcs.resource.entity.Attach;
import com.vci.ubcs.common.validator.ComprehensiveFileValidator;
import io.swagger.annotations.Api;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
@@ -31,8 +32,6 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
/**
 * å¯¹è±¡å­˜å‚¨ç«¯ç‚¹
@@ -59,6 +58,11 @@
     */
    private final IAttachService attachService;
    /**
     * æ–‡ä»¶å®‰å…¨æ£€æŸ¥
     */
    @Autowired
    private ComprehensiveFileValidator fileValidator;
    /**
     * åˆ›å»ºå­˜å‚¨æ¡¶
@@ -150,6 +154,11 @@
    @SneakyThrows
    @PostMapping("/put-file")
    public R<BladeFile> putFile(@RequestParam MultipartFile file) {
        // ä½¿ç”¨æ–‡ä»¶å®‰å…¨éªŒè¯å™¨
        ComprehensiveFileValidator.UploadValidationResult result = fileValidator.validateFile(file);
        if (!result.isValid()) {
            return R.fail(result.getMessage());
        }
        BladeFile bladeFile = ossBuilder.template().putFile(file.getOriginalFilename(), file.getInputStream());
        return R.data(bladeFile);
    }
@@ -164,41 +173,14 @@
    @SneakyThrows
    @PostMapping("/put-file-by-name")
    public R<BladeFile> putFile(@RequestParam String fileName, @RequestParam MultipartFile file) {
        // ä½¿ç”¨æ–‡ä»¶å®‰å…¨éªŒè¯å™¨
        ComprehensiveFileValidator.UploadValidationResult result = fileValidator.validateFile(file);
        if (!result.isValid()) {
            return R.fail(result.getMessage());
        }
        BladeFile bladeFile = ossBuilder.template().putFile(fileName, file.getInputStream());
        return R.data(bladeFile);
    }
//    /**
//     * ä¸Šä¼ æ–‡ä»¶å¹¶ä¿å­˜è‡³é™„件表
//     *
//     * @param file æ–‡ä»¶
//     * @return ObjectStat
//     */
//    @SneakyThrows
//    @PostMapping("/put-file-attach")
//    public R<BladeFile> putFileAttach(@RequestParam MultipartFile file) {
//        String fileName = file.getOriginalFilename();
//        BladeFile bladeFile = ossBuilder.template().putFile(fileName, file.getInputStream());
//        Long attachId = buildAttach(fileName, file.getSize(), bladeFile);
//        bladeFile.setAttachId(attachId);
//        return R.data(bladeFile);
//    }
//    /**
//     * ä¸Šä¼ æ–‡ä»¶å¹¶ä¿å­˜è‡³é™„件表
//     *
//     * @param fileName å­˜å‚¨æ¡¶å¯¹è±¡åç§°
//     * @param file     æ–‡ä»¶
//     * @return ObjectStat
//     */
//    @SneakyThrows
//    @PostMapping("/put-file-attach-by-name")
//    public R<BladeFile> putFileAttach(@RequestParam String fileName, @RequestParam MultipartFile file) {
//        BladeFile bladeFile = ossBuilder.template().putFile(fileName, file.getInputStream());
//        Long attachId = buildAttach(fileName, file.getSize(), bladeFile);
//        bladeFile.setAttachId(attachId);
//        return R.data(bladeFile);
//    }
    /**
     * æž„建附件表
Source/UBCS/ubcs-service/ubcs-code/src/main/java/com/vci/ubcs/code/controller/CodeClassifyController.java
@@ -25,6 +25,7 @@
import com.vci.ubcs.code.vo.pagemodel.CodeClassifyVO;
import com.vci.ubcs.code.vo.pagemodel.CodeImProtRusultVO;
import com.vci.ubcs.code.wrapper.CodeClassifyWrapper;
import com.vci.ubcs.common.validator.ComprehensiveFileValidator;
import com.vci.ubcs.omd.vo.BtmTypeAttributeVO;
import com.vci.ubcs.starter.revision.model.TreeQueryObject;
import com.vci.ubcs.starter.util.LocalFileUtil;
@@ -47,6 +48,7 @@
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
@@ -77,6 +79,12 @@
    private final ICodeClassifyService codeClassifyService;
    CodeClassifyMapper codeClassifyMapper;
    /**
     * æ–‡ä»¶å®‰å…¨æ£€æŸ¥
     */
    @Autowired
    private ComprehensiveFileValidator fileValidator;
    /**
     * ä¸»é¢˜åº“定义表 è¯¦æƒ…
@@ -277,6 +285,12 @@
     */
    @PostMapping("/importClassify")
    public R importClassify(MultipartFile file) {
        // ä½¿ç”¨æ–‡ä»¶å®‰å…¨éªŒè¯å™¨
        ComprehensiveFileValidator.UploadValidationResult result = fileValidator.validateFile(file);
        if (!result.isValid()) {
            return R.fail(result.getMessage());
        }
        String excelFileName = LocalFileUtil.getDefaultTempFolder() + File.separator + LocalFileUtil.getFileNameForIE(file.getOriginalFilename());
        File file1 = new File(excelFileName);
        try {
Source/UBCS/ubcs-service/ubcs-code/src/main/java/com/vci/ubcs/code/controller/MdmEngineController.java
@@ -10,6 +10,7 @@
import com.vci.ubcs.code.service.MdmEngineService;
import com.vci.ubcs.code.service.MdmIOService;
import com.vci.ubcs.code.vo.pagemodel.*;
import com.vci.ubcs.common.validator.ComprehensiveFileValidator;
import com.vci.ubcs.flow.core.dto.FlowStatusDTO;
import com.vci.ubcs.starter.annotation.VciBusinessLog;
import com.vci.ubcs.starter.revision.model.BaseModel;
@@ -43,21 +44,31 @@
     * æ—¥å¿—
     */
    private Logger logger = LoggerFactory.getLogger(getClass());
    /**
     * ä¸»æ•°æ®å¼•擎服务
     */
    @Autowired
    private MdmEngineService engineService;
    /**
     * ä¸»æ•°æ®å¯¼å…¥å¯¼å‡ºæœåŠ¡
     */
    @Autowired
    private MdmIOService mdmIOService;
    /**
     * æ—¥å¿—保存工具类
     */
    @Autowired
    private SaveLogUtil saveLogUtil;
    /**
     * æ–‡ä»¶å®‰å…¨æ£€æŸ¥
     */
    @Autowired
    private ComprehensiveFileValidator fileValidator;
    /**
     * ä¸‹è½½æ‰¹é‡ç”³è¯·çš„导入模板
@@ -112,6 +123,12 @@
    @VciBusinessLog(operateName = "导入批量编辑数据")
    @PostMapping("/batchImportEdit")
    public R batchImportEdit(String codeClassifyOid, String classifyAttr,MultipartFile file,HttpServletResponse response) throws Throwable {
        // ä½¿ç”¨æ–‡ä»¶å®‰å…¨éªŒè¯å™¨
        ComprehensiveFileValidator.UploadValidationResult validationResult = fileValidator.validateFile(file);
        if (!validationResult.isValid()) {
            return R.fail(validationResult.getMessage());
        }
        String excelFileName = LocalFileUtil.getDefaultTempFolder() + File.separator + file.getOriginalFilename();
        File file1 = new File(excelFileName);
        try {
@@ -218,6 +235,12 @@
    @VciBusinessLog(operateName = "批量申请编码的信息")
    @PostMapping("/batchImportCode")
    public R batchImportCode(String secDTOList, String codeClassifyOid, MultipartFile file, HttpServletResponse response) throws Throwable {
        // ä½¿ç”¨æ–‡ä»¶å®‰å…¨éªŒè¯å™¨
        ComprehensiveFileValidator.UploadValidationResult validationResult = fileValidator.validateFile(file);
        if (!validationResult.isValid()) {
            return R.fail(validationResult.getMessage());
        }
        CodeOrderDTO orderDTO = new CodeOrderDTO();
        orderDTO.setCodeClassifyOid(codeClassifyOid);
        if(StringUtils.isNotBlank(secDTOList)){
@@ -269,6 +292,12 @@
    @VciBusinessLog(operateName = "导入编码的历史数据")
    @PostMapping("/batchImportHistoryData")
    public R batchImportHistoryData(String codeClassifyOid, String classifyAttr,MultipartFile file,HttpServletResponse response) throws Throwable {
        // ä½¿ç”¨æ–‡ä»¶å®‰å…¨éªŒè¯å™¨
        ComprehensiveFileValidator.UploadValidationResult validationResult = fileValidator.validateFile(file);
        if (!validationResult.isValid()) {
            return R.fail(validationResult.getMessage());
        }
        String excelFileName = LocalFileUtil.getDefaultTempFolder() + File.separator + file.getOriginalFilename();
        File file1 = new File(excelFileName);
        try {
@@ -312,6 +341,12 @@
    @VciBusinessLog(operateName = "批量申请编码的信息")
    @PostMapping("/batchTopImportCode")
    public R batchTopImportCode(String codeClassifyOid, String classifyAttr,MultipartFile file,HttpServletResponse response) throws Throwable {
        // ä½¿ç”¨æ–‡ä»¶å®‰å…¨éªŒè¯å™¨
        ComprehensiveFileValidator.UploadValidationResult result = fileValidator.validateFile(file);
        if (!result.isValid()) {
            return R.fail(result.getMessage());
        }
        String excelFileName = LocalFileUtil.getDefaultTempFolder() + File.separator + file.getOriginalFilename();
        File file1 = new File(excelFileName);
        try {
@@ -867,6 +902,11 @@
     */
    @PostMapping("/importGroupCode")
    public R  importGroupCode(String codeClassifyOid,MultipartFile file,HttpServletResponse response){
        // ä½¿ç”¨æ–‡ä»¶å®‰å…¨éªŒè¯å™¨
        ComprehensiveFileValidator.UploadValidationResult result = fileValidator.validateFile(file);
        if (!result.isValid()) {
            return R.fail(result.getMessage());
        }
        String excelFileName = LocalFileUtil.getDefaultTempFolder() + File.separator + file.getOriginalFilename();
        File file1 = new File(excelFileName);
Source/UBCS/ubcs-service/ubcs-deploy/src/main/java/com/vci/ubcs/deploy/controller/DeployAppsController.java
@@ -1,6 +1,7 @@
package com.vci.ubcs.deploy.controller;
import com.alibaba.nacos.shaded.com.google.protobuf.ServiceException;
import com.vci.ubcs.common.validator.ComprehensiveFileValidator;
import com.vci.ubcs.deploy.entity.DeployApps;
import com.vci.ubcs.deploy.service.IDeployAppsService;
import com.vci.ubcs.deploy.vo.DeployAppsVO;
@@ -10,6 +11,7 @@
import org.springblade.core.tenant.annotation.NonDS;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.Func;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import springfox.documentation.annotations.ApiIgnore;
@@ -32,6 +34,12 @@
public class DeployAppsController {
    private final IDeployAppsService deployAppsService;
    /**
     * æ–‡ä»¶å®‰å…¨æ£€æŸ¥
     */
    @Autowired
    private ComprehensiveFileValidator fileValidator;
    /**
     * èŽ·å–æœåŠ¡è¿è¡Œåˆ—è¡¨
@@ -86,6 +94,11 @@
     */
    @PostMapping("/importUpdateServiceJar")
    public R importClassify(@RequestParam("files") MultipartFile[] files,@RequestParam String serverName) throws ServiceException {
        // ä½¿ç”¨æ–‡ä»¶å®‰å…¨éªŒè¯å™¨
        ComprehensiveFileValidator.MultiUploadValidationResult quickResult = fileValidator.validateFiles(files, true);
        if (!quickResult.isValid()) {
            return R.fail(quickResult.getMessage());
        }
        if(Func.isBlank(serverName)){
            return R.fail("Mandatory parameter service name not found!");
        }
Source/UBCS/ubcs-service/ubcs-system/src/main/java/com/vci/ubcs/system/controller/RegionController.java
@@ -20,6 +20,7 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.vci.ubcs.common.validator.ComprehensiveFileValidator;
import com.vci.ubcs.system.entity.Region;
import com.vci.ubcs.system.excel.RegionExcel;
import com.vci.ubcs.system.excel.RegionImporter;
@@ -35,6 +36,7 @@
import org.springblade.core.tenant.annotation.NonDS;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.DateUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import springfox.documentation.annotations.ApiIgnore;
@@ -58,6 +60,12 @@
public class RegionController extends BladeController {
    private final IRegionService regionService;
    /**
     * æ–‡ä»¶å®‰å…¨æ£€æŸ¥
     */
    @Autowired
    private ComprehensiveFileValidator fileValidator;
    /**
     * è¯¦æƒ…
@@ -170,6 +178,11 @@
    @ApiOperationSupport(order = 10)
    @ApiOperation(value = "导入行政区划", notes = "传入excel")
    public R importRegion(MultipartFile file, Integer isCovered) {
        // ä½¿ç”¨æ–‡ä»¶å®‰å…¨éªŒè¯å™¨
        ComprehensiveFileValidator.UploadValidationResult result = fileValidator.validateFile(file);
        if (!result.isValid()) {
            return R.fail(result.getMessage());
        }
        RegionImporter regionImporter = new RegionImporter(regionService, isCovered == 1);
        ExcelUtil.save(file, regionImporter, RegionExcel.class);
        return R.success("操作成功");
Source/UBCS/ubcs-service/ubcs-user/src/main/java/com/vci/ubcs/system/user/controller/UserController.java
@@ -21,6 +21,7 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.vci.ubcs.common.validator.ComprehensiveFileValidator;
import com.vci.ubcs.system.cache.NacosConfigCache;
import com.vci.ubcs.system.user.entity.User;
import com.vci.ubcs.system.user.excel.UserExcel;
@@ -32,8 +33,7 @@
import io.swagger.annotations.ApiParam;
import lombok.AllArgsConstructor;
import com.vci.ubcs.common.cache.CacheNames;
import org.apache.ibatis.annotations.Param;
import org.hibernate.validator.internal.util.logging.Log;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.cache.utils.CacheUtil;
import org.springblade.core.excel.util.ExcelUtil;
import org.springblade.core.mp.support.Condition;
@@ -50,6 +50,7 @@
import org.springblade.core.tool.utils.StringUtil;
import com.vci.ubcs.system.user.service.IUserService;
import com.vci.ubcs.system.user.vo.UserVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import springfox.documentation.annotations.ApiIgnore;
@@ -71,12 +72,19 @@
@RestController
@RequestMapping
@AllArgsConstructor
@lombok.extern.java.Log
@Slf4j
public class UserController {
    private final IUserService userService;
    private final BladeRedis bladeRedis;
    private final NacosConfigCache nacosConfigCache;
    /**
     * æ–‡ä»¶å®‰å…¨æ£€æŸ¥
     */
    private ComprehensiveFileValidator fileValidator;
    /**
     * æŸ¥è¯¢å•条
@@ -263,6 +271,11 @@
    @ApiOperationSupport(order = 12)
    @ApiOperation(value = "导入用户", notes = "传入excel")
    public R importUser(MultipartFile file, Integer isCovered) {
        // ä½¿ç”¨æ–‡ä»¶å®‰å…¨éªŒè¯å™¨
        ComprehensiveFileValidator.UploadValidationResult result = fileValidator.validateFile(file);
        if (!result.isValid()) {
            return R.fail(result.getMessage());
        }
        UserImporter userImporter = new UserImporter(userService, isCovered == 1);
        ExcelUtil.save(file, userImporter, UserExcel.class);
        return R.success("操作成功");