Java SpringBoot项目配置postgreSQL多数据源

1、依赖

这里只写多数据源的依赖,其他的postgresql、mybatis-plus等依赖,都在现在的项目中有

1
2
3
4
5
6
<dependency>    
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.xx</version>
<scope>compile</scope>
</dependency>

2、配置

ps:公司项目中,配置都写在主包的datasource中
配置多数据源
增加类,这里配置了两个数据源的样例

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
@Configuration
public class MultipleDataSourceConfig {

@Bean("master")
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource createMasterDataSource(){
return new DruidDataSource();
}

@Bean("slave1")
@ConfigurationProperties(prefix = "spring.datasource.slave1")
public DataSource createSlave1DataSource(){
return new DruidDataSource();
}

/**
* 设置动态数据源,通过@Primary 来确定主DataSource
* @return
*/
@Bean
@Primary
public DataSource createDynamicDataSource(@Qualifier("master") DataSource master, @Qualifier("slave1") DataSource slave1){
DynamicDataSource dynamicDataSource = new DynamicDataSource();
//设置默认数据源
dynamicDataSource.setDefaultTargetDataSource(master);
//配置多数据源
Map<Object, Object> map = new HashMap<>();
map.put("master",master);
map.put("slave1",slave1);
dynamicDataSource.setTargetDataSources(map);
return dynamicDataSource;
}
}

重写determineCurrentLookupKey() 方法

1
2
3
4
5
6
7
8
9
10
public class DynamicDataSource extends AbstractRoutingDataSource {

Logger logger = LoggerFactory.getLogger(DynamicDataSource.class);

@Override
protected Object determineCurrentLookupKey() {
logger.info("------------------当前数据源 {}", DynamicDataSourceSwitcher.getDataSource());
return DynamicDataSourceSwitcher.getDataSource();
}
}

AbstractRoutingDataSource 是spring jdbc提供的操作读数据源的抽象类,重写determineCurrentLookupKey() 指定获得当前数据源

实现操作数据源的类

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
public class DynamicDataSourceSwitcher {

static Logger logger = LoggerFactory.getLogger(DynamicDataSourceSwitcher.class);

public static final String Mater = "master";
public static final String Slave1 = "slave1";

private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

public static void setDataSource(String name){
logger.info("-------- 设置数据源数据源为 :{} ", name);
contextHolder.set(name);
}

public static String getDataSource(){
if (StringUtils.isEmpty(contextHolder.get())) {
setDataSource(Mater);
}
return contextHolder.get();
}

public static void cleanDataSource(){
contextHolder.remove();
}
}

实现注解使用数据源

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
@Retention(RetentionPolicy.RUNTIME)
@Target({
ElementType.METHOD
})
public @interface MyDataSource {
String value() default "master";
}

@Aspect
@Component
@Order(1)
public class DynamicDataSourceAspect {
private Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);

/**
* 切入点只对@Service注解的类上的@DataSource方法生效
* @param myDataSource
*/
@Pointcut(value="@within(org.springframework.stereotype.Service) && @annotation(myDataSource)" )
public void dynamicDataSourcePointCut(MyDataSource myDataSource){}

@Before(value = "dynamicDataSourcePointCut(myDataSource)")
public void switchDataSource(MyDataSource myDataSource) {
DynamicDataSourceSwitcher.setDataSource(myDataSource.value());
}

/**
* 切点执行完后 切换成主数据库
* @param myDataSource
*/
@After(value="dynamicDataSourcePointCut(myDataSource)")
public void after(MyDataSource myDataSource){
DynamicDataSourceSwitcher.cleanDataSource();
}
}

3、配置postgresql数据库地址

通过端口号区分两个数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
spring:
profiles: multi
datasource:
master:
url: jdbc:postgresql://127.0.0.1:5634/postgres
username: postgre
password: 123456
driver-class-name: org.postgresql.Driver
type: com.alibaba.druid.pool.DruidDataSource
name: master
initialize: true
filters: stat
slave1:
url: jdbc:postgresql://127.0.0.1:6064/postgres
username: postgre
password: 123456
driver-class-name: org.postgresql.Driver
type: com.alibaba.druid.pool.DruidDataSource
name: slave1
initialize: true
filters: stat

4、使用多数据源

在实现类方法上加注解

@MyDataSource(value = DynamicDataSourceSwitcher.Slave1)

Java任意类型文件上传通用接口

