简介:
Velocity 是 apache 组织下的一个 基于 java 的 模板引擎( template engine ),而这种 Apache velocity 模板引擎的简单使用可以更好的将样式设计和 java 开发分离开来。
举个简单例子,在给一些客户发送邮件时,邮件正文往往带有一些较规范的表格或链接,而在发送邮件时,我们当然可以在 java 代码中写正文时加入 html 标签修饰达到如下目的:
但如果在业务上遇到更为复杂,项目较为庞大,这就要求项目组分工较为明确,而此时的邮件开发人员( java 开发)对表单设计并不在行,换句话说 java 开发人员在开发邮件发送功能时即便花费较长时间用在利用 html 标签来修饰邮件正文内容,但其也很难达到专业的 Web designers 设计出来的水平。那么最能达到用户要求的方式是什么呢?是让每个人仅作自己最擅长的事情: java 开发人员仅作邮件功能, web designer 仅作邮件的类似 web 表单的正文。
上面所示的邮件中的表格( table )及其中的样式和连接( <a></a> )效果是web designer 使用 html 实现的一个模板,而 java 开发人员则提供了相关表格( table)单元格填充所需要的内容,模板引擎利用模板语言将两者进行完美结合,最终形成一个美观邮件内容。 Velocity 则提供了这种方式的实现,如下图所示:
Velocity 提供的这种方式的实现,其思想来源于 MVC 。 Web designer 设计好一个模板,里面使用 html 标签和模板语言( Velocity Template Language ),而 velocity模板引擎起到了控制作用, java 开发人员只需要将相关数据作为一个 map 或其他变量等信息提供给这个模板,即可完成相关工作。代码示意如下所示:
模板: templateDemo.vm :
</head> <center> <TABLE width=850 border=1 bordercolor="#000000" id="tabID"> <TBODY align="left"> <TR rowspan="2"> <TD colSpan=4><B><FONT color=blue>$map.get("applyFormId")</FONT></B></TD> </TR> <TR> <TD align=middle colSpan=4><FONT size=5><B>templateDemo</B></FONT></TD> </TR> <TR> <TD nowrap width="140"><B>标题:</B></TD> <TD> $map.get("Name")</TD> <TD nowrap><B>关键词</B></TD> <TD> $map.get("key")</TD> </TR> </TBODY> </TABLE> </center> </html>
VelocityEngine的使用
VelocityEngine ve = new VelocityEngine(); ve.setProperty(Velocity.RESOURCE_LOADER, "class"); ve.setProperty("class.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); // 设置类路径加载模板 ve.setProperty(Velocity.INPUT_ENCODING, "utf-8");// 设置输入字符集 ve.setProperty(Velocity.OUTPUT_ENCODING, "utf-8");// 设置输出字符集 ve.init();// 初始化模板引擎 Template t = ve.getTemplate("/templateDemo.vm");// 加载模板,相对于classpath路径 VelocityContext context = new VelocityContext(); HashMap<String, Object> result = new HashMap<String, Object>(); result.put("Name", "模板"); result.put("Key", "语言"); context.put("map", result); StringWriter writer = new StringWriter(); t.merge(context, writer); // 转换 writer.toString(); //形成最终结果
虽然模板引擎不仅仅使用在复杂的邮件内容的业务上,但这个简单例子让我们体会到了界面和业务数据分离的实现方式,这也是模板引擎的主要作用,即它 可以让界面与数据分离,从而大大提高开发效率,也使得项目更加容易重用优良的界面设计。
分隔符----------------------------------------------------------------------------------
发送邮件(Spring+Velocity)
最近有需实现邮件发送的功能,为了让邮件界面和业务数据分离,故采用Velocity模板引擎。需引入velocity的时候,在Maven配置文件中添加如下依赖:
1
2
3
4
5
6
7
8
9
10
|
<
dependency
>
<
groupId
>velocity</
groupId
>
<
artifactId
>velocity</
artifactId
>
<
version
>1.5</
version
>
</
dependency
>
<
dependency
>
<
groupId
>velocity-tools</
groupId
>
<
artifactId
>velocity-tools</
artifactId
>
<
version
>2.0-beta1</
version
>
</
dependency
>
|
接着在Spring的配置文件中定义添加如下配置:
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
|
<
bean
id
=
"velocityEngine"
class
=
"org.springframework.ui.velocity.VelocityEngineFactoryBean"
>
<
property
name
=
"velocityProperties"
>
<
props
>
<
prop
key
=
"resource.loader"
>file</
prop
>
<
prop
key
=
"file.resource.loader.class"
>org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader</
prop
>
<
prop
key
=
"file.resource.loader.cache"
>false</
prop
>
<
prop
key
=
"file.resource.loader.modificationCheckInterval"
>3</
prop
>
<
prop
key
=
"input.encoding"
>UTF-8</
prop
>
<
prop
key
=
"output.encoding"
>UTF-8</
prop
>
</
props
>
</
property
>
</
bean
>
<
bean
id
=
"javaMailSender"
class
=
"org.springframework.mail.javamail.JavaMailSenderImpl"
>
<
property
name
=
"host"
>
<
value
>${mail.host}</
value
>
</
property
>
<
property
name
=
"port"
>
<
value
>${mail.port}</
value
>
</
property
>
<
property
name
=
"javaMailProperties"
>
<
props
>
<
prop
key
=
"mail.smtp.auth"
>${mail.smtp.auth}</
prop
>
<
prop
key
=
"mail.smtp.timeout"
>${mail.smtp.timeout}</
prop
>
</
props
>
</
property
>
<
property
name
=
"username"
>
<
value
>${mail.username}</
value
>
</
property
>
<
property
name
=
"password"
>
<
value
>${mail.password}</
value
>
</
property
>
</
bean
>
|
需要在spring配置文件中加入如下配置:
1
2
3
4
5
|
<bean id=
"propertyConfigurer"
class
=
"org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"
>
<property name=
"location"
>
<value>classpath:config.properties</value>
</property>
</bean>
|
并在src/main/resources目录下的config.properties文件中加入配置:
1
2
3
4
5
6
|
mail.host=mail.ylmob.com
mail.port=
25
mail.username=pay
@ylmob
.com
mail.password=*************
mail.smtp.auth=
true
mail.smtp.timeout=
25000
|
有时习惯查看源码,但是Maven私服上面并没有源码,只能下载velocity-1.5.zip,并在解压,按照图中的指示操作就可以附加源码:
附加源码之后,选中你想看源码的类,按下F3键,就进入源码中。
org.apache.velocity.runtime.RuntimeConstants中定义了key值信息,部门key值如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
/**
* Key used to retrieve the names of the resource loaders to be used. In a properties file they may appear as the following:
*
* <p>resource.loader = classpath</p>
*/
String RESOURCE_LOADER =
"resource.loader"
;
/** The public handle for setting a path in the FileResourceLoader. */
String FILE_RESOURCE_LOADER_PATH =
"file.resource.loader.path"
;
/** The public handle for turning the caching on in the FileResourceLoader. */
String FILE_RESOURCE_LOADER_CACHE =
"file.resource.loader.cache"
;
|
定义模板的输入和输出模板编码的配置是:
1
2
|
<prop key=
"input.encoding"
>UTF-
8
</prop>
<prop key=
"output.encoding"
>UTF-
8
</prop>
|
配置检查模板更改时间间隔:
1
|
<
prop
key
=
"file.resource.loader.modificationCheckInterval"
>3</
prop
>
|
配置是否启用模板缓存:
1
|
<
prop
key
=
"file.resource.loader.cache"
>false</
prop
>
|
配置模板路径:
1
|
<
prop
key
=
"file.resource.loader.path"
>${path}</
prop
>
|
配置模板加载器类型:
1
|
<
prop
key
=
"resource.loader"
>file</
prop
>
|
配置加载器类:
1
|
<
prop
key
=
"file.resource.loader.class"
>org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader</
prop
>
|
定义一个用于发送邮件接口IMailSenderService,具体代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
package
com.astep.yunpay.service;
import
java.util.Map;
/**
* @Author DJM
* @CreateDate:2015-05-06 13:57:20
* @UpdateDate:2015-05-06 13:57:20
* @Deprecated:Send e-mail
*/
public
interface
IMailSenderService {
public
void
sendMailWithVelocityTemplate(String mailSubject, String templateLocation, Map<String, Object> velocityContext);
}
|
再写一个实现IMailSenderService接口的实现类MailSenderService,具体代码如下:
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
|
package
com.astep.yunpay.service.impl;
import
java.util.Map;
import
javax.annotation.Resource;
import
javax.mail.MessagingException;
import
javax.mail.internet.MimeMessage;
import
org.apache.velocity.app.VelocityEngine;
import
org.springframework.beans.factory.annotation.Autowired;
import
org.springframework.mail.javamail.JavaMailSender;
import
org.springframework.mail.javamail.MimeMessageHelper;
import
org.springframework.stereotype.Service;
import
org.springframework.ui.velocity.VelocityEngineUtils;
import
com.astep.yunpay.service.IMailSenderService;
import
com.astep.yunpay.service.ISsPropertyService;
/**
* @Author DJM
* @CreateDate:2015-05-06 13:57:20
* @UpdateDate:2015-05-06 13:57:20
* @Deprecated:Send e-mail
*/
@Service
(
"mailSenderService"
)
public
class
MailSenderService
implements
IMailSenderService {
@Autowired
private
ISsPropertyService ssPropertyService;
@Resource
private
JavaMailSender javaMailSender;
@Resource
private
VelocityEngine velocityEngine;
@Override
public
void
sendMailWithVelocityTemplate(String mailSubject, String templateLocation, Map<String, Object> velocityContext){
MimeMessage mailMessage = javaMailSender.createMimeMessage();
MimeMessageHelper messageHelper;
try
{
messageHelper =
new
MimeMessageHelper(mailMessage,
true
,
"utf-8"
);
// 设置邮件接收人
if
(ssPropertyService.getByKey(
"system.mail.to"
)==
null
||ssPropertyService.getByKey(
"system.mail.to"
).equals(
""
)){
return
;
}
messageHelper.setTo(ssPropertyService.getByKey(
"system.mail.to"
).trim().split(
";"
));
// 设置邮件发送人
messageHelper.setFrom(ssPropertyService.getByKey(
"mail.from"
).trim());
messageHelper.setSubject(mailSubject);
String mailText = VelocityEngineUtils.mergeTemplateIntoString(velocityEngine, templateLocation,
"utf-8"
, velocityContext);
messageHelper.setText(mailText,
true
);
javaMailSender.send(mailMessage);
}
catch
(MessagingException e) {
e.printStackTrace();
}
}
}
|
需要在代码中注意代码中,
1
|
messageHelper.setFrom(ssPropertyService.getByKey(
"mail.from"
).trim());
|
可以自由配置为shabi@shabi.com、laopozhaoxue@laopo.com、meinv@img.com等,只会影响下图中红色框框中的显示部分,邮件实际上是由pay@ylmob.com发送的。假如我们配置duoshengwa@laopo.com,红色框框中显示将是duoshengwa,就相当于给pay@ylmob.com穿上一层皮,即使发垃圾邮件,一般也不会被别人知道。随便说句,“mail.from”和"system.mail.to"系统后台配置的属性,存放于数据库中的数据。
这次需求是每天早晨八点发送排名前十位的内容提供商的排名以及金额统计详细和应用排名以及金额统计详细邮件。有了MailSenderService类,只需要传入邮件主题、模板文件的路径和一个Map<String, Object>类型的对象model,源码中的注释对model给出了说明
1
|
@param
model the Map that contains model names as keys and model objects as values
|
1
2
3
4
5
6
7
8
9
|
package
com.astep.yunpay.task;
/**
* @Author DJM
*/
public
interface
ITopTenCpAndAppDailyCensusSendEmailTaskService {
public
void
email();
}
|
实现该接口的类为:
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
|
package
com.astep.yunpay.task.impl;
import
java.util.ArrayList;
import
java.util.Date;
import
java.util.HashMap;
import
java.util.List;
import
java.util.Map;
import
org.springframework.beans.factory.annotation.Autowired;
import
org.springframework.scheduling.annotation.Scheduled;
import
org.springframework.stereotype.Service;
import
com.astep.common.util.CalculateUtil;
import
com.astep.common.util.DateUtil;
import
com.astep.common.util.DigitUtil;
import
com.astep.yunpay.service.IMailSenderService;
import
com.astep.yunpay.service.ISsPropertyService;
import
com.astep.yunpay.service.comprehensive.IComprehensiveService;
import
com.astep.yunpay.task.ITopTenCpAndAppDailyCensusSendEmailTaskService;
/**
* @Author DJM
* @Date:2015年03月18日
* @Time:上午11:10:25
* @Description:邮件发送每日TOP10的CP和应用统计报表
*/
@Service
public
class
TopTenCpAndAppDailyCensusSendEmailTaskService
implements
ITopTenCpAndAppDailyCensusSendEmailTaskService {
@Autowired
ISsPropertyService ssPropertyService;
@Autowired
IComprehensiveService comprehensiveService;
@Autowired
private
IMailSenderService mailSenderService;
private
static
boolean
isRunning =
false
;
@Scheduled
(cron =
"0 0 8 * * *"
)
//开发调试所采用的调度时间
//@Scheduled(cron = "0/30 * * * * *") //开发调试所采用的调度时间
public
void
email() {
if
(isRunning) {
return
;
}
isRunning =
true
;
Map<String, Object> velocityCpParameter =
new
HashMap<String, Object>();
velocityCpParameter = enchaseParameter(velocityCpParameter,
false
);
mailSenderService.sendMailWithVelocityTemplate(
"每日TOP10的CP统计报表"
,
"velocity/VelocityTemplateOfTopTenCpDailyCensusMail.vm"
, velocityCpParameter);
Map<String, Object> velocityAppParameter =
new
HashMap<String, Object>();
velocityAppParameter = enchaseParameter(velocityAppParameter,
true
);
mailSenderService.sendMailWithVelocityTemplate(
"每日TOP10的APP统计报表"
,
"velocity/VelocityTemplateOfTopTenAppDailyCensusMail.vm"
, velocityAppParameter);
isRunning =
false
;
}
public
Map<String, Object> enchaseParameter(Map<String, Object> velocityParameter,
boolean
isGourpApp){
velocityParameter.put(
"mailPlatformFlag"
, ssPropertyService.getByKey(
"mail.platform.flag"
) ==
null
?
""
: ssPropertyService.getByKey(
"mail.platform.flag"
));
velocityParameter.put(
"date"
, DateUtil.formatDate(DateUtil.addDays(
new
Date(), -
1
)));
velocityParameter.put(
"comprehensiveList"
, getComprehensiveList(isGourpApp));
velocityParameter.put(
"summationMap"
, getSummationMap(getComprehensiveList(isGourpApp)));
return
velocityParameter;
}
public
List<Map<String, Object>> getComprehensiveList(
boolean
isGourpApp){
Map<String, Object> condition =
new
HashMap<String, Object>();
Date addTimeStart = DateUtil.getDayBegin(DateUtil.addDays(
new
Date(), -
1
));
Date addTimeEnd = DateUtil.getDayEnd(addTimeStart);
if
(!isGourpApp){
condition.put(
"screening"
,
true
);
}
condition.put(
"screeningTop"
,
true
);
condition.put(
"addTimeStart"
, addTimeStart);
condition.put(
"addTimeEnd"
, addTimeEnd);
List<Map<String, Object>> comprehensiveList =
new
ArrayList<Map<String, Object>>();
comprehensiveList = comprehensiveService.census(condition,
"cpDailyCensus"
);
return
comprehensiveList;
}
public
Map<String, Object> getSummationMap(List<Map<String, Object>> comprehensiveList){
Map<String, Object> summationMap =
new
HashMap<String, Object>();
int
coSuccessSummation=
0
, poSuccessSummation=
0
, moSuccessSummation=
0
, mrSuccessSummation=
0
;
if
(comprehensiveList!=
null
&& comprehensiveList.size()>
0
){
for
(Map<String, Object> record: comprehensiveList){
coSuccessSummation+=DigitUtil.obtainWithoutDecimal(record.get(
"coS"
));
poSuccessSummation+=DigitUtil.obtainWithoutDecimal(record.get(
"poS"
));
moSuccessSummation+=DigitUtil.obtainWithoutDecimal(record.get(
"moS"
));
mrSuccessSummation+=DigitUtil.obtainWithoutDecimal(record.get(
"mrS"
));
}
}
summationMap.put(
"coSuccessSummation"
, coSuccessSummation);
summationMap.put(
"poSuccessSummation"
, poSuccessSummation);
summationMap.put(
"moSuccessSummation"
, moSuccessSummation);
summationMap.put(
"mrSuccessSummation"
, mrSuccessSummation);
summationMap.put(
"summationPercent"
, CalculateUtil.quotient(mrSuccessSummation, poSuccessSummation));
return
summationMap;
}
public
String toYuan(Object object){
int
number = DigitUtil.obtainWithoutDecimal(object);
Integer result = number/
100
;
return
result.toString();
}
}
|
velocity模板文件为:
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
|
##author: DJM
<!
DOCTYPE
html>
<
html
>
<
head
>
<
meta
charset
=
"utf-8"
>
<
meta
http-equiv
=
"X-UA-Compatible"
content
=
"IE=edge"
>
<
meta
name
=
"viewport"
content
=
"width=device-width, initial-scale=1"
>
<
title
>每日TOP10的APP统计报表</
title
>
<
style
>
#include("velocity/css/bootstrap.css")
</
style
>
</
head
>
<
body
>
<
h3
><
span
class
=
"label label-success"
>${mailPlatformFlag}-每日TOP10的APP统计报表(${date})</
span
></
h3
>
<
div
class
=
"panel panel-danger"
>
<
div
></
div
>
<
div
>
<
table
class
=
"table table-bordered table-hover"
>
<
tr
>
<
td
title
=
"时间"
>时间</
td
>
<
td
title
=
"应用名称"
>应用名称</
td
>
<
td
title
=
"所属CP"
>所属CP</
td
>
<
td
title
=
"确认订单金额"
>确认订单金额</
td
>
<
td
title
=
"PO成功金额"
>PO成功金额</
td
>
<
td
title
=
"MO成功金额"
>MO成功金额</
td
>
<
td
title
=
"MR成功金额"
>MR成功金额</
td
>
<
td
title
=
"计费转化率"
>计费转化率</
td
>
</
tr
>
#foreach($comprehensive in ${comprehensiveList})
<
tr
>
<
td
>${comprehensive.censusDay}</
td
>
<
td
title
=
"${comprehensive.appId}"
>${comprehensive.appName}</
td
>
<
td
>${comprehensive.shortName}</
td
>
#set($coS=${comprehensive.coS}/100)
<
td
>${coS}元</
td
>
#set($poS=${comprehensive.poS}/100)
<
td
>${poS}元</
td
>
#set($moS=${comprehensive.moS}/100)
<
td
>${moS}元</
td
>
#set($mrS=${comprehensive.mrS}/100)
<
td
>${mrS}元</
td
>
#if(${comprehensive.mrS} <= 0 || ${comprehensive.poS} <= 0)
<
td
>0%</
td
>
#else
#set($comprehensivePercent=${comprehensive.mrS}*10000/${comprehensive.poS}/100)
<
td
>${comprehensivePercent}%</
td
>
#end
</
tr
>
#end
<
tr
>
<
td
>合计</
td
>
<
td
>-</
td
>
<
td
>-</
td
>
#set($coSuccessSummation=${summationMap.coSuccessSummation}/100)
<
td
>${coSuccessSummation}元</
td
>
#set($poSuccessSummation=${summationMap.poSuccessSummation}/100)
<
td
>${poSuccessSummation}元</
td
>
#set($moSuccessSummation=${summationMap.moSuccessSummation}/100)
<
td
>${moSuccessSummation}元</
td
>
#set($mrSuccessSummation=${summationMap.mrSuccessSummation}/100)
<
td
>${mrSuccessSummation}元</
td
>
<
td
>${summationMap.summationPercent}%</
td
>
</
tr
>
</
table
>
</
div
>
</
div
>
<
h3
>
<
span
class
=
"glyphicon glyphicon-link"
aria-hidden
=
"true"
></
span
>
<
span
class
=
"label label-info"
><
a
href
=
"http://p.918ja.com:9020/login.do"
>进入移动支付统一合作平台</
a
></
span
>
</
h3
>
</
body
>
</
html
>
|
使用#include命令引入bootstrap.css文件:
1
2
3
|
<
style
>
#include("velocity/css/bootstrap.css")
</
style
>
|
发送的邮件内容为: