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
相关文章:

javaer快速入门 goweb框架 gin
gin 入门 前置条件 安装环境 配置代理 # 配置 GOPROXY 环境变量,以下三选一# 1. 七牛 CDN go env -w GOPROXYhttps://goproxy.cn,direct# 2. 阿里云 go env -w GOPROXYhttps://mirrors.aliyun.com/goproxy/,direct# 3. 官方 go env -w GOPROXYhttps://goproxy.…...
SQL - 数据类型
字符串类型 char(10),存储固定长度字符串 varchar(255),存储可变长度字符串 mediumtext,中文本,对于存储JSON对象、SCV字符串很好使 longtext,长文本,可以很好地存储教本或许多年地日志文件 tinytext&#…...

进程相关知识
进程和程序的区别 程序 程序是静态的,是存储在硬盘、SSD等存储介质中的一个文件,通常由源代码(如 .c 文件)编译生成的二进制可执行文件(如 a.out)。程序包含了指令和数据,但在未被执行时&#…...

萝卜快跑和端到端的自动驾驶(1)
先看一篇论文 2311.18636 (arxiv.org) 这篇论文里有一个非常好的图 比较了一下模块化任务(级联任务)和端到端自动驾驶的区别 首先什么叫模块化任务(级联) 如上图所示,左边的方块中的子方块,是展示了自动驾驶获取数据的途径,这里包括&…...
通信原理学习笔记
一个手机通话需要经过下面三个网络 类别接入网(Access Network)承载网(Transport Network)核心网(Core Network)定义连接终端用户与电信网络的部分。在接入网和核心网之间传输数据的网络。处理、交换和管理…...
系统编程---day4
1. 链接文件 命令行: ln -s 文件名 softlink 1.1 symlink int symlink(const char *oldpath, const char *newpath); 功能:创建一个链接向oldpath文件的新符号链接文件 参数:oldpath:被链接向的文件的路径 newpath:新符号链接文件 返回值:成功返回0,失败返回…...

01:电容的什么,各类电容的优缺点
1.电容是什么? 电容是由两块不连通的导体,已经中间的不导电材料组成 电容结构: 1.2电容的容量计算公式 C ε s d \displaystyle\frac{εs}{d} dεs 1.3常见电容的种类 1.4各类电容的特点...

Android+Jacoco+code-diff全量、增量覆盖率生成实战
背景 主要是记录下Android项目使用jacoco生成代码覆盖率的实战流程,目前已完成全量覆盖方案,仅使用jacoco就能实现; 由于我们的Android端是使用Java和kotlin语言,目前增量的方案code-diff仅针对Java代码,卡在kotlin文件的分析&am…...

