首先工具准备phantomjs下载地址:https://phantomjs.org/download.html
echarts-convert下载地址:https://gitee.com/saintlee/echartsconvert
pdf编辑器Adobe Acrobat DC
工具我已经打包好了链接:https://pan.baidu.com/s/1Om5Ap56uJNgsN8VatZCoGA?pwd=gdzx
提取码:gdzx
一
1.echarts-convert代码改写
将echarts-convert.js中图片位置,中文解码改为一层
2.如果是Linux系统将echarts-convert.js第53行注释掉
3.Linux中运行下边的代码,安装字体库
yum install bitmap-fonts bitmap-fonts-cjk
4.建立pdf模板
使用Adobe Acrobat DC编辑模板
使用表单工具建立模板填充,设置相关属性 ,文字,图片,表格设置方式相同。
二。代码准备
运行phantomjs
phantomjs 全路径+echarts-convert.js -s -p 6666
将编辑好的模板放到该目录下
pom.xml
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.9</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.20</version>
</dependency>
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!--用于jfreechart生成图片 -->
<dependency>
<groupId>org.jfree</groupId>
<artifactId>jfreechart</artifactId>
<version>1.5.0</version>
</dependency>
<!-- phantomjs服务端模式-->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.28</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.7</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.56</version>
</dependency>
<dependency>
<groupId>org.icepear.echarts</groupId>
<artifactId>echarts-java</artifactId>
<version>1.0.7</version>
</dependency>
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
public class HttpUtil {
public static String post(String url, Map<String, String> params, String charset)
throws ClientProtocolException, IOException {
String responseEntity = "";
// 创建CloseableHttpClient对象
CloseableHttpClient client = HttpClients.createDefault();
// 创建post方式请求对象
HttpPost httpPost = new HttpPost(url);
// 生成请求参数
List<NameValuePair> nameValuePairs = new ArrayList<>();
if (params != null) {
for (Entry<String, String> entry : params.entrySet()) {
nameValuePairs.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
}
}
// 将参数添加到post请求中
httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs, charset));
// 发送请求,获取结果(同步阻塞)
CloseableHttpResponse response = client.execute(httpPost);
// 获取响应实体
HttpEntity entity = response.getEntity();
if (entity != null) {
// 按指定编码转换结果实体为String类型
responseEntity = EntityUtils.toString(entity, charset);
}
// 释放资源
EntityUtils.consume(entity);
response.close();
return responseEntity;
}
}
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.apache.http.client.ClientProtocolException;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
public class EchartsUtil {
//phantomjs的ip和端口
private static String url = "http://localhost:6666";
private static final String SUCCESS_CODE = "1";
public static String generateEchartsBase64(String option) throws ClientProtocolException, IOException {
String base64 = "";
if (option == null) {
return base64;
}
option = option.replaceAll("\\s+", "").replaceAll("\"", "'");
// 将option字符串作为参数发送给echartsConvert服务器
Map<String, String> params = new HashMap<>();
params.put("opt", option);
String response = HttpUtil.post(url, params, "utf-8");
// 解析echartsConvert响应
JSONObject responseJson = JSON.parseObject(response);
String code = responseJson.getString("code");
// 如果echartsConvert正常返回
if (SUCCESS_CODE.equals(code)) {
base64 = responseJson.getString("data");
}
// 未正常返回
else {
String string = responseJson.getString("msg");
throw new RuntimeException(string);
}
return base64;
}
}
import com.itextpdf.text.*;
import com.itextpdf.text.pdf.*;
import lombok.experimental.UtilityClass;
import org.icepear.echarts.Bar;
import org.icepear.echarts.Line;
import org.icepear.echarts.Option;
import org.icepear.echarts.Pie;
import org.icepear.echarts.charts.line.LineSeries;
import org.icepear.echarts.charts.pie.PieSeries;
import org.icepear.echarts.components.coord.cartesian.CategoryAxis;
import org.icepear.echarts.components.coord.cartesian.ValueAxis;
import org.icepear.echarts.components.legend.Legend;
import org.icepear.echarts.components.title.Title;
import org.icepear.echarts.components.tooltip.Tooltip;
import org.icepear.echarts.origin.util.SeriesOption;
import org.icepear.echarts.render.ChartMeta;
import org.icepear.echarts.render.Engine;
import org.springframework.core.io.ClassPathResource;
import sun.misc.BASE64Decoder;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.*;
import java.util.List;
@UtilityClass
public class CreatePdfUtil {
//pdf模板页数
public static final Integer PDF_NUM = 13;
public static void createPdf(HttpServletResponse response,Map<String,Object> map) throws Exception {
//设置请求返回类型
response.setHeader("Content-Disposition", "attachment; filename=xxxx.pdf");
OutputStream outputStream = response.getOutputStream();
//模板路径,放到项目里用这个ClassPathResource
ClassPathResource TemplatePDF = new ClassPathResource("templates/temp.pdf");
//模板字体
BaseFont bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
//用于存储每页生成pdf流
ByteArrayOutputStream bos[] = new ByteArrayOutputStream[PDF_NUM];
for (Integer i = 0; i < PDF_NUM ; i++) {
HashMap<String, Object> textMap = (HashMap<String, Object>) map.get("text");
bos[i] = new ByteArrayOutputStream();
InputStream inputStream = TemplatePDF.getInputStream();
PdfReader reader = new PdfReader(inputStream);
PdfStamper stamp = new PdfStamper(reader, bos[i]);
AcroFields form = stamp.getAcroFields();
form.addSubstitutionFont(bf);
for (Map.Entry<String, Object> text : textMap.entrySet()) {
form.setField(text.getKey(), text.getValue().toString());
}
//添加统计图
HashMap<String, Object> imageMap = (HashMap<String, Object>) map.get("image");
for (Map.Entry<String, Object> image : imageMap.entrySet()) {
try {
//玫瑰🌹图
byte[] pie = generateImages(image.getValue().toString());
Image ajgImage = Image.getInstance(pie);
List<AcroFields.FieldPosition> list = form.getFieldPositions(image.getKey());
if (list != null && list.size() > 0){
int page = list.get(0).page;
Rectangle ajg = list.get(0).position;
//添加图片
PdfContentByte cb = stamp.getOverContent(page);
// 根据域大小设置缩放图片
ajgImage.scaleToFit(ajg.getWidth(), 600);
// 设置居中
ajgImage.setAlignment(Image.MIDDLE);
// 绝对定位
ajgImage.setAbsolutePosition(ajg.getLeft(), ajg.getBottom());
cb.addImage(ajgImage);
}
} catch (Exception e) {
e.printStackTrace();
}
}
//表格 一行数据是一个list
Map<String,List<List<String>>> tableMap = (Map<String, List<List<String>>>) map.get("table");
// 表格类
for (String key : tableMap.keySet()) {
List<List<String>> lists = tableMap.get(key);
int pageNo = form.getFieldPositions(key).get(0).page;
PdfContentByte pcb = stamp.getOverContent(pageNo);
Rectangle signRect = form.getFieldPositions(key).get(0).position;
//表格位置
int column = lists.get(0).size();
int row = lists.size();
PdfPTable table = new PdfPTable(column);
float tatalWidth = signRect.getRight() - signRect.getLeft() - 1;
int size = lists.get(0).size();
float width[] = new float[size];
for (int a = 0; a < size; a++) {
if (a == 0) {
width[a] = 60f;
} else {
width[a] = (tatalWidth - 60) / (size - 1);
}
}
table.setTotalWidth(width);
table.setLockedWidth(true);
table.setKeepTogether(true);
table.setSplitLate(false);
table.setSplitRows(true);
Font FontProve = new Font(bf, 10, 0);
//表格数据填写
for (int a = 0; a < row; a++) {
List<String> l = lists.get(a);
for (int j = 0; j < l.size(); j++) {
Paragraph paragraph = new Paragraph(String.valueOf(l.get(j)), FontProve);
PdfPCell cell = new PdfPCell(paragraph);
cell.setBorderWidth(1);
cell.setVerticalAlignment(Element.ALIGN_CENTER);
cell.setHorizontalAlignment(Element.ALIGN_CENTER);
cell.setLeading(0, (float) 1.4);
table.addCell(cell);
}
}
table.writeSelectedRows(0, -1, signRect.getLeft(), signRect.getTop(), pcb);
}
// 如果为false,生成的PDF文件可以编辑,如果为true,生成的PDF文件不可以编辑
stamp.setFormFlattening(true);
stamp.close();
Document doc = new Document();
PdfCopy copy = new PdfCopy(doc, bos[i]);
doc.open();
int pageNum = reader.getNumberOfPages();
for (int j = 1; j <= pageNum; j++) {
PdfImportedPage importPage = copy.getImportedPage(new PdfReader(bos[i].toByteArray()), j);
copy.addPage(importPage);
}
doc.close();
stamp.setFormFlattening(true);
stamp.close();
}
//创建并打开一个pdf对象
Document doc = new Document();
PdfCopy pdfCopy = new PdfCopy(doc, outputStream);
doc.open();
for (int i = 0; i < PDF_NUM ; i++) {
PdfImportedPage page = pdfCopy.getImportedPage(new PdfReader(bos[i].toByteArray()), i + 1);
pdfCopy.addPage(page);
}
//关闭
doc.close();
}
public static byte[] generateImages(String option) throws Exception {
BASE64Decoder decoder = new BASE64Decoder();
// 根据option参数
// 生成option字符串
String base64 = EchartsUtil.generateEchartsBase64(option);
// 解密
byte[] b = decoder.decodeBuffer(base64);
for (int i = 0; i < b.length; ++i) {
if (b[i] < 0) {
b[i] += 256;
}
}
return b;
}
public static void generateImage(String base64, String path) throws IOException {
BASE64Decoder decoder = new BASE64Decoder();
try (OutputStream out = new FileOutputStream(path)){
// 解密
byte[] b = decoder.decodeBuffer(base64);
for (int i = 0; i < b.length; ++i) {
if (b[i] < 0) {
b[i] += 256;
}
}
out.write(b);
out.flush();
}
}
/**
* 设置图片
* @param stamp
* @param from
* @param name
* @param url
* @throws Exception
*/
public void setImg(PdfStamper stamp, AcroFields from, String name,String url) throws Exception {
//添加图片
PdfContentByte cb = stamp.getOverContent(1);
//添加logo
Rectangle logo = from.getFieldPositions(name).get(0).position;
Image logoImage = Image.getInstance(url);
//根据域的大小缩放图片,我这里宽度在原有的域基础上加了100,你们可以自己调节
logoImage.scaleToFit(logo.getWidth() + 100, logo.getHeight());
logoImage.setAlignment(Image.MIDDLE);
logoImage.setAbsolutePosition(logo.getLeft(), logo.getBottom());
cb.addImage(logoImage);
}
/**
* 柱状图
* 使用 {@link Bar} 构建
*/
public static String getBar() {
// All methods in ECharts Java supports method chaining
Bar bar = new Bar()
.setLegend()
.setTooltip("item")
.addXAxis(new String[]{"Matcha Latte", "Milk Tea", "Cheese Cocoa", "Walnut Brownie"})
.addYAxis()
.addSeries("2020", new Number[]{43.3, 83.1, 86.4, 72.4})
.addSeries("2021", new Number[]{85.8, 73.4, 65.2, 53.9})
.addSeries("2022", new Number[]{93.7, 55.1, 82.5, 39.1});
Engine engine = new Engine();
// The render method will generate our EChart into a HTML file saved locally in the current directory.
// The name of the HTML can also be set by the first parameter of the function.
return engine.renderJsonOption(bar);
}
/**
* 折线图
* ECharts中,一切图表皆Option,使用 {@link Line} 构建
*/
public static String getLine1() {
// All methods in ECharts Java supports method chaining
Line line = new Line()
.setLegend()
.setTooltip("item")
.addXAxis(new String[]{"Matcha Latte", "Milk Tea", "Cheese Cocoa", "Walnut Brownie"})
.addYAxis()
.addSeries("2020", new Number[]{43.3, 83.1, 86.4, 72.4})
.addSeries("2021", new Number[]{85.8, 73.4, 65.2, 53.9})
.addSeries("2022", new Number[]{93.7, 55.1, 82.5, 39.1});
Engine engine = new Engine();
// The render method will generate our EChart into a HTML file saved locally in the current directory.
// The name of the HTML can also be set by the first parameter of the function.
// json 格式数据
return engine.renderJsonOption(line);
}
/**
* 折线图
* ECharts中,一切图表皆Option,使用 {@link Option} 构建
*/
public static String getLine2() {
Option option = new Option();
option.setLegend(new Legend().setData(new String[]{"2020", "2021", "2022"}));
option.setTooltip(new Tooltip().setTrigger("item"));
option.setXAxis(new CategoryAxis().setData(new String[]{"Matcha Latte", "Milk Tea", "Cheese Cocoa", "Walnut " +
"Brownie"}));
option.setYAxis(new ValueAxis().setType("value"));
LineSeries lineSeries1 = new LineSeries().setName("2020").setData(new Number[]{43.3, 83.1, 86.4, 72.4});
LineSeries lineSeries2 = new LineSeries().setName("2021").setData(new Number[]{85.8, 73.4, 65.2, 53.9});
LineSeries lineSeries3 = new LineSeries().setName("2022").setData(new Number[]{93.7, 55.1, 82.5, 39.1});
option.setSeries(new SeriesOption[]{lineSeries1, lineSeries2, lineSeries3});
Engine engine = new Engine();
return engine.renderJsonOption(option);
}
/**
* 饼图
* 使用 {@link Pie} 构建
* ref <a href="https://echarts.apache.org/examples/zh/editor.html?c=pie-simple">...</a>
*/
public static String getPie() {
List<PieData> list = new ArrayList<>();
list.add(new PieData("直接访问", 335));
list.add(new PieData("邮件营销", 310));
list.add(new PieData("联盟广告", 234));
list.add(new PieData("视频广告", 135));
list.add(new PieData("搜索引擎", 1548));
PieSeries pieSeries = new PieSeries()
.setName("访问来源")
.setType("pie")
.setRadius("55%")
.setData(list);
Pie pie = new Pie()
.setTitle(new Title().setText("Referer of a Website").setSubtext("Fake Data").setLeft("center"))
.setLegend(new Legend().setOrient("vertical").setLeft("left"))
.setTooltip("item")
.addSeries(pieSeries);
Engine engine = new Engine();
// The render method will generate our EChart into a HTML file saved locally in the current directory.
// The name of the HTML can also be set by the first parameter of the function.
// json 格式数据
return engine.renderJsonOption(pie);
}
/**
* 玫瑰图
* 使用 {@link Pie} 构建
* ref <a href="https://echarts.apache.org/examples/zh/editor.html?c=pie-simple">...</a>
*/
public static String getPe() {
List<PieData> list = new ArrayList<>();
list.add(new PieData("直接访问", 335));
list.add(new PieData("邮件营销", 310));
list.add(new PieData("联盟广告", 234));
list.add(new PieData("视频广告", 135));
list.add(new PieData("搜索引擎", 1548));
Number[] ints = new Number[2];
ints[0] = 50;
ints[1] = 250;
String[] str = new String[2];
str[0] = "50%";
str[1] = "50%";
PieSeries pieSeries = new PieSeries()
.setName("Nightingale Chart")
.setType("pie")
.setRadius(ints)
.setCenter(str)
.setRoseType("area")
.setData(list);
Pie pie = new Pie()
.setTitle(new Title().setText("Referer of a Website").setSubtext("Fake Data").setLeft("center"))
.setLegend(new Legend().setOrient("vertical").setLeft("left"))
.setTooltip("item")
.addSeries(pieSeries);
Engine engine = new Engine();
ChartMeta chartMeta = new ChartMeta("200", "200", engine.renderJsonOption(pie));
// The render method will generate our EChart into a HTML file saved locally in the current directory.
// The name of the HTML can also be set by the first parameter of the function.
// engine.render("demo-pie.html", pie);
// json 格式数据
return engine.renderJsonOption(pie);
}
/**
* 饼图数据
*/
static class PieData {
private String name;
private Integer value;
public PieData(String name, Integer value) {
this.name = name;
this.value = value;
}
}
}
import com.pdf.pdfexport.util.CreatePdfUtil;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController()
@RequestMapping("/export")
public class controller {
@GetMapping()
public void export(HttpServletResponse httpServletRequest){
HashMap<String, Object> textMap = new HashMap<>();
textMap.put("deptName","测试部门名称");
textMap.put("time","2023-06");
textMap.put("createTime","2023-08-26");
HashMap<String, Object> imageMap = new HashMap<>();
imageMap.put("pie",CreatePdfUtil.getPie());
List<String> list = new ArrayList<>();
list.add("标题");
list.add("日期");
list.add("金额");
list.add("备注");
List<String> list2 = new ArrayList<>();
list2.add("标题");
list2.add("2021-08-27");
list2.add("100000");
list2.add("");
List<String> list3 = new ArrayList<>();
list3.add("标题22");
list3.add("2021-08-29");
list3.add("200000");
list3.add("备注");
List<List<String>> List = new ArrayList<>();
List.add(list);
List.add(list2);
List.add(list3);
Map<String, List<List<String>>> listMap = new HashMap<>();
// 这里的listMap中key要和模板中的域名对应
listMap.put("table", List);
Map<String, Object> map = new HashMap<>();
map.put("table", listMap);
map.put("text", textMap);
map.put("image", imageMap);
try {
CreatePdfUtil.createPdf(httpServletRequest,map);
} catch (Exception e) {
e.printStackTrace();
}
}
}
三。运行项目
访问
http://localhost:8080/export
观察导出的模板
相当完美!!教程结束!!完结撒花🌹🌹🌹🌹🌹