HTTP框架应用实战 | 青训营

选择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, &param)
	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才可以访问。

相关推荐
Find1 个月前
MaxKB 集成langchain + Vue + PostgreSQL 的 本地大模型+本地知识库 构建私有大模型 | MarsCode AI刷题
青训营笔记
理tan王子1 个月前
伴学笔记 AI刷题 14.数组元素之和最小化 | 豆包MarsCode AI刷题
青训营笔记
理tan王子1 个月前
伴学笔记 AI刷题 25.DNA序列编辑距离 | 豆包MarsCode AI刷题
青训营笔记
理tan王子1 个月前
伴学笔记 AI刷题 9.超市里的货物架调整 | 豆包MarsCode AI刷题
青训营笔记
夭要7夜宵1 个月前
分而治之,主题分片Partition | 豆包MarsCode AI刷题
青训营笔记
三六1 个月前
刷题漫漫路(二)| 豆包MarsCode AI刷题
青训营笔记
tabzzz1 个月前
突破Zustand的局限性:与React ContentAPI搭配使用
前端·青训营笔记
Serendipity5651 个月前
Go 语言入门指南——单元测试 | 豆包MarsCode AI刷题;
青训营笔记
wml1 个月前
前端实践-使用React实现简单代办事项列表 | 豆包MarsCode AI刷题
青训营笔记
用户44710308932421 个月前
详解前端框架中的设计模式 | 豆包MarsCode AI刷题
青训营笔记