乌龟对对碰在线版
爆肝两天使用vue开发了一个在线版的乌龟对对碰小游戏之幸运对对碰。没有找到合适的乌龟素材,现在使用小兔子代替。 体验地址:幸运对对碰 | 乌龟对对碰小游戏 之前的python版本的乌龟对对碰:写文章-CSDN博客 乌龟对对碰-幸运对对碰...
如何更改select option边框颜色和选中的颜色
<!doctype html> <html> <head> <meta charset"utf-8"> <title>如何更改select option边框颜色和选中的颜色</title> </head><style>ul{border: 1px solid #000000;width: 500px;height: auto;background-color: aq…...

6. 数据结构—串的匹配算法
1.BF算法(暴力算法) //模式匹配(暴力算法) int Index(SString S,SString T){int i1,j1;while(i<S.length&&j<T.length){if(S[i]T[i]){i;j;}else{ii-j2; //最开始匹配的位置的后一个j1; //从头匹配 }}if(j>T.length)return i-T.length;return return 0…...

九大服务架构性能优化方式
来源:九大服务架构性能优化方式 目录 性能优化九大方式: 缓存 使用什么样的缓存 缓存常见问题 缓存淘汰 缓存数据一致性 并行化处理 批量化处理 数据压缩合并 无锁化 顺序写 分片化 避免请求 池化 异步处理 总结 最近做了一些服务性能优…...

【RabbitMQ】 相关概念 + 工作模式
本文将介绍一些MQ中常见的概念,同时也会简单实现一下RabbitMQ的工作流程。 MQ概念 Message Queue消息队列。是用来存储消息的队列,多用于分布式系统之间的通信。 系统间调用通常有:同步通信和异步通信。MQ就是在异步通信的时候使用的。 同…...
嵌入式学习 ——(Linux高级编程——进程)
目录 一、进程的含义 二、进程和程序的区别 三、进程的作用 四、进程的状态 五、进程的调度与上下文切换 六、查询进程相关命令 七、fork()函数 八、getpid()和getppid()函数 九、面试题解析: 十、应用场合及测试 一、进程的含义 进程指正在运行的程序&a…...

C++练习备忘录
1. 保留两位小数输出格式 #include <iostream> #include <iomanip> using namespace std; int main() {double S 0;S (15 25) * 20 / 2;cout << fixed << setprecision(2) << S;return 0; }2. 设置输出宽度 #include <iostream> #inclu…...
改善工作流
快捷键管理器 打开Editor->Shortcuts查看和编辑Unity中的快捷键 示例 ShiftSpace 窗口最大化 P 选择预制体 进入预制体编辑模式 单一检视窗口 选择组件,选择Properties打开一个窗口,显示组件信息;切换对象,窗口信息不会改变…...

迭代器失效
一、什么是迭代器失效 迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装,比如:vector的迭代器就是原生态指针T* 。因此迭代器失效,实际就是迭代器底层对应指针所指…...
@RequestParam @RequestBody @PathVariable 这三个注解对应的前端使用vue的http请求时不同的调用方式
1. RequestParam 用途:用于提取请求参数,常见于GET请求或表单提交。 Vue HTTP 请求示例: // 使用axios发送GET请求 axios.get(/api/users, { params: { id: 1, name: John } }); 2. RequestBody 用途:用于提取请求体…...
SQL - 索引
索引本质上是数据库引擎用来快速查找数据的数据结构,可以显著提高查询的性能,为了加快运行较慢的查询。创建索引 默认索引 create index 索引名 on 表名 (列名); 通过对列名进行创建索引,在查询的时候,数据库就能通过索引找到匹配…...
Oracle23ai新特性FOR LOOP循环控制结构增强
在Oracle数据库中,FOR LOOP是一种常用的循环控制结构,它允许你重复执行一系列语句固定次数或直到满足特定条件为止。然而,标准的Oracle PL/SQL中的FOR LOOP主要用于遍历集合(如数组或游标的结果集),而不是像…...

业务系统对接大模型的基础方案:架构设计与关键步骤
业务系统对接大模型:架构设计与关键步骤 在当今数字化转型的浪潮中,大语言模型(LLM)已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中,不仅可以优化用户体验,还能为业务决策提供…...

STM32F4基本定时器使用和原理详解
STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...

【机器视觉】单目测距——运动结构恢复
ps:图是随便找的,为了凑个封面 前言 在前面对光流法进行进一步改进,希望将2D光流推广至3D场景流时,发现2D转3D过程中存在尺度歧义问题,需要补全摄像头拍摄图像中缺失的深度信息,否则解空间不收敛…...
Python爬虫(二):爬虫完整流程
爬虫完整流程详解(7大核心步骤实战技巧) 一、爬虫完整工作流程 以下是爬虫开发的完整流程,我将结合具体技术点和实战经验展开说明: 1. 目标分析与前期准备 网站技术分析: 使用浏览器开发者工具(F12&…...
TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案
一、TRS收益互换的本质与业务逻辑 (一)概念解析 TRS(Total Return Swap)收益互换是一种金融衍生工具,指交易双方约定在未来一定期限内,基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...
Robots.txt 文件
什么是robots.txt? robots.txt 是一个位于网站根目录下的文本文件(如:https://example.com/robots.txt),它用于指导网络爬虫(如搜索引擎的蜘蛛程序)如何抓取该网站的内容。这个文件遵循 Robots…...
三体问题详解
从物理学角度,三体问题之所以不稳定,是因为三个天体在万有引力作用下相互作用,形成一个非线性耦合系统。我们可以从牛顿经典力学出发,列出具体的运动方程,并说明为何这个系统本质上是混沌的,无法得到一般解…...

自然语言处理——循环神经网络
自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元(GRU)长短期记忆神经网络(LSTM)…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统
目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索(基于物理空间 广播范围)2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...