压缩文件的巨坑
注意,如果压缩文件名称在文件夹下已存在,则只替换压缩包内部同名文件,需要调用者保证压缩包名唯一或者文件夹唯一、或者生成压缩文件前删除文件
文件处理工具(获取文件、文件流或字符串的编码方式)
/**
* 文件工具类
*
*/
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 + "文件后缀")
下载文件的步骤
- 找到文件的真实路径(文件在服务器中的真实位置、远程服务器、第三方服务器)
- 将文件读取为流
- 将读入流写给浏览器(浏览器将以自身的默认行为对文件进行操作,如能直接打开将在浏览器中打开)
- 将读入流写给读出流再给浏览器,命令浏览器直接下载
- 关闭流
中文乱码
方案一
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;
}
}