在当前项目
go mod init xxx (xxx根据具体项目更改)
go get -u github.com/gin-gonic/gin
快速入门
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.GET("hello", func(context *gin.Context) {
context.JSON(http.StatusOK,gin.H{
//定义的数据
"message":"gin快速入门",
})
})
r.Run()
}
r.Run():参数是端口号,默认8080
![](https://i-blog.csdnimg.cn/blog_migrate/4ed28accfd10a817ee1136ace48495d2.png)
RESTFUl Api
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
//get
r.GET("/student", func(context *gin.Context) {
context.JSON(http.StatusOK,gin.H{
"message":"查询学生信息",
})
})
//post
r.POST("create_student", func(context *gin.Context) {
context.JSON(http.StatusOK,gin.H{
"message":"创建学生信息",
})
})
r.PUT("update_student", func(context *gin.Context) {
context.JSON(http.StatusOK,gin.H{
"message":"修改学生信息",
})
})
r.DELETE("delete_student", func(context *gin.Context) {
context.JSON(http.StatusOK,gin.H{
"message":"删除学生信息",
})
})
r.Run()
}
访问文件
StaticFile(路径1,路径2)
路径1:是url请求的路径,路径2是文件所在的路径,另外go中没有相对路径的概念
func main() {
r := gin.Default()
r.StaticFile("/scene","./static/a.jpg")
r.Run()
}
请求方式:
Get : http://127.0.0.1:8080/scene
html渲染
在templates文件夹下创建html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>
账号是:{{.name}}
</h1>
<h1>
密码是:{{.pwd}}
</h1>
</body>
</html>
注意上面要渲染的数据格式
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.LoadHTMLGlob("./templates/*")
r.GET("/index", func(context *gin.Context) {
context.HTML(http.StatusOK,"index.html",gin.H{
"name":"小白",
"pwd":"123456",
})
})
r.Run()
}
获取url的参数
c *gin.Context : c.Query(变量名)
注意:r.Run(":8081") 或 r.Run("127.0.0.1:8081")
如果只写端口,需要带冒号
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.LoadHTMLGlob("./templates/*")
r.GET("/queryParam", func(c *gin.Context) {
name:=c.Query("name")
password:=c.Query("password")
fmt.Printf("name is %s ,pwd is %s",name,password)
c.HTML(http.StatusOK,"index.html",gin.H{
"name": name,
"pwd":password,
})
})
r.Run(":8081")
}
![](https://i-blog.csdnimg.cn/blog_migrate/d05e89c854285cb3fb664408f323cf2b.png)
postForm表单参数
PostForm(变量名)
获取表单参数
PostFormArray(变量名)
获取相同变量的值,返回一个数组
DefaultPostForm(变量名,默认值)
当没传递该变量名,设置该变量的值为参数2的值
MultipartForm()
参数为图片或文件
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
)
func _form(c *gin.Context) {
name := c.PostForm("name")
nameArr := c.PostFormArray("name") //可以接收name参数,合并为一个数组
pwd := c.PostForm("pwd")
addr := c.DefaultPostForm("addr", "广东省") //如果没传addr,设置默认值为"广东省",参数2位默认值
picture, _ := c.MultipartForm() //参数是文件
fmt.Printf("name is %v, pwd is %v\n",name,pwd)
c.JSON(http.StatusOK,gin.H{
"name":name,
"pwd":pwd,
"nameArr":nameArr,
"addr":addr,
"picture":picture,
})
}
func main() {
r := gin.Default()
r.POST("/index", _form)
r.Run()
}
请求内容
![](https://i-blog.csdnimg.cn/blog_migrate/46efb38c3c501f2a33153eac8c11478b.png)
返回
{
"addr": "普宁市",
"name": "哈哈",
"nameArr": [
"哈哈",
"小王"
],
"picture": {
"Value": {
"addr": [
"普宁市"
],
"name": [
"哈哈",
"小王"
],
"pwd": [
"123abc"
]
},
"File": {
"picture": [
{
"Filename": "a.jpg",
"Header": {
"Content-Disposition": [
"form-data; name=\"picture\"; filename=\"a.jpg\""
],
"Content-Type": [
"image/jpeg"
]
},
"Size": 151263
}
]
}
},
"pwd": "123abc"
}
原始参数
GetRawData()
func _row(c *gin.Context) {
data, err := c.GetRawData()
if err == nil {
fmt.Printf(string(data))
}
}
func main() {
r := gin.Default()
r.POST("/index", _form)
r.POST("/rawData",_row)
r.Run()
}
请求方式一: form-data
![](https://i-blog.csdnimg.cn/blog_migrate/ec9dd178d1604d6979e81b282413bc91.png)
打印结果
----------------------------859145593602517325033109
Content-Disposition: form-data; name="name"
哈哈
----------------------------859145593602517325033109
Content-Disposition: form-data; name="pwd"
123abc
----------------------------859145593602517325033109
Content-Disposition: form-data; name="name"
小王
----------------------------859145593602517325033109
Content-Disposition: form-data; name="addr"
普宁市
----------------------------859145593602517325033109--
请求方式二:x-www-form-urlencoded
![](https://i-blog.csdnimg.cn/blog_migrate/de1b4fe08db998e5c88725b9e68d7d76.png)
打印结果:各参数之间用&拼接
name=123&pwd=abc&addr=%!E(MISSING)5%!C(MISSING)%E4%!B(MISSING)A%!A(MISSING)C
请求方式三:json
![](https://i-blog.csdnimg.cn/blog_migrate/d894bd2b0852ece6c05d6a856ddd4d50.png)
{
"name":"小明",
"age":15
}
GetHeader(xxx):获取请求头
func _row(c *gin.Context) {
data, err := c.GetRawData()
if err == nil {
fmt.Println(string(data))
}
contentType := c.GetHeader("Content-Type")
switch contentType {
case "application/json":
type User struct{
Name string `json:"name"`
Age int `json:"age"`
}
var user User
err := json.Unmarshal(data, &user)
if err!=nil{
fmt.Println("异常")
}
fmt.Println("user:",user)
default:
return
}
}
func main() {
r := gin.Default()
r.POST("/index", _form)
r.POST("/rawData",_row)
r.Run()
}
![](https://i-blog.csdnimg.cn/blog_migrate/763edd5a27c37d5f0d439a7124d65e5d.png)
打印结果:
{
"name":"小明",
"age":15
}
user: {小明 15}
封装一个解析json到结构体的函数
func _row(c *gin.Context) {
type User struct{
Name string `json:"name"`
Age int `json:"age"`
Addr string `json:"addr"`
}
var user User
err := bindJson(c, &user)
if err!=nil {
fmt.Println("异常")
}
fmt.Println(user)
}
func main() {
r := gin.Default()
r.POST("/index", _form)
r.POST("/rawData",_row)
r.Run()
}
func bindJson(c *gin.Context ,obj any) (err error){
data, err := c.GetRawData()
contentType := c.GetHeader("Content-Type")
switch contentType {
case "application/json":
err := json.Unmarshal(data, &obj)
if err!=nil{
fmt.Println("异常")
}
default:
return nil
}
return nil
}
参数绑定
shouldBind
可以绑定json、query、param、yaml、xml,如果校验不通过会返回错误
shouldBindJSON
import (
"github.com/gin-gonic/gin"
"net/http"
)
type student struct {
Name string `json:"name"`
Age int `json:"age"`
Addr string `json:"addr"`
}
func main() {
r := gin.Default()
r.POST("/bind",_bindJson)
r.Run(":8081")
}
func _bindJson(c *gin.Context) {
var stu student
err := c.ShouldBindJSON(&stu)
if err!=nil {
c.JSON(404,"发生错误")
return
}
c.JSON(http.StatusOK,stu)
}
将请求体的数据绑定到结构体上,也就是类似Java的@RequestBody与bean之间的映射
![](https://i-blog.csdnimg.cn/blog_migrate/fbd0df116982014c6c0ea7ab6647a4fd.png)
ShouldBindQuery
url的参数绑定
如下:结构体每个属性的 form
代码中用的是 ShouldBindQuery,也可以用ShouldBind
type student struct {
Name string `json:"name" form:"name"`
Age string `json:"age" form:"age"`
Addr string `json:"addr" form:"addr"`
}
func main() {
r := gin.Default()
r.GET("/bind",_bindJson)
r.Run()
}
func _bindJson(c *gin.Context) {
var stu student
err := c.ShouldBindQuery(&stu)
if err != nil {
c.JSON(http.StatusBadGateway, gin.H{
"error": err.Error(),
})
}
c.JSON(http.StatusOK,stu)
}
![](https://i-blog.csdnimg.cn/blog_migrate/92d2dc4b472d3eb9804baf2aeb2a5c66.png)
ShouldBindUri
注意以下的结构体的属性中添加的 uri
以及
r.POST("/bind/:name/:age/:addr",_bindJson)
type student struct {
Name string `uri:"name"`
Age string `uri:"age" `
Addr string `uri:"addr"`
}
func main() {
r := gin.Default()
r.POST("/bind/:name/:age/:addr",_bindJson)
r.Run()
}
func _bindJson(c *gin.Context) {
var stu student
err := c.ShouldBindUri(&stu)
if err != nil {
c.JSON(http.StatusBadGateway, gin.H{
"error": err.Error(),
})
}
c.JSON(http.StatusOK,stu)
}
![](https://i-blog.csdnimg.cn/blog_migrate/12a0ecddf1b69745ab35496e52af5422.png)
日期验证:2006-01-02 15:04:05
文件上传
file, _ := c.FormFile("file") //参数是文件传递的的变量名
c.SaveUploadedFile(file,"./upload/"+filename) :参数2为存储的文件路径
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r :=gin.Default()
r.POST("/upload", func(c *gin.Context) {
file, _ := c.FormFile("file")
filename := file.Filename
size := file.Size
fmt.Printf("filename is %s,size is %d\n",filename,size/1024)
c.SaveUploadedFile(file,"./upload/"+filename)
c.JSON(http.StatusOK,"上传成功")
})
r.Run()
}
文件读取
func main() {
r :=gin.Default()
r.POST("/upload", func(c *gin.Context) {
file, _ := c.FormFile("file")
read_open, _ := file.Open()
data, _ := io.ReadAll(read_open)
fmt.Println(string(data))
c.JSON(http.StatusOK,string(data))
})
r.Run()
}
copy文件
这里我copy的是图片
create_file, _ := os.Create("./upload/1.jpg") //创建的文件
defer create_file.Close()
io.Copy(create_file,read_open) //复制
func main() {
r :=gin.Default()
r.POST("/upload", func(c *gin.Context) {
file, _ := c.FormFile("file")
read_open, _ := file.Open()
create_file, _ := os.Create("./upload/1.jpg")
defer create_file.Close()
io.Copy(create_file,read_open)
c.JSON(http.StatusOK,"上传成功")
})
r.Run()
}
多文件上传
func main() {
r :=gin.Default()
r.POST("/uploads", func(c *gin.Context) {
form, _ := c.MultipartForm()
files := form.File["files"] //变量名
for _, file := range files {
c.SaveUploadedFile(file,"./upload/"+file.Filename)
}
c.JSON(http.StatusOK,len(files))
})
r.Run()
}
文件下载
func main() {
r :=gin.Default()
r.GET("/download", func(c *gin.Context) {
//唤醒浏览器下载
c.Header("Content-Type","application/octet-stream")
c.Header("Content-Disposition","attachment;filename="+"冰墩墩.jpg")
c.File("upload/a.jpg")
})
r.Run()
}
![](https://i-blog.csdnimg.cn/blog_migrate/3a361a584db6faca09b6421cca4f8ced.png)
前后端模式下的文件下载
如果是前后端模式下,后端就只需要响应一个文件数据
文件名和其他信息就写在请求头中
c.Header("fileName","xxx .png")
c .Header( "msg","文件下载成功“)
c.File("uploads/12 .png")
前端写法
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对象
})
}
Abort() 后面的中间件不会执行
Next()方法, 多个中间件 next()之前的代码 按顺序执行,next()之后的代码 按中间件逆序执行
全局中间件 Use()
func m1 (c *gin.Context) {
fmt.Println("m1 .. in")
c.Abort()
fmt.Println("m1 .. out")
}
func main() {
r := gin.Default()
r.Use(m1)
r.GET("/m1", func(c *gin.Context) {
fmt.Println(" c in")
c.JSON(200,"好")
})
r.Run()
}
打印结果:
m1 .. in
m1 .. out
可以看出,
Abort()对本身中间件没影响。
中间件传值
set(key)
get(key)
type user struct {
Name string
Age int
}
func m1 (c *gin.Context) {
var user = user{"tom",10}
c.Set("user",user)
}
func main() {
r := gin.Default()
r.Use(m1)
r.GET("/m1", func(c *gin.Context) {
_user, _ := c.Get("user")
fmt.Println(_user)
//_user.Name 必须断言转成对应对象才可以这样使用
u := _user.(user)
fmt.Println(u.Name)
c.JSON(200,_user)
})
r.Run()
}
路由分组
Group(),括号里面的路径为访问的前缀
注意:如果返回的Resp里面的属性小写开头,则表示为私有的,响应时没有该属性。
type Resp struct {
Code int `json:"code"`
Data any `json:"data"`
Msg string `json:"msg"`
}
type Article struct {
Article string `json:"article"`
Content string `json:"content"`
}
func userView(c *gin.Context) {
var userList []User = []User{
{"tom",10},
{"amy",20},
}
var res = Resp{200,userList,"成功"}
fmt.Println(res)
c.JSON(200,res)
}
func articleView(c *gin.Context) {
var articleList []Article = []Article{
{"骆驼祥子","小说"},
{"星落凝成糖","电视剧"},
}
c.JSON(http.StatusOK,Resp{http.StatusOK,articleList,"成功"})
}
func main() {
r := gin.Default()
api := r.Group("/api")
api.GET("/user",userView)
api2 := r.Group("article")
api2.GET("/article",articleView)
r.Run()
}
注意:
![](https://i-blog.csdnimg.cn/blog_migrate/0750b33392bbe94cd0cfff2a6bca51bd.png)
![](https://i-blog.csdnimg.cn/blog_migrate/a3c9c1db1afb05908581785d214ab310.png)
![](https://i-blog.csdnimg.cn/blog_migrate/f18f3f41833af59bbcbc1d928ecc0864.png)
路由校验权限
type Resp struct {
Code int `json:"code"`
Data any `json:"data"`
Msg string `json:"msg"`
}
type User struct {
Name string `json:"name"`
Age int `json:"age"`
}
func userView(c *gin.Context) {
var userList []User = []User{
{"tom",10},
{"amy",20},
}
var res = Resp{200,userList,"成功"}
fmt.Println(res)
c.JSON(200,res)
}
func check_token(c *gin.Context){
token := c.GetHeader("token")
if token !="1234"{
c.JSON(404,Resp{400,nil,"权限校验失败"})
c.Abort() //拦截后续操作
}
c.Next()
return
}
func userInit(r *gin.RouterGroup) {
api := r.Group("/user_manager").Use(check_token)
api.GET("/user",userView)
}
func main() {
r := gin.Default()
api := r.Group("/api")
userInit(api)
api.GET("/login", func(c *gin.Context) {
c.JSON(200,"欢迎来到登录页面")
})
r.Run()
}
http://127.0.0.1:8080/api/user_manager/user 请求头带token=1234 能访问
因为上述代码使用了 api := r.Group("/user_manager").Use(check_token)
![](https://i-blog.csdnimg.cn/blog_migrate/96626facad8e945bad7cd29cec1d00d6.png)
![](https://i-blog.csdnimg.cn/blog_migrate/6168389659d7c95cab87fd3aac1a433a.png)
![](https://i-blog.csdnimg.cn/blog_migrate/dc4108798f0f6844e13bf372ca5477c6.png)
使用闭包的写法
func check_token(msg string) gin.HandlerFunc{
return func(c *gin.Context) {
token := c.GetHeader("token")
if token != "1234" {
c.JSON(404, Resp{400, nil, msg})
c.Abort() //拦截后续操作
}
c.Next()
return
}
}
func userInit(r *gin.RouterGroup) {
api := r.Group("/user_manager").Use(check_token("权限校验失败"))
api.GET("/user",userView)
}
修改日志样式
func LogFormatterParams(params gin.LogFormatterParams) string {
return fmt.Sprintf(
"[beichen] %s |%s %d %s| %s %s %s %s \n",
params.TimeStamp.Format( "206-01-02 15:04:05"),
params.StatusCodeColor(),params.StatusCode, params.ResetColor(),
params.MethodColor(),params.Method,params.ResetColor(),
params.Path,)
}
func main() {
gin.SetMode(gin.ReleaseMode)
r := gin.New()
r.Use(gin.LoggerWithFormatter(LogFormatterParams))
r.GET("/index")
r.Run()
}
![](https://i-blog.csdnimg.cn/blog_migrate/93a176ccf61d001a394759d41ed3e60a.png)
logrus
下载:
go get github.com/sirupsen/logrus
修改日志级别
logrus.SetLevel(logrus.DebugLevel)//修改日志级别
日志级别有
PanicLevel Level = iota
// FatalLevel level. Logs and then calls `logger.Exit(1)`. It will exit even if the
// logging level is set to Panic.
FatalLevel
// ErrorLevel level. Logs. Used for errors that should definitely be noted.
// Commonly used for hooks to send errors to an error tracking service.
ErrorLevel
// WarnLevel level. Non-critical entries that deserve eyes.
WarnLevel
// InfoLevel level. General operational entries about what's going on inside the
// application.
InfoLevel
// DebugLevel level. Usually only enabled when debugging. Very verbose logging.
DebugLevel
// TraceLevel level. Designates finer-grained informational events than the Debug.
TraceLevel
将日志打印到log文件
func main() {
file, _ := os.OpenFile( "a.log",os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
logrus.SetOutput(file)
logrus.SetLevel(logrus.DebugLevel)//修改日志级别
logrus.Error("出错了")
logrus.Warnln("警告")
logrus.Infof("输出")
logrus.Debugf("调试")
logrus.Println("打印")
fmt.Println(logrus.GetLevel())
}
同时输出到console和文件
logrus.SetOutput(io.MultiWriter(file,os.Stdout))