响应
gin提供了非常多的响应方法
例如 字符串、json、html等
json响应
现在大部分的前后端交互都是以json为主,所以gin中最常用的就是json响应
它的用法非常简单
c.JSON(200, gin.H{
"code": 0,
"msg": "ok",
})
但是我们都会对其进行一番封装,例如标准响应格式 code,data,msg
前端可以判断code的值来确定操作是否成功
不过code的定义就是每家公司都有不同的定义了
我比较习惯于code=0为操作成功的状态码,非0值就是具体的错误码,这样可以方便定位错误
例如 code=1001 是权限错误,code=1002 是资源不存在等
如何封装?
package res
import "github.com/gin-gonic/gin"
type Response struct {
Code int `json:"code"`
Data any `json:"data"`
Msg string `json:"msg"`
}
type Code int
const (
RoleErrCode Code = 1001
NetworkErrCode Code = 1002
)
var codeMap = map[Code]string{
RoleErrCode: "权限错误",
NetworkErrCode: "网络错误",
}
func init() {
// 可能是一个
}
func response(c *gin.Context, r Response) {
c.JSON(200, r)
}
func Ok(c *gin.Context, data any, msg string) {
response(c, Response{
Code: 0,
Data: data,
Msg: msg,
})
}
func OkWithData(c *gin.Context, data any) {
Ok(c, data, "成功")
}
func OkWithMsg(c *gin.Context, msg string) {
Ok(c, map[string]any{}, msg)
}
func Fail(c *gin.Context, code int, data any, msg string) {
response(c, Response{
Code: code,
Data: data,
Msg: msg,
})
}
func FailWithMsg(c *gin.Context, msg string) {
response(c, Response{
Code: 7,
Data: nil,
Msg: msg,
})
}
func FailWithCode(c *gin.Context, code Code) {
// 去找code对应的msg
msg, ok := codeMap[code]
if !ok {
msg = "未知错误"
}
response(c, Response{
Code: int(code),
Data: nil,
Msg: msg,
})
}
封装之后,使用就很简单了
res.OkWithMsg(c, "登陆成功")
res.OkWithData(c, map[string]any{
"name": "枫枫",
})
res.FailWithMsg(c, "参数错误")
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")
- 要设置Content-Type,唤起浏览器下载
- 只能是get请求
前端请求后端接口,然后唤起浏览器下载
c.Header("fileName", "xxx.png")
c.Header("msg", "文件下载成功")
c.File("uploads/12.png")
前端唤起浏览器下载的本质
<a href="文件地址" download="文件名">文件下载</a>
async downloadFile(row) {
this.$http({
method: 'post',
url: 'file/upload',
data:postData,
responseType: "blob"
}).then(res => {
const _res = res.data
let blob = new Blob([_res], {
type: 'application/png'
});
let downloadElement = document.createElement("a");
let href = window.URL.createObjectURL(blob); //创建下载的链接
downloadElement.href = href;
downloadElement.download = res.headers["fileName"]; //下载后文件名
document.body.appendChild(downloadElement);
downloadElement.click(); //点击下载
document.body.removeChild(downloadElement); //下载完成移除元素
window.URL.revokeObjectURL(href); //释放掉blob对象
})}
最好的做法
调下载接口的请求,后端不返回实际文件内容,而是生成一个临时下载地址
前端构造a标签,再请求这个接口唤起浏览器下载
静态文件
r.Static("static", "static") // 第一个参数是别名,第二个才是实际路径
r.StaticFile("abcd", "static/abc.txt")
静态文件的路径,不能再被路由使用了
请求
查询参数
?key=xxx&name=xxxx&name=yyyy 这种就被称为查询参数
查询参数不是GET请求专属的
name := c.Query("name")
age := c.DefaultQuery("age", "25")
keyList := c.QueryArray("key")
fmt.Println(name, age, keyList)
例如请求 ?name=fengfeng&age=123&key=123&key=124,输出为:
fengfeng 123 [123 124]
动态参数
用户的个人信息页面,他的路径
/users?id=123 // 查询参数的模式
/users/123 // 动态参数模式
r.GET("users/:id", func(c *gin.Context) {
userID := c.Param("id")
fmt.Println(userID)
})
表单参数
一般就是专指form表单
name := c.PostForm("name")
age, ok := c.GetPostForm("age")
fmt.Println(name)
fmt.Println(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/xxx/yyy/"+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/"+header.Filename)
}
}
})
关于接口测试工具
postman
apifox
注意:接口测试工具能走通的,前端请求不一定可以走通
- get请求带请求体
- ws加请求头
原始内容
不同的请求体对应的原始内容
body阅后即焚问题解决
byteData, _ := io.ReadAll(c.Request.Body)
fmt.Println(string(byteData))
// 读了之后,body就没了,阅后即焚
c.Request.Body = io.NopCloser(bytes.NewReader(byteData))
form-data
----------------------------853882779395683818968400
Content-Disposition: form-data; name="name"
枫枫
----------------------------853882779395683818968400
Content-Disposition: form-data; name="age"
1234
----------------------------853882779395683818968400--
对应的分隔符就是
Content-Type:[multipart/form-data; boundary=--------------------------052455317193517003536866]
x-www-form-urlencoded
url编码
name=%E6%9E%AB%E6%9E%AB&age=1234
json
{
"name": "枫枫",
"age": 23
}