一、Excel整合操作
1、POI与EasyExcel介绍
Apache POI官网: https://poi.apache.org/
Apache POI 简介是用Java编写的免费开源的跨平台的 Java API,Apache POI提供API给Java程式对Microsoft Office(Excel、WORD、PowerPoint、Visio等)格式档案读和写的功能。
基本功能(原生)
- HSSF – 提供读写Microsoft Excel各式档案的功能。(03版本)
- XSSF – 提供读写Microsoft Excel OOXML各式档案的功能。(07版本)
- HWPF – 提供读写Microsoft Word各式档案的功能。
- HSLF – 提供读写Microsof PowerPoint格式档案的功能。
- HDGF – 提供读写Microsoft Visio格式档案的功能。
Easy Excel官方文档:https://www.yuque.com/easyexcel/doc/easyexcel
Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。easyexcel重写了poi对07版Excel的解析,能够原本一个3M的excel用POI sax依然需要100M左右内存降低到几M,并且再大的excel不会出现内存溢出,03版依赖POI的sax模式。在上层做了模型转换的封装,让使用者更加简单方便
2、POI-Excel操作
导入maven依赖
<!--xls(03)-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.0.0</version>
</dependency>
<!--xlsx(07)-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.0.0</version>
</dependency>
<!--日期格式化工具-->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.10.10</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.70</version>
</dependency>
注: 03版本和07版本存在兼容性问题,03版本最多支持65535行
POI-Excel写
@SpringBootTest
public class ExcelWriteTest {
String path = "./";
// 基本写
@Test
void testWrite() throws Exception {
// 1、创建一个工作簿(03版本)
Workbook workbook = new HSSFWorkbook();
//07版本
// Workbook workbook = new XSSFWorkbook();
// 2、创建一个工作表
Sheet sheet = workbook.createSheet("shawn科技");
// 3、创建一行
Row row1 = sheet.createRow(0);
// 4、创建一个单元格(1,1)
Cell cell11 = row1.createCell(0);
cell11.setCellValue("今日新增访客");
// (1,2)
Cell cell12 = row1.createCell(1);
cell12.setCellValue(1000);
// 第二行
Row row2 = sheet.createRow(1);
Cell cell21 = row2.createCell(0);
cell21.setCellValue("统计时间");
// (2,2)
Cell cell22 = row2.createCell(1);
// 注意是joda的DateTime
String s = new DateTime().toString("yyyy-MM-dd HH:mm:ss");
cell22.setCellValue(s);
// 生成一张表(IO流,03版本)
FileOutputStream fileOutputStream = new FileOutputStream(path+"shanw科技.xls");
// 07版本
// FileOutputStream fileOutputStream = new FileOutputStream(path+"shanw科技.xlsx");
workbook.write(fileOutputStream);
// 关闭流
fileOutputStream.close();
System.out.println("版本生成完毕");
}
//大数据写
@Test
void testWriteBigData() throws Exception {
// 时间
long begin = System.currentTimeMillis();
// 1、创建一个工作簿(03版本)
Workbook workbook = new HSSFWorkbook();
// 07版本
// Workbook workbook = new XSSFWorkbook();
// 07版本加速,会产生临时文件
// Workbook workbook = new SXSSFWorkbook();
// 2、创建一个工作表
Sheet sheet = workbook.createSheet("shawn科技");
// 写入数据,07版本没有限制,但是会比较慢
for (int rowNum = 0; rowNum < 65536; rowNum++){
Row row = sheet.createRow(rowNum);
for (int cellNum = 0; cellNum < 10; cellNum++){
Cell cell = row.createCell(cellNum);
cell.setCellValue(cellNum);
}
}
// 生成一张表(IO流,03版本)
FileOutputStream fileOutputStream = new FileOutputStream(path+"shawn科技.xls");
// 07版本
// FileOutputStream fileOutputStream = new FileOutputStream(path+"shanw科技.xlsx");
workbook.write(fileOutputStream);
// 关闭流
fileOutputStream.close();
// 如果开启了SXSSFWorkbook,清除临时文件
// ((SXSSFWorkbook)workbook).dispose();
long end = System.currentTimeMillis();
System.out.println("时间为"+(double)(end-begin)/1000);
}
}
POI-Excel读
其中表格会员消费商品明细表.xls
内容如下
卡号 | 持卡人 | 手机号 | 消费日期 | 小票号 | 商品编号 |
---|---|---|---|---|---|
100088 | shawn | 12333333333 | 2021/2/22 | 0000201510200146 | PV700012 |
100089 | shawn22 | 12333333333 | 2021/2/22 | 0000201510200146 | PV700006 |
@SpringBootTest
public class ExcelReadTest {
String path = "./";
//基本读操作
@Test
public void testRead() throws Exception {
// 获取文件流
FileInputStream fileInputStream = new FileInputStream(path + "shawn科技.xls");
// 创建工作簿03版本,07版本相对应变化
Workbook workbook = new HSSFWorkbook(fileInputStream);
Sheet sheetAt = workbook.getSheetAt(0);
Row row = sheetAt.getRow(0);
Cell cell = row.getCell(0);
//类型注意
// cell.getNumericCellValue();
System.out.println(cell.getStringCellValue());
fileInputStream.close();
}
//读取内容
@Test
public void testCellType() throws Exception {
// 获取文件流
FileInputStream fileInputStream = new FileInputStream(path + "会员消费商品明细表.xls");
// 创建工作簿03版本,07版本相对应变化
Workbook workbook = new HSSFWorkbook(fileInputStream);
//获取标题,假设这个文件标题都在第一行
Sheet sheet = workbook.getSheetAt(0);
Row rowTitle = sheet.getRow(0);
if (rowTitle != null) {
// 重要
int physicalNumberOfCells = rowTitle.getPhysicalNumberOfCells();
for (int cellNum = 0; cellNum < physicalNumberOfCells; cellNum++) {
Cell cell = rowTitle.getCell(cellNum);
if (cell != null) {
String stringCellValue = cell.getStringCellValue();
System.out.print(stringCellValue + "|");
}
}
}
// 获取表中内容,可以封装成工具类使用,03版本和07有区别
int physicalNumberOfRows = sheet.getPhysicalNumberOfRows();
for (int rowNum = 1; rowNum < physicalNumberOfRows; rowNum++) {
Row rowData = sheet.getRow(rowNum);
if (rowData != null) {
//读取列
int cellCount = rowTitle.getPhysicalNumberOfCells();
for (int cellNum = 0; cellNum < cellCount; cellNum++) {
System.out.print("【" + (rowNum + 1) + "-" + (cellNum + 1) + "】");
Cell cell = rowData.getCell(cellNum);
if (cell != null) {
CellType cellType = cell.getCellType();
//判断单元格数据类型
String cellValue = "";
switch (cellType) {
case STRING://字符串
System.out.print("【STRING】");
cellValue = cell.getStringCellValue();
break;
case BOOLEAN://布尔
System.out.print("【BOOLEAN】");
cellValue = String.valueOf(cell.getBooleanCellValue());
break;
case BLANK://空
System.out.print("【BLANK】");
break;
case NUMERIC:
System.out.print("【NUMERIC】");
//cellValue = String.valueOf(cell.getNumericCellValue());
if (DateUtil.isCellDateFormatted(cell)) {//日期
System.out.print("【日期】");
Date date = cell.getDateCellValue();
cellValue = new DateTime(date).toString("yyyy-MM-dd");
} else {
// 不是日期格式,则防止当数字过长时以科学计数法显示
System.out.print("【转换成字符串】");
cell.setCellType(CellType.STRING);
cellValue = cell.toString();
}
break;
case ERROR:
System.out.print("【数据类型错误】");
break;
}
System.out.println(cellValue);
}
}
}
}
}
// 公式,了解
@Test
public void testFormula() throws Exception{
InputStream is = new FileInputStream(path + "计算公式.xls");
Workbook workbook = new HSSFWorkbook(is);
Sheet sheet = workbook.getSheetAt(0);
// 读取第五行第一列
Row row = sheet.getRow(4);
Cell cell = row.getCell(0);
//公式计算器
FormulaEvaluator formulaEvaluator = new HSSFFormulaEvaluator((HSSFWorkbook) workbook);
// 输出单元内容
CellType cellType = cell.getCellType();
switch (cellType) {
case FORMULA://公式
//得到公式
String formula = cell.getCellFormula();
System.out.println(formula);
CellValue evaluate = formulaEvaluator.evaluate(cell);
//String cellValue = String.valueOf(evaluate.getNumberValue());
String cellValue = evaluate.formatAsString();
System.out.println(cellValue);
break;
}
}
}
3、EasyExcel操作
Easy Excel官方文档:https://easyexcel.opensource.alibaba.com/
导入maven依赖,注意里面已经存在poi
和poi-ooxml
依赖,需要注释刚才我们自己加上去的
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.2.7</version>
</dependency>
读写都在文档中已详细写明
二、Pdf整合操作
1、模板导入
我们先使用WPS以Word的形式进行编辑制作出与客户需求一样的样式,然后直接另存为 xxx.pdf,然后用Adobe Acrobat DC打开我们刚才改过名字的PDF文件,点击右下角的“更多工具”按钮,点击“准备表单”按钮,接下来就需要详细的配置你的数据源了(数据源即:你代码中实体类中对应的数据(注意字段一定要一一对应),配置完毕就可以保存进行下面的代码编写工作了)
首先引入依赖
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13</version>
</dependency>
编写相应生成代码
public class GeneratePDFUtil {
// 利用模板生成pdf
public static void interviewReportPDF(Map<String, String> map) {
// 模板路径
String templatePath = "D:\\work\\data\\z1.pdf";
// 生成的新文件路径
String newPDFPath = "D:\\work\\data\\z11.pdf";
PdfReader reader;
FileOutputStream out;
ByteArrayOutputStream bos;
PdfStamper stamper;
try {
// 输出流
out = new FileOutputStream(newPDFPath);
// 读取pdf模板
reader = new PdfReader(templatePath);
bos = new ByteArrayOutputStream();
stamper = new PdfStamper(reader, bos);
AcroFields form = stamper.getAcroFields();
// 给表单添加中文字体 这里采用系统字体。不设置的话,中文可能无法显示
BaseFont bf = BaseFont.createFont("C:/Windows/Fonts/simfang.ttf", BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
form.addSubstitutionFont(bf);
//遍历map装入数据
for (Map.Entry<String, String> entry : map.entrySet()) {
form.setField(entry.getKey(), entry.getValue());
System.out.println("插入PDF数据----> key= " + entry.getKey() + " and value= " + entry.getValue());
}
// 如果为false那么生成的PDF文件还能编辑,一定要设为true
stamper.setFormFlattening(true);
stamper.close();
Document doc = new Document();
PdfCopy copy = new PdfCopy(doc, out);
doc.open();
// 这里是PDF的页数 如果有两页及以上,还需要构造PdfImportedPage
PdfImportedPage importPage1 = copy.getImportedPage(new PdfReader(bos.toByteArray()), 1);
copy.addPage(importPage1);
doc.close();
} catch (DocumentException | IOException e) {
e.printStackTrace();
}
}
// 测试
public static void main(String[] args) {
Map<String, String> m = new HashMap<>();
m.put("money", "$1234");
m.put("number", "23");
m.put("num", "1");
m.put("order", "32342423423423");
m.put("label", "好枪");
interviewReportPDF(m);
}
}
2、Html模板生成
引入依赖
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13.3</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
<dependency>
<groupId>com.itextpdf.tool</groupId>
<artifactId>xmlworker</artifactId>
<version>5.5.13.3</version>
</dependency>
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>flying-saucer-pdf-itext5</artifactId>
<version>9.1.22</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>ognl</groupId>
<artifactId>ognl</artifactId>
<version>3.3.3</version>
</dependency>
随后创建好对应的Java类
public abstract class AbstractTemplate {
// 使用thymeleaf模版引擎
private TemplateEngine engine;
// 模版名称
private String templateName;
private AbstractTemplate() {}
public AbstractTemplate(TemplateEngine engine, String templateName) {
this.engine = engine;
this.templateName=templateName;
}
/**
* 模版名称
*
* @return
*/
protected String templateName(){
return this.templateName;
}
/**
* 所有的参数数据
*
* @return
*/
private Map<String, Object> variables(){
Map<String, Object> variables = new HashMap<>();
// 对应html模版中的template变量,取值的时候就按照“${template.字段名}”格式,可自行修改
variables.put("template", this);
return variables;
};
/**
* 解析模版,生成html
*
* @return
*/
public String process() {
Context ctx = new Context();
// 设置model
ctx.setVariables(variables());
// 根据model解析成html字符串
return engine.process(templateName(), ctx);
}
public void parse2Pdf(String targetPdfFilePath) throws Exception {
String html = process();
// 通过html转换成pdf
HtmlUtil.html2Pdf(html, targetPdfFilePath);
}
}
@Data
public class Model extends AbstractTemplate {
// 构造函数
public Model(TemplateEngine engine, String templateName) {
super(engine, templateName);
}
// 金额
private BigDecimal money;
/** 油量 */
private Float oilQuantity;
/** 枪号 */
private Integer gunNumber;
/** 订单号 */
private String orderNumber;
/** 油品类别 */
private String oilCategory;
/** 订单生成时间 */
private String orderTime;
}
@Configuration
public class TemplateEngineConfig {
// 注入TemplateEngine模版引擎
@Bean
public TemplateEngine templateEngine(){
ClassLoaderTemplateResolver resolver = new ClassLoaderTemplateResolver();
// 设置模版前缀,相当于需要在资源文件夹中创建一个html2pdfTemplate文件夹,所有的模版都放在这个文件夹中
resolver.setPrefix("/html2pdfTemplate/");
// 设置模版后缀
resolver.setSuffix(".html");
resolver.setCharacterEncoding("UTF-8");
// 设置模版模型为HTML
resolver.setTemplateMode("HTML");
TemplateEngine engine = new TemplateEngine();
engine.setTemplateResolver(resolver);
return engine;
}
}
@Slf4j
public final class HtmlUtil {
private HtmlUtil() {
}
// 字体路径,放在资源目录下
// private static final String FONT_PATH = "D:\\work\\IDEA\\oil\\src\\main\\resources\\simsun.ttc";
private static final String FONT_PATH = "C:\\work\\simsun.ttc";
public static void file2Pdf(File htmlFile, String pdfFile) {
try (OutputStream os = new FileOutputStream(pdfFile)) {
String url = htmlFile.toURI().toURL().toString();
ITextRenderer renderer = new ITextRenderer();
renderer.setDocument(url);
// 解决中文支持
ITextFontResolver fontResolver = renderer.getFontResolver();
// 获取字体绝对路径,ApplicationContextUtil是我自己写的类
fontResolver.addFont(FONT_PATH, BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
renderer.layout();
renderer.createPDF(os);
} catch (Exception e) {
// 抛出自定义异常
log.error(e.getMessage());
}
}
public static void html2Pdf(String html, String pdfFile) throws Exception {
String pdfDir = StringUtils.substringBeforeLast(pdfFile, File.separator);
File file = new File(pdfDir);
if (!file.exists()) {
file.mkdirs();
}
try (OutputStream os = new FileOutputStream(pdfFile)) {
ITextRenderer renderer = new ITextRenderer();
renderer.setDocumentFromString(html);
// 解决中文支持
ITextFontResolver fontResolver = renderer.getFontResolver();
// 获取字体绝对路径,ApplicationContextUtil是我自己写的类
fontResolver.addFont(FONT_PATH, BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
renderer.layout();
renderer.createPDF(os);
} catch (Exception e) {
// 抛出自定义异常
log.error(e.getMessage());
throw new Exception(e);
}
}
}
配置模板文件,注意文件位置在resources/html2pdfTemplate/
下
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>报告模版</title>
<style>
/* <!-- 编写css--> */
.main2 {
width:100%;
margin-top: 50vh;
}
.main2>div{
width: 40%;
display: inline;
}
.left{
float: left;
text-align: center;
}
.right{
float: right;
text-align: center;
}
.main{
text-align: center;
margin-top: 80px;
padding-top: 50px;
}
</style>
</head>
<!-- 引入字体 -->
<body style="font-family: SimSun,serif;">
<div class="main">
<b>浙石油消费小票</b>
</div>
<!--<img src="img/oil.jpg" alt=""/>-->
<div class="main2">
<div class="left">消费金额</div>
<div class="right" th:text="${template.money}+'元'">200元</div>
<div class="left">油量</div>
<div class="right" th:text="${template.gunNumber}+'升'">27.1升</div>
<div class="left">枪号</div>
<div class="right" th:text="${template.oilQuantity}+'号抢'">3号抢</div>
<div class="left">订单号</div>
<div class="right" th:text="${template.orderNumber}">3454353464534453543534</div>
<div class="left">油品类别</div>
<div class="right" th:text="${template.oilCategory}+'号汽油'">98号汽油</div>
<div class="left">订单生成时间</div>
<div class="right" th:text="${template.orderTime}">2022年3月6日</div>
</div>
</body>
</html>
运行测试类,如果报错可以在启动类加上@SpringBootApplication(exclude = ThymeleafAutoConfiguration.class)
@RestController
@Slf4j
public class PrintController {
@Autowired
private TemplateEngine engine;
private static final String PATH = "C:\\work\\html2pdfTemplate\\";
@GetMapping("/print")
public void test01(){
System.out.println(1234);
// 创建model,需要指定模版引擎和具体的模版,“报告模版”指的是资源目录下/html2pdfTemplate/报告模版.html文件。如果是springboot项目,那么就是在resources文件夹下面
Model model = new Model(engine,"报告模版");
model.setMoney(BigDecimal.valueOf(2));
model.setOilQuantity(3F);
model.setGunNumber(4);
model.setOrderNumber(String.valueOf(5));
model.setOilCategory(String.valueOf(6));
model.setOrderTime("2022");
String filename = UUID.randomUUID().toString().replace("-", "");
try {
//生成pdf,指定目标文件路径
model.parse2Pdf(PATH + filename + ".pdf");
Thread.sleep(1000);
} catch (Exception e){
log.error("模板转化失败{}",e.getMessage());
}
}
}
三、文件的下载
Excel和Pdf的导出可以参考:https://mp.weixin.qq.com/s/ZMeDTClljXy-t-ZrvElH2A
下面是比较通用的Servlet文件下载
@GetMapping("/download")
public void download(HttpServletResponse response){
ServletOutputStream out = null;
FileInputStream input = null;
try {
//获取文件资源
String filePath = "D:\\share\\ppt.pdf";
File file = new File(filePath);
input = new FileInputStream(file);
//截取下载的文件名
//String realName = filePath.substring(filePath.lastIndexOf("\\") + 1);
// 设置文件ContentType类型,这样设置,会自动判断下载文件类型
response.setContentType("multipart/form-data");
// 设置编码
response.setCharacterEncoding("utf-8");
// 这里URLEncoder.encode可以防止中文乱码
String fileName = URLEncoder.encode("测试", "UTF-8");
// 给浏览器设置响应头:Content-Disposition 告诉浏览器以附件的形式打开这个文件
response.setHeader("Content-disposition", "inline;filename="+ fileName + ".pdf");
//从response中获取输出流
out = response.getOutputStream();
// 也可以一次性输出,大文件不友好
//int len = inputStream.available();
//byte[] bytes = new byte[len];
//inputStream.read(bytes);
// 流拷贝
int len = 0;
byte[] buffer = new byte[1024];
while ((len = input.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
input.close();
out.close();
}catch(Exception e) {
// 可以抛出自定义异常
System.out.println("");
}
}