1 流相关概念
字节流:
(1) 输入流
以Stream结尾的,以字节数组byte[]的形式来读
常见:文件字节输入流 FileInputStream 、BufferedInputStream
(2) 输出流
以Stream结尾的,以字节数组byte[]的形式来写
常见:文件字节输出流 FileOutputStream、BufferedOutputstream
字符流
(1) 输入流
以Reader结尾的,以字符形式来读
常见:文件字符输入流 FileReader、BufferedReader
(2) 输出流
以Writer结尾的,以字符的形式来写
常见:文件字符输出流 FileWriter、BufferedWriter
总结:
字节流使用的是ASCII码,处理的是二进制,由字节byte[]组成,字符流处理的是字符也就是String,本质上是byte[]和String。字节输入流的超类是InputStream,字节输出流的超类是OutputStream,字符输入流的超类是Reader,字符输出流的超类是Writer。
2 读取数据库/目的数据 组装成文件
(1)指定生成文件名称 路径
(2)创建输出流(字符输出流、指定输出的编码如:GBK)
(3)将数据写入到文件里
3 浏览器下载服务器上已有的文件模版
(1) 在服务器指定的路径放上对应的excel文件,使用文件工具类进行浏览器下载
文件工具类
package com.sinog.core.util;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
/**
* @desc 文件工具类
* @author ypc
* @date 2025/3/9 15:50
*/
@Slf4j
public class FileUtils {
private static final String MSIE_STR = "MSIE";
private static final String TRIDENT_STR = "Trident";
/**
* 整合版文件下载
* @param request request
* @param resp resp
* @param filePath filePath
* @param filename filename
*/
public static void downloadFile(HttpServletRequest request,HttpServletResponse resp,String filePath,String filename) throws java.io.UnsupportedEncodingException {
//获取浏览器
String userAgent = request.getHeader("User-Agent");
if(!userAgent.contains("Windows")){
PrintWriter writer = null;
resp.setContentType("text/html; charset=utf-8");
try {
writer = resp.getWriter();
writer.println("<script type=\"text/javascript\">alert('目前仅支持Windows安装包下载,其他客户端操作系统请自行下载或联系管理员');window.history.back(-1);</script>");
writer.flush();
} catch(IOException e) {
log.error("发生异常",e);
} finally {
Objects.requireNonNull(writer).close();
}
}
if(userAgent.contains(MSIE_STR) || userAgent.contains(TRIDENT_STR)) {
//IE浏览器处理
filename = URLEncoder.encode(filename,"UTF-8");
} else {
// 非IE浏览器的处理
filename = new String(filename.getBytes(StandardCharsets.UTF_8),StandardCharsets.ISO_8859_1);
}
File file = new File(filePath);
//判断文件父目录是否存在
if(file.exists()) {
resp.setContentType("application/force-download");
resp.setHeader("Content-Disposition","attachment;filename=" + filename);
resp.setContentLength((int)file.length());
byte[] buffer = new byte[1024];
//文件输入流
FileInputStream fis = null;
BufferedInputStream bis = null;
OutputStream os = null;
try {
fis = new FileInputStream(file);
bis = new BufferedInputStream(fis);
os = resp.getOutputStream();
int len;
while(-1 != (len = bis.read(buffer))) {
os.write(buffer,0,len);
}
resp.flushBuffer();
} catch(IOException e) {
PrintWriter writer = null;
resp.setContentType("text/html; charset=utf-8");
try {
writer = resp.getWriter();
writer.println("<script type=\"text/javascript\">alert('文件下载失败,请稍后重试');window.history.back(-1);</script>");
writer.flush();
} catch(IOException e1) {
log.error("发生异常",e1);
} finally {
Objects.requireNonNull(writer).close();
}
log.error("发生异常",e);
} finally {
try {
if(null != bis) {
bis.close();
}
} catch(IOException e) {
log.error("发生异常",e);
}
try {
if(null != os) {
os.close();
}
} catch(IOException e) {
log.error("发生异常",e);
}
try {
if(null != fis) {
fis.close();
}
} catch(IOException e) {
log.error("发生异常",e);
}
}
} else {
PrintWriter writer = null;
resp.setContentType("text/html; charset=utf-8");
try {
writer = resp.getWriter();
writer.println("<script type=\"text/javascript\">alert('文件不存在,请联系管理员');window.history.back(-1);</script>");
writer.flush();
} catch(IOException e) {
log.error("发生异常",e);
} finally {
Objects.requireNonNull(writer).close();
}
}
}
}
(2)使用poi现组装一个excel文件,然后立马用浏览器进行下载
1 使用poi生成workbook,然后将workbook输出到file中
package com.sinog.core.util;
import org.apache.poi.ss.usermodel.Workbook;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
public class PoiWriteExcel {
/**
* @return
* @author ypc
* @Description 将poi生成的workbook(工作簿)写成file文件
*/
public static File createFile(String filePath, String fileName, Workbook workbook) {
FileOutputStream fileOutputStream = null;
File exportFile = null;
//File.separator是Java中File类的一个静态字段,用于表示当前操作系统下的文件分隔符。
// 在不同操作系统中,文件分隔符可能不同,例如在Windows系统中使用反斜杠 \\ 作为文件分隔符,而在Unix/Linux系统中使用斜杠 / 作为文件分隔符。
// 使用File.separator可以确保代码在不同操作系统上都能正确使用相应的文件分隔符,从而提高代码的可移植性
String exportFilePath = filePath + File.separator + fileName;
//文件路径白名单检查是一种安全措施,用于确保应用程序只允许访问或操作特定的文件或目录。这种机制通常用于防止未授权的访问或潜在的安全漏洞,
// 例如路径遍历攻击(Path Traversal Attack),它允许攻击者访问系统上不应该被访问的文件。
try {
SecurityWhiteList.checkPath(exportFilePath);
exportFile= new File(exportFilePath);
if (!exportFile.getParentFile().exists()) {
// 父目录不存在,创建父目录
exportFile.getParentFile().mkdirs();
}
//尝试在当前路径下创建一个新的空文件。如果文件不存在,则创建文件并返回true;如果文件已经存在,则不创建新文件并返回false
exportFile.createNewFile();
fileOutputStream = new FileOutputStream(exportFilePath);
workbook.write(fileOutputStream);
//在使用FileOutputStream时,通常的做法是在数据写入完成后调用flush()方法,以确保所有缓冲的数据都被处理,
// 然后再调用close()方法关闭流。这样可以最大程度地保证数据的完整性和一致性。不调用flush()就直接调用close(),可能会导致部分数据丢失
fileOutputStream.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != fileOutputStream) {
try {
fileOutputStream.close();
}catch (IOException e) {
System.out.println(e.toString());
}
}
if (null != workbook) {
try {
workbook.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return exportFile;
}
}
2 通过上面的FileUtils中的downloadFile方法将生成file输出到浏览器下载
3 输入流 上传文件 转为工作簿
InputStream inputStream = file.getInputStream();
try(Workbook workbook = WorkbookFactory.create(inputStream)) {
// 对工作簿的操作
}
(3)导出功能 将页面查询的数据全部导出(不在服务器生成文件形式)
/**
* POI导出 -- 生成excel 下载
* @param response
*/
@Override
public void exportExcel(HttpServletResponse response) {
//定义excel的表头
String[] titles = {"指令名称","优先级","指令类型","指令描述"};
//创建工作簿 workbook - excel -- 因为POI没有定义
HSSFWorkbook hssfWorkbook = new HSSFWorkbook();
//在工作簿中创建Sheet -- 也可以给这个sheet起名字
HSSFSheet hssfSheet = hssfWorkbook.createSheet();
//在当前hssfSheet中创建Row 行 -- 来放置表头
HSSFRow hssfRow = hssfSheet.createRow(0);
//在第一行中进行表头数据的设置
for (int i = 0; i < titles.length; i++) {
//根据列来放表头
hssfRow.createCell(i).setCellValue(titles[i]);
}
//放数据 -- 查询所有数据
List<DispOrd> dispOrdList = this.list();
//循环数据 -- null写到前面
if (null != dispOrdList) {
for (int i = 0; i < dispOrdList.size(); i++) {
//取出每一行的数据
DispOrd dispOrd = dispOrdList.get(i);
//从第二行开始放入数据 -- 只能一列一列的进行放置 -- 提前和用户商量好excel的顺序
HSSFRow row = hssfSheet.createRow(i+1);
row.createCell(0).setCellValue(dispOrd.getOrderName());
row.createCell(1).setCellValue(dispOrd.getPriority());
row.createCell(2).setCellValue(dispOrd.getSpecType());
row.createCell(3).setCellValue(dispOrd.getOrderDesc());
}
}
//生成excel通过日期来命名
String fileName = new String(new SimpleDateFormat("yyyy-MM-dd").format(new Date()));
//导出功能
try {
//设置东西
response.setHeader("Content-disposition","attachment; filename=" + fileName + ".xls");
ServletOutputStream outputStream = response.getOutputStream();
//使用工作簿来写入东西
hssfWorkbook.write(outputStream);
//关闭流等 -- flush清空缓存区中的数据流 -- 因为读入数据完成不代表写入数据完成
outputStream.flush();
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}