1、准备初始化一个Maven项目

2、添加配置

在bootstrap.yml

配置文件上传最大值

1
2
3
4
5
6
7
8
spring:
application:
name: commonApi
servlet:
multipart:
enabled: true
max-file-size: 64MB # 配置multipart上传单个文件最大位数
max-request-size: 640MB

3、创建Controller类

定义文件上传接口

接口参数说明:
area和dataType属于控制参数,可以控制上传的文件保存到服务器的某个目录。

directory参数是在上面参数配置的目录下创建新的子目录,比如数据是定时上传的,我们可以按时间创建一个子目录,按日期分组创建文件夹。

fileBinary参数是我们上传的文件流数据,包含了文件名称等信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@ApiOperation(value = "通用文件上传接口", notes = "接口根据tdd配置的路径来查询要上传的地址,tdd里的配置路径会截取第一个$以前的内容并拼接上directory", response = Result.class, httpMethod = "POST")
@ApiImplicitParams({
@ApiImplicitParam(name = "area", value = "区域(common)", paramType = "query", dataType = "String", required = true),
@ApiImplicitParam(name = "dataType", value = "数据类型(pdfoutaddr)", paramType = "query", dataType = "String", required = true),
@ApiImplicitParam(name = "directory", value = "这个参数是要拼接到tdd配置的路径后面,指的是子目录", paramType = "query", dataType = "String", required = true),
@ApiImplicitParam(name = "fileBinary", value = "要上传的文件二进制流数据()", paramType = "query", dataType = "String", required = true)
})
@ApiOperationSupport(author = "庞欢腾")
@PostMapping("/uploading")
public Result<Boolean> uploading(
@RequestParam("fileBinary") MultipartFile fileBinary,
@RequestParam("area") String area,
@RequestParam("dataType") String dataType,
@RequestParam("directory") String directory) {
return wordToHtmlService.uploading(fileBinary, area, dataType, directory);
}

4、定义接口类并实现接口方法

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
@Override
public Result<Boolean> uploading(MultipartFile fileBinary, String area, String dataType, String directory) {

TDealdataDict tdd = tddRedisUtil.getTdd(area, dataType);
if (tdd == null) {
log.info("没有找到这个配置 area:" + area + " dataType:" + dataType);
return Result.failed("没有找到这个配置 area:" + area + " dataType:" + dataType);
}
// 读取上传路径
String filePath = tdd.getOrgPath();

if (fileBinary.isEmpty()) {
log.info("上传的文件为空!");
return Result.failed("上传的文件为空!");
}

if (OSNameUtil.isLinux()) {
if (!filePath.endsWith("/")) {
filePath += "/";
}
} else {
if (filePath.endsWith("\\")) {
filePath += "\\";
}
}

filePath = filePath + directory;

File exisfile = new File(filePath);
// 检测是否存在目录
if (!exisfile.exists()) {
exisfile.mkdirs();
}

// 文件名
String fileName = fileBinary.getOriginalFilename();
// 文件上传后的路径
File dest = new File(filePath + fileName);
log.info("文件路径为:"+filePath + fileName);
try {
fileBinary.transferTo(dest);
} catch (Exception e) {
log.info("上传异常!", e);
return Result.success(false);
}
return Result.success(true);
}

二、气象数据来源

基本概念
目前公司主要做气象数据的展示,气象数据,是指表示天气信息的数据,包含温度、空气湿度、降水量等天气信息。
一般每种气象数据,都是多种天气要素的合集。
分类
目前公司常用的气象数据有以下几类:

(1)站点类(地上的)

img

自动站数据闪电定位环境站数据

(2)雷达数据(白球)

img

(3)卫星数据(地球外的)

img

风云4卫星葵花8卫星

(4)数值模式

所谓数值天气预报模式,就是使用大型计算集群求解离散化的大气运动原始方程组,为方程组输入基于观测的预报初始场和边界场,并添加各种微尺度物理过程参数化方案,再利用观测数据进行同化和订正,最终得到随时间演化的预报场数据。

ec数据

(5) 同化数据

数据同化就是利用一系列约束条件将观测信息加到模式中,更改模式的初始状态和观测更为接近 (即尽可能接近真实大气状态的真实状态),来达到更好的预报效果。

一、气象基础知识(经纬度、时间、高度层)

(1)经纬度

