golang mysql web_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"

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值