輕量的基於 golang 的 web 開發實踐.html
golang 上手簡單, 第三方庫豐富, 對於業務沒那麼複雜的項目, 做爲 API 的後端也是不錯的選擇. 下面是對 golang 做爲 API 後端的 web 開發實踐總結.前端
API 後端的功能模塊基本已經固定, 基於本身的項目, 主要使用瞭如下模塊:node
golang 的 API 框架有不少, 我在項目中選擇了 gin 框架. 當時是出於如下幾點考慮:mysql
雖然選擇了 gin, 可是本文中使用的各個模塊都不是強依賴 gin 的, 替換任何一個模塊的代價都不會太大.react
gin 的使用很簡單, 主要代碼以下:linux
r := gin.Default() if gin.Mode() == "debug" { r.Use(cors.Default()) // 在 debug 模式下, 容許跨域訪問 } // ... 設置路由的代碼 if err := r.Run(":" + strconv.Itoa(port)); err != nil { log.Fatal(err) }
數據庫這層, 選用了 beego ORM 框架, 它的文檔比較好, 對主流的幾種關係數據庫也都支持. 表結構的定義:git
type User struct { Id string `orm:"pk" json:"id"` UserName string `orm:"unique" json:"username"` Password string `json:"password"` CreateAt time.Time `orm:"auto_now_add"` UpdateAt time.Time `orm:"auto_now"` } func init() { orm.RegisterModel(new(User)) }
數據庫的初始化:github
// mysql 配置, postgresql 或者 sqlite 使用其餘驅動 orm.RegisterDriver("default", orm.DRMySQL) // 註冊驅動 var conStr = fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8&loc=Local", c.DB.UserName, c.DB.Password, c.DB.Host, c.DB.Port, c.DB.DBName) orm.RegisterDataBase("default", "mysql", conStr) // sync database orm.RunSyncdb("default", false, false)
認證採用 jwt token, 使用了 gin-jwt 中間件. 加了認證中間件以後, 能夠配置路由是否須要認證:golang
authMiddleware := controller.JwtMiddleware() // *不須要* 認證的路由 r.POST("/register", controller.Register) r.POST("/login", authMiddleware.LoginHandler) // *須要* 認證的路由 authRoute := r.Group("/auth") authRoute.Use(authMiddleware.MiddlewareFunc()) { authRoute.GET("/test", func(c *gin.Context) { fmt.Println("hello") }) }
項目不是很複雜, 日誌採用了文件的方式, 選擇了 beego logs 模塊. 雖然使用了 beego logs, 可是爲了方便之後替換 logs 模塊, 在 beego logs 又封裝了一層.web
// Logger type Logger interface { Debug(format string, v ...interface{}) Info(format string, v ...interface{}) Warn(format string, v ...interface{}) Error(format string, v ...interface{}) } // 支持 console 和 file 2 種類型的 log func InitLogger(level, logType, logFilePath string) error { consoleLogger = nil fileLogger = nil if logType == ConsoleLog { consoleLogger = NewConsoleLogger(level) // 這裏實際是經過 beego logs 來實現功能的 } else if logType == FileLog { fileLogger = NewFileLogger(logFilePath, level) // 這裏實際是經過 beego logs 來實現功能的 } else { return fmt.Errorf("Log type is not valid\n") } return nil }
配置採用 toml 格式, 配置文件中通常存放不怎麼改變的內容, 改動比較頻繁的配置仍是放在數據庫比較好.
import ( "github.com/BurntSushi/toml" ) type Config struct { Server serverConfig `toml:"server"` DB dbConfig `toml:"db"` Logger loggerConfig `toml:"logger"` File fileConfig `toml:"file"` } type serverConfig struct { Port int `toml:"port"` } type dbConfig struct { Port int `toml:"port"` Host string `toml:"host"` DBName string `toml:"db_name"` UserName string `toml:"user_name"` Password string `toml:"password"` } type loggerConfig struct { Level string `toml:"level"` Type string `toml:"type"` LogPath string `toml:"logPath"` } type fileConfig struct { UploadDir string `toml:"uploadDir"` DownloadDir string `toml:"downloadDir"` } var conf *Config func GetConfig() *Config { return conf } func InitConfig(confPath string) error { _, err := toml.DecodeFile(confPath, &conf) return err }
本工程中靜態文件服務的目的是爲了發佈前端. 前端採用 react 開發, build 以後的代碼放在靜態服務目錄中. 使用 gin 框架的靜態服務中間件, 很容易實現此功能:
// static files r.Use(static.Serve("/", static.LocalFile("./public", true))) // 沒有路由匹配時, 回到首頁 r.NoRoute(func(c *gin.Context) { c.File("./public/index.html") })
上傳/下載 在 gin 框架中都有支持.
上傳
func UploadXls(c *gin.Context) { // ... 省略的處理 // upload form field name: uploadXls, 這個名字和前端能對上就行 // file 就是上傳文件的文件流 file, header, err := c.Request.FormFile("uploadXls") if err != nil { Fail(c, "param error: "+err.Error(), nil) return } // ... 省略的處理 }
下載
func DownloadXls(c *gin.Context) { // ... 省略的處理 c.File(downloadPath) }
基於上面幾個模塊, 通常業務不是很複雜的小應用均可以勝任. 開發以後, 就是打包發佈. 由於這個方案是針對小應用的, 因此把先後端都打包到一塊兒做爲一個總體發佈.
之全部採用 docker 方式打包, 是由於這種方式易於分發. docker file 以下:
# 編譯前端 FROM node:10.15-alpine as front-builder WORKDIR /user ARG VERSION=no-version ADD ./frontend/app-ui . RUN yarn RUN yarn build # 編譯前端 FROM golang:1.12.5-alpine3.9 as back-builder WORKDIR /go RUN mkdir -p ./src/app-api ADD ./backend/src/app-api ./src/app-api RUN go install app-api # 發佈應用 (這裏能夠用個更小的 linux image) FROM golang:1.12.5-alpine3.9 WORKDIR /app COPY --from=front-builder /user/build ./public COPY --from=back-builder /go/bin/app-api . ADD ./deploy/builder/settings.toml . CMD ["./app-api", "-f", "./settings.toml", "-prod"]
docker 的官方 image 基本都是 UTC 時區的, 因此插入數據庫的時間通常會慢 8 個小時. 因此, 在 docker 啓動或者打包的時候, 須要對時區作一些處理.
數據庫鏈接的設置
// 鏈接字符串中加上: loc=Local var conStr = fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8&loc=Local", c.DB.UserName, c.DB.Password, c.DB.Host, c.DB.Port, c.DB.DBName)
數據庫鏡像的設置 (環境變量中設置時區)
# -e TZ=Asia/Shanghai 就是設置時區 docker run --name xxx -e TZ=Asia/Shanghai -d mysql:5.7
應用鏡像的設置 (docker-compose.yml) 在 volumes 中設置時區和主機同樣
services: user: image: xxx:latest restart: always networks: - nnn volumes: - "/etc/localtime:/etc/localtime:ro"