选择HTTP框架Gin。
项目初始化
bash
mkdir goblog-server
cd goblog-server
code .
初始化git仓库,同步github。
csharp
git init
git remote add origin git@github.com:ChouE/goblog-server.git
// 第一次推送
git push -u origin master
go mod init github.com/ChouE/goblog-server
进行项目的初始化。
使用gin框架,创建main.go
文件。
go
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "pong"})
})
r.Run()
}
写入之后ide自动导入gin包,运行go mod tidy
更新go.mod文件,出现了直接依赖的gin包和一系列间接依赖的包。然后就可以运行起来了,go run main.go
。
csharp
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /ping --> main.main.func1 (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080
这里看到创建了一个引擎实例,使用了Logger和Recovery中间件。运行在Debug模式下,下面也给出了使用环境变量和代码的方式控制运行在release模式的方法,默认监听的是8080端口,注册了GET /ping
的路由。
打开浏览器,访问localhost:8080/ping
。得到:
json
{
"message": "pong"
}
使用curl
测试:
bash
curl http://localhost:8080/ping
{"message":"pong"}
框架流程
通过调用Gin.Default()
创建了默认的引擎,引入了两个中间件:
- Logger:输出请求日志
- Recovery:异常捕获,针对每次请求处理进行recovery,防止出现panic而使服务崩溃
go
// Default returns an Engine instance with the Logger and Recovery middleware already attached.
func Default() *Engine {
debugPrintWARNINGDefault()
engine := New()
engine.Use(Logger(), Recovery())
return engine
}
这里New()
负责的是创建引擎的逻辑。
r
就是返回的引擎Engine
类型,调用了相应类型的GET
方法,查看源码说明GET is a shortcut for router.Handle("GET", path, handlers)
,实际上就是包装了一层router.Handle()
。
- 计算路由的绝对路径
- 合并现有的和新注册的路由
- 将注册的路由追加到树中
可以在输出中看到:
css
[GIN-debug] GET /ping --> main.main.func1 (3 handlers)
这里是3 handlers,是合并了/ping和两个中间件。
项目设计
包括目录结构,接口方案,路由注册和数据库等。
目录结构
csharp
├─configs
├─docs
├─global
├─internal
├─pkg
├─scripts
├─storage
└─third_party
- configs: 配置文件
- docs:文档集合
- global:全局变量
- internal:中间模块
- pkg:模块包
- storage:临时文件
- scripts:构建,生成,分析相关的脚本
- third_party:第三方工具
数据库
使用gorm。
go
import _ "github.com/jinzhu/gorm/dialects/mysql"
func NewDBEngine(databaseSetting *setting.DatabaseSettingS) (*gorm.DB, error) {
s := "%s:%s@tcp(%s)/%s?charset=%s&parseTime=%t&loc=Local"
db, err := gorm.Open(databaseSetting.DBType, fmt.Sprintf(s,
databaseSetting.UserName,
databaseSetting.Password,
databaseSetting.Host,
databaseSetting.DBName,
databaseSetting.Charset,
databaseSetting.ParseTime,
))
if err != nil {
return nil, err
}
if global.ServerSetting.RunMode == "debug" {
db.LogMode(true)
}
db.SingularTable(true)
db.DB().SetMaxIdleConns(databaseSetting.MaxIdleConns)
db.DB().SetMaxOpenConns(databaseSetting.MaxOpenConns)
return db, nil
}
创建一个创建DB引擎的方法,同时需要引入mysql的驱动,不同的数据库引入不同的驱动,具体参考gorm的文档。
配置管理
使用第三方库viper。
在configs目录下新建config.yaml
文件,以文件的形式管理配置信息。
yaml
Server:
RunMode: debug
HttpPort: 8000
ReadTimeout: 60
WriteTimeout: 60
App:
DefaultPageSize: 10
MaxPageSize: 100
LogSavePath: storage/logs
LogFileName: app
LogFileExt: .log
Database:
DBType: mysql
Username: root # 填写你的数据库账号
Password: # 填写你的数据库密码
Host: 127.0.0.1:3306
DBName: blog_service
TablePrefix: blog_
Charset: utf8
ParseTime: True
MaxIdleConns: 10
MaxOpenConns: 30
Server是服务器设置,设置gin的运行模式、默认的监听端口,允许读取和写入的最大时间。App是应用配置,设置应用默认的每页数量,以及日志存储路径。Database则是数据库连接信息的配置。
日志
使用lumberjack。
支持设置所允许单日志文件的最大占用空间、最大生存周期、允许保留的最多旧文件数,如果出现超出设置项的情况,就会对日志文件进行滚动处理。
go
func setupLogger() error {
global.Logger = logger.NewLogger(&lumberjack.Logger{
Filename: global.AppSetting.LogSavePath + "/" + global.AppSetting.LogFileName + global.AppSetting.LogFileExt,
MaxSize: 600,
MaxAge: 10,
LocalTime: true,
}, "", log.LstdFlags).WithCaller(2)
return nil
}
使用了 lumberjack 作为日志库的 io.Writer,并且设置日志文件所允许的最大占用空间为 600MB、日志文件最大生存周期为 10 天,并且设置日志文件名的时间格式为本地时间。
接口管理
使用Swagger进行接口的设计和管理。
go
go install github.com/go-swagger/go-swagger/cmd/swagger
之后使用swag -v
查看是否安装成功。
写入注解:
注解 | 描述 |
---|---|
@Summary | 摘要 |
@Produce | API 可以产生的 MIME 类型的列表,MIME 类型你可以简单的理解为响应类型,例如:json、xml、html 等等 |
@Param | 参数格式,从左到右分别为:参数名、入参类型、数据类型、是否必填、注释 |
@Success | 响应成功,从左到右分别为:状态码、参数类型、数据类型、注释 |
@Failure | 响应失败,从左到右分别为:状态码、参数类型、数据类型、注释 |
@Router | 路由,从左到右分别为:路由地址,HTTP 方法 |
写完注释之后,使用命令swag init 即可在docs目录下生成对应的文件。 |
|
然后在routers.go中注册swager的路由。访问http://127.0.0.1:8000/swagger/index.html 。 |
使用validator进行参数验证。
选择获取多个tag的方法List进行测试:
go
func (t Tag) List(c *gin.Context) {
param := struct {
Name string `form:"name" binding:"max=100"`
State uint8 `form:"state,default=1" binding:"oneof=0 1"`
}{}
response := app.NewResponse(c)
valid, errs := app.BindAndValid(c, ¶m)
if !valid {
global.Logger.Errorf("app.BindAndValid errs: %v", errs)
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
return
}
response.ToResponse(gin.H{})
return
}
这里对name的限制是最大长度为100,对state的限制是0或者1,默认值为1.运行起来,然后使用curl命令进行GET测试。
bash
curl -X GET http://127.0.0.1:8000/api/v1/tags?state=9
{"code":10000001,"details":["State必须是[0 1]中的一个"],"msg":"入参错误"}
由于没有required限制,也就是参数不是必须的,那么请求时直接不带参数,也是可以正常返回的。
模块功能开发
功能 | HTTP 方法 | 路径 |
---|---|---|
新增标签 | POST | /tags |
删除指定标签 | DELETE | /tags/:id |
更新指定标签 | PUT | /tags/:id |
获取标签列表 | GET | /tags |
测试Tag的Create和List:
文件上传
上传文件的流程,通过原始文件名进行MD5加密,返回文件名,这样是为了不暴露原始文件名。
在获取文件时,检测文件后缀等,把这些写成方法,并且增加yaml配置文件中的相关配置。
csharp
r.StaticFS("/static", http.Dir(global.AppSetting.UploadSavePath))
在gin中实现一个File System只需要注册一下路由就可以了,这样在/static下就能够访问到上传的文件。
使用curl进行上传文件测试:
直接访问图片链接即可看到服务器上的图片。
JWT接口访问控制
JSON Web 令牌(JWT)是一个开放标准(RFC7519),它定义了一种紧凑且自包含的方式,用于在各方之间作为 JSON 对象安全地传输信息。 由于此信息是经过数字签名的,因此可以被验证和信任。 可以使用使用 RSA 或 ECDSA 的公用/专用密钥对对 JWT 进行签名。分为三部分,头部,有效载荷,签名。以.
分割开。
创建一个数据表,存储相关的信息。
需要实现的是,令牌的解析和验证。然后是将jwt绑定到需要保护的路由组上,这样在访问组内的接口时,必须有相应的token才可以访问。