• 微信公众号:美女很有趣。 工作之余,放松一下,关注即送10G+美女照片!

Java+PhantomJs实现后台生成Echarts图片 已成功部署Linux(完整源码+dockerfile)

互联网 diligentman 2小时前 2次浏览

Java+PhantomJs实现后台生成Echarts图片(完整源码)

  • 需求
  • 效果图
  • 实现
      • 引入依赖
      • 引入js文件
      • 拼接option (完整代码如下)
      • 生成图片
      • 附上拼接option时涉及的实体
      • 生成的折线图样式
  • 遇到的坑

需求

生成折线图定期发送邮件

效果图

Java+PhantomJs实现后台生成Echarts图片 已成功部署Linux(完整源码+dockerfile)

实现

引入依赖

<dependency>
            <groupId>com.github.abel533</groupId>
            <artifactId>ECharts</artifactId>
            <version>3.0.0.6</version>
</dependency>

引入js文件

  • echarts.min.js
    点击下载
  • jquery-3.5.1.min.js
    点击下载
  • echarts-convert.js 生成echart图片的脚本(完整代码如下)
(function () {
var system = require('system');
var fs = require('fs');
var config = {
    // define the location of js files
    //这样写要求这三个js在同一个文件夹下
    JQUERY: 'jquery-3.5.1.min.js',
    //ESL: 'esl.js',
    ECHARTS: 'echarts.min.js',
    // default container width and height
    DEFAULT_WIDTH: '400',
    DEFAULT_HEIGHT: '600'
}, parseParams, render, pick, usage;

usage = function () {
    console.log("nUsage: phantomjs echarts-convert.js -options options -outfile filename -width width -height height"
        + "OR"
        + "Usage: phantomjs echarts-convert.js -infile URL -outfile filename -width width -height heightn");
};

pick = function () {
    var args = arguments, i, arg, length = args.length;
    for (i = 0; i < length; i += 1) {
        arg = args[i];
        if (arg !== undefined && arg !== null && arg !== 'null' && arg != '0') {
            return arg;
        }
    }
};

parseParams = function () {
    var map = {}, i, key;
    if (system.args.length < 2) {
        usage();
        phantom.exit();
    }
    for (i = 0; i < system.args.length; i += 1) {
        if (system.args[i].charAt(0) === '-') {
            key = system.args[i].substr(1, i.length);
            if (key === 'infile') {
                // get string from file
                // force translate the key from infile to options.
                key = 'options';
                try {
                    map[key] = fs.read(system.args[i + 1]).replace(/^s+/, '');
                } catch (e) {
                    console.log('Error: cannot find file, ' + system.args[i + 1]);
                    phantom.exit();
                }
            } else {
                map[key] = system.args[i + 1].replace(/^s+/, '');
            }
        }
    }
    return map;
};

render = function (params) {
    var page = require('webpage').create(), createChart;

    var bodyMale = config.SVG_MALE;
    page.onConsoleMessage = function (msg) {
        console.log(msg);
    };

    page.onAlert = function (msg) {
        console.log(msg);
    };

    createChart = function (inputOption, width, height,config) {
        var counter = 0;
        function decrementImgCounter() {
            counter -= 1;
            if (counter < 1) {
                console.log(messages.imagesLoaded);
            }
        }

        function loadScript(varStr, codeStr) {
            var script = $('<script>').attr('type', 'text/javascript');
            script.html('var ' + varStr + ' = ' + codeStr);
            document.getElementsByTagName("head")[0].appendChild(script[0]);
            if (window[varStr] !== undefined) {
                console.log('Echarts.' + varStr + ' has been parsed');
            }
        }

        function loadImages() {
            var images = $('image'), i, img;
            if (images.length > 0) {
                counter = images.length;
                for (i = 0; i < images.length; i += 1) {
                    img = new Image();
                    img.onload = img.onerror = decrementImgCounter;
                    img.src = images[i].getAttribute('href');
                }
            } else {
                console.log('The images have been loaded');
            }
        }
        // load opitons
        if (inputOption != 'undefined') {
            // parse the options
            loadScript('options', inputOption);
            // disable the animation
            options.animation = false;
        }

        // we render the image, so we need set background to white.
        $(document.body).css('backgroundColor', 'white');
        var container = $("<div>").appendTo(document.body);
        container.attr('id', 'container');
        container.css({
            width: width,
            height: height
        });
        // render the chart
        var myChart = echarts.init(container[0]);
        myChart.setOption(options);
        // load images
        loadImages();
        return myChart.getDataURL();
    };

    // parse the params
    page.open("about:blank", function (status) {
        // inject the dependency js
        page.injectJs(config.ESL);
        page.injectJs(config.JQUERY);
        page.injectJs(config.ECHARTS);


        var width = pick(params.width, config.DEFAULT_WIDTH);
        var height = pick(params.height, config.DEFAULT_HEIGHT);

        // create the chart
        var base64 = page.evaluate(createChart, params.options, width, height,config);
        fs.write("base64.txt",base64);
        // define the clip-rectangle
        page.clipRect = {
            top: 0,
            left: 0,
            width: width,

            height: height
        };
        // render the image
        page.render(params.outfile);
        console.log('render complete:' + params.outfile);
        // exit
        phantom.exit();
    });
};
// get the args
var params = parseParams();

// validate the params
if (params.options === undefined || params.options.length === 0) {
    console.log("ERROR: No options or infile found.");
    usage();
    phantom.exit();
}
// set the default out file
if (params.outfile === undefined) {
    var tmpDir = fs.workingDirectory + '/tmp';
    // exists tmpDir and is it writable?
    if (!fs.exists(tmpDir)) {
        try {
            fs.makeDirectory(tmpDir);
        } catch (e) {
            console.log('ERROR: Cannot make tmp directory');
        }
    }
    params.outfile = tmpDir + "/" + new Date().getTime() + ".png";
}

// render the image
render(params);
}());

