• Spring Boot


    后台生成EChart报表图片并插入到Word文件中

    前期准备

    PhantomJS

    https://phantomjs.org/download.html

    官方介绍:

    PhantomJS是一个基于 WebKit 的服务器端JavaScript API。它全面支持web而不需浏览器支持,支持各种Web标准:DOM处理,CSS选择器, JSON,Canvas,和SVG。

    PhantomJS常用于页面自动化,网络监测,网页截屏,以及无界面测试等。

    通常我们使用PhantomJS作为爬虫工具。传统的爬虫只能单纯地爬取html的代码,对于js渲染的页面,就无法爬取,如ECharts统计图。而PhantomJS正可以解决此类问题。

    echarts-convert

    https://gitee.com/saintlee/echartsconvert

    一个配合phantomjs,在服务端生成EChart图片的工具包。

    ECharts-2.2.7.jar

    https://github.com/abel533/ECharts

    一个供Java开发使用的ECharts的开发包,主要目的是方便在Java中构造ECharts中可能用到的全部数据结构,如完整的结构Option。

    注:我的自用版本资源在这里

    链接: https://pan.baidu.com/s/1Y4hQZXtKglWq7LnISQtiUw  密码: 8alt

    生成EChart图片

    新建一个测试类 EChartWordDemo,后续的方法都加在这个类中

    /**
     * 后台生成EChart图片并插入Word测试类
     * 
     * @Author FanZhen
     * @Date 2021/3/12
     */
    public class EChartWordDemo {
    
        // ============== 这里要改成自己电脑对应的文件位置,服务器部署时可以通过环境变量等方式来动态改变它们的值 =================
        /**
         * echart-convert包的路径
         */
        private String eChartJSPath = "/Users/helios_fz/IdeaProjects/websocket/src/main/resources/echart/echarts-convert/echarts-convert1.js";
        /**
         * echart临时文件存储路径
         */
        private String eChartTempPath = "/Users/helios_fz/Desktop/a/b/";
        /**
         * phantomjs命令路径
         */
        private String phantomjsPath = "/Users/helios_fz/IdeaProjects/rbac/rbac-admin/src/main/resources/phantomjs/phantomjs-2.1.1-macosx/bin/phantomjs";
    
    }

    在pom文件中引入 ECharts.jar 的依赖。因为这个包在中心库没有,阿里云的资源我试了几次又没拉下来,所以我就配置静态引入了(这里也是要根据自己放置jar包的位置来调整配置):

            <!-- echart依赖 -->
            <dependency>
                <groupId>com.github.abel533</groupId>
                <artifactId>echarts</artifactId>
                <version>2.2.7</version>
                <scope>system</scope>
                <systemPath>${project.basedir}/src/main/resources/echart/ECharts-2.2.7.jar</systemPath>
            </dependency>

    拼接EChart初始化json的方法:

        /**
         * 生成EChart初始化json
         *
         * @param title 图片标题
         * @param xAxis x轴
         * @param line1 柱状图1
         * @param line2 柱状图2
         * @return json字符串
         */
        private String getEChartOption(String title, String xAxis, String line1, String line2) {
            return "{
    " +
                    "        color: ["#f8732c", "#0094c8"],
    " +
                    "        title: {
    " +
                    // 名字+流量
                    "          text: " + """ + title + """ + ",
    " +
                    "        },
    " +
                    "        tooltip: {
    " +
                    "          trigger: "axis",
    " +
                    "        },
    " +
                    "        legend: {
    " +
                    "          data: ["line1", "line2"],
    " +
                    "        },
    " +
                    "        toolbox: {
    " +
                    "          show: true,
    " +
                    "          feature: {
    " +
                    "            saveAsImage: { show: true },
    " +
                    "          },
    " +
                    "        },
    " +
                    "        calculable: true,
    " +
                    "        xAxis: {
    " +
                    "          type: "category",
    " +
                    "          data: " +
                    // X轴数据
                    xAxis +
                    ",
    " +
                    "          axisLabel: { interval: 0 },
    " +
                    "        },
    " +
                    "        yAxis: [
    " +
                    "          {
    " +
                    "            type: "value",
    " +
                    "            axisLabel: {
    " +
                    "              formatter: function (value) {
    " +
                    "                return value.toFixed(1) + "MB";
    " +
                    "              },
    " +
                    "            },
    " +
                    "          },
    " +
                    "        ],
    " +
                    "        series: [
    " +
                    "          {
    " +
                    "            name: "line1",
    " +
                    "            type: "bar",
    " +
                    "            symbol: "circle",
    " +
                    "            barMaxWidth: 40,
    " +
                    "            data: " +
                    // line1数据
                    line1 +
                    ",
    " +
                    "            itemStyle: {
    " +
                    "              //上方显示数值
    " +
                    "              normal: {
    " +
                    "                label: {
    " +
                    "                  show: true, //开启显示
    " +
                    "                  position: "top", //在上方显示
    " +
                    "                  textStyle: {
    " +
                    "                    //数值样式
    " +
                    "                    color: "black",
    " +
                    "                    // fontSize: 14
    " +
                    "                  },
    " +
                    "                },
    " +
                    "                color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
    " +
                    "                  { offset: 0, color: "#f8732c" },
    " +
                    "                  { offset: 1, color: "#FFCEBF" },
    " +
                    "                  { offset: 1, color: "#FFCEBF" },
    " +
                    "                ]),
    " +
                    "              },
    " +
                    "              emphasis: {
    " +
                    "                color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
    " +
                    "                  { offset: 1, color: "#f8732c" },
    " +
                    "                  { offset: 0, color: "#FFCEBF" },
    " +
                    "                  { offset: 0, color: "#FFCEBF" },
    " +
                    "                ]),
    " +
                    "              },
    " +
                    "            },
    " +
                    "          },
    " +
                    "          {
    " +
                    "            name: "line2",
    " +
                    "            type: "bar",
    " +
                    "            symbol: "circle",
    " +
                    "            barMaxWidth: 40,
    " +
                    "            data: " +
                    // line2数据
                    line2 +
                    ",
    " +
                    "            itemStyle: {
    " +
                    "              //上方显示数值
    " +
                    "              normal: {
    " +
                    "                label: {
    " +
                    "                  show: true, //开启显示
    " +
                    "                  position: "top", //在上方显示
    " +
                    "                  textStyle: {
    " +
                    "                    //数值样式
    " +
                    "                    color: "black",
    " +
                    "                    // fontSize: 14
    " +
                    "                  },
    " +
                    "                },
    " +
                    "                color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
    " +
                    "                  { offset: 0, color: "#0094C8" },
    " +
                    "                  { offset: 1, color: "#CEF2FF" },
    " +
                    "                  { offset: 1, color: "#CEF2FF" },
    " +
                    "                ]),
    " +
                    "              },
    " +
                    "              emphasis: {
    " +
                    "                color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
    " +
                    "                  { offset: 1, color: "#0094C8" },
    " +
                    "                  { offset: 0, color: "#CEF2FF" },
    " +
                    "                  { offset: 0, color: "#CEF2FF" },
    " +
                    "                ]),
    " +
                    "              },
    " +
                    "            },
    " +
                    "          },
    " +
                    "        ],
    " +
                    "      }";
        }
    View Code

    上边的代码折叠起来的原因有两点:

    • 这个json我是写死的,因为我这边的需求只是生成一个柱状图,其他类型也可以生成,只要把前端的json复制过来就行了
    • 这里也可以写一个动态的拼接方法,EChart.jar那个包就是做这部分工作的,可是我懒得写 = =

    生成EChart图片的代码:

        /**
         * 生成EChart图
         *
         * @param options      EChart初始化json
         * @param tmpPath      临时文件存放处
         * @param echartJsPath 第三方工具路径
         * @return
         */
        private String generateEChart(String options, String tmpPath, String echartJsPath) {
            // 生成Echart的初始化json文件
            String dataPath = writeFile(options, tmpPath);
            // 生成随机文件名
            String fileName = UUID.randomUUID().toString().substring(0, 8) + ".png";
            String path = tmpPath + fileName;
            try {
                // 文件路径(路径+文件名)
                File file = new File(path);
                // 文件不存在则创建文件,先创建目录
                if (!file.exists()) {
                    File dir = new File(file.getParent());
                    dir.mkdirs();
                    file.createNewFile();
                }
    
                // 这里只能写绝对路径,因为要执行系统命令行
                String cmd = phantomjsPath + " " +
                        echartJsPath +
                        " -infile " + dataPath +
                        " -outfile " + path;
                Process process = Runtime.getRuntime().exec(cmd);
    
                BufferedReader input = new BufferedReader(new InputStreamReader(process.getInputStream()));
                String line = "";
                while ((line = input.readLine()) != null) {
                    System.out.println(line);
                }
                input.close();
    
                // 删除生成的临时json文件
                File jsonFile = new File(dataPath);
                jsonFile.delete();
                return path;
            } catch (IOException e) {
                e.printStackTrace();
                return path;
            }
        }
    
        /**
         * 保存EChart临时json
         *
         * @param options echart初始化js
         * @param tmpPath 临时文件保存路径
         * @return 文件完整路径
         */
        private String writeFile(String options, String tmpPath) {
            String dataPath = tmpPath + UUID.randomUUID().toString().substring(0, 8) + ".json";
            try {
                /* 写入Txt文件 */
                // 相对路径,如果没有则要建立一个新的output.txt文件
                File writeName = new File(dataPath);
                // 文件不存在则创建文件,先创建目录
                if (!writeName.exists()) {
                    File dir = new File(writeName.getParent());
                    dir.mkdirs();
                    // 创建新文件
                    writeName.createNewFile();
                }
                BufferedWriter out = new BufferedWriter(new FileWriter(writeName));
                out.write(options);
                // 把缓存区内容压入文件
                out.flush();
                // 最后记得关闭文件
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return dataPath;
        }

    生成Word文件并插入EChart图片

    在pom文件中引入生成word需要的依赖:

            <!-- word依赖 -->
            <dependency>
                <groupId>com.lowagie</groupId>
                <artifactId>itext</artifactId>
                <version>2.1.5</version>
            </dependency>
            <dependency>
                <groupId>com.lowagie</groupId>
                <artifactId>itext-rtf</artifactId>
                <version>2.1.4</version>
            </dependency>
            <dependency>
                <groupId>com.itextpdf</groupId>
                <artifactId>itext-asian</artifactId>
                <version>5.2.0</version>
            </dependency>

    生成word并插入图片:

        /**
         * 生成word文件
         *
         * @param title      标题
         * @param content    正文
         * @param imagePaths 待插入的图片地址列表
         * @param docPath    word文件保存地址
         * @throws IOException
         * @throws com.lowagie.text.DocumentException
         */
        private void writeWord(String title, String content, List<String> imagePaths, String docPath) throws IOException, com.lowagie.text.DocumentException {
            // 创建文件
            File file = new File(docPath);
            Document document = new Document(PageSize.A4);
            RtfWriter2.getInstance(document, new FileOutputStream(file));
            document.open();
    
            // 初始化字体
            BaseFont bfChinese = BaseFont.createFont(BaseFont.HELVETICA, BaseFont.WINANSI, BaseFont.NOT_EMBEDDED);
            // 标题字体
            Font titleFont = new Font(bfChinese, 30, Font.BOLD);
            // 小标题字体
    //        Font littleTitleFont = new Font(bfChinese, 20, Font.BOLD);
            // 正文字体
            Font contextFont = new Font(bfChinese, 15, Font.NORMAL);
    
            // 设置大标题
            Paragraph titlePar = new Paragraph(title);
            titlePar.setFont(titleFont);
            titlePar.setAlignment(Element.ALIGN_CENTER);
            document.add(titlePar);
    
            // 正文
            Paragraph context = new Paragraph(content);
            context.setAlignment(Element.HEADER);
            context.setFont(contextFont);
            document.add(context);
    
            // 图片
            imagePaths.forEach(imagePath -> {
                Image img = null;
                try {
                    img = Image.getInstance(imagePath);
                    img.setAbsolutePosition(0, 0);
                    img.scalePercent(50f);
                    // 设置图片显示位置
                    img.setAlignment(Image.LEFT);
                    document.add(img);
                } catch (IOException | DocumentException e) {
                    e.printStackTrace();
                }
            });
    
            document.close();
        }

    调用方法生成图片并插入word中

        /**
         * 导出docx文件
         *
         * @param params   参数列表
         * @param response response 返回的文件流
         */
        public void getWordWithEchart(Map<String, Object> params, HttpServletResponse response) throws IOException {
    
            // 这一部分代码和业务逻辑强相关了,需要自己根据现实情况去实现
            List<String> imagePaths = new ArrayList<>();
            一个业务数组.forEach(id -> {
                // 这里的X轴其实有一个问题,就是用"MM-dd"形式传入其中的话,生成工具会默认这是一个减法运算。
                // 这个时候需要遍历一下X轴数据,在每一个数据外面加上一对转义的双引号,like this:"""
                String eChartOption = getEChartOption(
                        标题,
                        x轴,
                        柱状图1,
                        柱状图2);
                // 生成图片
                imagePaths.add(generateEChart(eChartOption, eChartTempPath, eChartJSPath));
    
            });
    
            // 根据EChart图片生成Word文档
            String title = "Word文档Title
    ";
            // doc文件命名
            String docName = UUID.randomUUID().toString().substring(0, 8) + ".docx";
            String docPath = eChartTempPath + docName;
    
            StringBuffer content = new StringBuffer();
            ...加一大堆生成word内容的逻辑
            content.append("
    ");
    
            try {
                // 生成word文件
                writeWord(title, content.toString(), imagePaths, docPath);
            } catch (IOException | DocumentException e) {
                e.printStackTrace();
            }
            //删除临时文件
            imagePaths.forEach(imagePath -> {
                File file = new File(imagePath);
                file.delete();
            });
            // 返回文件流
            File file = new File(docPath);
            // 八进制输出流
            response.setContentType("application/octet-stream");
            response.setHeader("content-type", "application/octet-stream");
            // 设置导出Word的名称
            response.setHeader("Content-disposition", "attachment;filename=" + "Word文档.docx");
            // 刷新缓冲
            response.flushBuffer();
            // 将doc文件流写入到返回
            // 根据路径获取要下载的文件输入流
            InputStream inputStream = new FileInputStream(file);
            OutputStream out = response.getOutputStream();
            //创建数据缓冲区
            byte[] b = new byte[1024];
            int length;
            while ((length = inputStream.read(b)) > 0) {
                //把文件流写到缓冲区里
                out.write(b, 0, length);
            }
            out.flush();
            out.close();
            inputStream.close();
    
            // 传输过后把doc文件删除
            file.delete();
        }

    注:这个demo在运行之后会把生成的文件都删掉,如果想看看生成的临时文件长成啥样,把代码中的文件删除操作都注释掉就可以了。

  • 相关阅读:
    部署Packbeat--Elastic Stack之十
    部署Winlogbeat--Elastic Stack之九
    解决git clone pull push慢必杀技
    Android内存泄露分析及内存管理小记
    android 8.0以后(sdk26)启动前台服务的问题探究
    打包编译.so流程
    AS升级编译报错:The SourceSet 'instrumentTest' is not recognized by the Android Gradle Plugin.
    三层fragment嵌套,接口回调方式
    recyclerview的onBindViewHolder中if之后要写else,否则可能显示有问题
    2018
  • 原文地址:https://www.cnblogs.com/helios-fz/p/14524566.html
Copyright © 2020-2023  润新知