文档操作

Word模板替换

引入依赖

使用Easypoi,文档地址:https://www.yuque.com/guomingde/easypoi

模板是处理复杂Excel或Word的简单方法,EasyPoi支持各种指令,最主要的就是各种fe的用法,整体风格和el表达式类似。
采用的写法是{{}}代表表达式,然后根据表达式里面的数据取值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<!--   以下依赖用于word模板导出 | word to pdf | pdf to word 
<!-- word导出 方式:easypoi-->
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-base</artifactId>
<version>4.4.0</version>
</dependency>
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-web</artifactId>
<version>4.4.0</version>
</dependency>
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-annotation</artifactId>
<version>4.4.0</version>
</dependency>
<!--注意:word中要使用循环等标签必须单独导入以下依赖-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>ooxml-schemas</artifactId>
<version>1.4</version>
</dependency>
<!--pdf-->
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j-export-fo</artifactId>
<version>6.1.0</version>
<!--因为项目中slf4j版本冲突,忽悠这个依赖中的slf4j版本-->
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 图片处理 -->
<dependency>
<groupId>com.github.jai-imageio</groupId>
<artifactId>jai-imageio-jpeg2000</artifactId>
<version>1.3.0</version>
</dependency>

Word模板工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
/**
* @ClassName WordUtil
* @Description 描述:easypoi导出工具类
**/
@Component
public class WordUtil {

@Value(value = "${file.path}")
private String uploadpath; //从yml文件读取文件下载路径

@Autowired
private ResourceLoader resourceLoader;

public String exportPdf(WordTemplateEnum templateType, Map<String, Object> params) throws IOException {
String temPath = templateType.getPath();
String absolutePath = resourceLoader.getResource("classpath:" + temPath).getFile().getAbsolutePath();
return exportPdf(absolutePath, params);
}

/**
* 导出PDF
*
* @param templatePath word模板地址
* @param params 替换的参数
* @return pdf的完全路径
*/
public String exportPdf(String templatePath, Map<String, Object> params) {
// 生成的wold文档文件名
String woldFileName = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + ".docx";
//保存的文件路径名
String saveDir = uploadpath + File.separator + "pdf";
// 生成的pdf文件名
String pdfFileName = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + ".pdf";
// 导出wold文档 返回值为生成的wold文档全路径
String word = exportWord(templatePath, saveDir, woldFileName, params);
Assert.notNull(word, "word路径不能为空");

// 自定义生成的pdf全路径
String pdfPath = saveDir + File.separator + pdfFileName;

// convertDocx2Pdf(word, pdfPath);
WordToPdfUtil.word2Pdf(word, pdfPath); // word转pdf
return pdfPath;
}


/**
* 导出word
* 模版变量中变量格式:{{foo}}
*
* @param templatePath word模板地址
* @param saveDir word文档保存的路径
* @param fileName 文件名
* @param params 替换的参数
*/
public String exportWord(String templatePath, String saveDir, String fileName, Map<String, Object> params) {
Assert.notNull(templatePath, "模板路径不能为空");
Assert.notNull(saveDir, "临时文件路径不能为空");
Assert.notNull(fileName, "导出文件名不能为空");
Assert.isTrue(fileName.endsWith(".docx"), "word导出请使用docx格式");
if (!saveDir.endsWith("/")) {
saveDir = saveDir + File.separator;
}

File dir = new File(saveDir);
if (!dir.exists()) {
dir.mkdirs();
}
String savePath = saveDir + fileName;

try {
XWPFDocument doc = WordExportUtil.exportWord07(templatePath, params);
FileOutputStream fos = new FileOutputStream(savePath);
doc.write(fos);
fos.flush();
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
return savePath;
}
}

Word转PDF

引入依赖

主要引入本地的jar包,使用的是Aspose.pdfAspose.words,Aspose为商用依赖,需要证书以及破解才能完美使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!-- word转pdf-->
<dependency>
<groupId>com.aspose-pdf-cracked</groupId>
<artifactId>aspose-pdf-cracked</artifactId>
<scope>system</scope>
<version>1.0</version>
<systemPath>${basedir}/lib/aspose-pdf-22.4.cracked.jar</systemPath>
</dependency>
<dependency>
<groupId>com.aspose-pdf</groupId>
<artifactId>aspose-pdf</artifactId>
<scope>system</scope>
<version>1.0</version>
<systemPath>${basedir}/lib/aspose-pdf-22.4.jar</systemPath>
</dependency>
<dependency>
<groupId>com.aspose.words</groupId>
<artifactId>aspose-words</artifactId>
<version>15.8.0</version>
<scope>system</scope>
<systemPath>${basedir}/lib/aspose-words-15.8.0-jdk16.jar</systemPath>
</dependency>

<!-- https://mvnrepository.com/artifact/org.apache.pdfbox/fontbox -->
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>fontbox</artifactId>
<version>2.0.9</version>
</dependency>

Word转PDF工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class WordToPdfUtil {

/**
* 使用Aspose.Words库将Word文档转换为PDF文件。
* 需要在代码中添加Aspose.Words的license。
* @param wordPath
* @param pdfPath
*/
public static void word2Pdf(String wordPath, String pdfPath){
FileOutputStream os =null;
try {
String s = "<License><Data><Products><Product>Aspose.Total for Java</Product><Product>Aspose.Words for Java</Product></Products><EditionType>Enterprise</EditionType><SubscriptionExpiry>20991231</SubscriptionExpiry><LicenseExpiry>20991231</LicenseExpiry><SerialNumber>8bfe198c-7f0c-4ef8-8ff0-acc3237bf0d7</SerialNumber></Data><Signature>sNLLKGMUdF0r8O1kKilWAGdgfs2BvJb/2Xp8p5iuDVfZXmhppo+d0Ran1P9TKdjV4ABwAgKXxJ3jcQTqE/2IRfqwnPf8itN8aFZlV3TJPYeD3yWE7IT55Gz6EijUpC7aKeoohTb4w2fpox58wWoF3SNp6sK6jDfiAUGEHYJ9pjU=</Signature></License>";
ByteArrayInputStream is = new ByteArrayInputStream(s.getBytes());
License license = new License();
license.setLicense(is);

File file = new File(pdfPath); // 新建一个空白pdf文档
os = new FileOutputStream(file);
Document doc = new Document(wordPath); // Address是将要被转化的word文档
doc.save(os, SaveFormat.PDF);
} catch (Exception e) {
e.printStackTrace();
}finally{
if(os != null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

Echarts后台生成

引入依赖

大佬封装的一套后台是直接使用Echarts的工具,官网文档:https://echarts.icepear.org/#/zh-cn/quick-start

1
2
3
4
5
6
7
8
9
10
11
12
<!--Echarts-->
<dependency>
<groupId>org.icepear.echarts</groupId>
<artifactId>echarts-java</artifactId>
<version>1.0.7</version>
<exclusions>
<exclusion>
<artifactId>slf4j-simple</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class FirstTest {
public static void main(String[] args) {
// test01();
test02();
}

private static void test02() {
// 折线图
Line lineChart = new Line()
.addXAxis(new CategoryAxis()
.setData(new String[] { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" })
.setBoundaryGap(false))
.addYAxis()
.addSeries(new LineSeries()
.setData(new Number[] { 820, 932, 901, 934, 1290, 1330, 1320 })
.setAreaStyle(new LineAreaStyle()));

Option option = lineChart.getOption(); // 获取Option对象,封装了大量方法,可以直接设置
option.setAnimation(false); // 取消动画效果
Engine engine = new Engine();
String jsonStr = engine.renderJsonOption(lineChart); // 转json
String htmlStr = engine.renderHtml(option); // 转html
}


private static void test01() {
// 柱状图
Bar bar = new Bar()
.setLegend()
.setTooltip("item")
.addXAxis(new String[] { "Matcha Latte", "Milk Tea", "Cheese Cocoa", "Walnut Brownie" })
.addYAxis()
.addSeries("2015", new Number[] { 43.3, 83.1, 86.4, 72.4 })
.addSeries("2016", new Number[] { 85.8, 73.4, 65.2, 53.9 })
.addSeries("2017", new Number[] { 93.7, 55.1, 82.5, 39.1 });
Engine engine = new Engine();
bar.getOption().setAnimation(false); // 取消动画
String str = engine.renderHtml(bar); // 生成html字符串
engine.render("index.html", bar); // 直接生成html
}
}

Freemarker模板使用

引入依赖

参考博客:https://blog.csdn.net/hyc123123123123/article/details/133886078

1
2
3
4
5
6
<!--freemarker-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
<version>2.5.1</version>
</dependency>

Freemarker工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class FreemarkerTool {
// 日志工厂
private static final Logger log = LoggerFactory.getLogger(FreemarkerTool.class);

/**
* 根据模板,利用提供的数据,生成文件
*
* @param sourceFile 模板文件名
* @param data 模版数据
* @param destFile 最终生成的文件,需要携带路径
*/
public static void data2html(String sourceFile, Map<String, Object> data, String destFile) throws IOException, TemplateException {

// 如果文件夹不存在 则创建
File destFolder = new File(destFile).getParentFile();
if (!destFolder.exists()) {
destFolder.mkdirs();
}

Writer out = null;
try {
out = new FileWriter(destFile);
Configuration cfg = new Configuration(Configuration.VERSION_2_3_29);
// 模板文件所在位置目录
cfg.setDirectoryForTemplateLoading(new File("C:/Users/Lenovo/Desktop/export/template"));
Template template = cfg.getTemplate(sourceFile);
template.process(data, out);
} catch (Exception e) {
log.error("模板生成报告html文件异常", e);
throw e;
} finally {
try {
if (out != null) {
out.flush();
out.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

Echarts模板

新建文件echart-test.ftl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<html>

<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>ECharts Demo</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/echarts/5.2.2/echarts.min.js"
integrity="sha512-ivdGNkeO+FTZH5ZoVC4gS4ovGSiWc+6v60/hvHkccaMN2BXchfKdvEZtviy5L4xSpF8NPsfS0EVNSGf+EsUdxA=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<style>
body {
margin: 0;
display: flex;
flex-direction: row;
justify-content: center;
}
#display-container {
width: 600px;
height: 600px;
border: 2px solid black;
}
</style>
</head>

<body>
<div id="container">

<div id="display-container">
</div>
</div>

<script type="text/javascript">

var chart = echarts.init(document.getElementById("display-container"));
var option = {
"animation": false,

"xAxis": {
"type": "category",
"axisTick": {
"alignWithLabel": true
},
"data": ${xAxisData!'[]'}
},
"yAxis": {
"type": "value"
},
"tooltip": {
"axisPointer": {
"type": "shadow"
},
"trigger": "axis"
},
"series": [
{
"type": "bar",
"name": "Direct",
"data": ${yAxisData!'[]'},
"barWidth": "60%"
}
]
}
chart.setOption(option);
</script>
</body>

</html>

使用

使用数据替换模板中的标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class FreemarkerTest {
public static void main(String[] args) throws IOException, TemplateException {
// 文件名
String sourceFile = "echart-test.ftl";
// 渲染存储数据
Map<String, Object> datas = new HashMap<String, Object>();
List<String> xAxisData = ListUtil.of("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun");
datas.put("xAxisData", JSON.toJSONString(xAxisData));
List<Integer> yAxisData = ListUtil.of(10, 52, 200, 334, 390, 330, 220);
datas.put("yAxisData", JSON.toJSONString(yAxisData));
//最终生成的文件路径
String destFile = "C:/Users/Lenovo/Desktop/export/echart-test-" + System.currentTimeMillis() + ".html";

FreemarkerTool.data2html(sourceFile, datas, destFile);

}
}

Html转图片

下载wkhtmltoimage

wkhtmltopdf,wkhtmltoimage是开源命令行工具,可以将html转为pdf或者image。

官网:https://wkhtmltopdf.org/downloads.html

原理:wkhtmltopdf&wkhtmltoimage 内嵌了一个QT浏览器,其原理是会使用该内嵌的浏览器打开html文件或链接,然后对该网页进行截图处理。

注意:导出的图片或pdf空白由于wkhtmltopdf&wkhtmltoimage 0.12.6 最新版发布于 2020-7-11, 其使用的QT浏览器由于版本比较旧,可能会无法识别较新版本的JavaScript语法,比如我们使用的Echarts组件,需要更改Style样式,以及通过Option取消Echarts的动画。

Html转图片工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class HtmlToImageUtil {

// wkhtmltoimage.exe在系统中的路径,一般在安装目录的bin下
private static String TO_IMG_TOOL = "D:/DevTools/wkhtmltopdf/bin/wkhtmltoimage.exe";

public static boolean htmlToImg(String srcPath, String destPath, Integer width) {
File file = new File(destPath);
File parent = file.getParentFile();
// 如果pdf保存路径不存在,则创建路径
if (!parent.exists()) {
parent.mkdirs();
}
StringBuilder cmd = new StringBuilder();
cmd.append(TO_IMG_TOOL);
cmd.append(" ");
// 1.--format.\<格式》:指定输出图像的格式。可以是PNG、JPEG、BMP等,默认为PNG。
cmd.append(" --format png ");
// 2 . –quality 75:就表示生成图片的质量为原来的 75%!
cmd.append(" --quality 75 ");

// 3 --width \<宽度\>:设置输出图像的宽度。可以使用像素(如800px)或其他单位(如cm、mm等)指定,默认为 1024像素。
if (width != null) {
cmd.append(" --width ");
cmd.append(width);
}

cmd.append(" ");
cmd.append(srcPath);
cmd.append(" ");
cmd.append(destPath);

boolean result = true;
try {
Process proc = Runtime.getRuntime().exec(cmd.toString());
HtmlToPdfThread error = new HtmlToPdfThread(proc.getErrorStream());
HtmlToPdfThread output = new HtmlToPdfThread(proc.getInputStream());
error.start();
output.start();
proc.waitFor();
} catch (Exception e) {
result = false;
e.printStackTrace();
}
return result;
}
}

最终需求

报表功能

定时生成报表,比如日报,周报,月报等数据。

  • 查询数据库,将数据通过Word模板展示。
    • 使用Eeasypoi,可以将数据和图片插入到Word模板中。
  • 统计分析,将统计分析的数据生成Echarts图表,并插入到Word中。
    • 使用echarts-java,将统计分析的数据生成图表Html文件。
    • 或者使用Freemarker,通过模板生成图标Html文件。
    • 使用wkhtmltoimage,将Html文件转为图片。
  • 预览,将Word转成PDF,以便浏览器预览。
    • 使用Aspose.pdf,将Word转为PDF文档。