play框架使用起來(18)

1、集成OpenID

OpenID是身份識別系統,具有開放,非集中等特點。我們只需要記錄OpenID授權用戶的使用信息,不必保持用戶的特定狀態,就可以在程序中很容易地識別新用戶。


補充:

OpenID是去中心化的網上身份認證系統。對於支持OpenID的網站,用戶不需要記住像用戶名和密碼這樣的傳統驗證標記。取而代之的是,他們只需要預先在一個作爲OpenID身份提供者(identity provider, IdP)的網站上註冊。OpenID是去中心化的,任何網站都可以使用OpenID來作爲用戶登錄的一種方式,任何網站也都可以作爲OpenID身份提供者。OpenID既解決了問題而又不需要依賴於中心性的網站來確認數字身份。


      下面實例將演示在Play應用中如何使用OpenID認證,以下是OpenID認證過程:

  • 針對每個用戶請求,先檢查用戶是否已經連接OpenID。
  • 如果用戶沒有連接OpenID,跳轉至用戶可以提交OpenID的頁面。
  • 將用戶提交的OpenID重定向到OpenID提供商。
  • 返回後,得到驗證通過的OpenID用戶信息,並將它保存在HTTP Session中。


      Play中的OpenID功能由play.libs.OpenID輔助類提供,基於OpenID4Java實現。我們在控制器中定義authenticate()方法,處理用戶提交的OpenID。該方法先進行用戶OpenID的檢查,如果已經連接,則將用戶信息保存在HTTP Session中,並顯示歡迎頁面。如果用戶是第一次提交OpenID,則將提交的OpenID重定向到OpenID提供商:

@Before(unless={"login", "authenticate"})
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
();
       
}
   
}
}

      創建Login.html模板,添加用戶提交OpenID的表單:

#{if flash.error}
<h1>${flash.error}</h1>
#{/if}
 
<form action="@{Application.authenticate()}" method="POST">
   
<label for="user">Whats your OpenID?</label>
   
<input type="text" name="user" id="user" />
   
<input type="submit" value="login..." />
</form>

      最後定義路由:

GET   /                     Application.index
GET  
/login                Application.login
*     /authenticate         Application.authenticate

2、Ajax請求

2.1 集成jQuery#

      Play默認集成了jQuery庫,並將其存放於應用的public/javascripts目錄。框架對jQuery進行了封裝,提供#{jsAction /}標籤簡化了請求異步調用控制器中的Action方法,因此我們可以通過jQuery非常方便地處理Ajax請求。

      本節將以簡單的Ajax應用爲例,介紹在Play框架中如何使用jQuery提供的Ajax支持。


2.2 使用#{jsAction /}#

      Play提供的#{jsAction /}標籤會返回一個JavaScript函數,該JavaScript函數由基於Action方法的URL連接和自由變量組成。它並不會自動執行Ajax請求,而需要我們先手動對返回的URL進行配置。

      下面介紹Play應用的Ajax實例。首先在控制器Hotels中定義Action方法list,用於處理瀏覽器異步提交的請求。list方法定義完成後爲其配製路由規則:

GET     /hotels/list        Hotels.list

      我們在客戶端中可以通過以下方式導入路由:

<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方法發送請求,請求中包含search,size和page三個參數。之後將請求保存在listAction變量中,使用load函數通過jQuery處理該請求(這裏處理的是HTTP GET請求)。以下就是所發送請求的具體URL:

GET /hotels/list?search=x&size=10&page=1

      以這種方式發送請求會返回HTML數據。當然,我們也可以在控制器中使用適當的渲染方法,返回其他格式的數據,比如renderJSON, renderXML或者直接使用XML的模版等。在客戶端接收到JSON或者XML數據後,可以通過jQuery進行格式轉換。如果讀者還想了解更多細節問題,可以查閱jQuery相關內容。

      如果讀者需要使用POST請求,只需要將jQuery方法進行轉換即可:

$.post(listAction(), function(data) {
  $
('#result').html(data);
});


2.3 使用#{jsRoute /}#

      Play提供的#{jsRoute /}標籤,可以幫助開發者更好地管理路由。#{jsRoute /}標籤的使用方法很簡單,並且與#{jsAction /}類似,但是不同的地方爲#{jsRoute /}標籤返回的是一個對象。該對象包含基於服務端Action的URL,以及相應的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 /}標籤所帶來的好處是顯而易見的,開發者只需要修改routes路由文件,就可以統一地改變HTTP方法,而不再需要一個一個查看和修改模板文件了。


3、管理數據庫升級

開發人員使用關係數據庫時,通常需要跟蹤和管理數據庫結構的升級和變化。當出現以下幾種情況時,我們需要使用更成熟的方式跟蹤和管理數據庫結構的變化:

  • 在團隊開發中,每個成員都需要知道數據庫結構的任何變化。
  • 當成品部署到服務器上後,需要採用安全穩定的方式去升級數據庫結構。
  • 當開發人員在不同的機器上工作時,需要保持數據庫同步。


注意:

如果採用JPA進行操作,Hibernate會自動處理數據庫結構的升級。如果讀者經常需要手動管理數據庫結構,並進行一些精細的調整,版本控制就變得必不可少了。


