使用者註冊、登入和登出是任何一個網站都必然會有的功能,可以說,這是重新造輪子做多的領域,每個做網站的人應該都做過很多遍。見微知著,從這麼一個小功能其實就可以看到所使用的web架構中的大部分東西。
今天就讓我們用這個基本模組來看看revel吧。
先整理一下我們選用的技術架構和組件:
web架構:revel
資料庫:mongodb
資料庫driver:mgo
工欲善其事,必先利其器,這裡著重推薦一個mongodb的GUI用戶端 - mongovue,可以說,如果沒有這個工具,在開發的過程中我們會痛苦許多許多。
這裡假設你已經有了對Go語言最基本的知識,已經配置好GOROOT和GOPATH。
首先,在GOPATH下面運行下面的命令安裝revel,並且把revel的工具編譯出來。
go get github.com/robfig/revel
go build –o bin/revel.exe github.com/robfig/revel/revel
完成之後去GOPATH\bin下面看看是否已經編譯出來了revel.exe。為了方便使用,我把GOPATH\bin添加到了環境變數PATH中。
到你希望存放工程檔案的地方運行
revel new myapp
整個工程的架構就建立好了,看下面的檔案夾結構就可以看出,revel是一個MVC架構。
此時整個工程就可以運行了,運行下面的命令列啟動網站。
revel run myapp
開啟瀏覽器 http://127.0.0.1:9000,就可以看到下面的結果
內部的細節暫時不多說,來吧,先讓使用者可以註冊。注意,在整個開發過程中大部分時候不需要重新啟動revel。
1. 準備Model
按照MVC的開發節奏,我們先準備model。在app目錄下建立一個models目錄,然後在裡面建立entity.go(這個檔案的命名大家可自便),開啟entity.go加入User的實體定義。
type User struct {
Email string
Nickname string
Password []byte
}
type MockUser struct {
Email string
Nickname string
Password string
ConfirmPassword string
}
為什麼定義MockUser呢?原因後面會提到。
現在寫dal(資料訪問層),在app\models目錄下建立dal.go。dal的寫法其實可以用revel的外掛程式機制,這裡為了避免一下子引入太多概念,先用這種簡單的方式。
package models
import (
"github.com/robfig/revel"
"labix.org/v2/mgo"
)
const (
DbName = "myapp"
UserCollection = "user"
)
type Dal struct {
session *mgo.Session
}
func NewDal() (*Dal, error) {
revel.Config.SetSection("db")
ip, found := revel.Config.String("ip")
if !found {
revel.ERROR.Fatal("Cannot load database ip from app.conf")
}
session, err := mgo.Dial(ip)
if err != nil {
return nil, err
}
return &Dal{session}, nil
}
func (d *Dal) Close() {
d.session.Close()
}
revel已經提供了配置系統,開啟conf\app.conf,添加下面內容
[db]
ip = 127.0.0.1
現在實現註冊需要用到的方法,在app\models目錄下添加檔案dal_account.go,代碼如下。
func (d *Dal) RegisterUser(mu *MockUser) error {
uc := d.session.DB(DbName).C(UserCollection)
//先檢查email和nickname是否已經被使用
i, _ := uc.Find(M{"nickname": mu.Nickname}).Count()
if i != 0 {
return errors.New("使用者暱稱已經被使用")
}
i, _ = uc.Find(M{"email": mu.Email}).Count()
if i != 0 {
return errors.New("郵件地址已經被使用")
}
var u User
u.Email = mu.Email
u.Nickname = mu.Nickname
u.Password, _ = bcrypt.GenerateFromPassword([]byte(mu.Password), bcrypt.DefaultCost)
err := uc.Insert(u)
return err
}
看出來MockUser存在的意義了嗎?使用者在頁面上填寫的是明文的密碼,這可不能直接存入資料庫,需要先加密,這裡用到了"code.google.com/p/go.crypto/bcrypt"這個庫。
2. 準備Controller
準備controller,在app\controllers建立一個檔案account.go,在裡面實現Account控制器,代碼如下。
package controllers
import (
"github.com/robfig/revel"
"myapp/app/models"
)
type Account struct {
*revel.Controller
}
func (c *Account) Register() revel.Result {
return c.Render()
}
func (c *Account) PostRegister(user *models.MockUser) revel.Result {
return c.Render()
}
3. 添加Route
準備route,開啟conf\routes,添加Register的URL映射。
# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~
module:testrunner
GET / App.Index
GET /register Account.Register
POST /register Account.PostRegister
# Ignore favicon requests
GET /favicon.ico 404
# Map static resources from the /app/public folder to the /public path
GET /public/*filepath Static.Serve("public")
# Catch all
* /:controller/:action :controller.:action
假定大家都知道Restful是啥意思,這裡就是把兩個url映射到了Controller的兩個Action。
可以看到,這裡定義了所有的URL到Controller之間的映射,很方便。這個檔案在運行前會被revel轉換成app\routes\routes.go檔案參與編譯。後面在講到ReverseRedirect的時候需要用到這個檔案裡的內容。
4. 準備View
準備view,在app\views下面建立檔案Register.html,關鍵內容如下
<form action="{{url "Account.PostRegister"}}" method="POST">
{{with $field := field "user.Email" .}}
<div class="control-group {{$field.ErrorClass}}">
<label class="control-label" for="{{$field.Id}}">電子郵件</label>
<div class="controls">
<input type="email" id="{{$field.Id}}" name="{{$field.Name}}" value="{{$field.Flash}}" required>
{{if $field.Error}}
<span class="help-inline">{{$field.Error}}</span>
{{end}}
</div>
</div>
{{end}}
…
一點一點解釋一下上面藍色部分關鍵字的含義。
url是revel提供的一個template function,可以很方便的把Controller的Action變成與之相對的url,它的運作原理實際上就是去剛才定義好的routes映射裡面尋找。
field是revel提供的一個template function,專門方便產生form,還記得PostRegister方法的簽名嗎?
func (c *Account) PostRegister(user *models.MockUser) revel.Result
它接受一個名為user的*models.User類型的參數,所以,使用{{with $field := field “user.Email”}}就可以通知revel將form的參數封裝到user結構中再傳遞給PostRegister。
我們都知道使用者註冊的時候填寫的值是需要做有效性檢驗的,當使用者填寫的值不符合標準時需要出現錯誤提示,通常來說會是下面這樣
$field.ErrorClass的作用就是當這個參數出現錯誤的時候可以方便的通過添加class的方式在頁面上顯示錯誤狀態。ErrorClass的值可以通過下面的代碼修改。
revel.ERROR_CLASS = "error"
$field.Id和$field.Name就不解釋了,大家待會兒開啟瀏覽器中看看產生的原始碼就明白了。
$field.Flash這裡就需要解釋一下Flash的概念了。
Flash是一個字典,適用於在兩個Request中間傳遞資料,資料存放區在cookie中。
大家都知道,HTTP協議本身是無狀態的,所以,考慮一下這個用例,使用者在註冊的時候輸入了一個無效的email地址,點擊註冊之後頁面重新整理了一下,“電子郵件”下面出現一行紅字“你輸入的Email地址無效”,此刻文字框裡面需要出現上次使用者輸入的值。那麼,$field.Flash就是在Flash裡去找以$field.Name為Key的值。
$field.Error就是在Flash裡找以$field.Name_error為Key的值,也就是中紅色的“密碼必須大於等於6位”這個錯誤資訊。
好了,現在大家就按照這個節奏在view中添加“暱稱”,“密碼”和“確認密碼”吧。
添加完成之後就去訪問http://127.0.0.1/register看看吧。是不是這樣呢?
revel會通過Controller.Action的名稱去尋找同名的view檔案,例如,Register方法對應的就是Register.html。這裡需要注意的一點是,revel是通過反射去尋找Controller.Render方法的調用者,而且只向上尋找一層。
例如,下面這段代碼是不能工作的。
func (c *Account) someMethod() revel.Result {
...
return c.Render()
}
func (c *Account) Register() revel.Result {
return c.someMethod()
}
5. 實現Controller
現在讓我們為PostRegister添加處理註冊的邏輯。
首先,驗證參數的有效性。
func (c *Account) PostRegister(user *models.MockUser) revel.Result {
c.Validation.Required(user)
c.Validation.Email(user.Email)
c.Validation.Required(user.Nickname)
c.Validation.Required(user.Password)
c.Validation.Required(user.ConfirmPassword == user.Password)
if c.Validation.HasErrors() {
c.FlashParams()
return c.Redirect((*Account).Register)
}
return c.Render()
}
revel提供了挺好用的Validation機制,上面的代碼應該不需要太多解釋,只有一行
c.FlashParams()
它的作用就是把form提交的參數原樣存入Flash中,還記得剛才的$field.Flash嗎?
現在去玩玩註冊頁面吧,填寫一些錯誤的值看看反應吧,嗯,你應該很快就會發現,錯誤資訊雖然已經顯示出來,但可惜卻是英文的,修改一下吧。
func (c *Account) PostRegister(user *models.MockUser) revel.Result {
c.Validation.Email(user.Email).Message("電子郵件格式無效")
c.Validation.Required(user.Nickname).Message("使用者暱稱不可為空")
c.Validation.Required(user.Password).Message("密碼不可為空")
c.Validation.Required(user.ConfirmPassword == user.Password).Message("兩次輸入的密碼不一致")
if c.Validation.HasErrors() {
c.FlashParams()
return c.Redirect((*Account).Register)
}
return c.Render()
}
Validation提供了好幾個常用的驗證方法,大家可以自己看看,應該是簡單易懂的。
繼續,當所有參數檢查都通過之後,就調用dal.RegisterUser方法將使用者資訊存入資料庫。
func (c *Account) PostRegister(user *models.MockUser) revel.Result {
c.Validation.Email(user.Email).Message("電子郵件格式無效")
c.Validation.Required(user.Nickname).Message("使用者暱稱不可為空")
c.Validation.Required(user.Password).Message("密碼不可為空")
c.Validation.Required(user.ConfirmPassword == user.Password).Message("兩次輸入的密碼不一致")
if c.Validation.HasErrors() {
c.FlashParams()
return c.Redirect((*Account).Register)
}
dal, err := models.NewDal()
if err != nil {
c.Response.Status = 500
return c.RenderError(err)
}
defer dal.Close()
err = dal.RegisterUser(user)
if err != nil {
c.Flash.Error(err.Error())
return c.Redirect((*Account).Register)
}
return c.Redirect((*Account).RegisterSuccessful)
}
func (c *Account) RegisterSuccessful() revel.Result {
return c.Render()
}
我增加了一個方法RegisterSuccessful,用於顯示註冊成功,大家別忘了在routes和view中添加相應的東西。
至此,使用者註冊已經完成。不知道大家注意到沒有,就算修改go代碼,依然不需要重新啟動revel,直接重新整理瀏覽器頁面就會發現新的代碼已經自動編譯並且啟用了。
這就是revel.exe存在的一個意義,它會監視整個工程的檔案修改情況,然後自動編譯和運行。開發的時候感覺就像在使用python和ruby一樣舒服。
今天已經提到了revel的不少知識點了,希望對大家有用,歡迎任何問題!
最後提一句,我們的創業產品 山坡網 就是完全用revel寫的,這是一個經得住考驗的架構。