背景
最近在做一个移动收银的项目,需要使用到银行的设备进行收银。由于使用的是银行设备,每次更新APP是都需要经过银行审核才能上线,这就加大了应用上线的复杂度。而且银行的pos机,性能也有限,通过http请求访问web页面会比较缓慢,会影响收银等操作,影响用户体验。
对于一个新项目来说,在没有经过长时间试运行之前,积累不够,稳定性肯定有所欠缺,基本都需要不断的迭代更新,不断的完善系统交互。估而老大希望将可以预见经常变化的页面的开发工作后置到服务端,避免频繁发布app。如小票打印,由后端生成好最终的打印模板并且输出,app在接收到模板后直接渲染打印,这样如果模板有变化也只需要发布服务端就好,不需要走app的上架流程。
Freemarker
页面模板化,我第一个想到的是Java模板引擎Freemarker。
FreeMarker 是一款 模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等)的通用工具。 它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件。
Freemarker可以将表现层和业务逻辑层代码完全分开,提高代码的可读性;同时能使得开发过程中的人员分工更加明确,实现前后端分离。
Freemarker实现前端页面热部署设计可以参考下图。Freemarker的技术文档网上特别丰富,本文不多做描述。
Handlebars.java
移动收银系统只需要将部分页面模板化,直接使用Freemarker感觉有点大材小用,也相对复杂。在老大的推荐下了解到了Handlebars.java。
Handlebars 是一种简单的 模板语言,相对于Freemarker更为简单。
它使用模板和输入对象来生成 HTML 或其他文本格式。Handlebars 模板看起来像常规的文本,但是它带有嵌入式的 Handlebars 表达式 。
基础用法
1、表达式是一些以双花括号 {{}} 括起来的内容;
2、在 Handlebars 中, {{expression}} 返回的值是 HTML 转义的。如果一个表达式的内容包含 &等HTML特殊字符时,Handlebars会自动转义。如果你不希望 Handlebars 转义字符的话,可以 修改为{{{expression}}};
3、Handlebars的入参可以是任何类型,String、Map、自定义对象;
4、Handlebars的模板可以是一个hbs、html等格式的文件,也可以是一个字符串。
if助手
{{#if condition}}
...
{{/if}}
for循环表达式(each助手)
{{#each list}}
...
{{/each}}
代码实现
maven
<dependency>
<groupId>com.github.jknack</groupId>
<artifactId>handlebars</artifactId>
<version>4.2.0</version>
</dependency>
加载解析模板------文件
private final static String LOCAL_ROOT_PATH = "/";
private final static String TEMPLATE_PREFIX = "/templates";
public static String compile(String templateName,Object var1){
TemplateLoader loader = new FileTemplateLoader(new File(LOCAL_ROOT_PATH));
loader.setPrefix(TEMPLATE_PREFIX);
Handlebars handlebars = new Handlebars(loader);
try {
Template template = handlebars.compile(templateName);
return template.apply(var1);
} catch (Exception e) {
LOGGER.warn(LogEvent.of(HANDLEBARS_LOG_EVENT,"handlebars 封装模板异常 message : {} , e : {}",e.getLocalizedMessage(),e));
}
return null;
}
加载解析模板------字符串
字符串形式更为便捷,可以丢到redis中,能提升性能,更新也更为方便
public static String compileInline(String templateName,Object var1){
String key = VposRedisConstant.VPOS_HANDLEBARS_KEY + templateName;
String templateString = getTemplateString(key,templateName);
if(StringUtils.isEmpty(templateString)){
return null;
}
Handlebars handlebars = new Handlebars();
try {
Template template = handlebars.compileInline(templateString);
return template.apply(var1);
} catch (Exception e) {
LOGGER.warn(LogEvent.of(HANDLEBARS_LOG_EVENT,"handlebars 封装模板异常 message : {} , e : {}",e.getLocalizedMessage(),e));
}
return null;
}
private static String getTemplateString(String key,String templateName){
String templateString = RedisUtil.getInstance().getString(key);
if(StringUtils.isNotEmpty(templateString)){
return templateString;
}
File template = FileUtil
.downloadFile(TEMPLATES_PATH, templateName + TEMPLATE_SUFFIX_HBS, TEMPLATE_TEMPORARY_PATH);
templateString = FileUtils.readFileToString(template, "utf-8");
if(StringUtils.isNotEmpty(templateString)){
FcsVposRedisUtil.getInstance().set(key,templateString,36000);
}
return templateString;
}
入参
String template = HandlebarsUtils.compileInline("sale.hbs",orderTemplate);
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class OrderTemplate {
private List<PayModel> PayModels;
private String totalPayAmount;
private double orderGoodsMoney;
private double orderDiscount;
}
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class PayModel{
private String payMethod;
private String payMethodName;
private String amount;
}
模板样例
<div class="diary-detail-h5">
<div class="d-flex flex-row justify-content-start align-items-center flex-wrapper">
<div class="d-flex flex-row flex-fill justify-content-start align-items-start flex-demo">
<p class="p-justify vpos-diary-bold">销售商品总金额:</p></div>
<div class="d-flex flex-row flex-fill justify-content-end align-items-start flex-demo">
<p class="vpos-diary-bold">¥{{orderGoodsMoney}}</p></div>
</div>
{{#if orderDiscount '!=' null}}
<div class="d-flex flex-row justify-content-start align-items-center flex-wrapper">
<div class="d-flex flex-row flex-fill justify-content-start align-items-start flex-demo">
<p class="p-justify vpos-diary-bold">销售优惠合计:</p></div>
<div class="d-flex flex-row flex-fill justify-content-end align-items-start flex-demo">
<p class="vpos-diary-bold">¥{{orderDiscount}}</p></div>
</div>
{{/if}}
<div class="vpos-diary-comp">
<p class="vpos-diary-bold vpos-detail-title">收款明细</p>
<div class="vpos-day-dummary-line-list">
{{#each PayModels}}
<div class="d-flex flex-row justify-content-start align-items-center flex-wrapper">
<div class="d-flex flex-row flex-fill justify-content-start align-items-start flex-demo">
<p class="p-justify">{{payMethodName}}:</p></div>
<div class="d-flex flex-row flex-fill justify-content-end align-items-start flex-demo">
<p>¥{{amount}}</p>
</div>
</div>
{{/each}}
<div class="d-flex flex-row justify-content-start align-items-center flex-wrapper">
<div class="d-flex flex-row flex-fill justify-content-start align-items-start flex-demo">
<p class="p-justify vpos-diary-bold">收款合计:</p></div>
<div class="d-flex flex-row flex-fill justify-content-end align-items-start flex-demo">
<p class="vpos-diary-bold">¥{{totalPayAmount}}</p></div>
</div>
</div>
</div>
</div>
参考资料
Handlebars.java还有很多其他用法,如with、unless、helpers等,详情见:https://github.com/jknack/handlebars.java
作者:张伟峰