3.1 升級腳本#

      Play通過編寫升級腳本來跟蹤數據庫結構的變化。這些腳本採用標準的SQL語句作爲語法規則,存放在應用程序的db/evolutions目錄下。腳本使用統一的命名規則:編寫的第一個腳本命名爲1.sql,第二個腳本爲2.sql,並以此類推。每個腳本都包含兩個部分:

  • Ups部分用於描述需要改變的地方。
  • Downs部分用於描述如何還原。


      以下是數據庫升級腳本的例子,起到引導作用:

# Users schema
 
# --- !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;


注意:

在編寫數據庫升級腳本時,須使用註釋明確地將Ups和Downs部分區分開。


      如果讀者已經在application.conf文件中配置了數據庫,並且編寫了數據庫升級腳本(放置在db/evolutions目錄下),應用在啓動時就會自動激活數據庫的升級模式。我們也可以強制將其關閉,只需在application.conf文件中進行如下配置:

evolutions.enabled=false
      一旦數據庫的升級模式被激活,框架會執行升級管理。當應用處於DEV模式,Play會在請求到達時檢查數據庫的結構,如果數據庫結構不是最新的,將顯示錯誤頁面,並建議使用合適的SQL腳本同步數據庫,如圖1所示。如果應用處於PROD模式,Play則會在整個應用啓動之前進行檢查。

(圖1 數據庫結構不同步)

      在頁面上點擊Apply evolutions按鈕,就可以直接執行SQL升級腳本。


補充:

如果應用使用內存數據庫(db=mem),Play會預先對數據庫進行檢測。如果數據庫爲空就會自動執行所有的升級腳本。


3.2 同步#

      協作開發中,保持應用同步非常重要。設想一下,如果有兩個開發者合力開發項目,開發者A因爲自己負責的功能模塊需要,創建了新的數據表並編寫了升級腳本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會將該升級腳本應用於開發者A的數據庫。與此同時,開發者B因功能需求修改了User表,也編寫了升級腳本,同樣命名爲2.sql:

# Update User
 
# --- !Ups
ALTER TABLE
User ADD age INT;
 
# --- !Downs
ALTER TABLE
User DROP age;

      開發者B先完成了負責的功能模塊,並提交了代碼(比如開發者採用Git進行管理)。開發者A需要先整合兩個人前半段的工作結果,才能開展後續工作,但是在執行git pull的時候,合併會出現如下衝突:

Auto-merging db/evolutions/2.sql
CONFLICT
(add/add): Merge conflict in db/evolutions/2.sql
Automatic merge failed; fix conflicts and then commit the result.

      這是因爲開發者A和開發者B都創建了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,作爲升級腳本的最新修訂版(注意:與前期開發者A應用到數據庫的2.sql不同)。Play會發現這個情況,並詢問開發者A是否同步數據庫,將最新的修訂版2.sql替換原來的版本,如圖2所示。

(圖2 同步升級腳本)


3.3 非同步狀態#

      如果腳本(比如3.sql)中存在錯誤,數據庫升級工作將無法完成。當出現這種情況時,Play會將數據庫的結構標記爲非同步狀態,並要求我們手動解決這個問題,之後才能繼續別的操作。下例的Ups部分存在錯誤:

# Add another column to User
 
# --- !Ups
ALTER TABLE
Userxxx ADD company varchar(255);
 
# --- !Downs
ALTER TABLE
User DROP company;

      該腳本在執行時會出現錯誤,Play將數據庫的結構標記爲非同步狀態,如圖3所示:

(圖3非同步狀態)

      錯誤必須得到修正後,才能進行後續操作。使用SQL命令手動更改數據庫結構:

ALTER TABLE User ADD company varchar(255);

      然後點擊Mark it resolved按鈕,通知框架問題已經解決。由於原先的升級腳本存在錯誤,必須將其修正。修改3.sql腳本文件:

# Add another column to User
 
# --- !Ups
ALTER TABLE
User ADD company varchar(255);
 
# --- !Downs
ALTER TABLE
User DROP company;

      Play會發現該升級腳本是最新的,因此覆蓋原有的3.sql執行,如圖4所示:

(圖4 成功執行數據庫結構同步)

      圖4 所顯示的是腳本正確執行的結果,之後可以繼續別的工作。


補充:

介紹一個開發小技巧:開發人員可以在每次開發前手動刪除原有的數據庫,當應用啓動時框架就會自動加載所有的升級腳本。這樣做的好處是可以使開發人員的數據庫一直保持最新版本。


3.4 升級命令#

      當應用處於DEV模式時,數據庫升級是交互式地進行的。但是在PROD模式下,必須先使用play evolutions命令將數據庫結構升級爲最新版本,纔可以開啓應用。當框架發現應用程序的數據庫不是最新版本時,控制檯會出現如下錯誤信息:

~        _            _ 
~  _ __ | | __ _ _  _| |
~ | '_ \| |/ _' | || |_|
~ |  __/|_|\____|\__ (_)
~ |_|            |__/  
~
~ 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命令同步數據庫:

$ 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自動執行數據庫升級腳本:

play evolutions:apply

      如果在成品數據庫上手動執行腳本,可以使用play evolutions:markApplied命令通知Play該數據庫已經是最新版本了:

play evolutions:markApplied

      當應用處於DEV模式,如果框架自動執行升級腳本失敗,就需要手動解決,並將數據庫結構標記爲已修正的:

play evolutions:resolve


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章