一、背景
最近公司有业务,需要传递数据将其生成静态HTML,然后HTML可转PDF,也可打印。网上查阅了一下freemarker,颇感兴趣,
学习使用后,记录过程,方便回顾,同时也希望能帮到各位猿友。
二、涉及技术
freemarker、springboot、lodop、itext
三、业务步骤
1.freemarker的maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
2.freemarker的配置(我用的是yml文件)
# 配置数据源
spring:
freemarker:
allow-request-override: false
cache: false
check-template-location: true
charset: UTF-8
content-type: text/html
expose-request-attributes: false
expose-session-attributes: false
expose-spring-macro-helpers: false
suffix: .html
settings:
default_encoding: UTF-8
enabled: true
request-context-attribute: rc
template-path : D:/yxhData/staticData/res/template/html
其中template-path为模版的路径
3.freemarker的宏
import java.io.IOException;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import com.chongdong.common.exception.APIException;
import com.chongdong.common.util.JacksonUtils;
import com.chongdong.common.util.freemarker.Freemarker;
import com.chongdong.data.entity.OrdBase;
import com.chongdong.data.entity.OrdExtend;
import com.chongdong.service.order.biz.OrdBaseBiz;
import freemarker.core.Environment;
import freemarker.template.TemplateDirectiveBody;
import freemarker.template.TemplateDirectiveModel;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
import freemarker.template.WrappingTemplateModel;
/**
* 订单宏
* @author yanxh
*
*/
@Repository
public class OrderDetail implements TemplateDirectiveModel {
@Autowired
private OrdBaseBiz ordBaseBiz;
/**
* @param env 系统环境变量,通常用它来输出相关内容,如Writer out = env.getOut();
* @param map 自定义标签传过来的对象,其key=自定义标签的参数名,value值是TemplateModel类型,而TemplateModel是一个接口类型,通常我们都使用TemplateScalarModel接口来替代它获取一个String 值,如TemplateScalarModel.getAsString();当然还有其它常用的替代接口,如TemplateNumberModel获取number,TemplateHashModel等
* @param loopVars 循环替代变量
* @param body 用于处理自定义标签中的内容;当标签是<@myDirective />格式时,body=null
*/
@Override
public void execute(Environment env, Map map, TemplateModel[] loopVars,
TemplateDirectiveBody body) throws TemplateException, IOException {
/**
* 订单ID字符串
*/
Long orderId = Freemarker.getLong(map, "orderId");
/**
* 获取订单信息
*/
OrdBase ordBase = null;
try {
ordBase = ordBaseBiz.getDetail(orderId);
} catch (APIException e) {
e.printStackTrace();
}
/**
* 获取dataJson
*/
Map<String, Object> jsonMap = null;
try {
OrdExtend ordExtend = ordBase.getOrdExtend();
if(ordExtend != null) {
String dataJson = ordExtend.getDataJson();
if(!StringUtils.isBlank(dataJson)) {
jsonMap = JacksonUtils.json2map(dataJson);
}
}
} catch (Exception e) {
e.printStackTrace();
}
loopVars[0]=WrappingTemplateModel.getDefaultObjectWrapper().wrap(ordBase);
loopVars[1]=WrappingTemplateModel.getDefaultObjectWrapper().wrap(jsonMap);
body.render(env.getOut());
}
}
这是一个订单详情的宏定义,参数为订单的ID,返回值为订单详细信息和订单扩展信息
4.freemarker的配置类
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import com.chongdong.service.order.biz.macro.OrdBatchDetail;
import com.chongdong.service.order.biz.macro.OrdBatchDetailWithField;
import com.chongdong.service.order.biz.macro.OrderDetail;
import freemarker.template.Configuration;
import freemarker.template.TemplateModelException;
@org.springframework.context.annotation.Configuration
public class FreemarkerConfig {
@Autowired
private Configuration configuration;
@Autowired
private OrderDetail orderDetail;
@Autowired
private OrdBatchDetail ordBatchDetail;
@Autowired
private OrdBatchDetailWithField ordBatchDetailWithField;
@Value("${spring.freemarker.template-path}")
private String templateLoaderPath; // 模板物理路径
@PostConstruct
public void setSharedVariable() throws TemplateModelException {
configuration.setSharedVaribles(getSharedVaribles());
configuration.setClassicCompatible(true); // 处理页面空值
// 使用物理地址作为模版路径
try {
configuration.setDirectoryForTemplateLoading(new File(templateLoaderPath));
} catch (IOException e) {
e.printStackTrace();
}
}
public Map<String, Object> getSharedVaribles() {
Map<String, Object> sharedVaribles = new HashMap<String, Object>();
sharedVaribles.put("_order", orderDetail);
sharedVaribles.put("_ordBatch", ordBatchDetail);
sharedVaribles.put("_ordBatchWithField", ordBatchDetailWithField);
return sharedVaribles;
}
}
配置类设置freemarker的一些参数,同时将宏加入到配置中
5.模版
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8" />
<link rel="stylesheet" href="../../res/css/reset.css" type="text/css" media="screen" />
<script type="text/javascript" src="../../res/js/jquery-1.9.1.min.js"></script>
</head>
<body>
<div class="printList_box">
<#list orderIds?split(",") as orderId>
<@_order orderId="${orderId}"; ordBase, jsonMap>
<div class="pb_logo"><img src="../../res/images/Buyers/pb_logo.png" /></div>
<h2 class="plb_h2">${ordBase.orgName}电子签收单</h2>
<table class="plb_table">
<tr>
<td class="plb_td_w10">订单编号</td>
<td>${ordBase.id?c}</td>
<td class="plb_td_w10">订单金额</td>
<td class="plb_td_w10" colspan="2">¥${ordBase.amount?c}</td>
</tr>
<tr>
<td class="plb_td_w10">报销批次</td>
<td>${batchNo}</td>
<td class="plb_td_w10">采购时间</td>
<td colspan="2">${ordBase.createTime?string('yyyy-MM-dd HH:mm:ss')}</td>
</tr>
<tr>
<td class="plb_td_w10">采购人(学号)</td>
<td>${ordBase.buyerName}(${jsonMap.buyerNo})</td>
<td class="plb_td_w10">所属实验室</td>
<td colspan="2">${jsonMap.byraccountDep}</td>
</tr>
<tr>
<td class="plb_td_w10">实验室负责人(工号)</td>
<td>${jsonMap.byraccountDirect2UerName}(${jsonMap.byraccountDirect2UerNo})</td>
<td class="plb_td_w10"></td>
<td colspan="2"></td>
</tr>
<tr class="y_tr_h50">
<td class="plb_td_w10">收货地址</td>
<td colspan="4">${ordBase.ordAddress.provinceName}${ordBase.ordAddress.cityName}${ordBase.ordAddress.address}</td>
</tr>
<tr>
<td class="plb_td_w10">供应商名称</td>
<td>${ordBase.prvName}</td>
<td class="plb_td_w10">经费出处</td>
<td colspan="2">${jsonMap.byraccountName}</td>
</tr>
<tr>
<td class="plb_td_w10">商品编号</td>
<td class="plb_td_w10">商品名称</td>
<td class="plb_td_w10">数量</td>
<td class="plb_td_w10">单价</td>
<td class="plb_td_w10">小计</td>
</tr>
<#list ordBase.ordProducts as prd>
<tr>
<td>${prd.code}</td>
<td>${prd.name} ${prd.nameEnglish}</td>
<td>${prd.prodCount?c}</td>
<td>¥${prd.tradePrice?c}</td>
<td>¥${(prd.prodCount * prd.tradePrice)?c}</td>
</tr>
</#list>
<tr class="td_b_none">
<#list jsonMap.approvalPoints as node>
<td>${node.approvalOrg}:${node.approvalerName}<br />${node.approvalTime!''}</td>
</#list>
<td>签收人:${jsonMap.signerName}<br />${jsonMap.signTime!''}</td>
</tr>
</table>
<br/>
<br/>
</@_order>
</#list>
</div>
</body>
</html>
<style>
body{
font-family:SimSun
}
.printList_box{
width: 90%;
margin: 0 auto;
height: auto;
position: relative;
padding-bottom: 50px;
}
.plb_h2{
text-align: center;
line-height: 40px;
font-size: 20px;
padding-top: 20px;
font-weight: bold;
}
.plb_table{
width: 100%;
border-collapse:collapse;
}
.plb_table tr{}
.plb_table tr td{
text-align: center;
border: 1px solid #ccc;
font-size: 12px;
line-height: 16px;
padding: 5px;
word-break: break-all;
word-wrap:break-word;
}
.plb_table tr.td_b_none td{
border: none;
text-align: left;
}
.plb_table tr.plb_tr01{
height: 28px;
}
.plb_table tr.plb_tr01 td{
font-size: 14px;
font-weight: bold;
text-align: left;
border: none;
}
.plb_table tr.plb_tr02{
height: 34px;
}
.plb_table tr td.b_r_none{
border-right: none;
}
.plb_table tr td.b_l_none{
border-left: none;
}
.table_Remarks{
width: 100%;
padding: 50px 0;
font-size: 14px;
line-height: 24px;
}
.plb_td_w70{
width: 70px;
}
.plb_td_w100{
width: 100px;
}
.plb_td_w10{
width: 120px;
}
.plb_purpose{
width: 90%;
height: 100%;
padding-left: 5%;
text-align: left;
}
.plb_box{
padding: 10px;
}
.plb_text_p01{
height: 100px;
text-align: left;
}
.plb_text_p02{
text-align: right;
}
.plb_text_p02 span{
display: inline-block;
padding: 0 50px 0 100px;
}
.Total_box{
width: 100%;
height: auto;
}
.Total_box p{
line-height: 40px;
text-align: right;
}
.Total_box div{}
.Total_box div span{
float: left;
display: inline-block;
width: 18%;
font-weight: bold;
font-size: 14px;
}
.Total_box div em{
float: right;
font-weight: bold;
font-size: 14px;
}
.pb_left{
position: absolute;
left: -20px;
top: 88px;
width: 10px;
}
.pb_right{
position: absolute;
right: -20px;
top: 88px;
width: 10px;
}
.pb_logo{
position: absolute;
right: 0;
top: 20px;
width: 170px;
opacity: 0.7;
}
.pb_logo img{
display: block;
width: 100%;
}
.pb_right span{
padding-bottom: 20px;
}
.plb_com_text{
width: 200px;
}
.plb_table tr td.pb_ta_left{
text-align: left;
font-weight: bold;
}
.pb_data_box{
width: 150px;
}
.pb_data_box span{
display: inline-block;
width: 50px;
text-align: left;
font-weight: bold;
}
.ys_box01{
text-align: left;
padding: 20px 0 20px 20px;
overflow: hidden;
}
.ys_box01 span{
float: left;
padding: 0 5px;
margin-right: 20px;
}
.ys_box01 input{
float: left;
margin-top: 3px;
}
.ys_box02{
text-align: left;
padding-bottom: 50px;
padding-left: 20px;
}
.ys_box02 p{
padding: 10px 0;
}
.ys_box02 span{
float: left;
padding-right: 20px;
}
.ys_box02 input{
float: left;
margin-top: 3px;
margin-right: 5px;
}
.ys_box02 font{
display: inline-block;
padding: 0 200px 100px 0;
}
.y_tr_h50{
height: 50px;
}
</style>
6.生成html
/**
* 生成html
* @param freemarkerCfg freemarker的默认配置
* @param data 需要传递至模版页面的参数
* @param templatePath 模版页面的名称
* @param htmlPath 生成HTML的存放路径
* @throws TemplateNotFoundException
* @throws MalformedTemplateNameException
* @throws ParseException
* @throws IOException
* @throws TemplateException
*/
public static void createHTML(Configuration freemarkerCfg,Map<String,Object> data,String templatePath,String htmlPath) throws TemplateNotFoundException,
MalformedTemplateNameException, ParseException, IOException, TemplateException{
Template template = freemarkerCfg.getTemplate(templatePath,"UTF-8");
File htmlFile = new File(htmlPath);
if(!htmlFile.getParentFile().exists()) {
htmlFile.getParentFile().mkdirs();
}
Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(htmlFile), "UTF-8"));
template.process(data, out);
out.flush();
out.close();
}
四、过程讲解
调用接口调试工具(我用的是自己搭建的swagger,具体步骤可参考5分钟学会swagger配置-中文界面配置,也可使用postman)访问接口调用第6步的方法,模版接到请求后开始向指定路径输出静态页面,期间调用宏查询相关数据。