play框架使用起来(18)
1、集成OpenID
OpenID是身份識別系統(tǒng),具有開放,非集中等特點。我們只需要記錄OpenID授權(quán)用戶的使用信息,不必保持用戶的特定狀態(tài),就可以在程序中很容易地識別新用戶。
OpenID是去中心化的網(wǎng)上身份認證系統(tǒng)。對于支持OpenID的網(wǎng)站,用戶不需要記住像用戶名和密碼這樣的傳統(tǒng)驗證標記。取而代之的是,他們只需要預(yù)先在一個作為OpenID身份提供者(identity provider, IdP)的網(wǎng)站上注冊。OpenID是去中心化的,任何網(wǎng)站都可以使用OpenID來作為用戶登錄的一種方式,任何網(wǎng)站也都可以作為OpenID身份提供者。OpenID既解決了問題而又不需要依賴于中心性的網(wǎng)站來確認數(shù)字身份。
??????下面實例將演示在Play應(yīng)用中如何使用OpenID認證,以下是OpenID認證過程:
- 針對每個用戶請求,先檢查用戶是否已經(jīng)連接OpenID。
- 如果用戶沒有連接OpenID,跳轉(zhuǎn)至用戶可以提交OpenID的頁面。
- 將用戶提交的OpenID重定向到OpenID提供商。
- 返回后,得到驗證通過的OpenID用戶信息,并將它保存在HTTP Session中。
??????Play中的OpenID功能由play.libs.OpenID輔助類提供,基于OpenID4Java實現(xiàn)。我們在控制器中定義authenticate()方法,處理用戶提交的OpenID。該方法先進行用戶OpenID的檢查,如果已經(jīng)連接,則將用戶信息保存在HTTP Session中,并顯示歡迎頁面。如果用戶是第一次提交OpenID,則將提交的OpenID重定向到OpenID提供商:
static void checkAuthenticated() {
? ? if(!session.contains("user")) {
? ? ? ? login();
? ? }
}
?
public static void index() {
? ? render("Hello %s!", session.get("user"));
}
? ? ?
public static void login() {
? ? render();
}
? ?
public static void authenticate(String user) {
? ? if(OpenID.isAuthenticationResponse()) {
? ? ? ? UserInfo verifiedUser = OpenID.getVerifiedID();
? ? ? ? if(verifiedUser == null) {
? ? ? ? ? ? flash.error("Oops. Authentication has failed");
? ? ? ? ? ? login();
? ? ? ? }
? ? ? ? session.put("user", verifiedUser.id);
? ? ? ? index();
? ? } else {
? ? ? ? if(!OpenID.id(user).verify()) { // will redirect the user
? ? ? ? ? ? flash.error("Cannot verify your OpenID");
? ? ? ? ? ? login();
? ? ? ? }
? ? }
}
??????創(chuàng)建Login.html模板,添加用戶提交OpenID的表單:
#{if flash.error}<h1>${flash.error}</h1>
#{/if}
?
<form action="@{Application.authenticate()}" method="POST">
? ? <label for="user">What’s your OpenID?</label>
? ? <input type="text" name="user" id="user" />
? ? <input type="submit" value="login..." />
</form>
??????最后定義路由:
GET ? / ? ? ? ? ? ? ? ? ? ? Application.indexGET ? /login ? ? ? ? ? ? ? ?Application.login
* ? ? /authenticate ? ? ? ? Application.authenticate
2、Ajax請求
2.1 集成jQuery#
??????Play默認集成了jQuery庫,并將其存放于應(yīng)用的public/javascripts目錄。框架對jQuery進行了封裝,提供#{jsAction /}標簽簡化了請求異步調(diào)用控制器中的Action方法,因此我們可以通過jQuery非常方便地處理Ajax請求。??????本節(jié)將以簡單的Ajax應(yīng)用為例,介紹在Play框架中如何使用jQuery提供的Ajax支持。
2.2 使用#{jsAction /}#
??????Play提供的#{jsAction /}標簽會返回一個JavaScript函數(shù),該JavaScript函數(shù)由基于Action方法的URL連接和自由變量組成。它并不會自動執(zhí)行Ajax請求,而需要我們先手動對返回的URL進行配置。
??????下面介紹Play應(yīng)用的Ajax實例。首先在控制器Hotels中定義Action方法list,用于處理瀏覽器異步提交的請求。list方法定義完成后為其配制路由規(guī)則:
GET ? ? /hotels/list ? ? ? ?Hotels.list??????我們在客戶端中可以通過以下方式導(dǎo)入路由:
<script type="text/javascript">? ?var listAction = #{jsAction @list(':search', ':size', ':page') /}
? ?$('#result').load(
? ? ? ?listAction({search: 'x', size: '10', page: '1'}),
? ? ? ?function() {
? ? ? ? ? ?$('#content').css('visibility', 'visible')
? ? ? ?}
? ?)
</script>
??????在這個例子中,我們向Hotels控制器中的list方法發(fā)送請求,請求中包含search,size和page三個參數(shù)。之后將請求保存在listAction變量中,使用load函數(shù)通過jQuery處理該請求(這里處理的是HTTP GET請求)。以下就是所發(fā)送請求的具體URL:
GET /hotels/list?search=x&size=10&page=1??????以這種方式發(fā)送請求會返回HTML數(shù)據(jù)。當然,我們也可以在控制器中使用適當?shù)匿秩痉椒?#xff0c;返回其他格式的數(shù)據(jù),比如renderJSON, renderXML或者直接使用XML的模版等。在客戶端接收到JSON或者XML數(shù)據(jù)后,可以通過jQuery進行格式轉(zhuǎn)換。如果讀者還想了解更多細節(jié)問題,可以查閱jQuery相關(guān)內(nèi)容。
??????如果讀者需要使用POST請求,只需要將jQuery方法進行轉(zhuǎn)換即可:
$.post(listAction(), function(data) {? $('#result').html(data);
});
2.3 使用#{jsRoute /}#
??????Play提供的#{jsRoute /}標簽,可以幫助開發(fā)者更好地管理路由。#{jsRoute /}標簽的使用方法很簡單,并且與#{jsAction /}類似,但是不同的地方為#{jsRoute /}標簽返回的是一個對象。該對象包含基于服務(wù)端Action的URL,以及相應(yīng)的HTTP方法(GET、POST等),具體范例如下所示。<script type="text/javascript">
? ? var updateUserRoute = #{jsRoute @Users.update(':id') /}
? ? $.ajax({
? ? ? url: updateUserRoute.url({id: userId}),
? ? ? type: updateUserRoute.method,
? ? ? data: 'user.name=Guillaume'
? ? });
</script>
??????使用#{jsRoute /}標簽所帶來的好處是顯而易見的,開發(fā)者只需要修改routes路由文件,就可以統(tǒng)一地改變HTTP方法,而不再需要一個一個查看和修改模板文件了。
3、管理數(shù)據(jù)庫升級
開發(fā)人員使用關(guān)系數(shù)據(jù)庫時,通常需要跟蹤和管理數(shù)據(jù)庫結(jié)構(gòu)的升級和變化。當出現(xiàn)以下幾種情況時,我們需要使用更成熟的方式跟蹤和管理數(shù)據(jù)庫結(jié)構(gòu)的變化:
- 在團隊開發(fā)中,每個成員都需要知道數(shù)據(jù)庫結(jié)構(gòu)的任何變化。
- 當成品部署到服務(wù)器上后,需要采用安全穩(wěn)定的方式去升級數(shù)據(jù)庫結(jié)構(gòu)。
- 當開發(fā)人員在不同的機器上工作時,需要保持數(shù)據(jù)庫同步。
如果采用JPA進行操作,Hibernate會自動處理數(shù)據(jù)庫結(jié)構(gòu)的升級。如果讀者經(jīng)常需要手動管理數(shù)據(jù)庫結(jié)構(gòu),并進行一些精細的調(diào)整,版本控制就變得必不可少了。
3.1 升級腳本#
??????Play通過編寫升級腳本來跟蹤數(shù)據(jù)庫結(jié)構(gòu)的變化。這些腳本采用標準的SQL語句作為語法規(guī)則,存放在應(yīng)用程序的db/evolutions目錄下。腳本使用統(tǒng)一的命名規(guī)則:編寫的第一個腳本命名為1.sql,第二個腳本為2.sql,并以此類推。每個腳本都包含兩個部分:
- Ups部分用于描述需要改變的地方。
- Downs部分用于描述如何還原。
??????以下是數(shù)據(jù)庫升級腳本的例子,起到引導(dǎo)作用:
?
# --- !Ups
?
CREATE TABLE User (
? ? id bigint(20) NOT NULL AUTO_INCREMENT,
? ? email varchar(255) NOT NULL,
? ? password varchar(255) NOT NULL,
? ? fullname varchar(255) NOT NULL,
? ? isAdmin boolean NOT NULL,
? ? PRIMARY KEY (id)
);
?
# --- !Downs
?
DROP TABLE User;
在編寫數(shù)據(jù)庫升級腳本時,須使用注釋明確地將Ups和Downs部分區(qū)分開。
??????如果讀者已經(jīng)在application.conf文件中配置了數(shù)據(jù)庫,并且編寫了數(shù)據(jù)庫升級腳本(放置在db/evolutions目錄下),應(yīng)用在啟動時就會自動激活數(shù)據(jù)庫的升級模式。我們也可以強制將其關(guān)閉,只需在application.conf文件中進行如下配置:
(圖1 數(shù)據(jù)庫結(jié)構(gòu)不同步)
??????在頁面上點擊Apply evolutions按鈕,就可以直接執(zhí)行SQL升級腳本。
如果應(yīng)用使用內(nèi)存數(shù)據(jù)庫(db=mem),Play會預(yù)先對數(shù)據(jù)庫進行檢測。如果數(shù)據(jù)庫為空就會自動執(zhí)行所有的升級腳本。
3.2 同步#
??????協(xié)作開發(fā)中,保持應(yīng)用同步非常重要。設(shè)想一下,如果有兩個開發(fā)者合力開發(fā)項目,開發(fā)者A因為自己負責的功能模塊需要,創(chuàng)建了新的數(shù)據(jù)表并編寫了升級腳本2.sql:
# Add Post?
# --- !Ups
CREATE TABLE Post (
? ? id bigint(20) NOT NULL AUTO_INCREMENT,
? ? title varchar(255) NOT NULL,
? ? content text NOT NULL,
? ? postedAt date NOT NULL,
? ? author_id bigint(20) NOT NULL,
? ? FOREIGN KEY (author_id) REFERENCES User(id),
? ? PRIMARY KEY (id)
);
?
# --- !Downs
DROP TABLE Post;
??????Play會將該升級腳本應(yīng)用于開發(fā)者A的數(shù)據(jù)庫。與此同時,開發(fā)者B因功能需求修改了User表,也編寫了升級腳本,同樣命名為2.sql:
# Update User?
# --- !Ups
ALTER TABLE User ADD age INT;
?
# --- !Downs
ALTER TABLE User DROP age;
??????開發(fā)者B先完成了負責的功能模塊,并提交了代碼(比如開發(fā)者采用Git進行管理)。開發(fā)者A需要先整合兩個人前半段的工作結(jié)果,才能開展后續(xù)工作,但是在執(zhí)行g(shù)it pull的時候,合并會出現(xiàn)如下沖突:
Auto-merging db/evolutions/2.sqlCONFLICT (add/add): Merge conflict in db/evolutions/2.sql
Automatic merge failed; fix conflicts and then commit the result.
??????這是因為開發(fā)者A和開發(fā)者B都創(chuàng)建了2.sql升級腳本,必須進行整合:
<<<<<<< HEAD# Add Post
?
# --- !Ups
CREATE TABLE Post (
? ? id bigint(20) NOT NULL AUTO_INCREMENT,
? ? title varchar(255) NOT NULL,
? ? content text NOT NULL,
? ? postedAt date NOT NULL,
? ? author_id bigint(20) NOT NULL,
? ? FOREIGN KEY (author_id) REFERENCES User(id),
? ? PRIMARY KEY (id)
);
?
# --- !Downs
DROP TABLE Post;
=======
# Update User
?
# --- !Ups
ALTER TABLE User ADD age INT;
?
# --- !Downs
ALTER TABLE User DROP age;
>>>>>>> devB
??????整合工作非常簡單,只需要將沖突的部分合并即可:
# Add Post and update User?
# --- !Ups
ALTER TABLE User ADD age INT;
?
CREATE TABLE Post (
? ? id bigint(20) NOT NULL AUTO_INCREMENT,
? ? title varchar(255) NOT NULL,
? ? content text NOT NULL,
? ? postedAt date NOT NULL,
? ? author_id bigint(20) NOT NULL,
? ? FOREIGN KEY (author_id) REFERENCES User(id),
? ? PRIMARY KEY (id)
);
?
# --- !Downs
ALTER TABLE User DROP age;
?
DROP TABLE Post;
??????將整合后的腳本命名為2.sql,作為升級腳本的最新修訂版(注意:與前期開發(fā)者A應(yīng)用到數(shù)據(jù)庫的2.sql不同)。Play會發(fā)現(xiàn)這個情況,并詢問開發(fā)者A是否同步數(shù)據(jù)庫,將最新的修訂版2.sql替換原來的版本,如圖2所示。
(圖2 同步升級腳本)
3.3 非同步狀態(tài)#
??????如果腳本(比如3.sql)中存在錯誤,數(shù)據(jù)庫升級工作將無法完成。當出現(xiàn)這種情況時,Play會將數(shù)據(jù)庫的結(jié)構(gòu)標記為非同步狀態(tài),并要求我們手動解決這個問題,之后才能繼續(xù)別的操作。下例的Ups部分存在錯誤:
# Add another column to User?
# --- !Ups
ALTER TABLE Userxxx ADD company varchar(255);
?
# --- !Downs
ALTER TABLE User DROP company;
??????該腳本在執(zhí)行時會出現(xiàn)錯誤,Play將數(shù)據(jù)庫的結(jié)構(gòu)標記為非同步狀態(tài),如圖3所示:
(圖3非同步狀態(tài))
??????錯誤必須得到修正后,才能進行后續(xù)操作。使用SQL命令手動更改數(shù)據(jù)庫結(jié)構(gòu):
ALTER TABLE User ADD company varchar(255);??????然后點擊Mark it resolved按鈕,通知框架問題已經(jīng)解決。由于原先的升級腳本存在錯誤,必須將其修正。修改3.sql腳本文件:
# Add another column to User?
# --- !Ups
ALTER TABLE User ADD company varchar(255);
?
# --- !Downs
ALTER TABLE User DROP company;
??????Play會發(fā)現(xiàn)該升級腳本是最新的,因此覆蓋原有的3.sql執(zhí)行,如圖4所示:
(圖4 成功執(zhí)行數(shù)據(jù)庫結(jié)構(gòu)同步)
??????圖4 所顯示的是腳本正確執(zhí)行的結(jié)果,之后可以繼續(xù)別的工作。
介紹一個開發(fā)小技巧:開發(fā)人員可以在每次開發(fā)前手動刪除原有的數(shù)據(jù)庫,當應(yīng)用啟動時框架就會自動加載所有的升級腳本。這樣做的好處是可以使開發(fā)人員的數(shù)據(jù)庫一直保持最新版本。
3.4 升級命令#
??????當應(yīng)用處于DEV模式時,數(shù)據(jù)庫升級是交互式地進行的。但是在PROD模式下,必須先使用play evolutions命令將數(shù)據(jù)庫結(jié)構(gòu)升級為最新版本,才可以開啟應(yīng)用。當框架發(fā)現(xiàn)應(yīng)用程序的數(shù)據(jù)庫不是最新版本時,控制臺會出現(xiàn)如下錯誤信息:
~ ? ? ? ?_ ? ? ? ? ? ?_~ ?_ __ | | __ _ _ ?_| |
~ | '_ \| |/ _' | || |_|
~ | ?__/|_|\____|\__ (_)
~ |_| ? ? ? ? ? ?|__/ ?
~
~ play! master-localbuild, http://www.playframework.org
~ framework ID is prod
~
~ Ctrl+C to stop
~
13:33:22 INFO ?~ Starting ~/test
13:33:22 INFO ?~ Precompiling ...
13:33:24 INFO ?~ Connected to jdbc:mysql://localhost
13:33:24 WARN ?~
13:33:24 WARN ?~ Your database is not up to date.
13:33:24 WARN ?~ Use `play evolutions` command to manage database evolutions.
13:33:24 ERROR ~
?
@662c6n234
Can't start in PROD mode with errors
?
Your database needs evolution!
An SQL script will be run on your database.
?
play.db.Evolutions$InvalidDatabaseRevision
? ? ? ? at play.db.Evolutions.checkEvolutionsState(Evolutions.java:323)
? ? ? ? at play.db.Evolutions.onApplicationStart(Evolutions.java:197)
? ? ? ? at play.Play.start(Play.java:452)
? ? ? ? at play.Play.init(Play.java:298)
? ? ? ? at play.server.Server.main(Server.java:141)
Exception in thread "main" play.db.Evolutions$InvalidDatabaseRevision
? ? ? ? at play.db.Evolutions.checkEvolutionsState(Evolutions.java:323)
? ? ? ? at play.db.Evolutions.onApplicationStart(Evolutions.java:197)
? ? ? ? at play.Play.start(Play.java:452)
? ? ? ? at play.Play.init(Play.java:298)
? ? ? ? at play.server.Server.main(Server.java:141)
??????該錯誤信息提示我們運行play evolutions命令同步數(shù)據(jù)庫:
$ play evolutions~ ? ? ? ?_ ? ? ? ? ? ?_
~ ?_ __ | | __ _ _ ?_| |
~ | '_ \| |/ _' | || |_|
~ | ?__/|_|\____|\__ (_)
~ |_| ? ? ? ? ? ?|__/ ?
~
~ play! master-localbuild, http://www.playframework.org
~ framework ID is gbo
~
~ Connected to jdbc:mysql://localhost
~ Application revision is 3 [15ed3f5] and Database revision is 0 [da39a3e]
~
~ Your database needs evolutions!
?
# ----------------------------------------------------------------------------
?
# --- Rev:1,Ups - 6b21167
?
CREATE TABLE User (
? ? id bigint(20) NOT NULL AUTO_INCREMENT,
? ? email varchar(255) NOT NULL,
? ? password varchar(255) NOT NULL,
? ? fullname varchar(255) NOT NULL,
? ? isAdmin boolean NOT NULL,
? ? PRIMARY KEY (id)
);
?
# --- Rev:2,Ups - 9cf7e12
?
ALTER TABLE User ADD age INT;
CREATE TABLE Post (
? ? id bigint(20) NOT NULL AUTO_INCREMENT,
? ? title varchar(255) NOT NULL,
? ? content text NOT NULL,
? ? postedAt date NOT NULL,
? ? author_id bigint(20) NOT NULL,
? ? FOREIGN KEY (author_id) REFERENCES User(id),
? ? PRIMARY KEY (id)
);
?
# --- Rev:3,Ups - 15ed3f5
?
ALTER TABLE User ADD company varchar(255);
?
# ----------------------------------------------------------------------------
?
~ Run `play evolutions:apply` to automatically apply this script to the db
~ or apply it yourself and mark it done using `play evolutions:markApplied`
~
??????使用play evolutions:apply命令可以通知Play自動執(zhí)行數(shù)據(jù)庫升級腳本:
play evolutions:apply??????如果在成品數(shù)據(jù)庫上手動執(zhí)行腳本,可以使用play evolutions:markApplied命令通知Play該數(shù)據(jù)庫已經(jīng)是最新版本了:
play evolutions:markApplied??????當應(yīng)用處于DEV模式,如果框架自動執(zhí)行升級腳本失敗,就需要手動解決,并將數(shù)據(jù)庫結(jié)構(gòu)標記為已修正的:
play evolutions:resolve
總結(jié)
以上是生活随笔為你收集整理的play框架使用起来(18)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 需求分析挑战之旅(疯狂的订餐系统)(6)
- 下一篇: Python爬虫实战:QQ空间全自动点赞