这是我参与「第五届青训营 」伴学笔记创作活动的第 8 天
课程内容与选题缘由
团队的项目合作已经到了正式开始的阶段了,为此本文记录并解释一下结营项目的框架是如何设计的,方便团队成员之间的协作。
CLD分层理念
Controller(Handler): 服务的入口。参数校验,负责处理路由,请求转发
Logic(Service): 服务层。处理业务逻辑
Dao: 负责数据与存储相关。
项目脚手架
- conf 配置文件
- dao 数据库
- middleware 中间件
- routes 路由分发
- handler 逻辑交互 与 业务处理分发
- logic 业务逻辑处理
- models 前端交互相关的结构体/ 数据库表记录
脚手架组件
Viper配置库
- conf
简介
Viper库的作用,方便项目配置的 整合 以及 修改。
通过将配置文件的内容 反编译到带tag的结构体中,
调用配置文件内容的时候就一目了然,方便他人理解,适合协作。
Viper库
go get github.com/spf13/viper
Viper结构体tag
mapstructure
Viper例程
//conf.go
type AppConfig struct{
Name string `mapstructure:"name"`
Mode string `mapstructure:"mode"`
Version string `mapstructure:"version"`
Port int `mapstructure:"port"`
*LogConfig `mapstructure:"log"`
*MySQLConfig `mapstructure:"mysql"`
*RedisConfig `mapstructure:"redis"`
}
type LogConfig struct{
}
type MySQLConfig struct{
}
//Conf全局变量 用来保存程序的所有配置信息
var Conf = new(AppConfig)
//Logger从以前监控 config,yaml文件,到监控Conf结构体
func Init() (err error) {
//读取配置文件
viper.SetConfigName("config") //配置文件名(无扩展名)
viper.SetConfigType("yaml") //配置文件名 的 扩展名
//viper.SetConfigFile("config.yaml")
//查找配置文件所在的路径
viper.AddConfigPath(".")
//查找并读取配置文件
err = viper.ReadInConfig()
if err != nil {
panic(fmt.Errorf("Fatal err config file : %s \n", err))
}
if err := viper.Unmarshal(Conf); err != nil{
fmt.Printf("viper.ReadInConfig() failed, err:#{err}\n")
}
//实时监控配置文件的变化
viper.WatchConfig()
//当配置文件变化之后 调用的一个回调函数
viper.OnConfigChange(func(e fsnotify.Event) {
fmt.Println("Config file changed:", e.Name)
if err := viper.Unmarshal(Conf); err != nil{
fmt.Printf("viper.ReadInConfig() failed, err:#{err}\n")
}
})
return
}
//main.go
//main函数中的日志初始化
if err := logger.Init(settings.Conf.LogConfig); err != nil {
fmt.Printf("Init logger failed, err;%v\n", err)
return
}
defer logger.Sync()
func Init(cfg *setting.LogConfig) (err error) {
writeSyncer := getLogWriter(
cfg.Filename,
cfg.MaxSize,
cfg.MaxBackups,
cfg.MaxAge,
)
encoder := getEncoder()
····
}
Viper库参考资料
Go语言配置管理神器--Viper中文教程 - 李文周的博客
Validator库参数校验
- handler
Validator函数的初始化 要放在main函数
简介
Validator库的作用,是对请求参数进行校验(比如用户密码不为空)
在代码中定义与请求参数相对应的模型(结构体),借助模型绑定快捷地解析来自POST请求中的参数,例如 gin 框架中的Bind和ShouldBind 方法。
Validator结构体tag
binding
tag参数:
gte ——大于
lte ——小于
required ——不为空
eqfield=ABC ——与名为ABC的变量的值相同
- 例程
type SignUpParam struct {
Age uint8 `json:"age" binding:"gte=1,lte=130"`
Name string `json:"name" binding:"required"`
Password string `json:"password" binding:"required"`
RePassword string `json:"re_password" binding:"required,eqfield=Password"`
}
func main() {
r := gin.Default()
r.POST("/signup", func(c *gin.Context) {
var u SignUpParam
//ShouldBind获取请求参数,此时validator发挥作用
//违反validator的错误,会通过err进行返回
if err := c.ShouldBind(&u); err != nil {
c.JSON(http.StatusOK, gin.H{
"msg": err.Error(),
})
return
}
c.JSON(http.StatusOK, "success")
})
_ = r.Run(":8999")
}
Validator库参考资料
Zap日志库
- logger
简介
本项目通过Zap库和Lumberjack库实现了一个日志器。该日志器的作用如下:
- 能够将事件记录到文件中,而不是应用程序控制台。
- 日志切割-能够根据文件大小、时间或间隔等来切割日志文件。
- 支持不同的日志级别记录。例如INFO,DEBUG,ERROR等。
- 能够在日志中打印运行过程的基本信息,如调用文件/函数名和行号,日志时间等。
日志器实现了两个中间件,必须全局路由都要使用:
GinLogger()接收gin框架默认的日志GinRecovery()日志记录中间件
日志内容会记录在当前项目工作目录的web_app.log文件中
Swagger库—接口文档
- doc
简介
Swagger是一种 用于描述使用JSON表示的 RESTful API的 接口描述语言
Swagger库用于API文档的生成和自动维护。生成的API文档的可维护性好,可以跟着代码的变动自动更新。
Swagger库
**go get -u github.com/swaggo/swag/cmd/swag**
具体使用过程
1. 添加注释
直接在接口代码函数首部添加声明式注释
2. 生成接口文档数据
swag init
如果你写的注释格式没问题,此时你的项目根目录下会多出一个docs文件夹。
./docs
├── docs.go
├── swagger.json
└── swagger.yaml
3. 引入gin-swagger & 渲染docs文档数据
项目代码中注册路由的地方(文件)
引入gin-swagger相关内容:
import (
// liwenzhou.com ...
_ "bluebell/docs" // 千万不要忘了导入把你上一步生成的docs
gs "github.com/swaggo/gin-swagger"
"github.com/swaggo/gin-swagger/swaggerFiles"
"github.com/gin-gonic/gin"
)
4. 注册swagger api相关路由
r.GET("/swagger/*any", gs.WrapHandler(swaggerFiles.Handler))
项目在本地运行后,打开浏览器访问http://localhost:8080/swagger/index.html 就能看到Swagger 2.0 Api文档了Swagger
Swagger注释教程 & 示例
main.go
// @title reddit_imitation项目接口文档
// @version 1.0
// @description 基于gin框架的仿reddit社区论坛后端项目
// @contact.name FrancisChoi
// @contact.url <https://github.com/FrancisChoi02/>
// @host 127.0.0.1:8080
// @BasePath /api/v1
func main() {
}
host:项目运行的端口BasePath:The base path on which the API is served. API服务的路径
controller.go
// GetPostListHandler2 升级版帖子列表接口
// @Summary 升级版帖子列表接口
// @Description 可按社区按时间或分数排序查询帖子列表接口
// @Tags 帖子相关接口
// @Accept application/json
// @Produce application/json
// @Param Authorization header string false "Bearer 用户令牌"
// @Param object query models.ParamPostList false "查询参数"
// @Security ApiKeyAuth
// @Success 200 {object} _ResponsePostList
// @Router /posts2 [get]
func GetPostListHandler2(c *gin.Context) {
// GET请求参数(query string):/api/v1/posts2?page=1&size=10&order=time
// 初始化结构体时指定初始参数
p := &models.ParamPostList{
Page: 1,
Size: 10,
Order: models.OrderTime,
}
if err := c.ShouldBindQuery(p); err != nil {
zap.L().Error("GetPostListHandler2 with invalid params", zap.Error(err))
ResponseError(c, CodeInvalidParam)
return
}
data, err := logic.GetPostListNew(p)
// 获取数据
if err != nil {
zap.L().Error("logic.GetPostList() failed", zap.Error(err))
ResponseError(c, CodeServerBusy)
return
}
ResponseSuccess(c, data)
// 返回响应
}