轻量的基于 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"