JAVA POI:导出数据与图片到word中

一、效果

话不多说,先看效果:
在这里插入图片描述

二、准备

1.引入poi的依赖

 <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>3.15</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>3.15</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-scratchpad</artifactId>
            <version>3.15</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>ooxml-schemas</artifactId>
            <version>1.4</version>
        </dependency>
        <dependency>
            <groupId>com.deepoove</groupId>
            <artifactId>poi-tl</artifactId>
            <version>1.0.0</version>
        </dependency>

2.准备word模板

使用poi导出word需要准备一份word模板,变量用{{变量}}标识,在代码中会将这些变量替换为需要的数据
在这里插入图片描述

三、代码实现

本是使用springboot搭建的项目环境,由于是给浏览器用户导出word,所以代码分为service与controller两部分:

1.WordService代码

  /**
     * @param data :需要替换的模板中的变量键值对
     * @param TemplatePath  :word模板在项目中的路径
     * @param pictureTag    :要替换为图片的变量名
     * @param wordName  :生成的word名称
     * @param request
     * @param response
     * @throws IOException
     */
    public void downWord(Map<String, Object> data, String TemplatePath, List<String>pictureTag,String wordName, HttpServletRequest request, HttpServletResponse response) throws IOException {
        try {
            ClassPathResource template = new ClassPathResource(TemplatePath);
            String projectPath = System.getProperty("user.dir");    //获取当前项目所在路径
            //创建一个临时模板,放在系统上(这步很无奈)
            File file = new File(projectPath+SEPARATER+"template.docx");
            InputStream templateInputStream = template.getInputStream();
            FileUtils.copyInputStreamToFile(templateInputStream,file);
            XWPFTemplate xwpfTemplate = XWPFTemplate.compile(file);
            Configure config = xwpfTemplate.getConfig();
            //设置处理策略,这一步很重要,本人在这个坑上跌了很久!!!
            for (String tag:pictureTag
                 ) {
                config.customPolicy(tag,new PictureRenderPolicy());
            }
            //将数据递交给模板处理
            xwpfTemplate.render(data);
            String docName = wordName + ".docx";
            File targetFile = new File(docName);
            FileOutputStream out = new FileOutputStream(targetFile);
            xwpfTemplate.write(out);
            out.flush();
            out.close();
            xwpfTemplate.close();
            // 下载输出到浏览器
            FileUtil.downFile(request,response,docName,targetFile);
            FileUtil.deleteDir(targetFile.getPath());
        } catch (Exception e) {
            log.info("文件生成失败:"+e.getMessage());
            throw e;
        }
    }

2.WordController

    public void exportData(PatrolWordDto dto,HttpServletRequest request, HttpServletResponse response) throws Exception {
            List<String> pictureTag =new ArrayList<>();
            Map<String,Object>data =new HashMap<>();
            //需要填充的数据,key为模板中的变量
            data.put("projectId",dto.getProjectId());
            data.put("patrolTypeId",dto.getPatrolTypeId());
            data.put("inspector",dto.getInspector());
            data.put("date",dto.getDate());
            data.put("beginTime",dto.getBeginTime());
            data.put("content",dto.getContent());
            data.put("result",dto.getResult());
            data.put("endTime",dto.getEndTime());
            data.put("note",dto.getNote());
            //这里是前台传给我的查询图片的id
            String[] files = dto.getFiles().split("-");
            int num =files.length;
            String templatePath=null;
            //因为图片数量不确定,设计了多种模板处理
            for (int i=0;i<num;i++){
                //这里我是从数据库获取图片路径
                String photoPath = wordService.getPhotoPath(files[i]);
                //这个类完全是为了获取图片长宽
                BufferedImage bufferImg = ImageIO.read(new File(photoPath));
//              获取的长宽数值较大,避免插入图片过大,我等比例缩小了10倍
                int width = bufferImg.getWidth()/10;
                int height = bufferImg.getHeight()/10;
                PictureRenderData pictureRenderData = new PictureRenderData(width, height,photoPath);
                data.put("image"+(i+1),pictureRenderData);
                //标识出其中的图片变量,需要特殊处理
                pictureTag.add("image"+(i+1));
            }
//           对应三种数量图片的模板路径
            switch (num){
                case 1:templatePath= "files/download/template/patroltemplate1.docx";break;
                case 2:templatePath= "files/download/template/patroltemplate2.docx";break;
                case 3:templatePath= "files/download/template/patroltemplate3.docx";break;
            }
            wordService.downWord(data,templatePath,pictureTag,"patrol",request,response);
	}

