1.1 生成验证码#
Play中的play.libs.Images类提供了生成验证码的支持,操作也非常简单。我们可以通过静态方法Images.captacha()快速生成默认大小为150*50的验证码图片,也可以使用Images.captacha(int width, int height)方法生成指定大小的验证码图片。
在Application控制器中增加captcha Action:
public static void captcha(){
Images.Captcha captcha=Images.captcha();
renderBinary(captcha);
}
Action定义完后,在路由文件中配置验证码的路由规则:
GET /captcha Application.captcha
打开http://localhost:9000/captcha查看验证码。每次刷新浏览器,都会生成随机的验证码。
10.4.2 添加噪点#
上例生成的验证码还是存在安全隐患的。我们通常会为验证码添加适当的噪点,降低验证码被程序识别的可能性。我们可以使用addNoise()方法为验证码添加默认为黑色的噪点,也可以使用addNoise(java.lang.String color)方法指定噪点的颜色。
修改Application控制器中的captcha Action:
public static void captcha(){
Images.Captcha captcha=Images.captcha();
captcha.addNoise();
renderBinary(captcha);
}
10.4.3 设置背景#
play.libs.Images提供的setBackground(java.lang.String color)方法可以为验证码设置背景色,下例将验证码背景色设置为#E4EAFD。
修改Application控制器中的captcha Action:
public static void captcha(){
Images.Captcha captcha=Images.captcha();
captcha.addNoise();
captcha.setBackground("#E4EAFD");
renderBinary(captcha);
}
此外还有为验证码添加渐变背景色的setBackground(java.lang.String from, java.lang.String to)方法;为验证码添加波浪曲线背景的setSquigglesBackground()方法,本节就不展开介绍了。
10.4.4 文本操作#
验证码中唯一的信息就是文本内容,通过getText()方法可以获取验证码中的文本内容。我们也可以通过getText(java.lang.String color)方法在获取文本内容的同时设置验证码中文本的颜色。
修改Application控制器中的captcha Action:
public static void captcha(){
Images.Captcha captcha=Images.captcha();
captcha.addNoise();
String code = captcha.getText("#ABCDEF");
captcha.setBackground("#E4EAFD");
renderBinary(captcha);
}
2、配置Log
Play内置的日志记录器是在Log4j之上的封装。Log4j的配置之简单使它遍及于越来越多的项目中,在开发中使用Log4j的好处主要可以归纳为以下几个方面:
- 通过修改配置文件,就可以决定日志信息的目的地————控制台、文件、GUI组件、甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等 。
- 通过修改配置文件,定义每一条日志信息的级别,控制日志的输出。我们可以在系统开发阶段打印详细的日志信息以便跟踪系统运行情况,当系统稳定后关闭日志输出,在能跟踪系统运行情况的同时,减少了垃圾代码(由System.out.println打印的信息)的产生。
- 整个系统采用统一的日志机制,有利于系统的规划。
Log4j是Apache的一个开放源代码项目。
2.1 程序日志#
play.Logger是Play的默认日志记录器,该日志记录器通过Log4j记录消息和异常。下例为记录简单的程序日志:
Logger.info("A log message");
Logger.error(ex, "Oops");
play.Logger类支持标准的Java格式化语法:
Logger.debug("The param was %s", param);
Logger.info("I want to log %s and %s and %s", a, b, c);
特定需求下,我们仍然可以直接使用Log4j来创建其他记录器:
org.apache.log4j.Logger.getLogger("another.logger");
2.2 日志级别#
通过修改application.conf配置可以更改play.Logger的日志记录级别:
application.log=INFO
修改此值无需重新启动服务器就可生效。此日志级别配置只针对应用程序产生的消息有效。
如果读者需要充分配置Log4j,我们可以在conf/目录下创建log4j.properties文件。由于conf目录是在classpath路径配置中的第一个元素,所以这个配置文件将会被所有库使用。标准Log4j配置如下:
log4j.rootLogger=ERROR, Console
log4j.logger.play=INFO
# Console
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%d{ABSOLUTE} %-5p ~ %m%n
读者可以参考默认配置并在此基础上根据自己的需求进行修改。
10.5.3 Play中Log4j配置详解#
Log4j支持两种配置文件,properties文件(log4j.properties),以及XML文件(log4j.xml),本书以log4j.properties配置文件为例进行讲解。
Log4j包含三个重要的组件,分别为Logger,Appender以及Layout。Logger被称作日志记录器,用于记录日志的类别;Appender可以被理解为输出源,即日志所输出的位置;Layout是日志布局,体现为日志以何种形式输出。
Logger
Logger(日志记录器)是日志处理的核心组件,负责处理日志记录的大部分操作。日志级别分为多种,从低到高分为ALL、TRACE、DEBUG、INFO、WARN、ERROR、FATAL、OFF,当然也可以自定义日志级别。标准的日志级别粒度划分如下:
Level ALL:所有信息。
Level TRACE:级别为细粒度的信息。
Level DEBUG: 级别为细粒度的信息,粒度粗于TRACE,该级别打印的信息有助于调试应用程序。
Level INFO: 级别为粗粒度的信息,该级别日志主要用于记录程序运行过程的信息。
Level WARN:警告信息,预报潜在危险。
Level ERROR:错误信息,但应用程序仍可继续运行。
Level FATAL:严重错误信息,往往会造成应用程序的退出。
Level OFF:不打印任何信息。
Log4j建议只使用其中的四个级别,因为这些已经可以满足绝大部分应用的需求,优先级从高到低分别是 ERROR、WARN、INFO、DEBUG。
根Logger配置语法为: log4j.rootLogger = [ level ] , [ appenderName],[ appenderName], ...
具体配置写法如下:
log4j.rootLogger=INFO,Console,DailyRolling
Appender
Appender(输出源)负责控制日志记录的输出,其语法为:log4j.appender.[ appenderName ] = fully.qualified.name.of.appender.class
其中appenderName在Logger中定义(如Console和DailyRolling),Log4j提供多种fully.qualified.name.of.appender.class:
org.apache.log4j.ConsoleAppender:将日志信息打印到控制台。
org.apache.log4j.FileAppender:将日志信息打印到文件中。
org.apache.log4j.DailyRollingFileAppender:将日志信息打印到文件中,并每天产生一个文件。
org.apache.log4j.RollingFileAppender:将日志信息打印到文件中,文件大小到达指定尺寸时产生一个新文件。
具体配置写法如下:
#将日志信息打印到文件中,并每天产生一个文件
log4j.appender.DailyRolling=org.apache.log4j.DailyRollingFileAppender
具体配置写法如下:
#将日志信息文件以.log格式保存在logs/目录下,并以Oopsplay命名。
log4j.appender.DailyRolling.File = logs/Oopsplay.log
Layout
Layout提供了四种日志输出样式,读者可以根据需求格式化日志的输出,其语法为:log4j.appender.[ appenderName ].Layout= fully.qualified.name.of.layout.class。
Log4j为Layout提供多种fully.qualified.name.of.appender.class:
org.apache.log4j.HTMLLayout:日志信息采用HTML表格形式布局。
org.apache.log4j.PatternLayout:日志信息采用灵活的指定布局模式。
org.apache.log4j.SimpleLayout:日志中包含信息的级别和信息字符串。
org.apache.log4j.TTCCLayout:日志中包含信息产生的时间、线程、类别等信息。
具体配置写法如下:
#将日志信息采用灵活的指定布局模式
log4j.appender.R.layout=org.apache.log4j.PatternLayout
%m:输出代码中指定的消息。
%p:输出优先级,即DEBUG,INFO,WARN,ERROR,FATAL。
%r:输出自应用启动到输出该log信息耗费的毫秒数。
%c:输出所属的类目,通常就是所在类的全名。
%t:输出产生该日志事件的线程名。
%n:输出一个回车换行符,Windows平台为“\r\n”,Unix平台为“\n”。
%l:输出日志事件的发生位置。
%d:输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyyy-MM-dd HH:mm:ss}。
其语法为:log4j.appender.[ appenderName ].Layout.[ option ] = [ value ]
具体配置写法如下:
log4j.appender.Console.layout.ConversionPattern=%d{ yyyy-MM-dd HH:mm:ss } %-5p %-20c ~ %m%n
配置实例
将日志输出到控制台:
log4j.rootLogger=INFO,Console
log4j.logger.play=INFO
#输出到控制台
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.Target=System.out
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern= %d{ yyyy-MM-dd HH:mm:ss } %-5p %-20c ~ %m%n
将日志输出到文件(文件大小到达指定尺寸时产生一个新文件):
log4j.rootLogger=INFO,Rolling
log4j.logger.play=INFO
#输出到日志文件(文件大小到达指定尺寸时产生一个新文件)
log4j.appender.Rolling=org.apache.log4j.RollingFileAppender
log4j.appender.Rolling.File=logs/Oopaplay.log
#当日志文件大于1MB时生成新的日志文件,后缀可以是KB, MB 或者是GB
log4j.appender.Rolling.MaxFileSize=1MB
#指定可以产生的滚动文件的最大数
log4j.appender.Rolling.MaxBackupIndex=10
log4j.appender.Rolling.layout=org.apache.log4j.PatternLayout
log4j.appender.Rolling.layout.ConversionPattern= %d{ yyyy-MM-dd HH:mm:ss } %-5p %-20c ~ %m%n
将日志输出到文件(每天产生一个文件):
log4j.rootLogger=INFO,DailyRolling
log4j.logger.play=INFO
#输出到日志文件(每天产生一个文件)
log4j.appender.DailyRolling = org.apache.log4j.DailyRollingFileAppender
log4j.appender.DailyRolling.File = logs/Oopaplay.log
log4j.appender.DailyRolling.layout = org.apache.log4j.PatternLayout
log4j.appender.DailyRolling.layout.ConversionPattern= %d{ yyyy-MM-dd HH:mm:ss } %-5p %-20c ~ %m%n
发送Email日志通知:
log4j.rootLogger=FATAL,MAIL
log4j.logger.play=INFO
#发送Email通知
log4j.appender.MAIL=org.apache.log4j.net.SMTPAppender
log4j.appender.MAIL.Threshold=FATAL
#设置缓存,当日志记录数为5条时,发送邮件。默认为1
log4j.appender.MAIL.BufferSize=5
#邮箱登录名,以oopsplay@126.com为例
log4j.appender.MAIL.SMTPUsername=oopsplay
#邮箱登录密码,密码为“123”
log4j.appender.MAIL.SMTPPassword= 123
#以网易126邮箱服务器为例
log4j.appender.MAIL.SMTPHost=smtp.126.com
#邮件主题
log4j.appender.MAIL.Subject=FATAL LOG
#邮箱名
log4j.appender.MAIL.From=oopsplay@126.com
#目的邮箱,多个邮箱可用逗号分隔
log4j.appender.MAIL.To=dhl@oopsplay.org,zst@oopsplay.org
log4j.appender.MAIL.layout=org.apache.log4j.PatternLayout
log4j.appender.MAIL.layout.ConversionPattern= %d{ yyyy-MM-dd HH:mm:ss } %-5p %-20c ~ %m%n
将日志记录到数据库中:
log4j.rootLogger=ERROR,DATEBASE
log4j.logger.play=INFO
#将日志记录数据库(以mysq数据库为例,数据库名为log,数据表名为ERROR_LOG,字段名为message)
log4j.appender.DATABASE=org.apache.log4j.jdbc.JDBCAppender
log4j.appender.DATABASE.Threshold=ERROR
log4j.appender.DATABASE.URL=jdbc:mysql://localhost:3306/log
log4j.appender.DATABASE.driver=com.mysql.jdbc.Driver
log4j.appender.DATABASE.user=root
log4j.appender.DATABASE.password=root
log4j.appender.DATABASE.sql=INSERT INTO ERROR_LOG (message) VALUES ("[Oopsplay]%d{yyyy-MM-dd HH:mm:ss} %t %p- %m%n")
log4j.appender.DATABASE.layout=org.apache.log4j.PatternLayout
log4j.appender.DATABASE.layout.ConversionPattern=[Oopsplay]%d{yyyy-MM-dd HH:mm:ss} %t %p- %m%n
3、
3.1 框架ID#
Play允许我们在安装时为每个框架设定一个ID,以此来解决上述问题。使用 play id 命令定义框架ID,如图3.1所示:
play id
(图3.1 play id命令定义ID)
之后在配置文件中我们可以使用ID作为配置项的前缀来区别配置:
application.name=Cool app
application.mode=dev
application.log=INFO
# id为gbo的配置
%gbo.application.log=DEBUG
%gbo.db=mem
# id为src的配置
%src.http.port=9500
# id为prod的配置
%prod.http.port=80
%prod.application.log=INFO
%prod.application.mode=prod
3.2 命令行中指定框架ID#
Play允许直接在命令行中使用特定的命令,指定框架ID运行。首先,在application.conf中进行如下配置:
application.mode=dev
%production.application.mode=prod
通过以下命令使程序在产品模式下运行:
play run --%production
play test命令与play run --%test命令等价。
4、国际化
配置框架ID
4.1 UTF-8 编码#
应用程序的编码一直是令人非常头痛的问题,因此Play给出了一个较好的解决方案:即所有的编码都采用UTF-8编码格式。
UTF-8是UNICODE的一种变长字符编码,又称万国码。并且已经标准化,可以显示所有语言的字符。
读者在开发Play应用时,请务必保持编码的一致:
- 以UTF-8编码方式编辑所有的源文件。
- 在HTTP请求头中定义适当的编码。
- 将HTML meta标签设置为UTF–8。
- 如果应用使用了数据库,将数据库编码配置为UTF-8格式,并且始终以UTF-8编码方式连接。
读者可能会感到奇怪,为什么Play的配置文件(甚至一些Java属性文件)都不是*.properties后缀格式的。这是因为Java规定属性文件(properties files)必须为iso-8859-1编码,而Play只支持UTF-8一种编码格式。
4.2 定义不同语言的messages文件#
为了使应用程序支持I18n,需要为不同区域的语言配置messages文件。我们可以直接在Play项目的conf/目录下创建messages文件(该文件实际就是Java属性文件),例如:
hello=Hello!
back=Back
也可以为不同语言指定不同的messages文件,并以ISO语言代码作为messages文件的扩展名。例如,创建conf/messages.zh文件,对应中文的国际化支持:
hello=你好!
back=后退
当我们创建了不同语言指定的messages文件之后,需要在conf/application.conf文件中进行配置,添加文件的引用:
application.langs=zh
<h1>&{'hello'}</h1>
4.3 定义支持的语言#
我们可以在conf/application.conf文件中设置程序支持的语言列表,适应多种语言的国际化:
application.langs=fr,en,ja
当用户第一次发送请求时,Play会解析HTTP请求头中Accept-language的信息,识别用户所使用的语言。之后Play会将识别的语言信息保存至PLAY_LANG cookie,并在以后的请求中使用同样的语言进行处理。
我们可以采用“语言_国家”的命名形式区别那些多样化的语言(en_US,en_GB,zh_CN或者zh_TW)。比如:在某个应用程序中,大部分用户来自美国,但同时需要支持英国英语,比较好的定义方式是将“en”作为美国英语,将“en_GB”作为英国英语。
HTTP请求头中的Accept-language常常不包含国家信息,因此需要慎用这种“语言_国家”的形式定义语言。
我们可以在Java代码中直接使用play.i18n.Lang对象获得当前使用的语言:
String lang = Lang.get();
play.i18n.Lang辅助类提供的change()方法可以改变用户语言:
Lang.change("zh");
使用change()方法改变用户语言后,新的语言信息会保存在用户的language cookie中。
4.4 定义时间格式#
我们可以在application.conf文件中进行如下属性设置,定义项目的统一时间格式:
date.format=yyy-MM-dd
date.format.fr=dd/MM/yyyy
此时如果在视图模板中使用${date.format()}进行渲染,format()方法将参照此配置进行格式规定。如果date变量作为HTTP参数进行绑定,也遵循配置中的时间格式。
5.5 本地化messages用法详解#
Messages参数
在程序代码中,我们可以使用play.i18n.Messages类获取messages文件中定义的内容:
public static void hello() {
renderText(Messages.get("hello"));
}
messages文件中的内容是可以使用标准的java.util.Formatter语法进行格式化的,因此当我们需要在messages文件中定义动态内容时,可以使用%s,%d等格式化输出:
hello=Hello %s!
如果messages文件中包含了动态内容,play.i18n.Messages类可以直接传递变量。修改hello Action:
public static void hello(String user) {
renderText(Messages.get("hello", user));
}
模板输出
当应用具备国际化支持后,我们可以在模板文件中使用 &{...} 显示本地化内容:
<h1>&{'hello'}</h1>
<h1>&{'hello', params.user}</h1>
多个参数
如果开发者在使用messages文件时需要使用多个传入参数,可以采用下例写法。该messages文件定义了guess的本地化操作,传入了两个十进制的integer参数。
guess=Please pick a number between %d and %d
在模板中就可以按照messages文件所定义的输出格式进行本地化操作了,但在使用时需要注意参数的顺序,具体使用方式如下。
<p>&{'guess', low, high}</p>
Please pick a number between 10 and 100
参数索引
在使用messages文件时,开发者可以通过索引定位,更加精确地使用传入参数,实现个性化的用法。下例代码展示了英文messages本地化的操作,包含了两个传入参数:
guess.characteristic=Guess %s’s %s.
定义的guess.characteristic包含两个传入参数,在模板中的具体使用如下:
<p>&{'guess.characteristic', person.name, 'age'}</p>
但是有些国家的语言并不是按照这个顺序的,比如法文就是按照相反的顺序书写这句话的。因此,我们在法文的本地化文件messages.fr中就需要使用索引,指定传入参数的顺序。
guess.characteristic=Devinez %2$s de %1$s.
Play还允许针对传入的age参数进行本地化操作,只需要在messages文件中使用&{...}标签即可。如果开发者希望改变模板中age的输出样式,可以将age作为key值,进行本地化设置,下例为messages.zh文件的写法:
guess.characteristic=猜猜%s的&{%s}。
age = 年龄
guess.characteristic=Devinez &{%2$s} de %1$s.
age = l’age
在messages文件中使用&{...}标签后,Play会按照key值进行遍历查找,获取相应的本地化内容。