用作标记地理位置信息,比如北京的经纬度(116°20′,39°56′),通常我们用到的经纬都是投影后的经纬度。

气象数据常用的经纬度信息包括单点型和范围型
单点型,表示一个点位的经纬度;范围型,表示一个矩形范围的经纬度;

(2)起报时间、预报时间、观测时间、预报数据、观测数据

观测数据:已经发生时间的天气记录,比如现在09时,由于数据传输、延迟等,可以拿到最新的观测数据是08时,当然也可以拿到08时之前的,每个时次的观测数据只记录当前发生的;
预报数据:在固定时间点预报未来一段时间天气状况,这个固定时间称为起报时间,通常是每日08时和20时,一个起报时间产生多个预报数据。比如现在1日22时,拿到最新起报时间就是20时的,假设预报时效3天、每小时一个,那么可以拿到1日20时–3月20时的72个预报数据;

注意:调用观测时间接口,后端返回的是最新观测时间;
只有预报数据才会有起报时间和预报时间,调用预报时间接口,后端一般返回的是最新的起报时间;
不同数据的起报时间和预报间隔是不一样的,比如EC数值模式8时、20时出,每小时一个,预报三天就是72个数据,而雷达6分钟就更新起报时间,每6分钟一个,预报2个小时大约20个数据;

(3)世界时、北京时

世界时:UTC 、格林尼治时间、世界标准时间
北京时:BTC、东八区时间
BTC = UTC + 8
一般来说,原始气象数据时间都是UTC(个别例外),后端处理返回的也是UTC,而前端网页展示需要用BTC
例如:后端返回202102120000,前端需展示为 2021-02-12 08:00 、2021/02/12 08:00

(4)高度层

一般气象数据,除了在经纬度范围上的一个范围性数据,还可能包含高度上的一个信息。间隔一定高度算做一层,经纬度范围数据是二维数据,多个高度层的经纬度二维数据叠加,就可以组成三维数据。
高度层分割一般包括两种:
气压层,按照一定气压间隔,设置一个一个的高度层,比如数值模式数据距离,按照一定距离间隔,设置一个一个高度层,比如雷达数据

Java基于Spire实现的word文档制作

Java基于Spire实现的word文档制作(提升10倍效率)

功能概述:

定义一个word模板文件,通过Java程序对模板填充数据,生成出新的word文档。

1、创建maven项目

推荐使用idea

2、添加pom依赖配置

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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<artifactId>api</artifactId>
<groupId>com.xxxx.server</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>word-api</artifactId>
<packaging>pom</packaging>
<name>word-api</name>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<repositories>
<repository>
<id>com.e-iceblue</id>
<name>e-iceblue</name>
<url>https://repo.e-iceblue.cn/repository/maven-public/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>e-iceblue</groupId>
<artifactId>spire.doc.free</artifactId>
<version>5.2.0</version>
</dependency>
<!--<dependency>
<groupId>e-iceblue</groupId>
<artifactId>spire.doc</artifactId>
<version>5.4.2</version>
</dependency>-->
<dependency>
<groupId>e-iceblue</groupId>
<artifactId>spire.pdf.free</artifactId>
<version>5.1.0</version>
</dependency>
</dependencies>
</project>

3、接口类开发

生成文档的接口makeWordNimble是一个固定参数的通用接口。

参数的分类为:

​ \1. 基础参数 2.文档内部文本类数据 3.文档内部图片类数据

文档内部数据使用的是Map类型字段接收前端传来的参数,程序根据Map参数的key来定位替换doc模板里的占位符。

图片数据是通过另一个图片上传接口先上传图片,然后生成文档接口里只需要传递上传到服务器上的图片名称。

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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
package com.hxkj.server.controller;

import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.hxkj.server.common.core.result.Result;
import com.hxkj.server.common.core.utils.TddRedisUtil;
import com.hxkj.server.entity.GeneraterWordData;
import com.hxkj.server.service.GeneraterWordService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* 要尊重编程
*
* @Author: 庞欢腾
* @Date: 2022/5/13
* @Description: data-server
*/
@Api(tags = "生成word文档接口")
@RestController
@RequestMapping("/generater-word")
public class GeneraterWordController {

GeneraterWordService generaterWordService;

@Autowired
public void setGeneraterWordService(GeneraterWordService generaterWordService) {
this.generaterWordService = generaterWordService;
}

@ApiOperation(value = "生成word文档接口", notes = "生成word文档接口", response = Result.class, httpMethod = "POST")
@ApiImplicitParams({
@ApiImplicitParam(name = "txt_data", value = "文本数据", paramType = "query", dataType = "Map", required = true),
@ApiImplicitParam(name = "image_data", value = "图像数据", paramType = "query", dataType = "Map", required = true),
@ApiImplicitParam(name = "inTemplatePathName", value = "输入模板地址名称", paramType = "query", dataType = "String", required = true),
@ApiImplicitParam(name = "outputPathName", value = "输出word文件地址名称", paramType = "query", dataType = "String", required = true)
})
@ApiOperationSupport(author = "庞欢腾")
@PostMapping("/generaterWord")
public Result<String> generaterWord(@RequestBody GeneraterWordData generaterWordData) {
return generaterWordService.baseGeneraterWord(generaterWordData);
}


@ApiOperation(value = "一个非常灵活随意的生成word文档接口", notes = "生成word文档接口,当使用的模板不是一个的时候,使用这个接口。" +
"模板名称参数是一个用逗号分割的任意个模板名称", response = Result.class, httpMethod = "POST")
@ApiImplicitParams({
@ApiImplicitParam(name = "txt_data", value = "文本数据", paramType = "query", dataType = "Map", required = true),
@ApiImplicitParam(name = "image_data", value = "图像数据", paramType = "query", dataType = "Map", required = true),
@ApiImplicitParam(name = "inTemplatePathName", value = "输入模板地址名称,参数是一个用逗号分割的任意个模板名称", paramType = "query", dataType = "String", required = true),
@ApiImplicitParam(name = "outputPathName", value = "输出word文件地址名称", paramType = "query", dataType = "String", required = true)
})
@ApiOperationSupport(author = "庞欢腾")
@PostMapping("/generaterWordNimble")
public Result<String> generaterWordNimble(@RequestBody GeneraterWordData generaterWordData) {
return generaterWordService.baseGeneraterWordNimble(generaterWordData);
}


@ApiOperation(value = "通用文档生成接口", notes = "2022年08月04日 生成word文档接口,当使用的模板不是一个的时候,使用这个接口。" +
"模板名称参数是一个用逗号分割的任意个模板名称", response = Result.class, httpMethod = "POST")
@ApiOperationSupport(author = "庞欢腾")
@PostMapping("/makeWordNimble")
public Result<String> makeWordNimble(@RequestBody GeneraterWordData generaterWordData) {
return generaterWordService.baseMakeWordNimble(generaterWordData);
}

@ApiOperation(value = "通用文档生成接口", notes = "2022-08-17 生成word文档接口,当使用的模板不是一个的时候,使用这个接口。" +
"模板名称参数是一个用逗号分割的任意个模板名称,(第二代接口,当图片数据不在同一个目录的时候,图片名称参数前面要指定子目录,例如:tdd配置的路径+子目录/图片名称.png)", response = Result.class, httpMethod = "POST")
@ApiOperationSupport(author = "庞欢腾")
@PostMapping("/makeWordNimblePng")
public Result<String> makeWordNimblePng(@RequestBody GeneraterWordData generaterWordData) {
return generaterWordService.baseMakeWordNimblePng(generaterWordData);
}


/**
* 测试用例
*/
/*public static void main(String[] args) {
GeneraterWord generaterWord = new GeneraterWordImpl();
String inTemplatePathName = "E:\\a工作文件\\郑州\\2022\u200E年\u200E\\精细化平台开发工作\\预警信息制作模板\\开发模板\\郑州市气象台预警模板.doc";
Map<String, String> data = new HashMap<>();
// data.put("name", "郑州");
data.put("td0table0", "31日09时t_31日10时t_31日11时t_31日12时t_31日13时t_31日14时t_31日15时t_31日16时t_31日17时t_31日18时t_31日19时t_31日20时");
data.put("td1table0", "31日09时t_31日10时t_31日11时t_31日12时t_31日13时t_31日14时t_31日15时t_31日16时t_31日17时t_31日18时t_31日19时t_31日20时");
data.put("td2table0", "31日09时t_31日10时t_31日11时t_31日12时t_31日13时t_31日14时t_31日15时t_31日16时t_31日17时t_31日18时t_31日19时t_31日20时");

//加载示例文档
Document document = new Document();
// "C:\\Users\\Administrator\\Desktop\\input.docx"
document.loadFromFile(inTemplatePathName);
// 文本
// generaterWord.replaceText(data, document);

//表格
generaterWord.replaceTable(data, document);

//保存文档
document.saveToFile("E:\\a工作文件\\郑州\\2022\u200E年\u200E\\精细化平台开发工作\\预警信息制作模板\\开发模板\\ReplaceAllMatchedText.docx", FileFormat.Docx_2013);

}*/

/**
* 测试循环生成文档功能
* @param args
*/
/*public static void main(String[] args) {
String root = "D:\\data\\word\\制作文档\\";
String out = root + "生成后的文件.docx";
Map<String, String>[] txt_data = new Map[2];
txt_data[0] = new HashMap<>();
txt_data[0].put("name", "这是第一个文本");
txt_data[1] = new HashMap<>();
txt_data[1].put("name", "这是第二个文本");

Map<String, String>[] image_data = new Map[2];
image_data[0] = new HashMap<>();
image_data[0].put("img2", root + "img1.png");
image_data[1] = new HashMap<>();
image_data[1].put("img2", root + "img3.png");

GeneraterWord generaterWord = new GeneraterWordImpl();
Document document = generaterWord.generaterWord(root + "气象信息快报_近小时降水量2.doc");
generaterWord.loopReplaceWord(txt_data, image_data, document, out);

}*/

}

