Java 用 PhantomJS+ECharts 在linux环境生成图片,然后发送带图片的邮件

第一步:下载并安装PhantomJS

下载地址:Download PhantomJS

下载完成后解压

tar -xjvf phantomjs-2.1.1-linux-x86_64.tar.bz2

将可执行文件放入系统路径,并进行连接(必须是全路径)

sudo ln -s home/software/phantomjs-2.1.1-linux-x86_64/bin/phantomjs /usr/bin/phantomjs

授权

chmod 777 phantomjs

测试Phantomjs

第二步:准备生成图片相关的脚本

1. echarts.min.js
下载地址:https://echarts.baidu.com/download.html

下载最新的 release 版本,解压出来的文件夹里的 dist 目录里可以找到最新版本的 echarts 库。

2. jquery-3.2.1.min.js
我用了3.2.1这个版本,具体版本没有限制。

3. echarts-convert.js

(function () {
var system = require('system');
var fs = require('fs');
var config = {
    // define the location of js files
    JQUERY: 'jquery-3.2.1.min.js',
    //ESL: 'esl.js',
    ECHARTS: 'echarts.min.js',
    // default container width and height
    DEFAULT_WIDTH: '600',
    DEFAULT_HEIGHT: '700'
}, 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 height\n");
};

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

注意:三个脚本放一起

第三步:后端代码编写

1.安装EChart 依赖

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

2.用echarts提供的sdk拼接生成options,并保存json文件到服务器,然后使用Java传cmd命令调用 PhantomJS + EchartsConvert 命令读取json,生成 echarts 图片

private InputStream createChart() {
     //choList 为List<choList>
	 //生成饼图图片
	 InputStream  pieInputStream = this.createChart(choList,"pie");
	 //生成柱状图
	 InputStream  histogramInputStream = this.createChart(choList,"histogram");
      
     // 发邮件
	 String title = "测试邮件" + DateUtils.getDate();
     String from= "ceshifajianren@163.com";
     String user= "ceshifajianren@163.com";
     String emailPassword= "******";
	 String fromCNname = "测试管理系统";
     String content = "测试邮件!"
     String[] addrs = {"ceshi@163.com"};
     try {
		SendMail.sendMailImage(from, fromCNname, user, emailPassword, content, title, addrs, "", pieInputStream, histogramInputStream);
	 } catch (Exception e) {
		log.error("发送邮件失败--->" + e.getMessage());
	 }
}
//生成图片
private InputStream createChart(List<choList> choList,String fileType) {
        String phantomjs = "/home/phantomjs/phantomjs/bin/phantomjs";
		String JSpath = "/home/phantomjs/echartsconvert/convert.js";
		String path = "/home/echartspath/";
		String dateTime = DateUtils.dateTime();
		String jsonfile = dateTime + fileType + ".json";
		String imagefile = dateTime + fileType + ".png";
		String option = "";
		InputStream fileInputStream = null;
		try {
			//生成option
			if("pie".equals(fileType)) {
				option = createPieChartOption(choList);
			}else {
				option = createHistogramOption(choList);
			}
			FtpUtil ftpshell = new FtpUtil("121.1.1.1",22,"ccc","ccc***");
			// option上传
			ftpshell.connect();
			ftpshell.writeContent(option, echartsPath, jsonfile);
			System.out.println("创建option");
			// 生成图片
			ftpshell.sshconnect();
			String cmd = phantomjsPath + " " + echartsconvertPath + " -infile " + echartsPath + jsonfile + " -outfile " + echartsPath + imagefile;
			System.out.println(cmd);
			ftpshell.execShell(cmd);
			//读取图片
			fileInputStream = ftpshell.getsftp(echartsPath + imagefile);
			ftpshell.disConnect();
		} catch (Exception e) {
			log.error("生成图片,读取图片失败--->" + e.getMessage());
		}
		return fileInputStream;
	}
//生成饼图
	private String createPieChartOption(List<choList> choList) {
		List<Map<String, Object>> list = new ArrayList<>();
		for (choListch : choList) {
			Map<String, Object> map = new HashMap<>();
            map.put("name", ch.getProcorr());
            map.put("value", ch.getStatis());
            list.add(map);
		}
        //创建Option
        Option option = new GsonOption();
        option.title(new Title().text("占比").x("middle"));
        option.tooltip(Trigger.item);
        option.legend(new Legend().orient(Orient.vertical).left("left"));
        //饼图数据
        Pie pie = new Pie("占比");
        //循环数据
        for (Map<String, Object> objectMap : list) {
            //饼图数据
            pie.data(new PieData(objectMap.get("name").toString() + " " + objectMap.get("value"), objectMap.get("value")));
        }
        //设置数据
        option.series(pie);
        String optionStr = option.toString().replace(" ", "");
        return optionStr;
    }
//生成柱状图
	private String createHistogramOption(List<choList> choList) {
		String[] colors = { "#546fc6", "#91cb74",
				"#fac859", "#ee6666", "#73c0de",
				"#3ba372", "#fb8351", "#9a60b4"};
		List<Map<String, Object>> list = new ArrayList<>();
		for (choListch : choList) {
			Map<String, Object> map = new HashMap<>();
            map.put("name", ch.getProcorr()); //人名
            map.put("value", ch.getStatis()); //数量
            map.put("itemStyle",
 					new ItemStyle().normal(new Normal().color(colors[new Random().nextInt(8)])));
            list.add(map);
		}
        //创建Option
        Option option = new GsonOption();
        option.title(new Title().text("统计").x("middle"));
        option.tooltip(Trigger.item);
        option.legend(new Legend().orient(Orient.vertical).left("left"));//设置布局方式
        
        //设置x轴数据
        CategoryAxis categoryAxis = new CategoryAxis();
        for (Changeorder ch : choList) {
        	categoryAxis.data(ch.getProcorr());
		}
        option.xAxis(categoryAxis);
        
        //设置y轴 这里不给指定数据  自动调整
        ValueAxis valueAxis = new ValueAxis();
        option.yAxis(valueAxis);
        
        //填充柱状图数据
        Bar bar = new Bar("统计");
        //循环数据
        for (Map<String, Object> objectMap : list) {
        	bar.data(objectMap);
        }
        //设置顶端显示数值
        bar.setLabel(new ItemStyle().normal(new Normal().show(true).position(Position.top)));
        //设置数据
        option.series(bar);
        String optionStr = option.toString().replace(" ", "");
        return optionStr;
    }

 3. 发送邮件

public static boolean sendMailImage(String from, String fromCNname,String user, String password, String text, String title, String[] addrs, String recipient, InputStream pieImageInputStream, InputStream histogramImageInputStream) throws Exception {
//    	1.创建参数配置, 用于连接邮件服务器的参数配置
        Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider());
        final String SSL_FACTORY = "javax.net.ssl.SSLSocketFactory";
        Properties props = System.getProperties();
        props.setProperty("mail.smtp.host", mailHost);
        props.setProperty("mail.smtp.socketFactory.class", SSL_FACTORY);
        props.setProperty("mail.smtp.socketFactory.fallback", "false");
        props.setProperty("mail.smtp.port", "465");
        props.setProperty("mail.smtp.socketFactory.port", "465");
        props.put("mail.smtp.auth", "true");
// 		2. 根据配置创建会话对象, 用于和邮件服务器交互,不要用defaultInstance!!!!!!
        Session session = Session.getInstance(props, new Authenticator() {
            @Override
            protected PasswordAuthentication getPasswordAuthentication() {
                return new PasswordAuthentication(user, password);
            }
        });
        session.setDebug(true);  // 设置为debug模式, 可以查看详细的发送 log
        // -- Create a new message -- 1. 创建一封邮件
        Message msg = new MimeMessage(session);
        // -- Set the FROM and TO fields --
        String nick = javax.mail.internet.MimeUtility.encodeText(fromCNname);   //设置发件人中文名称  ***<213321@163.com>
        msg.setFrom(new InternetAddress(nick+" <"+from+">"));
//        msg.setFrom(new InternetAddress(from));
        InternetAddress[] toAddrs = new InternetAddress[addrs.length];
        if (StringUtils.isNotBlank(recipient)) {
            InternetAddress reciAddr = new InternetAddress(recipient);  //抄送
            msg.setRecipient(Message.RecipientType.CC, reciAddr);
        }
        for (int i = 0; i < toAddrs.length; i++) {
            toAddrs[i] = new InternetAddress(addrs[i]);
        }
        msg.setRecipients(Message.RecipientType.TO, toAddrs);
        msg.setSubject(title);
        
        //创建富文本对象
        BodyPart mdp = new MimeBodyPart();//新建一个存放信件内容的BodyPart对象
        mdp.setContent(text, "text/html;charset=utf-8");//给BodyPart对象设置内容和格式/编码方式

        MimeMultipart mm = new MimeMultipart();//新建一个多功能邮件块对象用来存放BodyPart对象
        mm.addBodyPart(mdp);//将BodyPart加入到MimeMultipart对象中(可以加入多个BodyPart)
        mm.addBodyPart(createImageMimeBodyPart(pieImage)); //将图片插入MimeMultipart对象中
        mm.addBodyPart(createImageMimeBodyPart(histogramImage)); //将图片插入MimeMultipart对象中
        /**
         *  mixed:混合关系,一般正文和附件组合使用mixed,如果不设置,javamail默认会使用mixed
	     *  related:关联关系,一般html引用了图片这类内嵌资源,正文和图片组合使用related
	     *  alternative:同时存在纯文本与超文本,使用boundary分割
         */
        mm.setSubType("related");
        
        //将封装好的数据保存到Message中
        msg.setContent(mm);
        msg.setSentDate(new Date());
        
//        Transport.send(msg);
        System.out.println("Message sent.");
        return true;
    }
    
    // 封装图片MimeBodyPart
    private static MimeBodyPart createImageMimeBodyPart(InputStream imageInputStream) throws Exception {
       MimeBodyPart image = new MimeBodyPart();
       image.setDataHandler(new DataHandler(new ByteArrayDataSource(imageInputStream, "application/octet-stream")));  //javamail jaf
       image.setContentID(IdUtils.fastSimpleUUID());//设置对应资源文件的唯一标识符,即MIME协议对于邮件得结构组织格式中得content-id头字段
       return image;
    }

(使用-options不能生成图片,只能使用-infile,指定json文件全目录,原因未知) 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值