gin 入门
前置条件 安装环境
配置代理
# 配置 GOPROXY 环境变量,以下三选一
# 1. 七牛 CDN
go env -w GOPROXY=https://goproxy.cn,direct
# 2. 阿里云
go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/,direct
# 3. 官方
go env -w GOPROXY=https://goproxy.io,direct
安装gin
go get -u github.com/gin-gonic/gin
进入项目
启动类
/*
*
和Springboot一样 一个项目有一个main函数作为启动类
*/
func main() {
//创建路由
engine := gin.Default()
// 这里写的匿名方法实际可以在文件中写多个多个方法作为路由导入
engine.GET("/hello", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "Hello World",
"status": "success",
"data": gin.H{
"name": "张三",
"age": 20,
},
})
})
//启动项目 0.0.0.0:4444 本质就是http.ListenAndServe(":4444", engine)的封装
engine.Run(":4444") //监听4444端口 本机得任意ip的额4444端口都可以访问
//也可以原生http的方式启动
//http.ListenAndServe(":4444", engine)
}
这样本机项目就可以根指定端口启动了
其中gin.H
是 Gin 框架提供的一个便捷方式,用于将 Go 语言中的 map[string]interface{}
类型封装为 JSON 数据并返回给客户端。gin.H
本质上就是一个 map[string]interface{}
,但它提供了一种更简洁的语法来定义 JSON 对象。
源码:
type H map[string]any
当然也可以自定义一个通用返回类型
响应信息–自定义结构体
func helloworld(c *gin.Context) {
//gin的上下文向客户端写入JSON数据
c.JSON(http.StatusOK, gin.H{
"message": "Hello World!",
"code": "0000",
})
}
func main() {
router := gin.Default()
router.GET("/index", helloworld)
router.GET("/index2", returnR)
router.GET("/", Index)
router.GET("/error", ReturnError)
router.Run(":888")
}
func Index(context *gin.Context) {
//响应字符串
context.String(200, "Hello 枫枫!")
}
func ReturnError(c *gin.Context) {
c.Error(errors.New("演示响应错误")) //输出在控制台的呃呃error信息
}
//这样就可以实现返回自定以json 而不需要复写
func returnR(c *gin.Context) {
r := Result{Code: 0, Message: "success", Data: "data"}
c.JSON(200, r)
}
// 定义返回给前端的通用类型 后面的是tag :跟客户端进行序列化时候对应的key 首字母大写 给包外访问权限
type Result struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data"`
}
可以看到结构体字段的响应到前端 key 就转变为对应tag (下划线或者小驼峰命名),不写tag 的话就是默认字段名
tag 还可以做数据脱敏
type Userindo struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data"`
Password string `json:"password"`
}
如果直接返回密码,那么就太危险,go中可以序列化为json时候不进行序列化,也就不会返回给前端
type Result struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data"`
Password string `json:"-"` //不会进行序列化忽略空值字段
}
但是确不影响反序列化 json 序列化成为对象 (结构体)
type User struct {
Username string `json:"username"`
Password string `json:"-"` //omitempty 忽略空值字段
}
func logincontroller(c *gin.Context) {
var user User
// 将请求体绑定到 User 结构体
// 注意:这里的 ShouldBindJSON 仅适用于 JSON 格式的请求体 ShouldBind适用于表单格式的请求体
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效请求"})
return
}
fmt.Print(user.Username, user.Password)
// 输出接收到的用户名和密码
c.JSON(http.StatusOK, gin.H{
"username": user.Username,
"password": user.Password,
})
}
//.....绑定路由
router.POST("/login", logincontroller)
响应体并没有密码
同时反序列化也没办法接收到来自客户端的请求
type User struct {
Username string `json:"username"`
Password string `json:"-"` //omitempty 忽略空值字段
Age string `json:"age"` //omitempty 忽略空值字段
}
func logincontroller(c *gin.Context) {
var user User
// 将请求体绑定到 User 结构体
// 注意:这里的 ShouldBindJSON 仅适用于 JSON 格式的请求体 ShouldBind适用于表单格式的请求体
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效请求"})
return
}
// 打印接收到的用户名和密码
fmt.Println("Received username:", user.Username, "password:", user.Password, "age:", user.Age)
// 输出接收到的用户名和密码
c.JSON(http.StatusOK, gin.H{
"username": user.Username,
"password": user.Password,
})
}
密码的数据打印始终未空 所以还是返回前手动赋予空值 脱敏
返回其他类型
- xml
func returnXML(c *gin.Context) {
c.XML(http.StatusOK, gin.H{"user": "hanru", "message": "hey", "status": http.StatusOK})
}
只是使用的c.json变成了xml
-
YAML
这里使用Springboot 连接redid的配置代码块
func returnyml(c *gin.Context) {
config := gin.H{
"spring": gin.H{
"data": gin.H{
"redis": gin.H{
"database": 1,
"host": "localhost",
"port": 6379,
// "password": "",
// "timeout": "6000ms",
},
},
},
}
c.YAML(http.StatusOK, config)
}
游览器对于无法直接游览的文件会执行下载策略
打开后发现就是像输出的内容
并且输出的上诉格式都可以使用字符串,拼接,然后返回字符串,改变响应体的方式返回比如
-
json
func returnRj(c *gin.Context) { str := ` { "code":"0000", "message":"Hello World!", "data":{ "name":"John Doe" "age":30 "city":"New York"} } ` c.Header("Content-Type", "application/json") c.String(200, str) }
-
yaml
func returnYAML(c *gin.Context) { // 你的 Java 配置文件内容 yamlContent := ` spring: data: redis: database: 1 host: localhost port: 6379 #password: #timeout: 6000ms # 连接超时时长(毫秒) ` // 设置响应头 c.Header("Content-Type", "application/x-yaml") // 返回 YAML 内容 c.String(http.StatusOK, yamlContent) }
-
html
返回html文件之前需要先加载模板文件
//加载模板 router.LoadHTMLGlob("/templates/**/*") //定义路由 router.GET("/tem", func(c *gin.Context) { //根据完整文件名渲染模板,并传递参数 c.HTML(http.StatusOK, "index.html", gin.H{ "title": "你好世界", }) })
模板接收参数
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
hello world! {{ .title }}
</body>
</html>
-
响应重定向
router.GET("/redirect", func(c *gin.Context) { //支持内部和外部的重定向 c.Redirect(http.StatusMovedPermanently, "http://www.bilibili.com/") //外部则需要协议名写全,内部只需要写一个路由即可 })
接收请求信息
作为一个web框架除了响应客户端 还需要重客户端接收信息
接收querry方式参数
func queryUserinfo(c *gin.Context) {
fmt.Println(c.Query("user")) //只会拿到第一个查询参数
value, exists := c.GetQuery("user")
fmt.Printf("当前参数是否存在: %v, 值: %s\n", exists, value) //判断是否存在查询参数
fmt.Println(c.QueryArray("user")) // 拿到多个相同的查询参数
fmt.Println(c.DefaultQuery("addr", "四川省"))
}
func main() {
router := gin.Default()
router.GET("/queryUser", queryUserinfo)
router.Run(":8")
}
输出:
restful形式接收请求
func restful(c *gin.Context) {
fmt.Println(c.Param("user_id"))
fmt.Println(c.Param("addr_id"))
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "success",
"data": gin.H{
"user_id": c.Param("user_id"),
"addr_id": c.Param("addr_id"),
},
})
}
//路由绑定
router.GET("/restfulUser/:user_id/:addr_id", restful)
接收到参数并且返回
表单数据
一般和json一样都是post方式携带的,表单 PostForm
可以接收
multipart/form-data;
和application/x-www-form-urlencoded
func postForm(c *gin.Context) {
fmt.Println(c.PostForm("name")) //接收第一个参数
fmt.Println(c.PostFormArray("name")) //同名数组参数
fmt.Println(c.DefaultPostForm("addr", "四川省")) // 如果用户没传,就使用默认值
forms, err := c.MultipartForm() // 接收所有的form参数,包括文件
fmt.Println(forms, err)
}
原始数据 json
func _raw(c *gin.Context) {
body, _ := c.GetRawData() //获取原始请求体
fmt.Println(string(body))
contentType := c.GetHeader("Content-Type")
switch contentType {
case "application/json":
// json解析到结构体
type User struct {
Name string `json:"name"`
Age int `json:"age"`
}
var user User
//原始json包解析数据到结构体
err := json.Unmarshal(body, &user)
if err != nil {
fmt.Println(err.Error())
}
//解析后结构体
fmt.Println(user)
}
}
这里使用的解析,再绑定到结构体的方法是go中的json包自带的方法,当然gin也有封装好自带的解析方法
json的序列化方法;jsonData, err := json.Marshal(person)
请求头信息
在Spring的系列框架中,过滤器,拦截器来实现的权限验证都需要使用到请求头信息,所以对应请求头的获取是很重要的
func getHeader(c *gin.Context) {
// 首字母大小写不区分 单词与单词之间用 - 连接
// 用于获取一个请求头
fmt.Println(c.GetHeader("User-Agent"))
//fmt.Println(c.GetHeader("user-agent"))
//fmt.Println(c.GetHeader("user-Agent"))
//fmt.Println(c.GetHeader("user-AGent"))
// Header 是一个普通的 map[string][]string
fmt.Println(c.Request.Header) //getheader就是获取一个具体数据,如果有多个值,则返回一个数组
// 如果是使用 Get方法或者是 .GetHeader,那么可以不用区分大小写,并且返回第一个value
fmt.Println(c.Request.Header.Get("User-Agent"))
fmt.Println(c.Request.Header["User-Agent"])
// 如果是用map的取值方式,请注意大小写问题
fmt.Println(c.Request.Header["user-agent"])
// 自定义的请求头,用Get方法也是免大小写
fmt.Println(c.Request.Header.Get("Token"))
fmt.Println(c.Request.Header.Get("token"))
c.JSON(200, gin.H{
"msg": "获取请求头信息成功",
"code": 0,
"data": c.Request.Header,
})
}
响应头信息
/**
* 设置响应头de1api 就是上下文.header
*/
func setResHeaders(c *gin.Context) {
// 设置响应头
c.Header("Authorization", "jhgeu%hsgsasokasalsoaisaposa845jUIF83jh")
//c.Header("Content-Type", "application/text; charset=utf-8")
c.JSON(0, gin.H{"data": "看看响应头"})
}
数据绑定
如果是原生JSON绑定结构体使用的是,反序列化
type User struct {
Name string `json:"name"`
Age int `json:"age"`
Sex string `json:"sex"`
}
var user User
//原始json包解析数据到结构体
err := json.Unmarshal(body, &user)
gin中的接收请求并且绑定结构体的相关api
json绑定在结构体
采用ShouldBindJSON api
func getUser(c *gin.Context) {
var user *User = new(User)
//绑定json到结构体
err := c.ShouldBindJSON(user)
if err != nil {
c.JSON(200, gin.H{"msg": "绑定失败"})
return
}
c.JSON(200, user)
}
query参数绑定结构体
ShouldBindQuery api,注意需要给结构体在添加tags form:···· 表明 是序列化的表单 我这里测试的时候query 绑定api 也需要该参数才可以绑定
type User struct {
Name string `json:"name" form:"name"`
Age int `json:"age" form:"age"`
Sex string `json:"sex" form:"sex"`
}
func getUserBy(c *gin.Context) {
user := User{}
if err := c.ShouldBindQuery(&user); err != nil {
c.JSON(200, gin.H{"msg": "绑定失败"})
return
}
fmt.Printf("user:%v\n", user)
c.JSON(200, user)
}
表单数据绑定
ShouldBind
会根据请求头中的content-type去自动绑定
form-data的参数也用这个,tag用form
默认的tag就是form
type User struct {
Name string `json:"name" form:"name"`
Age int `json:"age" form:"age"`
Sex string `json:"sex" form:"sex"`
}
//虽然接收数据用的form格式但是jsontag还是需要的 序列化给前端的时候 会根据tag序列化字段
func getUserByform(c *gin.Context) {
u := new(User)
if err := c.ShouldBind(u); err != nil {
c.JSON(200, gin.H{"msg": "绑定失败"})
return
}
fmt.Printf("user:%v\n", u)
c.JSON(200, u)
}
绑定resful参数
type User struct {
Name string `json:"name" uri:"name"`
Age int `json:"age" uri:"age"`
Sex string `json:"sex" uri:"sex"`
}
func getUserByResful(c *gin.Context) {
u := new(User)
if err := c.ShouldBindUri(u); err != nil {
c.JSON(200, gin.H{"msg": "绑定失败"})
return
}
fmt.Printf("user:%v\n", u)
c.JSON(200, u)
}
//路由
router.GET("/info/:name/:age/:sex", getUserByResful)
利用tag 实现java中的参数校验
在java中实现参数java需要@valid注解,但是在go中有tag绑定即可实现
go内置绑定判断 bind绑定器
需要使用参数验证功能,需要加binding tag
// 不能为空,并且不能没有这个字段
required: 必填字段,如:binding:"required"
// 针对字符串的长度
min 最小长度,如:binding:"min=5"
max 最大长度,如:binding:"max=10"
len 长度,如:binding:"len=6"
// 针对数字的大小
eq 等于,如:binding:"eq=3"
ne 不等于,如:binding:"ne=12"
gt 大于,如:binding:"gt=10"
gte 大于等于,如:binding:"gte=10"
lt 小于,如:binding:"lt=10"
lte 小于等于,如:binding:"lte=10"
// 针对同级字段的
eqfield 等于其他字段的值,如:PassWord string `binding:"eqfield=Password"`
nefield 不等于其他字段的值
- 忽略字段,如:binding:"-"
gin 附加tag
// 枚举 只能是red 或green
oneof=red green
// 字符串
contains=fengfeng // 包含fengfeng的字符串
excludes // 不包含
startswith // 字符串前缀
endswith // 字符串后缀
// 数组
dive // dive后面的验证就是针对数组中的每一个元素
// 网络验证
ip
ipv4
ipv6
uri
url
// uri 在于I(Identifier)是统一资源标示符,可以唯一标识一个资源。
// url 在于Locater,是统一资源定位符,提供找到该资源的确切路径
// 日期验证 1月2号下午3点4分5秒在2006年
datetime=2006-01-02
使用校验字段
type User struct {
Name string `json:"name" form:"name" uri:"name" binding:"required" msg:"姓名不能为空"`
Age int `json:"age" form:"age" uri:"age" binding:"gte=0,lte=100" msg:"年龄信息不合法"`
Sex string `json:"sex" form:"sex" uri:"sex" binding:"oneof=男 女" msg:"性别不能为其他"`
}
此时书传递的参数不和binding的判断的话 就无法绑定在结构体上,并且msg的数据就不为nil
获取校验信息
利用反射自己实现 go中追求轻量级别的代码 对于过滤器,自定义响应处理等都需要手动实现
func GetValidMsg(err error, obj any) string {
// 使用的时候,需要传obj的指针
getObj := reflect.TypeOf(obj)
// 将err接口断言为具体类型
var errs validator.ValidationErrors
if errors.As(err, &errs) {
// 断言成功
for _, e := range errs {
// 循环每一个错误信息
// 根据报错字段名,获取结构体的具体字段
if f, exits := getObj.Elem().FieldByName(e.Field()); exits {
msg := f.Tag.Get("msg")
return msg
}
}
}
return err.Error()
}
func getUserByform(c *gin.Context) {
u := new(User)
if err := c.ShouldBind(u); err != nil {
msg := GetValidMsg(err, u)
c.JSON(200, gin.H{"msg": msg})
return
}
fmt.Printf("user:%v\n", u)
c.JSON(200, u)
}
自定义绑定校验规则
编写函数
func signValid(fl validator.FieldLevel) bool {
//源码是Field() reflect.Value 反射包裹的字段值
age := fl.Field().Interface().(int)//这个断言的类型 是一会绑定过的tag 类型
if age != 18 {
return false
}
return true
}
注册
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
v.RegisterValidation("Agesign", signValid)
}
使用
type User struct {
Name string `json:"name" form:"name" uri:"name" binding:"required" msg:"姓名不能为空"`
Age int `json:"age" form:"age" uri:"age" binding:"Agesign" msg:"年龄信息不合法"`
Sex string `json:"sex" form:"sex" uri:"sex" binding:"oneof=男 女" msg:"性别不能为其他"`
}
gin中的文件传输
获取请求体中的文件
c.FormFile(“file”) 单文件可以采用该方式
func main() {
router := gin.Default()
// 为 multipart forms 设置较低的内存限制 (默认是 32 MiB)
// 单位是字节, << 是左移预算符号,等价于 8 * 2^20
// gin对文件上传大小的默认值是32MB
router.MaxMultipartMemory = 8 << 20 // 8 MiB
router.POST("/upload", func(c *gin.Context) {
// 单文件
file, _ := c.FormFile("file")
log.Println(file.Filename)
path := "./" + file.Filename
// 上传文件至指定的完整文件路径
c.SaveUploadedFile(file, path)
c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))
})
router.Run(":8080")
}
一开始入门接收请求表单哪里也有个方法可以获取表单字段 但是返回的字符串
value := c.PostForm("file")
但是可以通过获取整个表单 然后再获取文件这个字段的文件数组 这样进行上传也是一样的效果
if form, err := c.MultipartForm(); err == nil {
files := form.File["file"]
for _, file := range files {
log.Println(file.Filename)
path := "./" + file.Filename
// 上传文件至指定的完整文件路径
c.SaveUploadedFile(file, path)
}
}
multipartform的源码
FormFile(“file”)的源码
所以俩者都是一样的 主要是获取fileHeader的指针
保存文件接口
SaveUploadedFile
c.SaveUploadedFile(file, path) // 文件对象 文件路径,注意要从项目根路径开始写
还有种方式就是go种io原生的打开文件
file, _ := c.FormFile("file")
log.Println(file.Filename)
// 读取文件中的数据,返回文件对象
fileRead, _ := file.Open()
dst := "./" + file.Filename
// 创建一个文件
out, err := os.Create(dst)
if err != nil {
fmt.Println(err)
}
defer out.Close()
// 拷贝文件对象到out中
io.Copy(out, fileRead)
下载文件
在Springboot中,对于下载文件逻辑,一般采用在响应体中,把文件读取为二进制流在写入响应体 标清楚响应头
而go中也类似
c.File("文件地址")
func downloadFile(c *gin.Context) {
c.Header("Content-Type", "application/octet-stream") // 表示是文件流,唤起浏览器下载,一般设置了这个,就要设置文件名
c.Header("Content-Disposition", "attachment; filename="+"18禁的安装包.apk") // 用来指定下载下来的文件名
c.Header("Content-Transfer-Encoding", "binary") // 表示传输过程中的编码形式,乱码问题可能就是因为它
c.File("./da_1701653930625.apk")
}
中间件详解
对应java中的很多重要业务逻辑,验证权限,身份认证,错误处理都是有专门的过滤器链,以及handler 接口实现,gin作为轻量级别的框架,这些内容采用中间的形式实现
func indexHandler(c *gin.Context) {
fmt.Println("index.....")
c.JSON(http.StatusOK, gin.H{
"msg": "index",
})
}
// 定义一个中间件
func m1(c *gin.Context) {
fmt.Println("在方法响应回json之前执行.........")
}
func m2(c *gin.Context) {
fmt.Println("在方法响应回json之后执行.........")
c.JSON(http.StatusOK, gin.H{
"msg": "index之后",
})
}
func main() {
r := gin.Default()
//m1处于indexHandler函数的前面,请求来之后,先走m1,再走index 在m2
r.GET("/index", m1, indexHandler, m2)
_ = r.Run()//默认端口8080
}
也就是路由匹配的这些函数都是中间件,并且根据放入的顺序不同 执行不同
拦截中间件
当调用abort 方法后 后续中间件不再执行
func indexHandler(c *gin.Context) {
fmt.Println("中间件拦截后 后面的请求就不会执行了.....")
c.Abort()
c.JSON(http.StatusOK, gin.H{
"msg": "index",
})
}
// 定义一个中间件
func m1(c *gin.Context) {
fmt.Println("在方法响应回json之前执行.........")
}
func m2(c *gin.Context) {
fmt.Println("在方法响应回json之后执行.........")
c.JSON(http.StatusOK, gin.H{
"msg": "index之后",
})
}
//路由绑定
r.GET("/index", m1, indexHandler, m2)
由于index中使用了abort会中断中间件链路的执行
控制中间件的执行流程
c.Next() ,确实如此。当你在中间件中调用 c.Next()
后,Gin 会将控制权交给下一个中间件或路由处理器,让它们按照顺序执行。然而,一旦这些后续中间件和处理器执行完毕,控制权会回到调用 c.Next()
的中间件中,继续执行 c.Next()
之后的代码。 主要用于控制流程 ,如果打印错误
func testNext(c *gin.Context) {
fmt.Println("执行当前中间件逻辑,但是先让执行下一个中间件执行")//1
c.Next()
fmt.Println("下一个中间件执行完毕 继续执行当前中间件...")//4
}
func testNext2(c *gin.Context) {
fmt.Println("第二个中间件执行 继续执行当前逻辑...")//2
c.Next()
fmt.Println("第二个执行完毕...")//3
}
//路由
r.GET("/", testNext, testNext2)
这里顺序分析 先执行 1,然后next控制权给下一个中间件,到达输出2的位置,由于没有下一个中间件所以执行3,然后回到第一个next中间件处执行4
全局注册中间件
写一个中间件
func initfilter(c *gin.Context) {
fmt.Println("全局中间件开始执行.........")
c.Next() //交给下一个中间件执行
fmt.Println("全局中间件结束执行.........")
}
//路由全局注册
r.Use(initfilter)
访问任意路由 发现全局中间件是第一个执行的 那么有意思的就来了(过滤器请求执行前,拦截器执行后)
如果多个全局中间件 即是注册顺序
因为源码是添加到一个函数数组(包含gin封装的http上下文的)
中间件传递数据
采用set (key,value)的方式 由于每一个请求都是隔离的 上下文之之间就算有同名key也不担心数据冲突 ,是不是很想java的Threadlocal,和过滤器存入用户认证信息的过程。只能说各个设计之间都有相似性
func indexHandler(c *gin.Context) {
fmt.Println("中间件拦截后 后面的请求就不会执行了.....")
c.Abort() // 直接中止请求,不再执行后面的逻辑 但是当前后面的代码块会执行
if id, exists := c.Get("userid"); exists {
c.JSON(http.StatusOK, gin.H{
"msg": "index",
"data": gin.H{
"userid": id,
},
})
}
}
func initfilter(c *gin.Context) {
fmt.Println("全局中间件开始执行.........")
c.Set("userid", 237)
c.Next() //交给下一个中间件执行
fmt.Println("全局中间件结束执行.........")
}
路由分组
对于全局中间件 可能应用的范围不同 所以gin中支持分组 并且还是链式的
r.Group("/api").POST().GET()
r.Group("/api").Use().Use()
此时路由地址会变成组级
func main() {
r := gin.Default()
r.Use(initfilter)
group1 := r.Group("/api")
group2 := r.Group("/test")
group2.Use(Case)
group1.Use(initfilter2)
group1.GET("/index", m1, indexHandler, m2)
group2.GET("/index", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "test index",
})
})
//m1处于indexHandler函数的前面,请求来之后,先走m1,再走index
_ = r.Run()
}
中间件执行顺序 全局->分组
注意确保中间件的注册在路由或路由组定义之前进行,以确保中间件能够正确应用。中间件的作用范围是从它注册的位置开始到定义的路由或路由组为止,所以正确的顺序和位置非常重要。
路由源码
// Default returns an Engine instance with the Logger and Recovery middleware already attached.
func Default(opts ...OptionFunc) *Engine {
debugPrintWARNINGDefault()
engine := New()
engine.Use(Logger(), Recovery())
return engine.With(opts...)
}
源码部分也是启动了 俩个中间件 一个日志 日志这个就是输出的路由和各个log
主要是recovery 正常的go程序遇到panic会停止recovery做的就算回复并且捕捉
func main() {
engine := gin.Default()
engine.GET("/panic", func(c *gin.Context) {
panic("演示错误")
})
engine.Run(":8080")
}
整合 swagger
作为一个web框架 已经可以做到和前端交互,orm和数据库交互的过程篇幅问题不做演示,但是可以提一下swagger 这个javaer都不会陌生 knife这些 既然目前可以完成了接口交互 那么接口文档就必不可少
先写一个简单的服务案列
func main() {
router := gin.Default()
router.GET("/", Ping)
router.Run(":80")
}
func Ping(ctx *gin.Context) {
ctx.JSON(200, gin.H{
"message": fmt.Sprintf("Hello World!%s", ctx.Query("name")),
})
}
安装 swaggo
#SWAGGER 命令行
go install github.com/swaggo/swag/cmd/swag@latest
##源码依赖
go get github.com/swaggo/swag
##打包的静态文件
go get github.com/swaggo/files@latest
#适配版本库
go get github.com/swaggo/gin-swagger@latest
目录分级 实际开发中有 main.go(swagger只会根据同目录的main 进行初始化)主要是负责启动
go-swagger-example/
├── go.mod
├── main.go # 入口文件
├── controllers/ # 控制器包
│ └── hello.go
└── routes/ # 路由配置包
└── routes.go
/ HelloHandler godoc
// @Summary Say Hello
// @Description Responds with a message "Hello, World!"
// @Tags example
// @Accept json
// @Produce json
// @Success 200 {string} string "Hello, World!"
// @Router /hello [get]
func HelloHandler(c *gin.Context) {
c.JSON(200, gin.H{
"message": "Hello, World!",
})
}
在代码中,Swagger 注释使用 // @
开头的特殊注释。关键的注释包括:
@title
:API 的标题。@version
:API 版本。@description
:API 的描述。@contact.name
:联系人姓名。@license.name
和@license.url
:API 的许可信息。@BasePath
:API 的基础路径。@Summary
和@Description
:为特定的处理程序提供简要描述和详细描述。@Success
:定义成功响应的结构。@Router
:定义路由路径和 HTTP 方法。
控制台输出
swag init
访问
http://localhost:8080/swagger/index.html