核心实现类

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
/**
* 图片没用自动拼接当前日期
* @param generaterWordData
* @return
*/
private GeneraterWordData StitchingPathNoPng(GeneraterWordData generaterWordData) {
String mouth = DateUtil.getMouth();
/**
* 1.读取配置路径
*/
String area = generaterWordData.getArea();
String dataType = generaterWordData.getDataType();
TDealdataDict tdd = tddRedisUtil.getTdd(area, dataType);
if (tdd == null) {
log.info("tdd配置未找到!" + area + " " + dataType);
return null;
}
String orgPath = tdd.getOrgPath();
if (!(orgPath.endsWith("/") || orgPath.endsWith("\\"))) {
orgPath += "/";
}

/**
* 2.图片数据需要拼接前路径
*/
Map<String, String> image_data = generaterWordData.getImage_data();
for (Map.Entry<String, String> entry : image_data.entrySet()) {
StringBuilder imgname = new StringBuilder();
String value = entry.getValue();
if (value.contains(",_")) {
String[] split = value.split(",_");
for (String s : split) {
imgname.append(orgPath).append(s).append(",_");
}
value = imgname.substring(0, imgname.length() - 2);
} else {
value = orgPath + value;
}
entry.setValue(value);
}
generaterWordData.setImage_data(image_data);

/**
* 3.输出文档需要拼接前路径
*/
String outputPathName = generaterWordData.getOutputPathName();
generaterWordData.setOutputPathName(orgPath + mouth + "/" + outputPathName);

/**
* 4.模板名称需要拼接前路径
*/
String templateName = generaterWordData.getTemplateName();
// 模板数据需要拼接前路径
String[] templates = templateName.split(",|,");
StringBuilder name = new StringBuilder();
for (String template : templates) {
name.append(orgPath).append(template).append(",");
}
templateName = name.substring(0, name.length() - 1);
generaterWordData.setTemplateName(templateName);

return generaterWordData;
}

4、制作模板文件

注意:word模板文件必须是.doc为后缀的文档

新建一个doc文档,直接在文档里编写模板

①编辑模板

一、文本占位符用“#”包裹,例如:#data#

二、图片站位就是,在word里添加一张图片,图片的名称就是接口里传参的key值

例如:图片名称是img1.png

image_data: {

​ “img1”:”气象预警icon_暴雪-橙.png”

}

img

制作好doc模板文件后,将模板文件上传到服务器指定目录

在数据库里需要定义一个路径配置字典表,用来存储word文档的生成输出路径和保存模板文件路径

5、文档制作需求分类

实际需求中往往遇到的需求是很复杂的

S类文档制作是一张完整的模板文件,填充固定个数的替换符号,生成出新的文档。

SS类文档制作是多段内容根据客户选择生成出结果文档,比如客户想要文档的A、B部分,那么生成出来的文档就不包含C部分。

SSS类文档制作是文档切片后,其中一片内容可能根据数据需要循环展示,例如B分片,后台数据有3条,这三条数据需要按照模板B的格式,循环渲染出来,文档输出的结果就是ABBBC

