Replace the spellings of github/Github with GitHub

This commit is contained in:
TAKAHASHI Shuuji
2019-06-17 06:52:04 +09:00
parent f939c7e6cb
commit 02bf9fc253
50 changed files with 3675 additions and 3675 deletions

View File

@@ -1,191 +1,191 @@
# 1.2 GOPATH 與工作空間
前面我們在安裝 Go 的時候看到需要設定 GOPATH 變數Go 從 1.1 版本到 1.7 必須設定這個變數,而且不能和 Go 的安裝目錄一樣,這個目錄用來存放 Go 原始碼Go 的可執行檔案以及相應的編譯之後的套件檔案。所以這個目錄下面有三個子目錄src、bin、pkg
從 go 1.8 開始GOPATH 環境變數現在有一個預設值,如果它沒有被設定。 它在 Unix 上預設為$HOME/go在 Windows 上預設為%USERPROFILE%/go。
## GOPATH 設定
go 命令依賴一個重要的環境變數:$GOPATH
Windows 系統中環境變數的形式為`%GOPATH%`,本書主要使用 Unix 形式Windows 使用者請自行替換。
*(注:這個不是 Go 安裝目錄。下面以筆者的工作目錄為範例,如果你想不一樣請把 GOPATH 替換成你的工作目錄。)*
在類別 Unix 環境下大概這樣設定:
```sh
export GOPATH=/home/apple/mygo
```
為了方便,應該建立以上資料夾,並且上一行加入到 `.bashrc` 或者 `.zshrc` 或者自己的 `sh` 的配置檔案中。
Windows 設定如下,建立一個環境變數名稱叫做 GOPATH
```sh
GOPATH=c:\mygo
```
GOPATH 允許多個目錄,當有多個目錄時,請注意分隔符,多個目錄的時候 Windows 是分號Linux 系統是冒號,當有多個 GOPATH 時,預設會將 go get 的內容放在第一個目錄下。
以上 $GOPATH 目錄約定有三個子目錄:
- src 存放原始碼(比如:.go .c .h .s 等)
- pkg 編譯後生成的檔案(比如:.a
- bin 編譯後生成的可執行檔案(為了方便,可以把此目錄加入到 $PATH 變數中,如果有多個 gopath那麼使用`${GOPATH//://bin:}/bin`新增所有的 bin 目錄)
以後我所有的例子都是以 mygo 作為我的 gopath 目錄
## 程式碼目錄結構規劃
GOPATH 下的 src 目錄就是接下來開發程式的主要目錄,所有的原始碼都是放在這個目錄下面,那麼一般我們的做法就是一個目錄一個專案,例如: $GOPATH/src/mymath 表示 mymath 這個套件或者可執行應用,這個根據 package 是 main 還是其他來決定main 的話就是可執行應用,其他的話就是套件,這個會在後續詳細介紹 package。
所以當建立應用或者一個程式碼套件時都是在 src 目錄下建立一個資料夾,資料夾名稱一般是程式碼套件名稱,當然也允許多階層目錄,例如在 src 下面建立了目錄$GOPATH/src/github.com/astaxie/beedb 那麼這個套件路徑就是"github.com/astaxie/beedb",套件名稱是最後一個目錄 beedb
下面我就以 mymath 為例來講述如何編寫套件,執行如下程式碼
```sh
cd $GOPATH/src
mkdir mymath
```
建立檔案 sqrt.go內容如下
```go
// $GOPATH/src/mymath/sqrt.go 原始碼如下:
package mymath
func Sqrt(x float64) float64 {
z := 0.0
for i := 0; i < 1000; i++ {
z -= (z*z - x) / (2 * x)
}
return z
}
```
這樣我的套件目錄和程式碼已經建立完畢,注意:一般建議 package 的名稱和目錄名保持一致
## 編譯應用
上面我們已經建立了自己的套件,如何進行編譯安裝呢?有兩種方式可以進行安裝
1、只要進入對應的套件目錄然後執行`go install`,就可以安裝了
2、在任意的目錄執行如下程式碼`go install mymath`
安裝完之後,我們可以進入如下目錄
```sh
cd $GOPATH/pkg/${GOOS}_${GOARCH}
//可以看到如下檔案
mymath.a
```
這個.a 檔案是套件,那麼我們如何進行呼叫呢?
接下來我們建立一個應用程式來呼叫這個套件
建立套件 mathapp
```sh
cd $GOPATH/src
mkdir mathapp
cd mathapp
vim main.go
```
`$GOPATH/src/mathapp/main.go`原始碼:
```go
package main
import (
"mymath"
"fmt"
)
func main() {
fmt.Printf("Hello, world. Sqrt(2) = %v\n", mymath.Sqrt(2))
}
```
可以看到這個的 package 是`main`import 裡面呼叫的套件是`mymath`,這個就是相對於`$GOPATH/src`的路徑,如果是多階層目錄,就在 import 裡面引入多階層目錄,如果你有多個 GOPATH也是一樣Go 會自動在多個`$GOPATH/src`中尋找。
如何編譯程式呢?進入該應用目錄,然後執行`go build`,那麼在該目錄下面會產生一個 mathapp 的可執行檔案
```sh
./mathapp
```
輸出如下內容
```sh
Hello, world. Sqrt(2) = 1.414213562373095
```
如何安裝該應用,進入該目錄執行`go install`,那麼在$GOPATH/bin/下增加了一個可執行檔案 mathapp, 還記得前面我們把`$GOPATH/bin`加到我們的 PATH 裡面了,這樣可以在命令列輸入如下命令就可以執行
```sh
mathapp
```
也是輸出如下內容
Hello, world. Sqrt(2) = 1.414213562373095
這裡我們展示如何編譯和安裝一個可執行的應用,以及如何設計我們的目錄結構。
## 取得遠端套件
go 語言有一個取得遠端套件的工具就是`go get`,目前 go get 支援多數開源社群(例如:github、googlecode、bitbucket、Launchpad)
go get github.com/astaxie/beedb
>go get -u 參數可以自動更新套件,而且當 go get 的時候會自動取得該套件依賴的其他第三方套件
透過這個命令可以取得相應的原始碼,對應的開源平臺採用不同的原始碼控制工具,例如 github 採用 git、googlecode 採用 hg所以要想取得這些原始碼必須先安裝相應的原始碼控制工具
透過上面取得的程式碼在我們本地的原始碼相應的程式碼結構如下
$GOPATH
src
|--github.com
|-astaxie
|-beedb
pkg
|--相應平臺
|-github.com
|--astaxie
|beedb.a
go get 本質上可以理解為首先第一步是透過原始碼工具 clone 程式碼到 src 下面,然後執行`go install`
在程式碼中如何使用遠端套件,很簡單的就是和使用本地套件一樣,只要在開頭 import 相應的路徑就可以
import "github.com/astaxie/beedb"
## 程式的整體結構
透過上面建立的我本地的 mygo 的目錄結構如下所示
bin/
mathapp
pkg/
平臺名/ 如darwin_amd64、linux_amd64
mymath.a
github.com/
astaxie/
beedb.a
src/
mathapp
main.go
mymath/
sqrt.go
github.com/
astaxie/
beedb/
beedb.go
util.go
從上面的結構我們可以很清晰的看到bin 目錄下面存的是編譯之後可執行的檔案pkg 下面存放的是套件src 下面儲存的是應用原始碼
## links
* [目錄](<preface.md>)
* 上一節: [安裝 Go](<01.1.md>)
* 下一節: [GO 命令](<01.3.md>)
# 1.2 GOPATH 與工作空間
前面我們在安裝 Go 的時候看到需要設定 GOPATH 變數Go 從 1.1 版本到 1.7 必須設定這個變數,而且不能和 Go 的安裝目錄一樣,這個目錄用來存放 Go 原始碼Go 的可執行檔案以及相應的編譯之後的套件檔案。所以這個目錄下面有三個子目錄src、bin、pkg
從 go 1.8 開始GOPATH 環境變數現在有一個預設值,如果它沒有被設定。 它在 Unix 上預設為$HOME/go在 Windows 上預設為%USERPROFILE%/go。
## GOPATH 設定
go 命令依賴一個重要的環境變數:$GOPATH
Windows 系統中環境變數的形式為`%GOPATH%`,本書主要使用 Unix 形式Windows 使用者請自行替換。
*(注:這個不是 Go 安裝目錄。下面以筆者的工作目錄為範例,如果你想不一樣請把 GOPATH 替換成你的工作目錄。)*
在類別 Unix 環境下大概這樣設定:
```sh
export GOPATH=/home/apple/mygo
```
為了方便,應該建立以上資料夾,並且上一行加入到 `.bashrc` 或者 `.zshrc` 或者自己的 `sh` 的配置檔案中。
Windows 設定如下,建立一個環境變數名稱叫做 GOPATH
```sh
GOPATH=c:\mygo
```
GOPATH 允許多個目錄,當有多個目錄時,請注意分隔符,多個目錄的時候 Windows 是分號Linux 系統是冒號,當有多個 GOPATH 時,預設會將 go get 的內容放在第一個目錄下。
以上 $GOPATH 目錄約定有三個子目錄:
- src 存放原始碼(比如:.go .c .h .s 等)
- pkg 編譯後生成的檔案(比如:.a
- bin 編譯後生成的可執行檔案(為了方便,可以把此目錄加入到 $PATH 變數中,如果有多個 gopath那麼使用`${GOPATH//://bin:}/bin`新增所有的 bin 目錄)
以後我所有的例子都是以 mygo 作為我的 gopath 目錄
## 程式碼目錄結構規劃
GOPATH 下的 src 目錄就是接下來開發程式的主要目錄,所有的原始碼都是放在這個目錄下面,那麼一般我們的做法就是一個目錄一個專案,例如: $GOPATH/src/mymath 表示 mymath 這個套件或者可執行應用,這個根據 package 是 main 還是其他來決定main 的話就是可執行應用,其他的話就是套件,這個會在後續詳細介紹 package。
所以當建立應用或者一個程式碼套件時都是在 src 目錄下建立一個資料夾,資料夾名稱一般是程式碼套件名稱,當然也允許多階層目錄,例如在 src 下面建立了目錄$GOPATH/src/github.com/astaxie/beedb 那麼這個套件路徑就是"github.com/astaxie/beedb",套件名稱是最後一個目錄 beedb
下面我就以 mymath 為例來講述如何編寫套件,執行如下程式碼
```sh
cd $GOPATH/src
mkdir mymath
```
建立檔案 sqrt.go內容如下
```go
// $GOPATH/src/mymath/sqrt.go 原始碼如下:
package mymath
func Sqrt(x float64) float64 {
z := 0.0
for i := 0; i < 1000; i++ {
z -= (z*z - x) / (2 * x)
}
return z
}
```
這樣我的套件目錄和程式碼已經建立完畢,注意:一般建議 package 的名稱和目錄名保持一致
## 編譯應用
上面我們已經建立了自己的套件,如何進行編譯安裝呢?有兩種方式可以進行安裝
1、只要進入對應的套件目錄然後執行`go install`,就可以安裝了
2、在任意的目錄執行如下程式碼`go install mymath`
安裝完之後,我們可以進入如下目錄
```sh
cd $GOPATH/pkg/${GOOS}_${GOARCH}
//可以看到如下檔案
mymath.a
```
這個.a 檔案是套件,那麼我們如何進行呼叫呢?
接下來我們建立一個應用程式來呼叫這個套件
建立套件 mathapp
```sh
cd $GOPATH/src
mkdir mathapp
cd mathapp
vim main.go
```
`$GOPATH/src/mathapp/main.go`原始碼:
```go
package main
import (
"mymath"
"fmt"
)
func main() {
fmt.Printf("Hello, world. Sqrt(2) = %v\n", mymath.Sqrt(2))
}
```
可以看到這個的 package 是`main`import 裡面呼叫的套件是`mymath`,這個就是相對於`$GOPATH/src`的路徑,如果是多階層目錄,就在 import 裡面引入多階層目錄,如果你有多個 GOPATH也是一樣Go 會自動在多個`$GOPATH/src`中尋找。
如何編譯程式呢?進入該應用目錄,然後執行`go build`,那麼在該目錄下面會產生一個 mathapp 的可執行檔案
```sh
./mathapp
```
輸出如下內容
```sh
Hello, world. Sqrt(2) = 1.414213562373095
```
如何安裝該應用,進入該目錄執行`go install`,那麼在$GOPATH/bin/下增加了一個可執行檔案 mathapp, 還記得前面我們把`$GOPATH/bin`加到我們的 PATH 裡面了,這樣可以在命令列輸入如下命令就可以執行
```sh
mathapp
```
也是輸出如下內容
Hello, world. Sqrt(2) = 1.414213562373095
這裡我們展示如何編譯和安裝一個可執行的應用,以及如何設計我們的目錄結構。
## 取得遠端套件
go 語言有一個取得遠端套件的工具就是`go get`,目前 go get 支援多數開源社群(例如:GitHub、googlecode、bitbucket、Launchpad)
go get github.com/astaxie/beedb
>go get -u 參數可以自動更新套件,而且當 go get 的時候會自動取得該套件依賴的其他第三方套件
透過這個命令可以取得相應的原始碼,對應的開源平臺採用不同的原始碼控制工具,例如 GitHub 採用 git、googlecode 採用 hg所以要想取得這些原始碼必須先安裝相應的原始碼控制工具
透過上面取得的程式碼在我們本地的原始碼相應的程式碼結構如下
$GOPATH
src
|--github.com
|-astaxie
|-beedb
pkg
|--相應平臺
|-github.com
|--astaxie
|beedb.a
go get 本質上可以理解為首先第一步是透過原始碼工具 clone 程式碼到 src 下面,然後執行`go install`
在程式碼中如何使用遠端套件,很簡單的就是和使用本地套件一樣,只要在開頭 import 相應的路徑就可以
import "github.com/astaxie/beedb"
## 程式的整體結構
透過上面建立的我本地的 mygo 的目錄結構如下所示
bin/
mathapp
pkg/
平臺名/ 如darwin_amd64、linux_amd64
mymath.a
github.com/
astaxie/
beedb.a
src/
mathapp
main.go
mymath/
sqrt.go
github.com/
astaxie/
beedb/
beedb.go
util.go
從上面的結構我們可以很清晰的看到bin 目錄下面存的是編譯之後可執行的檔案pkg 下面存放的是套件src 下面儲存的是應用原始碼
## links
* [目錄](<preface.md>)
* 上一節: [安裝 Go](<01.1.md>)
* 下一節: [GO 命令](<01.3.md>)

View File

@@ -1,211 +1,211 @@
# 1.3 Go 命令
## Go 命令
Go 語言自帶有一套完整的命令列工具,你可以透過在命令列中執行 `go` 來檢視它們:
![](images/1.1.mac.png)
圖 1.3 Go 命令顯示詳細的資訊
這些命令對於我們平時編寫的程式碼非常有用,接下來就讓我們了解一些常用的命令。
## go build
這個命令主要用於編譯程式碼。在套件的編譯過程中,若有必要,會同時編譯與之相關聯的套件。
- 如果是普通套件,就像我們在 1.2 節中編寫的 `mymath` 套件那樣,當你執行`go build`之後,它不會產生任何檔案。如果你需要在`$GOPATH/pkg`下產生相應的檔案,那就得執行`go install`
- 如果是 `main` 套件,當你執行`go build`之後,它就會在當前目錄下產生一個可執行檔案。如果你需要在`$GOPATH/bin`下產生相應的檔案,需要執行`go install`,或者使用`go build -o 路徑/a.exe`
- 如果某個專案資料夾下有多個檔案,而你只想編譯某個檔案,就可在`go build`之後加上檔名,例如`go build a.go``go build`命令預設會編譯當前目錄下的所有 go 檔案。
- 你也可以指定編譯輸出的檔名。例如 1.2 節中的 `mathapp` 應用,我們可以指定`go build -o astaxie.exe`,預設情況是你的 package 名(非 main 套件),或者是第一個原始檔的檔名(main 套件)。
實際上package 名在 [Go 語言規範](https://golang.org/ref/spec)中指程式碼中“package”後使用的名稱此名稱可以與資料夾名不同。預設產生的可執行檔名是資料夾名。
- go build 會忽略目錄下以“_”或“.”開頭的 go 檔案。
- 如果你的原始碼針對不同的作業系統需要不同的處理,那麼你可以根據不同的作業系統字尾來命名檔案。例如有一個讀取陣列的程式,它對於不同的作業系統可能有如下幾個原始檔:
array_linux.go
array_darwin.go
array_windows.go
array_freebsd.go
`go build`的時候會選擇性地編譯以系統名結尾的檔案Linux、Darwin、Windows、Freebsd。例如 Linux 系統下面編譯只會選擇 array_linux.go 檔案,其它系統命名字尾檔案全部忽略。
參數的介紹
- `-o` 指定輸出的檔名,可以帶上路徑,例如 `go build -o a/b/c`
- `-i` 安裝相應的套件,編譯+`go install`
- `-a` 更新全部已經是最新的套件的,但是對標準套件不適用
- `-n` 把需要執行的編譯命令顯示出來,但是不執行,這樣就可以很容易的知道底層是如何執行的
- `-p n` 指定可以並行可執行的編譯數目,預設是 CPU 數目
- `-race` 開啟編譯的時候自動檢測資料競爭的情況,目前只支援 64 位的機器
- `-v` 顯示出來我們正在編譯的套件名
- `-work` 顯示出來編譯時候的臨時資料夾名稱,並且如果已經存在的話就不要刪除
- `-x` 顯示出來執行的命令,其實就是和`-n`的結果類似,只是這個會執行
- `-ccflags 'arg list'` 傳遞參數給 5c, 6c, 8c 呼叫
- `-compiler name` 指定相應的編譯器gccgo 還是 gc
- `-gccgoflags 'arg list'` 傳遞參數給 gccgo 編譯連線呼叫
- `-gcflags 'arg list'` 傳遞參數給 5g, 6g, 8g 呼叫
- `-installsuffix suffix` 為了和預設的安裝套件區別開來,採用這個字首來重新安裝那些依賴的套件,`-race`的時候預設已經是`-installsuffix race`,大家可以透過`-n`命令來驗證
- `-ldflags 'flag list'` 傳遞參數給 5l, 6l, 8l 呼叫
- `-tags 'tag list'` 設定在編譯的時候可以適配的那些 tag詳細的 tag 限制參考裡面的 [Build Constraints](http://golang.org/pkg/go/build/)
## go clean
這個命令是用來移除當前原始碼套件和關聯原始碼套件裡面編譯產生的檔案。這些檔案包括
_obj/ 舊的 object 目錄,由 Makefiles 遺留
_test/ 舊的 test 目錄,由 Makefiles 遺留
_testmain.go 舊的 gotest 檔案,由 Makefiles 遺留
test.out 舊的 test 記錄,由 Makefiles 遺留
build.out 舊的 test 記錄,由 Makefiles 遺留
*.[568ao] object 檔案,由 Makefiles 遺留
DIR(.exe) 由 go build 產生
DIR.test(.exe) 由 go test -c 產生
MAINFILE(.exe) 由 go build MAINFILE.go 產生
*.so 由 SWIG 產生
我一般都是利用這個命令清除編譯檔案,然後 github 提交原始碼,在本機測試的時候這些編譯檔案都是和系統相關的,但是對於原始碼管理來說沒必要。
$ go clean -i -n
cd /Users/astaxie/develop/gopath/src/mathapp
rm -f mathapp mathapp.exe mathapp.test mathapp.test.exe app app.exe
rm -f /Users/astaxie/develop/gopath/bin/mathapp
參數介紹
- `-i` 清除關聯的安裝的套件和可執行檔案,也就是透過 go install 安裝的檔案
- `-n` 把需要執行的清除命令顯示出來,但是不執行,這樣就可以很容易的知道底層是如何執行的
- `-r` 迴圈的清除在 import 中引入的套件
- `-x` 顯示出來執行的詳細命令,其實就是`-n`列印的執行版本
## go fmt
有過 C/C++經驗的讀者會知道,一些人經常為程式碼採取 K&R 風格還是 ANSI 風格而爭論不休。在 go 中,程式碼則有標準的風格。由於之前已經有的一些習慣或其它的原因我們常將程式碼寫成 ANSI 風格或者其它更合適自己的格式,這將為人們在閱讀別人的程式碼時新增不必要的負擔,所以 go 強制了程式碼格式比如左大括號必須放在行尾不按照此格式的程式碼將不能編譯透過為了減少浪費在排版上的時間go 工具集中提供了一個`go fmt`命令 它可以幫你格式化你寫好的程式碼檔案,使你寫程式碼的時候不需要關心格式,你只需要在寫完之後執行`go fmt <檔名>.go`,你的程式碼就被修改成了標準格式,但是我平常很少用到這個命令,因為開發工具裡面一般都帶了儲存時候自動格式化功能,這個功能其實在底層就是呼叫了`go fmt`。接下來的一節我將講述兩個工具,這兩個工具都自帶了儲存檔案時自動化`go fmt`功能。
使用 go fmt 命令,其實是呼叫了 gofmt而且需要參數-w否則格式化結果不會寫入檔案。gofmt -w -l src可以格式化整個專案。
所以 go fmt 是 gofmt 的上層一個封裝命令,如果想要更多的自訂格式化,可以參考 [gofmt](http://golang.org/cmd/gofmt/)
gofmt 的參數介紹
- `-l` 顯示那些需要格式化的檔案
- `-w` 把改寫後的內容直接寫入到檔案中,而不是作為結果列印到標準輸出。
- `-r` 新增形如“a[b:len(a)] -> a[b:]”的重寫規則,方便我們做批量替換
- `-s` 簡化檔案中的程式碼
- `-d` 顯示格式化前後的 diff 而不是寫入檔案,預設是 false
- `-e` 列印所有的語法錯誤到標準輸出。如果不使用此標記,則只會列印不同行的前 10 個錯誤。
- `-cpuprofile` 支援除錯模式,寫入相應的 cpufile 到指定的檔案
## go get
這個命令是用來動態取得遠端程式碼套件的,目前支援的有 BitBucket、GitHub、Google Code 和 Launchpad。這個命令在內部實際上分成了兩步操作第一步是下載原始碼套件第二步是執行`go install`。下載原始碼套件的 go 工具會自動根據不同的域名呼叫不同的原始碼工具,對應關係如下:
BitBucket (Mercurial Git)
GitHub (Git)
Google Code Project Hosting (Git, Mercurial, Subversion)
Launchpad (Bazaar)
所以為了`go get` 能正常工作,你必須確保安裝了合適的原始碼管理工具,並同時把這些命令加入你的 PATH 中。其實`go get`支援自訂域名的功能,具體參見`go help remote`
參數介紹:
- `-d` 只下載不安裝
- `-f` 只有在你包含了`-u`參數的時候才有效,不讓`-u`去驗證 import 中的每一個都已經取得了,這對於本地 fork 的套件特別有用
- `-fix` 在取得原始碼之後先執行 fix然後再去做其他的事情
- `-t` 同時也下載需要為執行測試所需要的套件
- `-u` 強制使用網路去更新套件和它的相依套件
- `-v` 顯示執行的命令
## go install
這個命令在內部實際上分成了兩步操作:第一步是產生結果檔案(可執行檔案或者.a 套件),第二步會把編譯好的結果移到`$GOPATH/pkg`或者`$GOPATH/bin`
參數支援`go build`的編譯參數。大家只要記住一個參數`-v`就好了,這個隨時隨地的可以檢視底層的執行資訊。
## go test
執行這個命令,會自動讀取原始碼目錄下面名為`*_test.go`的檔案,產生並執行測試用的可執行檔案。輸出的資訊類似
ok archive/tar 0.011s
FAIL archive/zip 0.022s
ok compress/gzip 0.033s
...
預設的情況下,不需要任何的參數,它會自動把你原始碼套件下面所有 test 檔案測試完畢,當然你也可以帶上參數,詳情請參考`go help testflag`
這裡我介紹幾個我們常用的參數:
- `-bench regexp` 執行相應的 benchmarks例如 `-bench=.`
- `-cover` 開啟測試覆蓋率
- `-run regexp` 只執行 regexp 匹配的函式,例如 `-run=Array` 那麼就執行包含有 Array 開頭的函式
- `-v` 顯示測試的詳細命令
## go tool
`go tool`下面下載聚集了很多命令這裡我們只介紹兩個fix 和 vet
- `go tool fix .` 用來修復以前老版本的程式碼到新版本,例如 go1 之前老版本的程式碼轉化到 go1例如 API 的變化
- `go tool vet directory|files` 用來分析當前目錄的程式碼是否都是正確的程式碼,例如是不是呼叫 fmt.Printf 裡面的參數不正確,例如函式裡面提前 return 了然後出現了無用程式碼之類別的。
## go generate
這個命令是從 Go1.4 開始才設計的,用於在編譯前自動化產生某類別程式碼。`go generate``go build`是完全不一樣的命令,透過分析原始碼中特殊的註釋,然後執行相應的命令。這些命令都是很明確的,沒有任何的依賴在裡面。而且大家在用這個之前心裡面一定要有一個觀念,這個 `go generate` 是給你用的,不是給使用你這個套件的人用的,主要是方便你自動產生一些程式碼。
這裡我們來舉一個簡單的例子,例如我們經常會使用 `yacc` 來產生程式碼,那麼我們常用這樣的命令:
go tool yacc -o gopher.go -p parser gopher.y
-o 指定了輸出的檔名, -p 指定了 package 的名稱,這是一個單獨的命令,如果我們想讓`go generate`來觸發這個命令,那麼就可以在當前目錄的任意一個`xxx.go`檔案裡面的任意位置增加一行如下的註釋:
//go:generate go tool yacc -o gopher.go -p parser gopher.y
這裡我們注意了,`//go:generate`是沒有任何空格的,這其實就是一個固定的格式,在掃描原始碼檔案的時候就是根據這個來判斷的。
所以我們可以透過如下的命令來產生,編譯,測試。如果`gopher.y`檔案有修改,那麼就重新執行`go generate`重新產生檔案就好。
$ go generate
$ go build
$ go test
## godoc
在 Go1.2 版本之前還支援`go doc`命令,但是之後全部移到了 godoc 這個命令下,需要這樣安裝`go get golang.org/x/tools/cmd/godoc`
很多人說 go 不需要任何的第三方文件,例如 chm 手冊之類別的(其實我已經做了一個了,[chm 手冊](https://github.com/astaxie/godoc)),因為它內部就有一個很強大的文件工具。
如何檢視相應 package 的文件呢?
例如 builtin 套件,那麼執行`godoc builtin`
如果是 http 套件,那麼執行`godoc net/http`
檢視某一個套件裡面的函式,那麼執行`godoc fmt Printf`
也可以檢視相應的程式碼,執行`godoc -src fmt Printf`
透過命令在命令列執行 godoc -http=:埠號 比如`godoc -http=:8080`。然後在瀏覽器中開啟`127.0.0.1:8080`,你將會看到一個 golang.org 的本地 copy 版本,透過它你可以查詢 pkg 文件等其它內容。如果你設定了 GOPATH在 pkg 分類下,不但會列出標準套件的文件,還會列出你本地 `GOPATH` 中所有專案的相關文件,這對於經常被牆的使用者來說是一個不錯的選擇。
## 其它命令
go 還提供了其它很多的工具,例如下面的這些工具
go version 檢視 go 當前的版本
go env 檢視當前 go 的環境變數
go list 列出當前全部安裝的 package
go run 編譯並執行 Go 程式
以上這些工具還有很多參數沒有一一介紹,使用者可以使用`go help 命令 ` 取得更詳細的幫助資訊。
## links
* [目錄](<preface.md>)
* 上一節: [GOPATH 與工作空間](<01.2.md>)
* 下一節: [Go 開發工具](<01.4.md>)
# 1.3 Go 命令
## Go 命令
Go 語言自帶有一套完整的命令列工具,你可以透過在命令列中執行 `go` 來檢視它們:
![](images/1.1.mac.png)
圖 1.3 Go 命令顯示詳細的資訊
這些命令對於我們平時編寫的程式碼非常有用,接下來就讓我們了解一些常用的命令。
## go build
這個命令主要用於編譯程式碼。在套件的編譯過程中,若有必要,會同時編譯與之相關聯的套件。
- 如果是普通套件,就像我們在 1.2 節中編寫的 `mymath` 套件那樣,當你執行`go build`之後,它不會產生任何檔案。如果你需要在`$GOPATH/pkg`下產生相應的檔案,那就得執行`go install`
- 如果是 `main` 套件,當你執行`go build`之後,它就會在當前目錄下產生一個可執行檔案。如果你需要在`$GOPATH/bin`下產生相應的檔案,需要執行`go install`,或者使用`go build -o 路徑/a.exe`
- 如果某個專案資料夾下有多個檔案,而你只想編譯某個檔案,就可在`go build`之後加上檔名,例如`go build a.go``go build`命令預設會編譯當前目錄下的所有 go 檔案。
- 你也可以指定編譯輸出的檔名。例如 1.2 節中的 `mathapp` 應用,我們可以指定`go build -o astaxie.exe`,預設情況是你的 package 名(非 main 套件),或者是第一個原始檔的檔名(main 套件)。
實際上package 名在 [Go 語言規範](https://golang.org/ref/spec)中指程式碼中“package”後使用的名稱此名稱可以與資料夾名不同。預設產生的可執行檔名是資料夾名。
- go build 會忽略目錄下以“_”或“.”開頭的 go 檔案。
- 如果你的原始碼針對不同的作業系統需要不同的處理,那麼你可以根據不同的作業系統字尾來命名檔案。例如有一個讀取陣列的程式,它對於不同的作業系統可能有如下幾個原始檔:
array_linux.go
array_darwin.go
array_windows.go
array_freebsd.go
`go build`的時候會選擇性地編譯以系統名結尾的檔案Linux、Darwin、Windows、Freebsd。例如 Linux 系統下面編譯只會選擇 array_linux.go 檔案,其它系統命名字尾檔案全部忽略。
參數的介紹
- `-o` 指定輸出的檔名,可以帶上路徑,例如 `go build -o a/b/c`
- `-i` 安裝相應的套件,編譯+`go install`
- `-a` 更新全部已經是最新的套件的,但是對標準套件不適用
- `-n` 把需要執行的編譯命令顯示出來,但是不執行,這樣就可以很容易的知道底層是如何執行的
- `-p n` 指定可以並行可執行的編譯數目,預設是 CPU 數目
- `-race` 開啟編譯的時候自動檢測資料競爭的情況,目前只支援 64 位的機器
- `-v` 顯示出來我們正在編譯的套件名
- `-work` 顯示出來編譯時候的臨時資料夾名稱,並且如果已經存在的話就不要刪除
- `-x` 顯示出來執行的命令,其實就是和`-n`的結果類似,只是這個會執行
- `-ccflags 'arg list'` 傳遞參數給 5c, 6c, 8c 呼叫
- `-compiler name` 指定相應的編譯器gccgo 還是 gc
- `-gccgoflags 'arg list'` 傳遞參數給 gccgo 編譯連線呼叫
- `-gcflags 'arg list'` 傳遞參數給 5g, 6g, 8g 呼叫
- `-installsuffix suffix` 為了和預設的安裝套件區別開來,採用這個字首來重新安裝那些依賴的套件,`-race`的時候預設已經是`-installsuffix race`,大家可以透過`-n`命令來驗證
- `-ldflags 'flag list'` 傳遞參數給 5l, 6l, 8l 呼叫
- `-tags 'tag list'` 設定在編譯的時候可以適配的那些 tag詳細的 tag 限制參考裡面的 [Build Constraints](http://golang.org/pkg/go/build/)
## go clean
這個命令是用來移除當前原始碼套件和關聯原始碼套件裡面編譯產生的檔案。這些檔案包括
_obj/ 舊的 object 目錄,由 Makefiles 遺留
_test/ 舊的 test 目錄,由 Makefiles 遺留
_testmain.go 舊的 gotest 檔案,由 Makefiles 遺留
test.out 舊的 test 記錄,由 Makefiles 遺留
build.out 舊的 test 記錄,由 Makefiles 遺留
*.[568ao] object 檔案,由 Makefiles 遺留
DIR(.exe) 由 go build 產生
DIR.test(.exe) 由 go test -c 產生
MAINFILE(.exe) 由 go build MAINFILE.go 產生
*.so 由 SWIG 產生
我一般都是利用這個命令清除編譯檔案,然後 GitHub 提交原始碼,在本機測試的時候這些編譯檔案都是和系統相關的,但是對於原始碼管理來說沒必要。
$ go clean -i -n
cd /Users/astaxie/develop/gopath/src/mathapp
rm -f mathapp mathapp.exe mathapp.test mathapp.test.exe app app.exe
rm -f /Users/astaxie/develop/gopath/bin/mathapp
參數介紹
- `-i` 清除關聯的安裝的套件和可執行檔案,也就是透過 go install 安裝的檔案
- `-n` 把需要執行的清除命令顯示出來,但是不執行,這樣就可以很容易的知道底層是如何執行的
- `-r` 迴圈的清除在 import 中引入的套件
- `-x` 顯示出來執行的詳細命令,其實就是`-n`列印的執行版本
## go fmt
有過 C/C++經驗的讀者會知道,一些人經常為程式碼採取 K&R 風格還是 ANSI 風格而爭論不休。在 go 中,程式碼則有標準的風格。由於之前已經有的一些習慣或其它的原因我們常將程式碼寫成 ANSI 風格或者其它更合適自己的格式,這將為人們在閱讀別人的程式碼時新增不必要的負擔,所以 go 強制了程式碼格式比如左大括號必須放在行尾不按照此格式的程式碼將不能編譯透過為了減少浪費在排版上的時間go 工具集中提供了一個`go fmt`命令 它可以幫你格式化你寫好的程式碼檔案,使你寫程式碼的時候不需要關心格式,你只需要在寫完之後執行`go fmt <檔名>.go`,你的程式碼就被修改成了標準格式,但是我平常很少用到這個命令,因為開發工具裡面一般都帶了儲存時候自動格式化功能,這個功能其實在底層就是呼叫了`go fmt`。接下來的一節我將講述兩個工具,這兩個工具都自帶了儲存檔案時自動化`go fmt`功能。
使用 go fmt 命令,其實是呼叫了 gofmt而且需要參數-w否則格式化結果不會寫入檔案。gofmt -w -l src可以格式化整個專案。
所以 go fmt 是 gofmt 的上層一個封裝命令,如果想要更多的自訂格式化,可以參考 [gofmt](http://golang.org/cmd/gofmt/)
gofmt 的參數介紹
- `-l` 顯示那些需要格式化的檔案
- `-w` 把改寫後的內容直接寫入到檔案中,而不是作為結果列印到標準輸出。
- `-r` 新增形如“a[b:len(a)] -> a[b:]”的重寫規則,方便我們做批量替換
- `-s` 簡化檔案中的程式碼
- `-d` 顯示格式化前後的 diff 而不是寫入檔案,預設是 false
- `-e` 列印所有的語法錯誤到標準輸出。如果不使用此標記,則只會列印不同行的前 10 個錯誤。
- `-cpuprofile` 支援除錯模式,寫入相應的 cpufile 到指定的檔案
## go get
這個命令是用來動態取得遠端程式碼套件的,目前支援的有 BitBucket、GitHub、Google Code 和 Launchpad。這個命令在內部實際上分成了兩步操作第一步是下載原始碼套件第二步是執行`go install`。下載原始碼套件的 go 工具會自動根據不同的域名呼叫不同的原始碼工具,對應關係如下:
BitBucket (Mercurial Git)
GitHub (Git)
Google Code Project Hosting (Git, Mercurial, Subversion)
Launchpad (Bazaar)
所以為了`go get` 能正常工作,你必須確保安裝了合適的原始碼管理工具,並同時把這些命令加入你的 PATH 中。其實`go get`支援自訂域名的功能,具體參見`go help remote`
參數介紹:
- `-d` 只下載不安裝
- `-f` 只有在你包含了`-u`參數的時候才有效,不讓`-u`去驗證 import 中的每一個都已經取得了,這對於本地 fork 的套件特別有用
- `-fix` 在取得原始碼之後先執行 fix然後再去做其他的事情
- `-t` 同時也下載需要為執行測試所需要的套件
- `-u` 強制使用網路去更新套件和它的相依套件
- `-v` 顯示執行的命令
## go install
這個命令在內部實際上分成了兩步操作:第一步是產生結果檔案(可執行檔案或者.a 套件),第二步會把編譯好的結果移到`$GOPATH/pkg`或者`$GOPATH/bin`
參數支援`go build`的編譯參數。大家只要記住一個參數`-v`就好了,這個隨時隨地的可以檢視底層的執行資訊。
## go test
執行這個命令,會自動讀取原始碼目錄下面名為`*_test.go`的檔案,產生並執行測試用的可執行檔案。輸出的資訊類似
ok archive/tar 0.011s
FAIL archive/zip 0.022s
ok compress/gzip 0.033s
...
預設的情況下,不需要任何的參數,它會自動把你原始碼套件下面所有 test 檔案測試完畢,當然你也可以帶上參數,詳情請參考`go help testflag`
這裡我介紹幾個我們常用的參數:
- `-bench regexp` 執行相應的 benchmarks例如 `-bench=.`
- `-cover` 開啟測試覆蓋率
- `-run regexp` 只執行 regexp 匹配的函式,例如 `-run=Array` 那麼就執行包含有 Array 開頭的函式
- `-v` 顯示測試的詳細命令
## go tool
`go tool`下面下載聚集了很多命令這裡我們只介紹兩個fix 和 vet
- `go tool fix .` 用來修復以前老版本的程式碼到新版本,例如 go1 之前老版本的程式碼轉化到 go1例如 API 的變化
- `go tool vet directory|files` 用來分析當前目錄的程式碼是否都是正確的程式碼,例如是不是呼叫 fmt.Printf 裡面的參數不正確,例如函式裡面提前 return 了然後出現了無用程式碼之類別的。
## go generate
這個命令是從 Go1.4 開始才設計的,用於在編譯前自動化產生某類別程式碼。`go generate``go build`是完全不一樣的命令,透過分析原始碼中特殊的註釋,然後執行相應的命令。這些命令都是很明確的,沒有任何的依賴在裡面。而且大家在用這個之前心裡面一定要有一個觀念,這個 `go generate` 是給你用的,不是給使用你這個套件的人用的,主要是方便你自動產生一些程式碼。
這裡我們來舉一個簡單的例子,例如我們經常會使用 `yacc` 來產生程式碼,那麼我們常用這樣的命令:
go tool yacc -o gopher.go -p parser gopher.y
-o 指定了輸出的檔名, -p 指定了 package 的名稱,這是一個單獨的命令,如果我們想讓`go generate`來觸發這個命令,那麼就可以在當前目錄的任意一個`xxx.go`檔案裡面的任意位置增加一行如下的註釋:
//go:generate go tool yacc -o gopher.go -p parser gopher.y
這裡我們注意了,`//go:generate`是沒有任何空格的,這其實就是一個固定的格式,在掃描原始碼檔案的時候就是根據這個來判斷的。
所以我們可以透過如下的命令來產生,編譯,測試。如果`gopher.y`檔案有修改,那麼就重新執行`go generate`重新產生檔案就好。
$ go generate
$ go build
$ go test
## godoc
在 Go1.2 版本之前還支援`go doc`命令,但是之後全部移到了 godoc 這個命令下,需要這樣安裝`go get golang.org/x/tools/cmd/godoc`
很多人說 go 不需要任何的第三方文件,例如 chm 手冊之類別的(其實我已經做了一個了,[chm 手冊](https://github.com/astaxie/godoc)),因為它內部就有一個很強大的文件工具。
如何檢視相應 package 的文件呢?
例如 builtin 套件,那麼執行`godoc builtin`
如果是 http 套件,那麼執行`godoc net/http`
檢視某一個套件裡面的函式,那麼執行`godoc fmt Printf`
也可以檢視相應的程式碼,執行`godoc -src fmt Printf`
透過命令在命令列執行 godoc -http=:埠號 比如`godoc -http=:8080`。然後在瀏覽器中開啟`127.0.0.1:8080`,你將會看到一個 golang.org 的本地 copy 版本,透過它你可以查詢 pkg 文件等其它內容。如果你設定了 GOPATH在 pkg 分類下,不但會列出標準套件的文件,還會列出你本地 `GOPATH` 中所有專案的相關文件,這對於經常被牆的使用者來說是一個不錯的選擇。
## 其它命令
go 還提供了其它很多的工具,例如下面的這些工具
go version 檢視 go 當前的版本
go env 檢視當前 go 的環境變數
go list 列出當前全部安裝的 package
go run 編譯並執行 Go 程式
以上這些工具還有很多參數沒有一一介紹,使用者可以使用`go help 命令 ` 取得更詳細的幫助資訊。
## links
* [目錄](<preface.md>)
* 上一節: [GOPATH 與工作空間](<01.2.md>)
* 下一節: [Go 開發工具](<01.4.md>)

File diff suppressed because it is too large Load Diff

View File

@@ -13,7 +13,7 @@ Go 實現的支援 PostgreSQL 的驅動也很多,因為國外很多人在開
- https://github.com/jbarham/gopgsqldriver 支援 database/sql 驅動,純 Go 寫的
- https://github.com/lxn/go-pgsql 支援 database/sql 驅動,純 Go 寫的
在下面的範例中我採用了第一個驅動,因為它目前使用的人最多,在 github 上也比較活躍。
在下面的範例中我採用了第一個驅動,因為它目前使用的人最多,在 GitHub 上也比較活躍。
## 範例程式碼
資料庫建表語句:

View File

@@ -1,7 +1,7 @@
# 13.6 小結
這一章我們主要介紹了如何實現一個基礎的 Go 語言框架,框架包含有路由設計,由於 Go 內建的 http 套件中路由的一些不足點,我們設計了動態路由規則,然後介紹了 MVC 模式中的 Controller 設計controller 實現了 REST 的實現,這個主要思路來源於 tornado 框架,然後設計實現了範本的 layout 以及自動化渲染等技術,主要採用了 Go 內建的範本引擎,最後我們介紹了一些輔助的日誌、配置等資訊的設計,透過這些設計我們實現了一個基礎的框架 beego目前該框架已經開源在 github最後我們透過 beego 實現了一個部落格系統,透過範例程式碼詳細的展現了如何快速的開發一個站點。
## links
* [目錄](<preface.md>)
* 上一章: [實現部落格的增刪改](<13.5.md>)
* 下一節: [擴充套件 Web 框架](<14.0.md>)
# 13.6 小結
這一章我們主要介紹了如何實現一個基礎的 Go 語言框架,框架包含有路由設計,由於 Go 內建的 http 套件中路由的一些不足點,我們設計了動態路由規則,然後介紹了 MVC 模式中的 Controller 設計controller 實現了 REST 的實現,這個主要思路來源於 tornado 框架,然後設計實現了範本的 layout 以及自動化渲染等技術,主要採用了 Go 內建的範本引擎,最後我們介紹了一些輔助的日誌、配置等資訊的設計,透過這些設計我們實現了一個基礎的框架 beego目前該框架已經開源在 GitHub最後我們透過 beego 實現了一個部落格系統,透過範例程式碼詳細的展現了如何快速的開發一個站點。
## links
* [目錄](<preface.md>)
* 上一章: [實現部落格的增刪改](<13.5.md>)
* 下一節: [擴充套件 Web 框架](<14.0.md>)

View File

@@ -1,275 +1,275 @@
# 14.4 使用者認證
在開發 Web 應用過程中,使用者認證是開發者經常遇到的問題,使用者登入、註冊、登出等操作,而一般認證也分為三個方面的認證
- HTTP Basic 和 HTTP Digest 認證
- 第三方整合認證QQ、微博、豆瓣、OPENID、google、github、facebook 和 twitter 等
- 自訂的使用者登入、註冊、登出,一般都是基於 session、cookie 認證
beego 目前沒有針對這三種方式進行任何形式的整合,但是可以充分的利用第三方開源函式庫來實現上面的三種方式的使用者認證,不過後續 beego 會對前面兩種認證逐步整合。
## HTTP Basic 和 HTTP Digest 認證
這兩個認證是一些應用採用的比較簡單的認證,目前已經有開源的第三方函式庫支援這兩個認證:
```Go
github.com/abbot/go-http-auth
```
下面程式碼示範了如何把這個函式庫引入 beego 中從而實現認證:
```Go
package controllers
import (
"github.com/abbot/go-http-auth"
"github.com/astaxie/beego"
)
func Secret(user, realm string) string {
if user == "john" {
// password is "hello"
return "$1$dlPL2MqE$oQmn16q49SqdmhenQuNgs1"
}
return ""
}
type MainController struct {
beego.Controller
}
func (this *MainController) Prepare() {
a := auth.NewBasicAuthenticator("example.com", Secret)
if username := a.CheckAuth(this.Ctx.Request); username == "" {
a.RequireAuth(this.Ctx.ResponseWriter, this.Ctx.Request)
}
}
func (this *MainController) Get() {
this.Data["Username"] = "astaxie"
this.Data["Email"] = "astaxie@gmail.com"
this.TplNames = "index.tpl"
}
```
上面程式碼利用了 beego 的 prepare 函式,在執行正常邏輯之前呼叫了認證函式,這樣就非常簡單的實現了 http authdigest 的認證也是同樣的原理。
## oauth 和 oauth2 的認證
oauth 和 oauth2 是目前比較流行的兩種認證方式,還好第三方有一個函式庫實現了這個認證,但是是國外實現的,並沒有 QQ、微博之類別的國內應用認證整合
```Go
github.com/bradrydzewski/go.auth
```
下面程式碼示範了如何把該函式庫引入 beego 中從而實現 oauth 的認證,這裡以 github 為例示範:
1. 新增兩條路由
```Go
beego.RegisterController("/auth/login", &controllers.GithubController{})
beego.RegisterController("/mainpage", &controllers.PageController{})
```
2. 然後我們處理 GithubController 登陸的頁面:
```Go
package controllers
import (
"github.com/astaxie/beego"
"github.com/bradrydzewski/go.auth"
)
const (
githubClientKey = "a0864ea791ce7e7bd0df"
githubSecretKey = "a0ec09a647a688a64a28f6190b5a0d2705df56ca"
)
type GithubController struct {
beego.Controller
}
func (this *GithubController) Get() {
// set the auth parameters
auth.Config.CookieSecret = []byte("7H9xiimk2QdTdYI7rDddfJeV")
auth.Config.LoginSuccessRedirect = "/mainpage"
auth.Config.CookieSecure = false
githubHandler := auth.Github(githubClientKey, githubSecretKey)
githubHandler.ServeHTTP(this.Ctx.ResponseWriter, this.Ctx.Request)
}
```
3. 處理登陸成功之後的頁面
```Go
package controllers
import (
"github.com/astaxie/beego"
"github.com/bradrydzewski/go.auth"
"net/http"
"net/url"
)
type PageController struct {
beego.Controller
}
func (this *PageController) Get() {
// set the auth parameters
auth.Config.CookieSecret = []byte("7H9xiimk2QdTdYI7rDddfJeV")
auth.Config.LoginSuccessRedirect = "/mainpage"
auth.Config.CookieSecure = false
user, err := auth.GetUserCookie(this.Ctx.Request)
//if no active user session then authorize user
if err != nil || user.Id() == "" {
http.Redirect(this.Ctx.ResponseWriter, this.Ctx.Request, auth.Config.LoginRedirect, http.StatusSeeOther)
return
}
//else, add the user to the URL and continue
this.Ctx.Request.URL.User = url.User(user.Id())
this.Data["pic"] = user.Picture()
this.Data["id"] = user.Id()
this.Data["name"] = user.Name()
this.TplNames = "home.tpl"
}
```
整個的流程如下,首先開啟瀏覽器輸入地址:
![](images/14.4.github.png)
圖 14.4 顯示帶有登入按鈕的首頁
然後點選連結出現如下介面:
![](images/14.4.github2.png)
圖 14.5 點選登入按鈕後顯示 github 的授權頁
然後點選 Authorize app 就出現如下介面:
![](images/14.4.github3.png)
圖 14.6 授權登入之後顯示的取得到的 github 資訊頁
## 自訂認證
自訂的認證一般都是和 session 結合驗證的,如下程式碼來源於一個基於 beego 的開源部落格:
```Go
//登陸處理
func (this *LoginController) Post() {
this.TplNames = "login.tpl"
this.Ctx.Request.ParseForm()
username := this.Ctx.Request.Form.Get("username")
password := this.Ctx.Request.Form.Get("password")
md5Password := md5.New()
io.WriteString(md5Password, password)
buffer := bytes.NewBuffer(nil)
fmt.Fprintf(buffer, "%x", md5Password.Sum(nil))
newPass := buffer.String()
now := time.Now().Format("2006-01-02 15:04:05")
userInfo := models.GetUserInfo(username)
if userInfo.Password == newPass {
var users models.User
users.Last_logintime = now
models.UpdateUserInfo(users)
//登入成功設定 session
sess := globalSessions.SessionStart(this.Ctx.ResponseWriter, this.Ctx.Request)
sess.Set("uid", userInfo.Id)
sess.Set("uname", userInfo.Username)
this.Ctx.Redirect(302, "/")
}
}
//註冊處理
func (this *RegController) Post() {
this.TplNames = "reg.tpl"
this.Ctx.Request.ParseForm()
username := this.Ctx.Request.Form.Get("username")
password := this.Ctx.Request.Form.Get("password")
usererr := checkUsername(username)
fmt.Println(usererr)
if usererr == false {
this.Data["UsernameErr"] = "Username error, Please to again"
return
}
passerr := checkPassword(password)
if passerr == false {
this.Data["PasswordErr"] = "Password error, Please to again"
return
}
md5Password := md5.New()
io.WriteString(md5Password, password)
buffer := bytes.NewBuffer(nil)
fmt.Fprintf(buffer, "%x", md5Password.Sum(nil))
newPass := buffer.String()
now := time.Now().Format("2006-01-02 15:04:05")
userInfo := models.GetUserInfo(username)
if userInfo.Username == "" {
var users models.User
users.Username = username
users.Password = newPass
users.Created = now
users.Last_logintime = now
models.AddUser(users)
//登入成功設定 session
sess := globalSessions.SessionStart(this.Ctx.ResponseWriter, this.Ctx.Request)
sess.Set("uid", userInfo.Id)
sess.Set("uname", userInfo.Username)
this.Ctx.Redirect(302, "/")
} else {
this.Data["UsernameErr"] = "User already exists"
}
}
func checkPassword(password string) (b bool) {
if ok, _ := regexp.MatchString("^[a-zA-Z0-9]{4,16}$", password); !ok {
return false
}
return true
}
func checkUsername(username string) (b bool) {
if ok, _ := regexp.MatchString("^[a-zA-Z0-9]{4,16}$", username); !ok {
return false
}
return true
}
```
有了使用者登陸和註冊之後,其他模組的地方可以增加如下這樣的使用者是否登陸的判斷:
```Go
func (this *AddBlogController) Prepare() {
sess := globalSessions.SessionStart(this.Ctx.ResponseWriter, this.Ctx.Request)
sess_uid := sess.Get("userid")
sess_username := sess.Get("username")
if sess_uid == nil {
this.Ctx.Redirect(302, "/admin/login")
return
}
this.Data["Username"] = sess_username
}
```
## links
* [目錄](<preface.md>)
* 上一節: [表單及驗證支援](<14.3.md>)
* 下一節: [多語言支援](<14.5.md>)
# 14.4 使用者認證
在開發 Web 應用過程中,使用者認證是開發者經常遇到的問題,使用者登入、註冊、登出等操作,而一般認證也分為三個方面的認證
- HTTP Basic 和 HTTP Digest 認證
- 第三方整合認證QQ、微博、豆瓣、OPENID、google、GitHub、facebook 和 twitter 等
- 自訂的使用者登入、註冊、登出,一般都是基於 session、cookie 認證
beego 目前沒有針對這三種方式進行任何形式的整合,但是可以充分的利用第三方開源函式庫來實現上面的三種方式的使用者認證,不過後續 beego 會對前面兩種認證逐步整合。
## HTTP Basic 和 HTTP Digest 認證
這兩個認證是一些應用採用的比較簡單的認證,目前已經有開源的第三方函式庫支援這兩個認證:
```Go
github.com/abbot/go-http-auth
```
下面程式碼示範了如何把這個函式庫引入 beego 中從而實現認證:
```Go
package controllers
import (
"github.com/abbot/go-http-auth"
"github.com/astaxie/beego"
)
func Secret(user, realm string) string {
if user == "john" {
// password is "hello"
return "$1$dlPL2MqE$oQmn16q49SqdmhenQuNgs1"
}
return ""
}
type MainController struct {
beego.Controller
}
func (this *MainController) Prepare() {
a := auth.NewBasicAuthenticator("example.com", Secret)
if username := a.CheckAuth(this.Ctx.Request); username == "" {
a.RequireAuth(this.Ctx.ResponseWriter, this.Ctx.Request)
}
}
func (this *MainController) Get() {
this.Data["Username"] = "astaxie"
this.Data["Email"] = "astaxie@gmail.com"
this.TplNames = "index.tpl"
}
```
上面程式碼利用了 beego 的 prepare 函式,在執行正常邏輯之前呼叫了認證函式,這樣就非常簡單的實現了 http authdigest 的認證也是同樣的原理。
## oauth 和 oauth2 的認證
oauth 和 oauth2 是目前比較流行的兩種認證方式,還好第三方有一個函式庫實現了這個認證,但是是國外實現的,並沒有 QQ、微博之類別的國內應用認證整合
```Go
github.com/bradrydzewski/go.auth
```
下面程式碼示範了如何把該函式庫引入 beego 中從而實現 oauth 的認證,這裡以 GitHub 為例示範:
1. 新增兩條路由
```Go
beego.RegisterController("/auth/login", &controllers.GithubController{})
beego.RegisterController("/mainpage", &controllers.PageController{})
```
2. 然後我們處理 GithubController 登陸的頁面:
```Go
package controllers
import (
"github.com/astaxie/beego"
"github.com/bradrydzewski/go.auth"
)
const (
githubClientKey = "a0864ea791ce7e7bd0df"
githubSecretKey = "a0ec09a647a688a64a28f6190b5a0d2705df56ca"
)
type GithubController struct {
beego.Controller
}
func (this *GithubController) Get() {
// set the auth parameters
auth.Config.CookieSecret = []byte("7H9xiimk2QdTdYI7rDddfJeV")
auth.Config.LoginSuccessRedirect = "/mainpage"
auth.Config.CookieSecure = false
githubHandler := auth.Github(githubClientKey, githubSecretKey)
githubHandler.ServeHTTP(this.Ctx.ResponseWriter, this.Ctx.Request)
}
```
3. 處理登陸成功之後的頁面
```Go
package controllers
import (
"github.com/astaxie/beego"
"github.com/bradrydzewski/go.auth"
"net/http"
"net/url"
)
type PageController struct {
beego.Controller
}
func (this *PageController) Get() {
// set the auth parameters
auth.Config.CookieSecret = []byte("7H9xiimk2QdTdYI7rDddfJeV")
auth.Config.LoginSuccessRedirect = "/mainpage"
auth.Config.CookieSecure = false
user, err := auth.GetUserCookie(this.Ctx.Request)
//if no active user session then authorize user
if err != nil || user.Id() == "" {
http.Redirect(this.Ctx.ResponseWriter, this.Ctx.Request, auth.Config.LoginRedirect, http.StatusSeeOther)
return
}
//else, add the user to the URL and continue
this.Ctx.Request.URL.User = url.User(user.Id())
this.Data["pic"] = user.Picture()
this.Data["id"] = user.Id()
this.Data["name"] = user.Name()
this.TplNames = "home.tpl"
}
```
整個的流程如下,首先開啟瀏覽器輸入地址:
![](images/14.4.github.png)
圖 14.4 顯示帶有登入按鈕的首頁
然後點選連結出現如下介面:
![](images/14.4.github2.png)
圖 14.5 點選登入按鈕後顯示 GitHub 的授權頁
然後點選 Authorize app 就出現如下介面:
![](images/14.4.github3.png)
圖 14.6 授權登入之後顯示的取得到的 GitHub 資訊頁
## 自訂認證
自訂的認證一般都是和 session 結合驗證的,如下程式碼來源於一個基於 beego 的開源部落格:
```Go
//登陸處理
func (this *LoginController) Post() {
this.TplNames = "login.tpl"
this.Ctx.Request.ParseForm()
username := this.Ctx.Request.Form.Get("username")
password := this.Ctx.Request.Form.Get("password")
md5Password := md5.New()
io.WriteString(md5Password, password)
buffer := bytes.NewBuffer(nil)
fmt.Fprintf(buffer, "%x", md5Password.Sum(nil))
newPass := buffer.String()
now := time.Now().Format("2006-01-02 15:04:05")
userInfo := models.GetUserInfo(username)
if userInfo.Password == newPass {
var users models.User
users.Last_logintime = now
models.UpdateUserInfo(users)
//登入成功設定 session
sess := globalSessions.SessionStart(this.Ctx.ResponseWriter, this.Ctx.Request)
sess.Set("uid", userInfo.Id)
sess.Set("uname", userInfo.Username)
this.Ctx.Redirect(302, "/")
}
}
//註冊處理
func (this *RegController) Post() {
this.TplNames = "reg.tpl"
this.Ctx.Request.ParseForm()
username := this.Ctx.Request.Form.Get("username")
password := this.Ctx.Request.Form.Get("password")
usererr := checkUsername(username)
fmt.Println(usererr)
if usererr == false {
this.Data["UsernameErr"] = "Username error, Please to again"
return
}
passerr := checkPassword(password)
if passerr == false {
this.Data["PasswordErr"] = "Password error, Please to again"
return
}
md5Password := md5.New()
io.WriteString(md5Password, password)
buffer := bytes.NewBuffer(nil)
fmt.Fprintf(buffer, "%x", md5Password.Sum(nil))
newPass := buffer.String()
now := time.Now().Format("2006-01-02 15:04:05")
userInfo := models.GetUserInfo(username)
if userInfo.Username == "" {
var users models.User
users.Username = username
users.Password = newPass
users.Created = now
users.Last_logintime = now
models.AddUser(users)
//登入成功設定 session
sess := globalSessions.SessionStart(this.Ctx.ResponseWriter, this.Ctx.Request)
sess.Set("uid", userInfo.Id)
sess.Set("uname", userInfo.Username)
this.Ctx.Redirect(302, "/")
} else {
this.Data["UsernameErr"] = "User already exists"
}
}
func checkPassword(password string) (b bool) {
if ok, _ := regexp.MatchString("^[a-zA-Z0-9]{4,16}$", password); !ok {
return false
}
return true
}
func checkUsername(username string) (b bool) {
if ok, _ := regexp.MatchString("^[a-zA-Z0-9]{4,16}$", username); !ok {
return false
}
return true
}
```
有了使用者登陸和註冊之後,其他模組的地方可以增加如下這樣的使用者是否登陸的判斷:
```Go
func (this *AddBlogController) Prepare() {
sess := globalSessions.SessionStart(this.Ctx.ResponseWriter, this.Ctx.Request)
sess_uid := sess.Get("userid")
sess_username := sess.Get("username")
if sess_uid == nil {
this.Ctx.Redirect(302, "/admin/login")
return
}
this.Data["Username"] = sess_username
}
```
## links
* [目錄](<preface.md>)
* 上一節: [表單及驗證支援](<14.3.md>)
* 下一節: [多語言支援](<14.5.md>)

View File

@@ -11,7 +11,7 @@ import (
"strings"
)
// 开发者 github token
// 开发者 GitHub token
const token = ""
// 定义一个访问者结构体