项目脚手架搭建 | 青训营笔记

138 阅读5分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 8 天

课程内容与选题缘由

团队的项目合作已经到了正式开始的阶段了,为此本文记录并解释一下结营项目的框架是如何设计的,方便团队成员之间的协作。

CLD分层理念

1

Controller(Handler): 服务的入口。参数校验,负责处理路由,请求转发

Logic(Service): 服务层。处理业务逻辑

Dao: 负责数据与存储相关。

项目脚手架

2

  • conf 配置文件
  • dao 数据库
  • middleware 中间件
  • routes 路由分发
  • handler 逻辑交互 与 业务处理分发
  • logic 业务逻辑处理
  • models 前端交互相关的结构体/ 数据库表记录

脚手架组件

Viper配置库

  • conf

3

简介

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

4

Validator函数的初始化 要放在main函数

简介

Validator库的作用,是对请求参数进行校验(比如用户密码不为空)

在代码中定义与请求参数相对应的模型(结构体),借助模型绑定快捷地解析来自POST请求中的参数,例如 gin 框架中的BindShouldBind 方法。

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库参考资料

validator库参数校验若干实用技巧 - 李文周的博客

Zap日志库

  • logger

5

简介

本项目通过Zap库和Lumberjack库实现了一个日志器。该日志器的作用如下:

  • 能够将事件记录到文件中,而不是应用程序控制台。
  • 日志切割-能够根据文件大小、时间或间隔等来切割日志文件
  • 支持不同的日志级别记录。例如INFO,DEBUG,ERROR等。
  • 能够在日志中打印运行过程的基本信息,如调用文件/函数名和行号,日志时间等。

日志器实现了两个中间件,必须全局路由都要使用:

  • GinLogger() 接收gin框架默认的日志
  • GinRecovery() 日志记录中间件

日志内容会记录在当前项目工作目录的web_app.log文件中

Swagger库—接口文档

  • doc

6

简介

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

8

Swagger注释教程 & 示例

API Operation

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)
	// 返回响应
}

9.png