Spring整合Excel与Pdf

一、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等)格式档案读和写的功能。

基本功能(原生)

  1. HSSF – 提供读写Microsoft Excel各式档案的功能。(03版本)
  2. XSSF – 提供读写Microsoft Excel OOXML各式档案的功能。(07版本)
  3. HWPF – 提供读写Microsoft Word各式档案的功能。
  4. HSLF – 提供读写Microsof PowerPoint格式档案的功能。
  5. 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内容如下

卡号持卡人手机号消费日期小票号商品编号
100088shawn123333333332021/2/220000201510200146PV700012
100089shawn22123333333332021/2/220000201510200146PV700006
@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依赖,注意里面已经存在poipoi-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模板生成

参考:Java后台生成pdf文件

引入依赖

<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("");
    }

}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值