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
#郵箱登錄名,以[email protected]爲例
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值進行遍歷查找,獲取相應的本地化內容。