拼接option (完整代码如下)

public String getOption(String title, String ytitle, List<String> categorie, List<Serie> series) {
    GsonOption option = new GsonOption();

    // 大标题、位置
    option.title().text(title).x("left");

    List<String> types = series.stream().map(Serie::getName).collect(Collectors.toList());
    // 设置图例
    option.legend().data(types.toArray()).x("center");
    
    // 在轴上触发提示数据
    option.tooltip().trigger(Trigger.axis);

    // 工具栏 显示,保存为图片 实际是一个下载图标不需要所以注掉了
    // option.toolbox().show(true).feature(Tool.saveAsImage);

    // x轴
    com.github.abel533.echarts.axis.CategoryAxis category =
        new com.github.abel533.echarts.axis.CategoryAxis();
    category.data(categorie.toArray());
    // 起始和结束两端空白策略
    category.boundaryGap(false);

    // option.grid().x(180);

    // y轴
    com.github.abel533.echarts.axis.ValueAxis ecsY =
        new com.github.abel533.echarts.axis.ValueAxis();
    ecsY.name(ytitle).position("left").axisLine().lineStyle().color(Color.BLACK);
    //不允许出现小数位,坐标轴最小值1
    ecsY.minInterval(1);

    TextStyle textStyle = new TextStyle();
    textStyle.setFontSize(18);

    // 循环数据
    for (int i = 0; i < types.size(); i++) {
      Line line = new Line();
      String type = types.get(i);
      line.name(type);
      int size = series.get(i).getData().size();
      for (int j = 0; j < size; j++) {
        line.data(series.get(i).getData().get(j));
      }
      // 设置平滑线条
      // line.smooth(true);
      option.series(line);
    }

    // 横轴为类别、纵轴为值
    option.xAxis(category);
    option.yAxis(ecsY);

	// 处理没有数据的情况
    NoDataLoadingOption noDataLoadingOption = new NoDataLoadingOption();
    Effect effect = new Effect();
    effect.setShow(true);
    EffectOption effectOption = new EffectOption();
    effectOption.setEffect(0);
    noDataLoadingOption.setEffect(effect);
    noDataLoadingOption.setText("暂无数据");
    option.noDataLoadingOption(noDataLoadingOption);

    String optionStr = option.toString().replace(" ", "");
    log.info(optionStr);
    return optionStr;
  }

生成图片

public String generateEChart(String options, String imgName, int width, int height) {
    String path = "forum-app/src/main/resources/images/" + imgName;
    try {
      // 文件路径(路径+文件名)
      File file = new File(path);
      // 文件不存在则创建文件,先创建目录
      if (!file.exists()) {
        File dir = new File(file.getParent());
        dir.mkdirs();
        file.createNewFile();
      }
      // echarts-convert.js 脚本所在 path
      //本地这样写是没问题的
      String JSpath = "forum-common/src/main/resources/js/echarts-convert.js";
      log.info("start cmd exec generate png");
      String cmd =
          "phantomjs "
              + JSpath
              + " -options "
              + options
              + " -outfile "
              + path
              + " -width "
              + width
              + " -height "
              + height
              + "";
      Process process = Runtime.getRuntime().exec(cmd);
      process.waitFor();
      log.info("end cmd exec generate png");
      BufferedReader input = new BufferedReader(new InputStreamReader(process.getInputStream()));
      String line = "";
      while ((line = input.readLine()) != null) {
        log.info(line);
      }
      input.close();
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      return path;
    }
  }

附上拼接option时涉及的实体

public class Serie implements Serializable {

  private static final long serialVersionUID = 1L;
  private String name;// 名字
  private Vector<Object> data;// 数据值ֵ

  /**
   *
   * @param name
   *            名称(线条名称)
   * @param array
   *            数据(线条上的所有数据值)
   */
  public Serie(String name, Object[] array) {
    this.name = name;
    if (array != null) {
      data = new Vector<Object>(array.length);
      for (int i = 0; i < array.length; i++) {
        data.add(array[i]);
      }
    }
  }


}

生成的折线图样式

遇到的坑

本地测试一切很完美,高高兴兴部署到linux上测试,效果如下。
Java+PhantomJs实现后台生成Echarts图片 已成功部署Linux(完整源码+dockerfile)
生成的都是0kb的图片,心凉了一半。
赶紧去看看日志

{"title":{"text":"问答浏览量","x":"left"},"toolbox":{"feature":{"saveAsImage":{"show":true,"title":"保存为图片","type":"png","lang":["点击保存"]}},"show":true},"tooltip":{"trigger":"axis"},"legend":{"data":["TRANTOR","GAIA","GAIAAPP"],"x":"center"},"xAxis":[{"type":"category","boundaryGap":false,"data":["2021-09-06","2021-09-07","2021-09-08","2021-09-09","2021-09-10","2021-09-11","2021-09-12"]}],"yAxis":[{"type":"value","position":"left","name":"","axisLine":{"lineStyle":{"color":{"value":-16777216,"falpha":0.0}}}}],"series":[{"name":"TRANTOR","type":"line","data":[0,0,0,2,0,3,2]},{"name":"GAIA","type":"line","data":[0,0,0,0,0,0,0]},{"name":"GAIAAPP","type":"line","data":[0,0,0,0,0,0,0]}],"noDataLoadingOption":{"text":"暂无数据","effect":{"show":true}}}
2021-09-12 19:19:37.340 [1053940:http-nio-8080-exec-10] INFO  pub.developers.forum.common.support.GenerateEChartUtil - start cmd exec generate png
2021-09-12 19:19:37.425 [1054025:http-nio-8080-exec-10] INFO  pub.developers.forum.common.support.GenerateEChartUtil - end cmd exec generate png

日志看起来一切都很正常,心又凉了一大半。
稳住,不能慌!
排查思路:

  1. 执行命令写的是相对路径,linux环境上可能找不到我安装的phantomjs执行文件
  2. echarts-convert.js文件没找到。

解决方案:

配置环境变量,将js文件拷贝至容器目录下。
附上dockerfile添加内容:

WORKDIR /
COPY ./forum-common/src/main/resources/js/echarts-convert.js /app/echarts-convert.js
COPY ./forum-common/src/main/resources/js/echarts.min.js /app/echarts.min.js
COPY ./forum-common/src/main/resources/js/jquery-3.5.1.min.js /app/jquery-3.5.1.min.js

#安装bzip2解压PhantomJS压缩包
RUN yum -y install bzip2

#Echarts支持中文
RUN yum -y install bitmap-fonts bitmap-fonts-cjk

# PhantomJS
ENV PHANTOMJS_VERSION 2.1.1
RUN wget --no-check-certificate -q -O - https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-$PHANTOMJS_VERSION-linux-x86_64.tar.bz2 | tar xjC /opt
RUN ln -s /opt/phantomjs-$PHANTOMJS_VERSION-linux-x86_64/bin/phantomjs /usr/bin/phantomjs
ENV PJS_HOME=/opt/phantomjs-$PHANTOMJS_VERSION-linux-x86_64
ENV PATH=$PATH:$PJS_HOME/bin

修改generateEChart()方法中JSpath

String JSpath = "/app/echarts-convert.js";

修改echarts-convert.js 中其他两个js的路径
Java+PhantomJs实现后台生成Echarts图片 已成功部署Linux(完整源码+dockerfile)

发布,完美!


喜欢 (0)