问题场景
后端需要定时发邮件,邮件正文带图片,图片要求每次即时生成。
开发环境
idea+Java8+springboot2
echart-convert.js
phantomjs-2.1.1与字体
分析
phantomjs可以模拟浏览器执行js请求ajax等效果,俗称无头浏览器,可以用于客户端渲染。
步骤
- 下载上述资源,放入工程里,待调用。
- 安装phantomjs-2.1.1及微软雅黑字体[图表中文乱码-服务器可能需要安装字体]
字体:
- yum install -y mkfontscale
- yum install -y fontconfig
- mkdir /usr/share/fonts/chinese
- cp msyh.ttf /usr/share/fonts/chinese
- mkfontscale
- mkfontdir
- fc-cache -fv
# 查看是否安装成功
- fc-list
phantomjs-2.1.1
- yum install -y bzip2
# just do it
- yum install -y fontconfig freetype libfreetype.so.6
- tar -jxvf phantomjs-2.1.1-linux-x86_64.tar.bz2 -C /usr/local/
# 创建软链接
- ln -s /usr/local/phantomjs-2.1.1-linux-x86_64/bin/phantomjs /usr/local/bin/phantomjs
# 验证安装是否成功
- phantomjs --version
- 引入maven依赖
<dependency>
<groupId>com.github.abel533</groupId>
<artifactId>ECharts</artifactId>
<version>2.2.7</version>
</dependency>
- 后台组织好echart生成图片的option
- 调用下面的代码
import com.github.abel533.echarts.Grid;
import com.github.abel533.echarts.Label;
import com.github.abel533.echarts.code.Trigger;
import com.github.abel533.echarts.series.Bar;
import com.github.abel533.echarts.style.ItemStyle;
import com.github.abel533.echarts.style.TextStyle;
import com.github.abel533.echarts.style.itemstyle.Normal;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.ResourceUtils;
import com.github.abel533.echarts.axis.AxisLabel;
import com.github.abel533.echarts.axis.CategoryAxis;
import com.github.abel533.echarts.axis.ValueAxis;
import com.github.abel533.echarts.code.Magic;
import com.github.abel533.echarts.code.Tool;
import com.github.abel533.echarts.feature.MagicType;
import com.github.abel533.echarts.json.GsonOption;
import com.github.abel533.echarts.series.Line;
import java.io.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 〈〉
*
* @author yangyouxing
* @create 2019-12-18
* @since 1.0.0
*/
@Slf4j
@Service
public class ImageServiceImpl {
/**
* 调用phantomjs产生图片
* @param options echarts的options
* @param imgName 图片名称路径
* @param width 1680
* @param height 500
* @return
*/
public String createEchartImage(String options, String imgName, Long width, Long height) {
String path = "";
BufferedReader input = null;
String cmd = "";
try {
// 工具类自己搞
if (OSUtil.isLinux()) {
path = "/usr/local/download/" + imgName;
} else {
path = ResourceUtils.getURL("classpath:download").getPath() + File.separator + imgName;
}
File file = new File(path);
if (file.exists()) {
file.delete();
}
File fileParent = file.getParentFile();
if(!fileParent.exists()){
fileParent.mkdirs();
}
file.createNewFile();
String jsPath;
// 区分服务器和本地环境
if (OSUtil.isLinux()) {
jsPath = "/usr/local/echarts/echarts-convert.js";
} else {
// resources下创建echarts文件夹
jsPath = ResourceUtils.getURL("classpath:echarts").getPath() + File.separator + "echarts-convert.js";
}
log.info("start cmd exec generate png");
cmd = "/usr/local/bin/phantomjs " + jsPath + " -options " + options + " -outfile " + path + " -width " + width + " -height " + height;
log.info("cmd: {}", cmd);
Process process = Runtime.getRuntime().exec(cmd);
log.info("end cmd exec generate png");
input = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = input.readLine()) != null) {
log.info(line);
}
log.info("close");
} catch (Exception e) {
e.printStackTrace();
log.error("echarts error: {}, cmd: {}, options: {}, imageName: {}", e.getMessage(), cmd, options, imgName, e);
} finally {
if (input != null) {
try {
input.close();
} catch (IOException e) {
e.printStackTrace();
log.error("关闭流失败, {}, options: {}, imageName: {}", e.getMessage(), options, imgName, e);
}
}
}
return path;
}
/**
* 折线图
* @param types y轴值
* @param yColor y轴颜色-多个请自行换类型
* @param title 图表标题
* @param titleAglin 标题对齐
* @param rotate 横坐标旋转角度
* @param interval 刻度间隔
* @param xDataList x轴值
* @param yDataList y轴值
* @param axisName 横坐标名
* @param isHorizontal 是否颠倒x与y轴
* @return
*/
public String getLineEchartOption(String[] types, String yColor, String title, String titleAglin,
Integer rotate, Integer interval, List<String> xDataList, List<String> yDataList, String axisName, boolean isHorizontal) {
GsonOption option = new GsonOption();
// 大标题、位置
option.title().text(title).x(titleAglin);
// 提示工具
option.tooltip().show(true).trigger(Trigger.axis);
// 工具栏
option.toolbox().show(true).feature(Tool.mark, Tool.dataView, new MagicType(Magic.line, Magic.bar), Tool.restore, Tool.saveAsImage);
// 图例
for (String legend : types) {
option.legend(legend);
}
// 循环给y轴赋值数据
for (int i = 0; i < types.length; i++) {
Line line = new Line();
String type = types[i];
line.name(type);
for (String yy : yDataList) {
line.data(yy);
}
option.series(line);
}
// 轴分类
CategoryAxis x = new CategoryAxis();
for (String cate : xDataList) {
x.data(cate);
}
x.name(axisName);
// 设置横坐标旋转角度
AxisLabel xLabel = new AxisLabel();
xLabel.setRotate(rotate);
xLabel.setInterval(interval);
x.setAxisLabel(xLabel);
// 横轴为类别、纵轴为值
if (isHorizontal) {
// x轴
option.xAxis(x);
// y轴
ValueAxis ecsY = new ValueAxis();
ecsY.axisLine().lineStyle().color(yColor);
option.yAxis(ecsY);
} else {
// 横轴为值、纵轴为类别
// x轴
option.xAxis(new ValueAxis());
// y轴
option.yAxis(x);
}
Grid grid = new Grid();
// 控制x轴文字与左边的距离
grid.setX(70);
// 控制x2轴文字与右边的距离
grid.setX2(100);
// y2可以控制倾斜的文字与底部的距离
grid.setY2(100);
// y可以控制倾斜的文字与顶部的距离
grid.setY(100);
option.setGrid(grid);
String optionStr = option.toString().replace(" ", "");
log.info("line-options: {}", optionStr);
return optionStr;
}
/**
* 柱状图
* @param types y轴值
* @param typeColors y轴颜色
* @param title 图表标题
* @param titleAglin 标题对齐
* @param rotate 横坐标旋转角度
* @param interval 刻度间隔
* @param xDataList x轴值
* @param yDataList y轴值
* @param axisName 横坐标名
* @param isHorizontal 是否颠倒x与y轴
* @return
*/
public String getBarEchartOption(String[] types, String[] typeColors, String title, String titleAglin,
Integer rotate, Integer interval, String[] xDataList, Integer[] yDataList, String axisName, boolean isHorizontal) {
GsonOption option = new GsonOption();
// 大标题、位置
option.title().text(title).x(titleAglin);
//显示工具提示,设置提示格式
option.tooltip().show(true).trigger(Trigger.axis);
// 工具栏
option.toolbox().show(true).feature(Tool.mark, Tool.dataView, new MagicType(Magic.line, Magic.bar), Tool.restore, Tool.saveAsImage);
// 图例
option.legend(types[0]);
// 图类别(柱状图)
Bar bar = new Bar(types[0]);
int size = xDataList.length;
if (size > 30) {
bar.setBarWidth(5);
} else {
bar.setBarWidth(30);
}
// 循环数据
for (int i = 0; i < size; i++) {
String color = typeColors[i % typeColors.length];
// 类目对应的柱状图
/**
* itemStyle: {
* normal: {
* label: {
* show: true, //开启显示
* position: 'top', //在上方显示
* textStyle: { //数值样式
* color: 'black',
* fontSize: 16
* }
* }
* }
* }
*/
Label label = new Label();
label.show(true);
label.position("top");
TextStyle textStyle = new TextStyle();
textStyle.color("black");
textStyle.fontSize(16);
label.textStyle(textStyle);
ItemStyle itemStyle = new ItemStyle();
Normal normal = new Normal();
normal.color(color);
normal.label(label);
itemStyle.normal(normal);
Map<String, Object> map = new HashMap<>(16);
map.put("value", yDataList[i]);
map.put("itemStyle", itemStyle);
bar.data(map);
}
// 轴分类
CategoryAxis x = new CategoryAxis();
for (String cate : xDataList) {
x.data(cate);
}
x.name(axisName);
// 设置横坐标旋转角度
AxisLabel xLabel = new AxisLabel();
xLabel.setRotate(rotate);
xLabel.setInterval(interval);
x.setAxisLabel(xLabel);
// 横轴为类别、纵轴为值
if (isHorizontal) {
// x轴
option.xAxis(x);
// y轴
option.yAxis(new ValueAxis());
} else {
// 横轴为值、纵轴为类别
// x轴
option.xAxis(new ValueAxis());
// y轴
option.yAxis(x);
}
option.series(bar);
Grid grid = new Grid();
// 控制x轴文字与左边的距离
grid.setX(70);
// 控制x2轴文字与右边的距离
grid.setX2(100);
// y2可以控制倾斜的文字与底部的距离
grid.setY2(100);
// y可以控制倾斜的文字与顶部的距离
grid.setY(100);
option.setGrid(grid);
String optionStr = option.toString().replace(" ", "");
log.info("bar-options: {}", optionStr);
return optionStr;
}
}