1. 日志文件
package mainimport ("io""os""github.com/gin-gonic/gin"
)func main() {gin.DisableConsoleColor()// Logging to a file.f, _ := os.Create("gin.log")gin.DefaultWriter = io.MultiWriter(f)// 如果需要同時將日志寫入文件和控制臺,請使用以下代碼。// gin.DefaultWriter = io.MultiWriter(f, os.Stdout)r := gin.Default()r.GET("/ping", func(c *gin.Context) {c.String(200, "pong")})r.Run()
}
效果演示:
2. Air實時加載
本章我們要介紹一個神器——Air能夠實時監聽項目的代碼文件,在代碼發生變更之后自動重新編譯并執行,大大提高gin框架項目的開發效率。
2.1.1. 為什么需要實時加載?
之前使用Python編寫Web項目的時候,常見的Flask或Django框架都是支持實時加載的,你修改了項目代碼之后,程序能夠自動重新加載并執行(live-reload),這在日常的開發階段是十分方便的。
在使用Go語言的gin框架在本地做開發調試的時候,經常需要在變更代碼之后頻繁的按下Ctrl+C停止程序并重新編譯再執行,這樣就不是很方便。
2.1.2. Air介紹
怎樣才能在基于gin框架開發時實現實時加載功能呢?像這種煩惱肯定不會只是你一個人的煩惱,所以我報著肯定有現成輪子的心態開始了全網大搜索。果不其然就在Github上找到了一個工具:Air[1]。它支持以下特性:
- 彩色日志輸出
- 自定義構建或二進制命令
- 支持忽略子目錄
- 啟動后支持監聽新目錄
- 更好的構建過程
2.1.3. 安裝Air
Go
這也是最經典的安裝方式:
go get -u github.com/cosmtrek/air
MacOS
curl -fLo air https://git.io/darwin_air
Linux
curl -fLo air https://git.io/linux_air
Windows
curl -fLo air.exe https://git.io/windows_air
Dcoker
docker run -it --rm \-w "<PROJECT>" \-e "air_wd=<PROJECT>" \-v $(pwd):<PROJECT> \-p <PORT>:<APP SERVER PORT> \cosmtrek/air-c <CONF>
然后按照下面的方式在docker中運行你的項目:
docker run -it --rm \-w "/go/src/github.com/cosmtrek/hub" \-v $(pwd):/go/src/github.com/cosmtrek/hub \-p 9090:9090 \cosmtrek/air
2.1.4. 使用Air
為了敲命令更簡單更方便,你應該把alias air='~/.air'
加到你的.bashrc
或.zshrc
中。
首先進入你的項目目錄:
cd /path/to/your_project
最簡單的用法就是直接執行下面的命令:
# 首先在當前目錄下查找 `.air.conf`配置文件,如果找不到就使用默認的
air -c .air.conf
推薦的使用方法是:
# 1. 在當前目錄創建一個新的配置文件.air.conf
touch .air.conf# 2. 復制 `air.conf.example` 中的內容到這個文件,然后根據你的需要去修改它# 3. 使用你的配置運行 air, 如果文件名是 `.air.conf`,只需要執行 `air`。
air
air_example.conf示例
完整的air_example.conf示例配置如下,可以根據自己的需要修改。
# [Air](https://github.com/cosmtrek/air) TOML 格式的配置文件# 工作目錄
# 使用 . 或絕對路徑,請注意 `tmp_dir` 目錄必須在 `root` 目錄下
root = "."
tmp_dir = "tmp"[build]
# 只需要寫你平常編譯使用的shell命令。你也可以使用 `make`
cmd = "go build -o ./tmp/main ."
# 由`cmd`命令得到的二進制文件名
bin = "tmp/main"
# 自定義的二進制,可以添加額外的編譯標識例如添加 GIN_MODE=release
full_bin = "APP_ENV=dev APP_USER=air ./tmp/main"
# 監聽以下文件擴展名的文件.
include_ext = ["go", "tpl", "tmpl", "html"]
# 忽略這些文件擴展名或目錄
exclude_dir = ["assets", "tmp", "vendor", "frontend/node_modules"]
# 監聽以下指定目錄的文件
include_dir = []
# 排除以下文件
exclude_file = []
# 如果文件更改過于頻繁,則沒有必要在每次更改時都觸發構建。可以設置觸發構建的延遲時間
delay = 1000 # ms
# 發生構建錯誤時,停止運行舊的二進制文件。
stop_on_error = true
# air的日志文件名,該日志文件放置在你的`tmp_dir`中
log = "air_errors.log"[log]
# 顯示日志時間
time = true[color]
# 自定義每個部分顯示的顏色。如果找不到顏色,使用原始的應用程序日志。
main = "magenta"
watcher = "cyan"
build = "yellow"
runner = "green"[misc]
# 退出時刪除tmp目錄
clean_on_exit = true
3. gin驗證碼
在開發的過程中,我們有些接口為了防止被惡意調用,我們會采用加驗證碼的方式,例如:發送短信的接口,為了防止短信接口被頻繁調用造成損失;注冊的接口,為了防止惡意注冊。在這里為大家推薦一個驗證碼的類庫,方便大家學習使用。
github.com/dchest/captcha
web端是怎么實現驗證碼的功能呢?
- 提供一個路由,先在session里寫入鍵值對(k->v),把值寫在圖片上,然后生成圖片,顯示在瀏覽器上面
- 前端將圖片中的內容發送給后后端,后端根據session中的k取得v,比對校驗。如果通過繼續下一步的邏輯,失敗給出錯誤提示
API接口驗證碼實現方式類似,可以把鍵值對存儲在起來,驗證的時候把鍵值對傳輸過來一起校驗。這里我只給出了web端的方法,愛動手的小伙伴可以自己嘗試一下。
后端
package mainimport ("bytes""github.com/dchest/captcha""github.com/gin-contrib/sessions""github.com/gin-contrib/sessions/cookie""github.com/gin-gonic/gin""net/http""time"
)// 中間件,處理session
func Session(keyPairs string) gin.HandlerFunc {store := SessionConfig()return sessions.Sessions(keyPairs, store)
}
func SessionConfig() sessions.Store {sessionMaxAge := 3600sessionSecret := "topgoer"var store sessions.Storestore = cookie.NewStore([]byte(sessionSecret))store.Options(sessions.Options{MaxAge: sessionMaxAge, //secondsPath: "/",})return store
}func Captcha(c *gin.Context, length ...int) {l := captcha.DefaultLenw, h := 107, 36if len(length) == 1 {l = length[0]}if len(length) == 2 {w = length[1]}if len(length) == 3 {h = length[2]}captchaId := captcha.NewLen(l)session := sessions.Default(c)session.Set("captcha", captchaId)_ = session.Save()_ = Serve(c.Writer, c.Request, captchaId, ".png", "zh", false, w, h)
}
func CaptchaVerify(c *gin.Context, code string) bool {session := sessions.Default(c)if captchaId := session.Get("captcha"); captchaId != nil {session.Delete("captcha")_ = session.Save()if captcha.VerifyString(captchaId.(string), code) {return true} else {return false}} else {return false}
}
func Serve(w http.ResponseWriter, r *http.Request, id, ext, lang string, download bool, width, height int) error {w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")w.Header().Set("Pragma", "no-cache")w.Header().Set("Expires", "0")var content bytes.Bufferswitch ext {case ".png":w.Header().Set("Content-Type", "image/png")_ = captcha.WriteImage(&content, id, width, height)case ".wav":w.Header().Set("Content-Type", "audio/x-wav")_ = captcha.WriteAudio(&content, id, lang)default:return captcha.ErrNotFound}if download {w.Header().Set("Content-Type", "application/octet-stream")}http.ServeContent(w, r, id+ext, time.Time{}, bytes.NewReader(content.Bytes()))return nil
}func main() {router := gin.Default()router.LoadHTMLGlob("./*.html")router.Use(Session("topgoer"))router.GET("/captcha", func(c *gin.Context) {Captcha(c, 4)})router.GET("/", func(c *gin.Context) {c.HTML(http.StatusOK, "index.html", nil)})router.GET("/captcha/verify/:value", func(c *gin.Context) {value := c.Param("value")if CaptchaVerify(c, value) {c.JSON(http.StatusOK, gin.H{"status": 0, "msg": "success"})} else {c.JSON(http.StatusOK, gin.H{"status": 1, "msg": "failed"})}})router.Run(":8080")
}
前端頁面
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>www.topgoer.com驗證碼</title>
</head>
<body>
<img src="/captcha" onclick="this.src='/captcha?v='+Math.random()">
</body>
</html>
瀏覽器訪問http://127.0.0.1:8080
訪問http://127.0.0.1:8080/captcha/verify/5721?進行驗證
{"msg": "failed","status": 1}