3.工具类FileUtil

 /**
     * 下载文件到浏览器
     * @param request
     * @param response
     * @param filename 要下载的文件名
     * @param file     需要下载的文件对象
     * @throws IOException
     */
    public static void downFile(HttpServletRequest request, HttpServletResponse response, String filename, File file) throws IOException {
        //  文件存在才下载
        if (file.exists()) {
            OutputStream out = null;
            FileInputStream in = null;
            // 1.读取要下载的内容
            in = new FileInputStream(file);

            // 2. 告诉浏览器下载的方式以及一些设置
            // 解决文件名乱码问题,获取浏览器类型,转换对应文件名编码格式,IE要求文件名必须是utf-8, firefo要求是iso-8859-1编码
            String agent = request.getHeader("user-agent");
            if (agent.contains("FireFox")) {
                filename = new String(filename.getBytes("UTF-8"), "iso-8859-1");
            } else {
                filename = URLEncoder.encode(filename, "UTF-8");
            }
            // 设置下载文件的mineType,告诉浏览器下载文件类型
            String mineType = request.getServletContext().getMimeType(filename);
            response.setContentType(mineType);
            // 设置一个响应头,无论是否被浏览器解析,都下载
            response.setHeader("Content-disposition", "attachment; filename=" + filename);
            // 将要下载的文件内容通过输出流写到浏览器
            out = response.getOutputStream();
            int len = 0;
            byte[] buffer = new byte[1024];
            while ((len = in.read(buffer)) > 0) {
                out.write(buffer, 0, len);
            }
            if (out != null) {
                out.close();
            }
            if (in != null) {
                in.close();
            }
        }
    }

    /**
     * 递归删除目录下的所有文件及子目录下所有文件
     *
     * @param filePath 将要删除的文件目录路径
     * @return boolean Returns "true" if all deletions were successful.
     * If a deletion fails, the method stops attempting to
     * delete and returns "false".
     */
    public static boolean deleteDir(String filePath) {
        File dir = new File(filePath);
        if (dir.isDirectory()) {
            String[] children = dir.list();
            //递归删除目录中的子目录下
            for (int i = 0; i < children.length; i++) {
                boolean success = deleteDir(filePath + File.separator + children[i]);
                if (!success) {
                    return false;
                }
            }
        }
        // 目录此时为空,可以删除
        return dir.delete();

然后直接在浏览器访问controller的路径并传参就可以直接下载导出的word了,本人的项目路径是酱紫的:

http://127.0.0.1:8080/exportPatrolWord?projectId=sada&patrolTypeId=asd&inspector=asd&date=asd&beginTime=asd&endTime=asd&content=asd&result=asd&files=1&note=asd

四、总结

本人也是被项目逼着翻了很多博客找到的代码,本来以为拿来能直接用,结果还有暗坑。主要有两个:
1.下载出来的word中本该是图片的地方是一串字符。调试看源码才发现默认的数据处理策略是TextRenderPolicy,这样就会对图片数据错误处理,结果就是把图片类的类名输出,而不是把图片输出,所以在递交模板数据前一定要配置对于图片标签用PictureRenderPolicy进行处理

 	for (String tag:pictureTagRenderPolicy()) {
        config.customPolicy(tag,new PictureRenderPolicy());
    }	

2.项目在idea中能运行,打包发布到linux后报异常。主要原因是打包成jar后,程序无法直接读取放在jar包中的word模板文件,只能获取到资源文件的二进制流。所以我不得已新建了一个file,然后将流复制到新文件中,使用该文件作为模板。只是这样会造成资源浪费,每次都会重新复制模板文件,所以最好是在项目初始化时进行该操作即可。各位大佬有更好的办法麻烦指点,谢谢!

ClassPathResource template = new ClassPathResource(TemplatePath);
File file = new File(projectPath+SEPARATER+"template.docx");
InputStream templateInputStream = template.getInputStream();
FileUtils.copyInputStreamToFile(templateInputStream,file);

参考:

Java POI 导出带有图片的word

  • 3
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 18
    评论
Java可以使用Apache POI和Freemarker两个库来实现根据模板导出数据word的解决方案。 Apache POI是一个Java API,可以用于读写Microsoft Office格式的文档,包括Word、Excel和PowerPoint等。使用Apache POI,可以在Java程序创建、修改和读取Word文档,将数据填充到Word文档的模板,生成新的Word文档。 Freemarker是一个模板引擎,可以将数据填充到模板,生成新的文本文件,包括HTML、XML、JSON和Word等。使用Freemarker,可以将数据填充到Word文档的模板,生成新的Word文档。 具体实现步骤如下: 1. 创建Word文档模板,使用Word软件设计好需要填充数据的文档模板。 2. 使用Apache POI读取Word文档模板,获取到需要填充数据的位置和格式。 3. 使用Freemarker将数据填充到Word文档模板,生成新的Word文档。 4. 将生成的新的Word文档保存到文件或输出到浏览器。 这是一个基本的实现流程,具体实现细节可以参考相关的文档和示例代码。以下是一个基本的示例代码: ``` // 1. 创建Word文档模板 FileInputStream fis = new FileInputStream("template.docx"); XWPFDocument templateDoc = new XWPFDocument(fis); fis.close(); // 2. 使用Apache POI读取Word文档模板 for (XWPFParagraph paragraph : templateDoc.getParagraphs()) { List<XWPFRun> runs = paragraph.getRuns(); for (XWPFRun run : runs) { String text = run.getText(0); if (text != null && text.contains("${")) { // 找到需要填充的位置 // 可以使用正则表达式或者字符串替换等方式进行定位 } } } // 3. 使用Freemarker将数据填充到Word文档模板 Configuration cfg = new Configuration(Configuration.VERSION_2_3_30); cfg.setDefaultEncoding("UTF-8"); cfg.setClassForTemplateLoading(this.getClass(), "/templates"); Template template = cfg.getTemplate("template.ftl"); Map<String, Object> data = new HashMap<>(); data.put("name", "张三"); data.put("age", 20); StringWriter sw = new StringWriter(); template.process(data, sw); String content = sw.toString(); // 4. 将生成的新的Word文档保存到文件或输出到浏览器 XWPFDocument newDoc = new XWPFDocument(); XWPFParagraph newParagraph = newDoc.createParagraph(); XWPFRun newRun = newParagraph.createRun(); newRun.setText(content); FileOutputStream fos = new FileOutputStream("output.docx"); newDoc.write(fos); fos.close(); newDoc.close(); ``` 上述代码使用了一个模板文件`template.ftl`,该文件包含了需要填充的数据的位置和格式。在代码,使用Freemarker将数据填充到模板,生成新的内容`content`,然后将新的内容生成为一个新的Word文档并保存到文件。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值