img

6、项目应用场景

img

码字不易,有问题请联系我 vx:yce99867066

Java使用freemarker制作word文档完整笔记

freemarker模板引擎

1、创建一个maven项目

2、添加pom.xml依赖

1
2
3
4
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
</dependency>

3、添加配置文件

方法1(推荐这种配置):

也可以在bootstrp.yml配置文件里添加配置

1
2
3
freemarker:
suffix: .ftl
template-loader-path: classpath:/staticftl

注意:需要注意缩进

img

方法2:

img

setTemplateLoaderPath 参数是指定我们定义好的模板存放目录

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.hxkj.server.api.haiyangserver.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import java.util.HashMap;
import java.util.Map;
/**
* @Author pht
* @Description word模板配置
* @Date 2021/6/24
**/
@Configuration
public class ApplicationConfig {
@Bean(name = "freeMarkerConfigurer")
public FreeMarkerConfigurer freeMarkerConfigurer() {
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
configurer.setDefaultEncoding("UTF-8");
configurer.setTemplateLoaderPath("classpath:/staticftl");
Map<String, Object> variables = new HashMap<>();
variables.put("xml_escape", "fmXmlEscape");
configurer.setFreemarkerVariables(variables);
return configurer;
}
}

4、模板制作

4.1 拿到原始模板文件

img

上图是一个word文件,框选的区域内容是需要程序动态填充的。然后需要整理模板格式,根据模板需要定义替换符关键字。

4.2 建立替换占位符号

创建一个txt文件

1
2
3
4
5
6
7
8
9
10
11
12
制作人
${producer}:悟空

制作日期
${cdate}:2021年11月22日

分析区域
${area1}:河北省(市)
${area2}:东海海域

分析月份
${month}:7—8

把对应需要动态替换值的位置填充上关键字,并把黄色的标注去掉

如果有图片,需要把图片插入并调整好位置。图片的替换关键字在转换成xml后做。

替换完成word文件里的占位符后,结果如下

img

模板文档中${}是占位符,即生成Word文档时占位符会被真实的数据替换。例如${name}在生成文档时会被name这个属性的值替换 ,${userObj.name}在生成文档时会被userObj这个对象的name属性的值替换。

4.3 将.doc文档另存为.xml文件

img

把word文档转成xml文件

另存为,选择xml格式

使用notepad++打开xml模板

首先格式化,需要添加xml插件

注意:另存为时选择Word2003XML文档(*.xml)

使用notepad++编辑工具打开这个xml文件

你会发现里面都是一些看不懂的乱码

此时不要慌

打开效果:

img

安装一个插件

img

使用这个插件对xml文件格式整理

img

整理完成以后变得清晰了许多

img

4.4 需要修订占位符

需要修订一下模板,用搜索Ctrl + f查询$字符,保证我们定义的占位符是完全挨着的${producer},如果看到替换关键字没有在一起,需要把中间的内容删除

img

img

4.5 拿到.xml模板后处理图片占位

配置图片的替换关键字,搜索image

把binaryData节点里面的字符串删除,改为${image1}

img

4.6 表格数据渲染配置

如果有表格

<#list list_table as list_table>

${list_table.t1} ${list_table.t2} ${list_table.t3} ${list_table.t4} ${list_table.t5}

</#list>

使用循环语句渲染比较方便。需要在<w:tr>标签的外层添加,如右图所示

<#list list_table as list_table>

</#list>

进行包裹语句块儿。

注意表格里的替换符要用#list里面定义的list_table,例如${list_table.val1}

${list_table.val2}

这里相当于从一个map对象里取出两个值。

img

所有关键字调整好后,保存,修改文件后缀为.ftl

然后放入项目对应目录下resources。模板编辑完成。

img

5、开发生成文档接口

程序主要分成3个部分

(1)准备要填充到模板里的数据

(2)把数据根据替换位置关键字打包到map对象里

(3)配置word输出位置,FreeMarker执行word生成

在Impl实现类里注入FreeMarkerConfigurer

1
2
3
4
5
FreeMarkerConfigurer freeMarkerConfigurer;
@Autowired
public void setFreeMarkerConfigurer(FreeMarkerConfigurer freeMarkerConfigurer) {
this.freeMarkerConfigurer = freeMarkerConfigurer;
}

填充模板的数据可以是前端传来的数据,也可以是从数据库获取的数据

