Gin 学习笔记 02-Gin 响应和请求

响应

json 响应

对Response 封装

package res

import "github.com/gin-gonic/gin"

type Response struct {
	Code int    `json:"code"`
	Msg  string `json:"msg"`
	Data any    `json:"data"`
}

var codeMap = map[int]string{
	1001: "权限错误",
	1002: "角色错误",
	1003: "参数错误",
}

func response(context *gin.Context, code int, data any, msg string) {
	context.JSON(200, Response{
		Code: code,
		Msg:  msg,
		Data: data,
	})
}
func Ok(context *gin.Context, data any, msg string) {
	response(context, 0, data, msg)
}

func OkWithData(context *gin.Context, data any) {
	Ok(context, data, "操作成功")
}

func OkWithMsg(context *gin.Context, msg string) {
	Ok(context, gin.H{}, msg)
}

// 错误的封装
func Fail(context *gin.Context, code int, data any, msg string) {
	response(context, code, data, msg)
}

func FailWithMsg(context *gin.Context, msg string) {
	response(context, 1001, nil, msg)

}

func FailWithCode(context *gin.Context, code int) {
	msg, ok := codeMap[code]
	if !ok {
		msg = "服务错误"
	}
	response(context, code, nil, msg)
}
  • response唯一的实际出口,所有 HTTP 状态码都固定写 200enter.go:12),把”业务状态”和”HTTP 状态”分离——HTTP 永远 200,业务成功失败靠 code 字段表达。这是 RESTful 风格之外另一种常见约定(国内项目很流行)。

  • 业务代码只调 Ok / Fail,不直接碰 response,保证所有响应都走同一个出口,不会有人偷偷返回不一样的格式。

  • 只关心数据 → OkWithData(c, user) 自动填”操作成功”

  • 只关心提示 → OkWithMsg(c, "删除成功") 自动填空 data

好处:handler 代码更短、更聚焦业务,不用每次都手写 msg。这是”语法糖”,没有它们用 Ok 也能做到,但写了能少敲很多字。

package main

import (
	"gin_study/study_response/res"

	"github.com/gin-gonic/gin"
)

func main() {
	//初始化
	r := gin.Default()
	//绑定路由
	r.GET("/login", func(context *gin.Context) {
		res.OkWithMsg(context, "成功")
	})
	r.GET("/users", func(context *gin.Context) {
		res.OkWithData(context, map[string]any{
			"name": "张三",
			"age":  18,
		})
	})
	r.POST("/users", func(context *gin.Context) {
		res.FailWithMsg(context, "失败")
	})
	//绑定端口运行
	r.Run(":8080")
}

html 响应

使用LoadHTMLGlob加载一个目录下的所有html文件

也可以使用LoadHTMLFiles加载单个html文件

load之后,下面才能用这个文件名

package main

import "github.com/gin-gonic/gin"

func main() {
  r := gin.Default()
  // 加载模板 只有这里加载了模板,下面才能用
  r.LoadHTMLGlob("templates/*")
  //r.LoadHTMLFiles("templates/index.html")
  r.GET("", func(c *gin.Context) {
    c.HTML(200, "index.html", nil)
  })
  r.Run(":8080")
}

HTML的第三个参数是可以向HTML中传递数据

但是现在都是前后端分离的时代了,也很少使用后端返回模板了,知道怎么用就好

c.HTML(200, "index.html", map[string]any{
    "title": "这是网页标题",
  })

html文件中使用

<title>{{.title}}</title>

其实借助这个功能,可以很方便的修改网站的标题和logo

响应文件

用于浏览器直接请求这个接口唤起下载

c.Header("Content-Type", "application/octet-stream")              // 表示是文件流,唤起浏览器下载,一般设置了这个,就要设置文件名
c.Header("Content-Disposition", "attachment; filename=3.文件下载.go") // 用来指定下载下来的文件名
c.File("3.文件下载.go")
  1. 要设置Content-Type,唤起浏览器下载
  2. 只能是get请求

前端请求后端接口,然后唤起浏览器下载

c.Header("fileName", "xxx.png")
c.Header("msg", "文件下载成功")
c.File("uploads/12.png")

最好的做法

调下载接口的请求,后端不返回实际文件内容,而是生成一个临时下载地址

前端构造a标签,再请求这个接口唤起浏览器下载

静态文件

r.Static("st" /*别名*/, "static")// 第一个参数是别名,第二个才是实际路径
//http://localhost:8080/st/hello.txt
r.StaticFile("abc", "static/hello.txt")
//http://localhost:8080/abc

静态文件的路径,不能再被路由使用了

请求

查询参数

?key=xxx&name=xxxx&name=yyyy 这种就被称为查询参数

查询参数不是GET请求专属的

	r.GET("", func(c *gin.Context) {
		name := c.Query("name")
		age := c.DefaultQuery("age", "25")
		keyList := c.QueryArray("key")
		fmt.Println(name, age, keyList)
	})
//http://localhost:8080/?name=zhangsan&age=18&key=123&key=333
zhangsan 18 [123 333]

动态参数

用户的个人信息页面,他的路径

/users?id=123 // 查询参数的模式
/users/123 // 动态参数模式
r.GET("users/:id/:name", func(c *gin.Context) {
	userId := c.Param("id")
	userName := c.Param("name")
	fmt.Println(userId, userName)
})

表单参数

form 表单

r.POST("users", func(c *gin.Context) {
    name := c.PostForm("name")
    //通过 ok 来判断是否传了数据
    age, ok := c.GetPostForm("age")
    fmt.Println(name, age, ok)

})

文件上传

r.POST("users", func(c *gin.Context) {
	fileHeader, err := c.FormFile("file")
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println(fileHeader.Filename) //文件名
	fmt.Println(fileHeader.Size)     //单位是字节
	file, _ := fileHeader.Open()
	byteData, _ := io.ReadAll(file)
	err = os.WriteFile("xxx.jpg", byteData, 0666)
	fmt.Println(err)

})

简单的方法

err = c.SaveUploadedFile(fileHeader, "uploads/"+fileHeader.Filename)
fmt.Println(err)

多文件

r.POST("users", func(c *gin.Context) {
	form, err := c.MultipartForm()
	if err != nil {
		fmt.Println(err)
		return
	}
	for _, headers := range form.File {
		for _, header := range headers {
			c.SaveUploadedFile(header, "uploads/muilt/"+header.Filename)
		}

	}
})

接口测试工具

Postman

apifox

注意:接口测试工具能走通的,前端请求不一定可以走通

  1. get请求带请求体
  2. ws加请求头