压缩文件的巨坑

 注意,如果压缩文件名称在文件夹下已存在,则只替换压缩包内部同名文件,需要调用者保证压缩包名唯一或者文件夹唯一、或者生成压缩文件前删除文件

文件处理工具(获取文件、文件流或字符串的编码方式)

/**
 * 文件工具类
 * 
 */
public class FileUtil {

	/**
	 * 获取本地文件的编码格式
	 * 
	 * @param file 要判断的文件编码格式
	 * 	
	 */
	public static String getLocalFileEncode(File localFile) {

		/*
		 * cpDetector是探测器,它把探测任务交给具体的探测实现类的实例完成。
		 * cpDetector内置了一些常用的探测实现类,这些探测实现类的实例可以通过add方法 加进来,如ParsingDetector、ByteOrderMarkDetector、JChardetFacade、ASCIIDetector、UnicodeDetector。
		 * cpDetector按照“谁最先返回非空的探测结果,就以该结果为准”的原则返回探测到的字符集编码。cpDetector是基于统计学原理的,不保证完全正确。
		 */
		CodepageDetectorProxy codepageDetector = CodepageDetectorProxy.getInstance();
		codepageDetector.add(new ParsingDetector(false));//ParsingDetector可用于检查HTML、XML等文件或字符流的编码,构造方法中的参数用于指示是否显示探测过程的详细信息,为false不显示。
		codepageDetector.add(JChardetFacade.getInstance());//JChardetFacade封装了由Mozilla组织提供的JChardet,它可以完成大多数文件的编码 测定。所以,一般有了这个探测器就可满足大多数项目的要求,如果你还不放心,可以再多加几个探测器,比如下面的ASCIIDetector、UnicodeDetector等。
		codepageDetector.add(new ByteOrderMarkDetector());  
		codepageDetector.add(ASCIIDetector.getInstance());//ASCIIDetector用于ASCII编码测定
		codepageDetector.add(UnicodeDetector.getInstance());//UnicodeDetector用于Unicode家族编码的测定
		Charset charset = null;
		try {
			charset = codepageDetector.detectCodepage(localFile.toURI().toURL());
			if (charset != null){
				return charset.name();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	
	/**
	 * 获得远程URL文件的编码格式
	 * 
	 * @param url 远程文件的URL路径
	 * 
	 */
	public static String getURLFileEncode(URL url) {

		/*
		 * cpDetector是探测器,它把探测任务交给具体的探测实现类的实例完成。
		 * cpDetector内置了一些常用的探测实现类,这些探测实现类的实例可以通过add方法 加进来,如ParsingDetector、ByteOrderMarkDetector、JChardetFacade、ASCIIDetector、UnicodeDetector。
		 * cpDetector按照“谁最先返回非空的探测结果,就以该结果为准”的原则返回探测到的字符集编码。cpDetector是基于统计学原理的,不保证完全正确。
		 */
		CodepageDetectorProxy codepageDetector = CodepageDetectorProxy.getInstance();
		codepageDetector.add(new ParsingDetector(false));//ParsingDetector可用于检查HTML、XML等文件或字符流的编码,构造方法中的参数用于指示是否显示探测过程的详细信息,为false不显示。
		codepageDetector.add(JChardetFacade.getInstance());//JChardetFacade封装了由Mozilla组织提供的JChardet,它可以完成大多数文件的编码 测定。所以,一般有了这个探测器就可满足大多数项目的要求,如果你还不放心,可以再多加几个探测器,比如下面的ASCIIDetector、UnicodeDetector等。
		codepageDetector.add(ASCIIDetector.getInstance());//ASCIIDetector用于ASCII编码测定
		codepageDetector.add(UnicodeDetector.getInstance());//UnicodeDetector用于Unicode家族编码的测定
		Charset charset = null;
		try {
			charset = codepageDetector.detectCodepage(url);
			if (charset != null){
				return charset.name();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	
	/**
	 * 获得文件流的编码格式
	 * 
	 * @param inputStream 文件流
	 * 
	 */
	public static String getInputStreamEncode(InputStream inputStream) {

		/*
		 * cpDetector是探测器,它把探测任务交给具体的探测实现类的实例完成。
		 * cpDetector内置了一些常用的探测实现类,这些探测实现类的实例可以通过add方法 加进来,如ParsingDetector、ByteOrderMarkDetector、JChardetFacade、ASCIIDetector、UnicodeDetector。
		 * cpDetector按照“谁最先返回非空的探测结果,就以该结果为准”的原则返回探测到的字符集编码。cpDetector是基于统计学原理的,不保证完全正确。
		 */
		CodepageDetectorProxy codepageDetector = CodepageDetectorProxy.getInstance();
		codepageDetector.add(new ParsingDetector(false));//ParsingDetector可用于检查HTML、XML等文件或字符流的编码,构造方法中的参数用于指示是否显示探测过程的详细信息,为false不显示。
		codepageDetector.add(JChardetFacade.getInstance());//JChardetFacade封装了由Mozilla组织提供的JChardet,它可以完成大多数文件的编码 测定。所以,一般有了这个探测器就可满足大多数项目的要求,如果你还不放心,可以再多加几个探测器,比如下面的ASCIIDetector、UnicodeDetector等。
		codepageDetector.add(ASCIIDetector.getInstance());//ASCIIDetector用于ASCII编码测定
		codepageDetector.add(UnicodeDetector.getInstance());//UnicodeDetector用于Unicode家族编码的测定
		Charset charset = null;
		try {
			charset = codepageDetector.detectCodepage(inputStream, 0);
			if (charset != null){
				return charset.name();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	
	/**
	 * 获得字符串的编码格式
	 * 
	 * @param stringValue 要判断的文件编码格式字符串
	 * 
	 */
	public static String getStringEncode(String stringValue) {

		/*
		 * cpDetector是探测器,它把探测任务交给具体的探测实现类的实例完成。
		 * cpDetector内置了一些常用的探测实现类,这些探测实现类的实例可以通过add方法 加进来,如ParsingDetector、ByteOrderMarkDetector、JChardetFacade、ASCIIDetector、UnicodeDetector。
		 * cpDetector按照“谁最先返回非空的探测结果,就以该结果为准”的原则返回探测到的字符集编码。cpDetector是基于统计学原理的,不保证完全正确。
		 */
		CodepageDetectorProxy codepageDetector = CodepageDetectorProxy.getInstance();
		codepageDetector.add(new ParsingDetector(false));//ParsingDetector可用于检查HTML、XML等文件或字符流的编码,构造方法中的参数用于指示是否显示探测过程的详细信息,为false不显示。
		codepageDetector.add(JChardetFacade.getInstance());//JChardetFacade封装了由Mozilla组织提供的JChardet,它可以完成大多数文件的编码 测定。所以,一般有了这个探测器就可满足大多数项目的要求,如果你还不放心,可以再多加几个探测器,比如下面的ASCIIDetector、UnicodeDetector等。
		codepageDetector.add(ASCIIDetector.getInstance());//ASCIIDetector用于ASCII编码测定
		codepageDetector.add(UnicodeDetector.getInstance());//UnicodeDetector用于Unicode家族编码的测定
		Charset charset = null;
		try {
			InputStream inputStream = new ByteArrayInputStream(stringValue.getBytes());
			charset = codepageDetector.detectCodepage(inputStream, 3);
			if (charset != null){
				return charset.name();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
}

文件下载,中文乱码

下载文件的关键是设置响应头的Content-disposition!!!HttpHeaders  HttpServletResponse

httpServletResponse.setHeader("Content-disposition", "attachment;filename=" + filename + "文件后缀")

下载文件的步骤

  1. 找到文件的真实路径(文件在服务器中的真实位置、远程服务器、第三方服务器)
  2. 将文件读取为流
  3. 将读入流写给浏览器(浏览器将以自身的默认行为对文件进行操作,如能直接打开将在浏览器中打开)
  4. 将读入流写给读出流再给浏览器,命令浏览器直接下载
  5. 关闭流

中文乱码

方案一

Windows平台处理

try {
        fileNameZip = new String(fileNameZip.getBytes("GBK"), "ISO-8859-1");
    } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
    }

Linux平台

try { 
        fileNameZip = new String(fileNameZip.getBytes("UTF-8"), "ISO-8859-1"); // 直接请求,中文名称正常 
    } catch (UnsupportedEncodingException e) { 
        e.printStackTrace();
    }

请求头设置:Content-Disposition; attachment意为附件

response.setHeader("Content-Disposition", "attachment;filename="+fileNameZip+".zip");

方案二

直接使用jdk自带的URLEncoder.encode

response.setHeader("Content-Disposition", "attachment;filename="+URLEncoder.encode(fileNameZip,"UTF-8")+".zip");
response.setContentType("application/force-download");// 设置强制下载不打开
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.ArrayUtils;


/**
 * 文件处理工具类
 *
 */
public class FileUtils extends org.apache.commons.io.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 {
            if (os != null) {
                try {
                    os.close();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
        }
    }

    /**
     * 删除文件
     *
     * @param filePath 文件
     * @return
     */
    public static boolean deleteFile(String filePath) {
        boolean flag = false;
        File file = new File(filePath);
        // 路径为文件且不为空则进行删除
        if (file.isFile() && file.exists()) {
            file.delete();
            flag = true;
        }
        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");
    }
}

 服务相关配置

/**
 * 服务相关配置
 *
 * 
 */
@Component
public class ServerConfig {
    /**
     * 获取完整的请求路径,包括:域名,端口,上下文访问路径
     *
     * @return 服务地址
     */
    public String getUrl() {
        HttpServletRequest request = ServletUtils.getRequest();
        return getDomain(request);
    }

    public static String getDomain(HttpServletRequest request) {
        StringBuffer url = request.getRequestURL();
        String contextPath = request.getServletContext().getContextPath();
        return url.delete(url.length() - request.getRequestURI().length(), url.length()).append(contextPath).toString();
    }
}

配置

/**
 * 读取项目相关配置
 *
 * 
 */
@Component
@ConfigurationProperties(prefix = "project")
public class ProjectConfig {
    /**
     * 项目名称
     */
    private String name;

    /**
     * 版本
     */
    private String version;

    /**
     * 版权年份
     */
    private String copyrightYear;

    /**
     * 实例演示开关
     */
    private boolean demoEnabled;

    /**
     * 上传路径
     */
    private static String profile;

    /**
     * 获取地址开关
     */
    private static boolean addressEnabled;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }

    public String getCopyrightYear() {
        return copyrightYear;
    }

    public void setCopyrightYear(String copyrightYear) {
        this.copyrightYear = copyrightYear;
    }

    public boolean isDemoEnabled() {
        return demoEnabled;
    }

    public void setDemoEnabled(boolean demoEnabled) {
        this.demoEnabled = demoEnabled;
    }

    public static String getProfile() {
        return profile;
    }

    public void setProfile(String profile) {
        RuoYiConfig.profile = profile;
    }

    public static boolean isAddressEnabled() {
        return addressEnabled;
    }

    public void setAddressEnabled(boolean addressEnabled) {
        RuoYiConfig.addressEnabled = addressEnabled;
    }

    /**
     * 获取头像上传路径
     */
    public static String getAvatarPath() {
        return getProfile() + "/avatar";
    }

    /**
     * 获取下载路径
     */
    public static String getDownloadPath() {
        return getProfile() + "/download/";
    }

    /**
     * 获取上传路径
     */
    public static String getUploadPath() {
        return getProfile() + "/upload";
    }
}

文件上传与下载

@RestController
@Slf4j
public class FileUpDownLoadController {


    @Autowired
    private ServerConfig serverConfig;

    /**
     * 通用下载请求
     *
     * @param fileName 文件名称
     * @param delete   是否删除
     */
    @GetMapping("common/download")
    public void fileDownload(String fileName, Boolean delete, HttpServletResponse response, HttpServletRequest request) {
        try {
            if (!FileUtils.checkAllowDownload(fileName)) {
                throw new Exception(StringUtils.format("文件名称({})非法,不允许下载。 ", fileName));
            }
            String realFileName = System.currentTimeMillis() + fileName.substring(fileName.indexOf("_") + 1);
            String filePath = ProjectConfig.getDownloadPath() + fileName;

            response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
            FileUtils.setAttachmentResponseHeader(response, realFileName);
            FileUtils.writeBytes(filePath, response.getOutputStream());
            if (delete) {
                FileUtils.deleteFile(filePath);
            }
        } catch (Exception e) {
            log.error("下载文件失败", e);
        }
    }

    /**
     * 通用上传请求
     */
    @PostMapping("/common/upload")
    public AjaxResult uploadFile(MultipartFile file) throws Exception {
        try {
            // 上传文件路径
            String filePath = RuoYiConfig.getUploadPath();
            // 上传并返回新文件名称
            String fileName = FileUploadUtils.upload(filePath, file);
            String url = serverConfig.getUrl() + fileName;
            AjaxResult ajax = AjaxResult.success();
            ajax.put("fileName", fileName);
            ajax.put("url", url);
            return ajax;
        } catch (Exception e) {
            return AjaxResult.error(e.getMessage());
        }
    }

    /**
     * 本地资源通用下载
     */
    @GetMapping("/common/download/resource")
    public void resourceDownload(String resource, HttpServletRequest request, HttpServletResponse response)
            throws Exception {
        try {
            if (!FileUtils.checkAllowDownload(resource)) {
                throw new Exception(StringUtils.format("资源文件({})非法,不允许下载。 ", resource));
            }
            // 本地资源路径
            String localPath = ProjectConfig.getProfile();
            // 数据库资源地址
            String downloadPath = localPath + StringUtils.substringAfter(resource, Constants.RESOURCE_PREFIX);
            // 下载名称
            String downloadName = StringUtils.substringAfterLast(downloadPath, "/");
            response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
            FileUtils.setAttachmentResponseHeader(response, downloadName);
            FileUtils.writeBytes(downloadPath, response.getOutputStream());
        } catch (Exception e) {
            log.error("下载文件失败", e);
        }
    }
}

 保证服务器存在该盘符

# 项目相关配置
project:
  # 名称
  name: project
  # 版本
  version: 3.4.0
  # 版权年份
  copyrightYear: 2021
  # 实例演示开关
  demoEnabled: true
  # 文件路径 示例( Windows配置D:/project/uploadPath,Linux配置 /home/project/uploadPath)
  profile: D:/project/uploadPath
  # 获取ip地址开关
  addressEnabled: false
  # 验证码类型 math 数组计算 char 字符验证
  captchaType: math

验证码

依赖

<!-- 验证码 -->
        <dependency>
            <groupId>com.github.penggle</groupId>
            <artifactId>kaptcha</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>javax.servlet-api</artifactId>
                    <groupId>javax.servlet</groupId>
                </exclusion>
            </exclusions>
        </dependency>
@RestController
public class CaptchaController {
    @Resource(name = "captchaProducer")
    private Producer captchaProducer;

    @Resource(name = "captchaProducerMath")
    private Producer captchaProducerMath;

    @Autowired
    private RedisCache redisCache;

    // 验证码类型
    @Value("${project.captchaType}")
    private String captchaType;

    /**
     * 生成验证码
     */
    @GetMapping("/captchaImage")
    public AjaxResult getCode(HttpServletResponse response) throws IOException {
        // 保存验证码信息
        String uuid = IdUtils.simpleUUID();
        String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;

        String capStr = null, code = null;
        BufferedImage image = null;

        // 生成验证码
        if ("math".equals(captchaType)) {
            String capText = captchaProducerMath.createText();
            capStr = capText.substring(0, capText.lastIndexOf("@"));
            code = capText.substring(capText.lastIndexOf("@") + 1);
            image = captchaProducerMath.createImage(capStr);
        } else if ("char".equals(captchaType)) {
            capStr = code = captchaProducer.createText();
            image = captchaProducer.createImage(capStr);
        }

        redisCache.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);
        // 转换流信息写出
        FastByteArrayOutputStream os = new FastByteArrayOutputStream();
        try {
            ImageIO.write(image, "jpg", os);
        } catch (IOException e) {
            return AjaxResult.error(e.getMessage());
        }

        AjaxResult ajax = AjaxResult.success();
        ajax.put("uuid", uuid);
        ajax.put("img", Base64.encode(os.toByteArray()));
        return ajax;
    }
}