打包map,如果有list就put一个map进去,map的key就是前面定义模板时设置的占位符。

img

图片,传递绝对地址,就可以获取图片可视化的二级支流,BASE64Encoder

img

开始生成word,需要准备的参数:

dataMap 填充模板的数据

ftlName 模板名称

docPath 输出地址+文件名

img

然后就ok了

下面是完整代码:

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
@Override
public Result getGeneratedLetters(WeatherInfoLetters weatherInfoLetters) {
// word文档输出的目录,上传的图片也在这个目录里
String wordoutPath = "D:/xxx/";
// 测试输出地址
// wordoutPath = "D:\\ideaproject\\data\\word\\";
// 配置使用的模板名称
String templetName = "气象信息快报.ftl";
/**
* 打包数据到map
*/
Map<String, Object> templetData = dataPackage(“传入需要打包的数据”);
// 生成word文档
// 输出的文档名称
String docName = wordoutPath + "气象信息快报.doc";
// 执行生成
Boolean process = process(templetData, templetName, docName);
if (!process) {
return Result.queryIsnull("生成报表文档失败!");
}


/**
* 下面你可以根据业务做任何事,比如:需要在线预览,可以转出一份pdf提供在线预览
* 把word文档转出pdf文件,pdf文件和word使用同一个输出地址wordoutPath
*/
String outpdfname = docName.substring(0, docName.lastIndexOf("."));
Result<String> pdf = new WordToPDFUtil().wordTransformPdf(docName, outpdfname, false);
String pdfname = outpdfname.substring(1 + outpdfname.lastIndexOf("/"));

return Result.success(docName);
}

码字不易,有问题请联系我 vx:yce99867066

庞欢腾的自建博客

欢迎来到我的博客系统

我将在这里分享我工作中的一些经历。

费曼学习法

费曼学习法

前言

“首要的原则是不要欺骗你自己,因为自己是最容易被欺骗的”

以前上学的时候没有网络,获取知识的来源主要是身边的人和学校给买的书。没有科学的学习观念和学习方法,很难做到学习成绩优秀,到了大学才有机会接触网络,开始自己主动的探索知识。然后学习到了费曼学习法,和大家分享交流。

正文

一、费曼学习法(自己的使用方案)

费曼学习法的灵感来源于诺贝尔物理学家获得者理查德·费曼。回到1965年,他因自己的工作获得过诺贝尔奖——量子电动力学。

知识有两种类型:

​ 第一类知识注重了解某个事物的名称。

​ 第二类知识注重了解某件事物。

理查德·费曼能够理解这两者之间的差异,这也是他成功最重要的原因之一。

费曼学习法可以简化为四个单词:

​ Concept(概念)

​ Teach(教给别人)

​ Review(回顾)

​ Simplify(简化)

(1)准备工作

在一张白纸上写自己想要学习的主题,想象把它教给一个9岁的孩子,刚好够理解一些简单的概念和关系。

(2)简单描述

不要使用复杂的词汇和行话,这样会掩盖自己不明白的东西,只是在糊弄我们自己。

自始至终用孩子都可以理解的简单的语言写出一个想法,迫使自己在更深层次上理解这个概念,简化观点之间的关系和联系,清楚自己还不明白的地方。

不是简单的感念,而是要举例子,把这个概念运用进去

(3)找出模糊点

在第一步中,会发现卡壳、忘记的、不能解释和不能联系概念,这样就找到了自己知识的边缘。

然后回归原始的学习资料,重新学习,直到可以用简单的术语解释这一概念。

认定自己知识的界限,会限制可能犯的错误,并且在应用这一知识时,增加成功的机率。

(4)简化(条理化)

检查自己手写的笔记,确保上面没有从原材料中借用任何行话。

将这些笔记用用简单的语言组织成一个流畅的故事。

将这个故事讲出来,尽量使它足够简单,避免混乱。

(5)教给别人

最后把它教授给另一个对这个话题知之甚少的人,确保自己的理解没什么问题。

检测知识最终的途径是你能有能力把它传播给另一个人。

拓展:学生可能会提出“为什么这个公式是正确的?”,所以就需要我们更多的学习相关知识。

Hello World

hello

感谢父母将我带来这个世界。

感谢身边所有的人教会我成长。

我希望能给这个世界带来更多的激情。

我们加油!!!( •̀ ω •́ )y