golang web 方案
概要
轻量的基于 golang 的 web 开发实践.
golang 上手简单, 第三方库丰富, 对于业务没那么复杂的项目, 作为 API 的后端也是不错的选择. 下面是对 golang 作为 API 后端的 web 开发实践总结.
开发
API 后端的功能模块基本已经固定, 基于自己的项目, 主要使用了以下模块:
- web 框架: 整个方案的核心
- 数据库: orm 框架
- 认证: 访问的安全
- 日志: 辅助调试和运维
- 配置: 提高服务的灵活性
- 静态文件服务: 部署打包后的前端
- 上传/下载: 其实也是 web 框架提供的功能, 单独提出来是因为和一般的 JSON API 不太一样
web 框架
golang 的 API 框架有很多, 我在项目中选择了 gin 框架. 当时是出于以下几点考虑:
- 成熟度: gin 早就进入 v1 稳定版, 使用的项目也很多, 成熟度没有问题
- 性能: gin 的性能在众多 golang web 框架中不是最好的, 但也不差, 具体可以参见 gin 的 README
- 活跃度: github 上的 commit 可以看出, gin 虽然很稳定, 更新频率还可以
- 周边支持: gin 的插件非常多, 还有个 contrib 项目, 常用的各种插件基本都有, 另外, gin 的插件写起来也很简单
虽然选择了 gin, 但是本文中使用的各个模块都不是强依赖 gin 的, 替换任何一个模块的代价都不会太大.
gin 的使用很简单, 主要代码如下:
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 框架, 它的文档比较好, 对主流的几种关系数据库也都支持. 表结构的定义:
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))
}
数据库的初始化:
// 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 中间件. 加了认证中间件之后, 可以配置路由是否需要认证:
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 又封装了一层.
// 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 方式打包, 是因为这种方式易于分发. 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"
golang web 方案的更多相关文章
- net core与golang web
Asp.net core与golang web简单对比测试 最近因为工作需要接触了go语言,又恰好asp.net core发布RC2,就想简单做个对比测试. 下面是测试环境: CPU:E3-1230 ...
- golang web实战之二(iris)
之前写了一篇为:golang web实战之一(beego,mvc postgresql) 听说iris更好: 1. iris hello world package main import &quo ...
- golang学习笔记9 beego nginx 部署 nginx 反向代理 golang web
golang学习笔记9 beego nginx 部署 nginx 反向代理 golang web Nginx 部署 - beego: 简约 & 强大并存的 Go 应用框架https://bee ...
- golang web框架设计7:整合框架
把前面写好的路由器,控制器,日志,都整合在一起 全局变量和初始化 定义一些框架的全局变量 var ( BeeApp *App AppName string AppPath string StaticD ...
- golang web框架设计6:上下文设计
context,翻译为上下文,为什么要设计这个结构?就是把http的请求和响应,以及参数结合在一起,便于集中处理信息,以后框架的扩展等.好多框架比如gin,都是有这个上下文结构. context结构为 ...
- golang web框架设计5:配置设计
配置信息的解析,实现的是一个key=value,键值对的一个配置文件,类似于ini的配置格式,然后解析这个文件,把解析的数据保存到map中,最后调用的时候通过几个string,int之类的函数返回相应 ...
- golang web框架设计4:日志设计
beego的日志设计思路来自于seelog,根据不同的level来记录日志,beego设计的日志是一个轻量级的,采用系统log.Logger接口,默认输出到os.Stdout,用户可以实现这个接口然后 ...
- golang web框架设计3:controller设计
继续学习golang web框架设计 controller作用 MVC设计模式里面的这个C,控制器. Model是后台返回的数据: View是渲染页面,通常是HTML的模板页面: Controller ...
- golang web框架设计2:自定义路由
继续学习谢大的Go web框架设计 HTTP路由 http路由负责将一个http的请求交到对应的函数处理(或者一个struct的方法),路由在框架中相当于一个事件处理器,而这个时间包括 用户请求的路径 ...
随机推荐
- 云原生生态周报 Vol. 13 | Forrester 发布企业级容器平台报告
业界要闻 近日,全球知名市场调研机构 Forrester 发布首个企业级公共云容器平台报告.其中,阿里云容器服务的市场表现全球前三.中国第一,同时创造中国企业最好成绩,进入强劲表现者象限.报告显示,阿 ...
- Gitlab 部署汉化及邮件配置
Gitlab 简介 Gitlab 是一个基于git私有代码管理的服务集成. Nginx:静态web服务器. gitlab-shell:用于处理Git命令和修改authorized keys列表. gi ...
- Git多账号配置
在一台电脑上配置多个不同的 ssh key 前言 如果拥有多个Git远程仓库,尤其是其中一个是工作中使用的仓库,只使用一个ssh key安全性很低,建议为不同Git远程仓库配置不同的ssh key. ...
- 基于Spring Cloud Netflix的TCC柔性事务和EDA事件驱动示例
Solar Spring Cloud为开发者提供了快速构建分布式系统中的一些常见工具,如分布式配置中心,服务发现与注册中心,智能路由,服务熔断及降级,消息总线,分布式追踪的解决方案等. 本次实战以模拟 ...
- addEventListener和JavaScript的事件机制
JavaScript的事件处理分为两个阶段: 捕获阶段:从根节点向event.target层层传递 冒泡阶段:从event.target向根节点层层传递 addEventListener(eventN ...
- android studio学习----android studio断点调试
先编译好要调试的程序. 1.设置断点 选定要设置断点的代码行,在行号的区域后面单击鼠标左键即可. 2.开启调试会话 点击红色箭头指向的小虫子,开始进入调试. IDE下方出现Debug视图,红色的箭头指 ...
- iOS tableView侧滑删除的第三方控件
(到我的文件中,下载“tableview中cell测滑删除的第三方控件”),使用方法如下: 在tableView中的.m中,设置cell的方法上,事例代码如下,其中,EaseConversationC ...
- day 35
目录 单表操作 分组 group by having order by limit 使用顺序 多表操作 外键 一对多 多对多 一对一 多表联查 单表操作 分组 group by 分组指的是:将所有记录 ...
- oracle dg状态检查及相关命令
oracle dg 状态检查 先检查备库的归档日志同步情况 SELECT NAME,applied FROM v$archived_log; alter database recover manage ...
- springcloud学习之路: (一) 最简单的搭建springcloud的方法
参考资料: [JavaEE] 五分钟搭建SpringCloud环境, 进入微服务时代 感谢上篇博文大佬带领走进springcloud世界, 本博文主要目的为记录自己学习springcloud的点点滴滴 ...