javaer快速入门 goweb框架 gin

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: 1host: localhostport: 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][]stringfmt.Println(c.Request.Header) //getheader就是获取一个具体数据,如果有多个值,则返回一个数组// 如果是使用 Get方法或者是 .GetHeader,那么可以不用区分大小写,并且返回第一个valuefmt.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.ValidationErrorsif 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对文件上传大小的默认值是32MBrouter.MaxMultipartMemory = 8 << 20 // 8 MiBrouter.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 在m2r.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("执行当前中间件逻辑,但是先让执行下一个中间件执行")//1c.Next()fmt.Println("下一个中间件执行完毕  继续执行当前中间件...")//4
}
func testNext2(c *gin.Context) {fmt.Println("第二个中间件执行  继续执行当前逻辑...")//2c.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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/877447.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

鸿蒙内核源码分析——(自旋锁篇)

本篇说清楚自旋锁 读本篇之前建议先读系列篇 进程/线程篇. 内核中哪些地方会用到自旋锁?看图: 概述 自旋锁顾名思义&#xff0c;是一把自动旋转的锁&#xff0c;这很像厕所里的锁&#xff0c;进入前标记是绿色可用的&#xff0c;进入格子间后&#xff0c;手一带&#xff0c…

10分钟学会LVM逻辑卷

华子目录 前言认识LVMLVM基本概念LVM整体流程LVM管理命令pvs&#xff0c;vgs&#xff0c;lvs命令pvs基本用法选项示例 vgs基本用法选项示例 lvs基本用法 pvcreate&#xff0c;vgcreate&#xff0c;lvcreate命令pvcreate示例 vgcreate基本用法示例选项 lvcreate基本用法示例 pvr…

Python爬虫入门教程(非常详细)适合零基础小白

一、什么是爬虫&#xff1f; 1.简单介绍爬虫 爬虫的全称为网络爬虫&#xff0c;简称爬虫&#xff0c;别名有网络机器人&#xff0c;网络蜘蛛等等。 网络爬虫是一种自动获取网页内容的程序&#xff0c;为搜索引擎提供了重要的数据支撑。搜索引擎通过网络爬虫技术&#xff0c;将…

【电路笔记】-无源衰减器总结

无源衰减器总结 文章目录 无源衰减器总结1、概述2、L-型无源衰减器设计3、T-型无源衰减器设计4、桥接 T 型衰减器设计5、π型无源衰减器设计无源衰减器是一个纯电阻网络,可用于控制输出信号的电平。 1、概述 无源衰减器是一种纯电阻网络,用于削弱或“衰减”传输线的信号电平…

Element UI中报dateObject.getTime is not a function解决方法~

1、错误信息。 2、该报错原因是Element UI中日期组件的校验规则是type: "date",而一般我们从后台拿到的数据是字符串型的&#xff0c;不满足预期&#xff0c;就会报错。 3、解决方法。 去掉日子组件中的type: "date"校验规则即可。 rules: {newName: [{…

EasyCVR视频汇聚平台:深度解析GB/T 28181协议下的视频资源整合与应用

随着安防技术的快速发展和智慧城市建设的推进&#xff0c;视频监控系统作为公共安全、城市管理、企业运营等领域的重要基础设施&#xff0c;其重要性和应用范围不断扩大。在这一过程中&#xff0c;GB/T 28181作为国家标准中关于视频监控设备通信协议的规范&#xff0c;正逐渐受…

C2M商业模式分析与运营平台建设解决方案(四)

C2M商业模式以消费者需求驱动生产制造&#xff0c;实现个性化与效率的双赢。本解决方案将围绕构建智能化、数据驱动的运营平台&#xff0c;通过精准把握市场需求、优化生产流程、强化供应链管理&#xff0c;打造高效、敏捷、柔性的C2M运营体系&#xff0c;助力企业快速响应市场…

华为AR1220配置GRE隧道

1.GRE隧道的配置 GRE隧道的配置过程,包括设置接口IP地址、配置GRE隧道接口和参数、配置静态路由以及测试隧道连通性。GRE隧道作为一种标准协议,支持多协议传输,但不提供加密,并且可能导致CPU资源消耗大和调试复杂等问题。本文采用华为AR1220路由器来示例说明。 配置…

【电路笔记】-桥接 T 型衰减器

桥接 T 型衰减器 文章目录 桥接 T 型衰减器1、概述2、桥接 T 型衰减器示例 13、可变桥接 T 型衰减器4、完全可调衰减器5、可切换桥接 T 型衰减器Bridged-T 衰减器是另一种电阻衰减器设计,它是标准对称 T 垫衰减器的变体。 1、概述 顾名思义,桥接 T 形衰减器具有一个额外的电…

Cesium模型制作,解决Cesium加载glb/GLTF显示太黑不在中心等问题

Cesium模型制作&#xff0c;解决Cesium加载glb/GLTF显示太黑不在中心等问题 QQ可以联系这里&#xff0c;谢谢

Spring SSM框架--MVC

SSM框架–Mybatis 一、介绍 Spring 框架是一个资源整合的框架&#xff0c;可以整合一切可以整合的资源&#xff08;Spring 自身和第三方&#xff09;&#xff0c;是一个庞大的生态&#xff0c;包含很多子框架&#xff1a;Spring Framework、Spring Boot、Spring Data、Spring…

C++高性能编程:ZeroMQ vs Fast-DDS发布-订阅模式下性能对比与分析

文章目录 0. 引言1. 目标&#xff1a;ZeroMQ与Fast-DDS性能对比2. ZeroMQ vs Fast-DDS - 延迟基准测试2.1 一对一发布-订阅延迟2.2 一对多发布-订阅延迟 3. ZeroMQ vs Fast-DDS - 吞吐量基准测试4. 方法论5. 结论6. 参考 0. 引言 高要求的分布式系统催生了对轻量级且高性能中间…

C#MVC返回DataTable到前端展示。

很久没写博客了&#xff0c;闭关太久&#xff0c;失踪人口回归&#xff0c;给诸位道友整点绝活。 交代下背景&#xff1a;要做一个行转列的汇总统计&#xff0c;而且&#xff0c;由于是行转列&#xff0c;列的数量不固定&#xff0c;所以&#xff0c;没法使用正常的SqlSugar框…

el-tree树状控件,定位到选中的节点的位置

效果图 在el-tree 控件加 :render-content"renderContent" 在掉接口的方法中 实际有用的是setTimeout 方法和this.$refs.xxxxxx.setCheckedKeys([industrycodeList]) if(res.data.swindustrylist.length>0){res.data.swindustrylist.forEach(item > {industry…

STM32之SPI读写W25Q128芯片

SPI简介 STM32的SPI是一个串行外设接口。它允许STM32微控制器与其他设备&#xff08;如传感器、存储器等&#xff09;进行高速、全双工、同步的串行通信。通常包含SCLK&#xff08;串行时钟&#xff09;、MOSI&#xff08;主设备输出/从设备输入Master Output Slave Input&…

Linux系统编程 --- 多线程

线程&#xff1a;是进程内的一个执行分支&#xff0c;线程的执行粒度&#xff0c;要比进程要细。 一、线程的概念 1、Linux中线程该如何理解 地址空间就是进程的资源窗口。 在一个程序里的一个执行路线就叫做线程&#xff08;thread&#xff09;。更准确的定义是&#xff1…

聊聊场景及场景测试

在我们进行测试过程中&#xff0c;有一种黑盒测试叫场景测试&#xff0c;我们完全是从用户的角度去理解系统&#xff0c;从而可以挖掘用户的隐含需求。 场景是指用户会使用这个系统来完成预定目标的所有情况的集合。 场景本身也代表了用户的需求&#xff0c;所以我们可以认为…

SpringBoot+Vue在线商城(电子商城)系统-附源码与配套论文

摘 要 随着互联网技术的发展和普及&#xff0c;电子商务在全球范围内得到了迅猛的发展&#xff0c;已经成为了一种重要的商业模式和生活方式。电子商城是电子商务的重要组成部分&#xff0c;是一个基于互联网的商业模式和交易平台&#xff0c;通过网络进行产品和服务的销售。…

计算机图形学 | 动画模拟

动画模拟 布料模拟 质点弹簧系统&#xff1a; 红色部分很弱地阻挡对折 Steep connection FEM:有限元方法 粒子系统 粒子系统本质上就是在定义个体和群体的关系。 动画帧率 VR游戏要不晕需要达到90fps Forward Kinematics Inverse Kinematics 只告诉末端p点&#xff0c;中间…

Delphi5实现色板程序——滑块型组件实例

效果图 参考 Delphi程序设计基础&#xff1a;教程、实验、习题 代码 unit Unit1;interfaceusesSysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,Dialogs, Forms,Form, Formprpt, ExtCtrls, StdCtrls;typeTForm1 class(MForm)Label1: TLabel;Label2: …