基本使用
安装(提前配置好gopath环境变量, mod包管理)
go get -u "github.com/gin-gonic/gin"
运行
go run main.go
一.个人项目展示
- 使用
github.com/gin-gonic/gin轻量级web框架 - 使用
gorm.io/gorm数据库第三方包 - 使用
gopkg.in/yaml.v3读取yaml配置文件 - 使用
github.com/sirupsen/logrus日志配置 - 使用
github.com/satori/go.uuid生成uuid - ...等待补充
二.响应
- 使用"-"来隐藏此属性的响应, 防止响应重要数据
type UserInfo struct {
Name string `json:"user"`
Password string `json:"-"`
}
func main(){
router := gin.Default("templates/*")
router.StaticFile("/static/xxx.png","static/xxx.png")
router.GET("/", func(ctx *gin.Context){
ctx.JSON(200, ")
})
router.GET("/", func(ctx *gin.Context){
ctx.String(200, "你好")
})
router.GET("/", func(ctx *gin.Context){
ctx.XML(200, gin.H{"name": "陈海峰"})
})
router.GET("/", func(ctx *gin.Context){
ctx.YAML(200, gin.H{"name": "陈海峰"})
})
router.GET("/", func(ctx *gin.Context){
ctx.HTML(200, "index.html", {"name": "陈海峰"})
})
}
1.设置响应头
func TestResponseHeader(ctx *gin.Context) {
ctx.Header("Token", "chenhaifeng11")
ctx.Header("Content-Type", "application/text;charset=utf-8")
ctx.JSON(200, gin.H{"data": "看看响应头"})
}
2.重定向
func TestApi(ctx *gin.Context) {
// ctx.JSON(200, gin.H{"msg": "测试api"})
// 重定向
ctx.Redirect(301, "https://www.baidu.com")
}
三.请求
请求头
func TestRequestHeader(ctx *gin.Context) {
// 1. 不区分大小写
fmt.Println(ctx.GetHeader("User-Agent"))
// 2. 获取整个请求头, 再从map中get
fmt.Println(ctx.Request.Header.Get("User-Agent"))
}
路径url参数params获取
func TestQueryApi(ctx *gin.Context) {
// url http://127.0.0.1:9090/api/test?user=10&name=chf&em=10&em=20
// 有可能空
fmt.Println(ctx.Query("user"))
// name := ctx.Query("name")
fmt.Println(ctx.QueryArray("em")) // 列表
fmt.Println(ctx.GetQuery("user")) // 已经判断是否传了
ctx.JSON(200, gin.H{"msg": "获取params数据"})
}
url占位参数
func TestParamsApi(ctx *gin.Context) {
// 路由地址 /api/test/:user_id
fmt.Println(ctx.Param("user_id"))
// 路由地址 /api/test/:user_id/:info_id
fmt.Println(ctx.Param("user_id"))
fmt.Println(ctx.Param("info_id"))
}
form表单获取
func TestFormApi(ctx *gin.Context) {
fmt.Println(ctx.PostForm("user"))
fmt.Println(ctx.DefaultPostForm("user", "chf"))
fmt.Println(ctx.DefaultPostForm("user", "chf"))
forms, err := ctx.MultipartForm()
fmt.Println(forms, err) // 接受所有的form参数, 放到map里
ctx.JSON(200, gin.H{"msg": "获取form表单数据"})
}
raw数据获取
func TestRawDataApi(ctx *gin.Context) {
type User struct {
Name string `json:"name"`
Age int `json:"age"`
}
var user User
body, _ := ctx.GetRawData()
contentType := ctx.GetHeader("Content-Type")
switch contentType {
case "application/json":
// json解析
err := json.Unmarshal(body, &user)
if err != nil {
fmt.Println(err.Error())
}
}
fmt.Println(user)
ctx.JSON(200, user)
}
四.请求参数绑定bind
- 可以不传, 传了就必须正确
结构体
type UserInfo struct {
Name string `json:"name" form:"name" uri:"name"`
Age int `json:"age" form:"age" uri:"age"`
Sex string `json:"sex" form:"sex" uri:"sex"`
}
JSON数据绑定
func TestBindJson(ctx *gin.Context) {
// 结构体定义json标签
var userInfo UserInfo
err := ctx.ShouldBindJSON(&userInfo)
if err != nil {
ctx.JSON(200, gin.H{"msg": "你错了"})
}
ctx.JSON(200, userInfo)
}
url路径参数绑定
func TestBindQuery(ctx *gin.Context) {
// 路由 /api/test/?name=陈海峰&age=19&sex=男
// 结构体定义form标签
var userInfo UserInfo
err := ctx.ShouldBindQuery(&userInfo)
if err != nil {
ctx.JSON(200, gin.H{"msg": "你错了"})
}
ctx.JSON(200, userInfo)
}
url占位符绑定
func TestBindUri(ctx *gin.Context) {
// 路由 /api/test/:name/:age/:sex
// 结构体定义uri标签
var userInfo UserInfo
err := ctx.ShouldBindUri(&userInfo)
if err != nil {
ctx.JSON(200, gin.H{"msg": "你错了"})
}
ctx.JSON(200, userInfo)
}
form表单绑定
func TestBindForm(ctx *gin.Context) {
// 结构体定义form标签
var userInfo UserInfo
err := ctx.ShouldBind(&userInfo) // 根据congent-type来判断自动绑定, 默认就是form
if err != nil {
ctx.JSON(200, gin.H{"msg": "错误了"})
}
ctx.JSON(200, userInfo)
}
bing数据验证
- required 必填
- min, max 最大最小
- eq, ne, gt , gte, lt, lte, len 等于, 不等于, 大于, 大于等于, 小于, 小于等于, 长度
- eqfield 等于某字段的值, 例如 binging:"Password=RePassword"
- nefield 不等于某字段的值
- dive 数组验证,卸载dive后面的每一个都会进行验证 binding:"required,dive,startswitch=like"
- ip 地址验证
- url 地址
- uri 地址,地址的子集
- datetime 时间
type SignUserInfo struct {
Name string `json:"name" binding:"min=4,max=6,required"`
Age int `json:"age"`
Password string `json:"password"`
RePassword string `json:"re_password"`
Sex string `json:"sex" binding:"oneof=man woman"`
LikeList []string `json:"like_list" binding:"required,dive,startswitch=like"`
Ip string `json:"ip" binding:"ip"`
Url string `json:"url" binding:"url"`
Uri string `json:"uri" binding:"uri"`
Date string `json:"data" binding:"datatime=2006-01-02 15:04:05"`
}
func TestVerifyData(ctx *gin.Context) {
// 数据验证
// required 必填
// min, max 最大最小
// eq, ne, gt , gte, lt, lte, len 等于, 不等于, 大于, 大于等于, 小于, 小于等于, 长度
// eqfield 等于某字段的值, 例如 binging:"Password=RePassword"
// nefield 不等于某字段的值
// oneof 枚举 binding:"oneof=man woman"
// contains 包含 binding:"contains=f"
// excludes 不包含
// startswitch 前缀
// endswitch 后缀
// 数组验证
// dive 数组验证,卸载dive后面的每一个都会进行验证 binding:"required,dive,startswitch=like"
// ip 地址验证
// url 地址
// uri 地址,地址的子集
// datetime 时间
var signUserInfo SignUserInfo
err := ctx.ShouldBindJSON(&signUserInfo)
if err != nil {
ctx.JSON(200, gin.H{"msg": "错了"})
}
ctx.JSON(200, signUserInfo)
}
自定义验证器
func SignFunc(fl validator.FieldLevel) bool {
var nameList []string = []string{"陈海峰", "张三", "欧zz"}
for _, nameStr := range nameList {
name := fl.Field().Interface().(string)
if name == nameStr {
return false
}
}
return true
}
// 在主路由下方注册自定义验证
func SignValid() {
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
v.RegisterValidation("sign", SignFunc)
v.RegisterValidation("sign1", SignFunc1)
v.RegisterValidation("sign2", SignFunc2)
...
}
}
五.文件上传
前端使用form-data 选择 file上传
// 文件上传
func TestUpload(ctx *gin.Context) {
file, _ := ctx.FormFile("file")
// 方法1 保存文件到本地
fmt.Println(file.Filename)
fmt.Println(file.Size / 1024) // 默认是字节
ctx.SaveUploadedFile(file, "/media/1.png")
// 方法2 读取上传的文件
readerFile, _ := file.Open()
data, _ := io.ReadAll(readerFile)
fmt.Println(string(data))
// 方法3 读取文件写入到本地
readerFile1, _ := file.Open()
writeFile, _ := os.Create("./media/2.png")
defer writeFile.Close()
n, _ := io.Copy(writeFile, readerFile1)
fmt.Println(n)
ctx.JSON(200, gin.H{"msg": "上传成功"})
}
// 上传多个
func TestUploads(ctx *gin.Context) {
form, _ := ctx.MultipartForm()
fiels := form.File["uploads"]
for _, file := range fiels {
ctx.SaveUploadedFile(file, "/media/"+file.Filename)
}
ctx.JSON(200, gin.H{"msg": fmt.Sprintf("成功上传%d个文件", len(fiels))})
}
六.文件下载
// 下载
func TestDownload(ctx *gin.Context) {
// 方式1
ctx.Header("Content-Type", "application/octet-stream") // 数据流
ctx.Header("Content-Disposition", "attachment; filename="+"牛逼.png") // 文件名
ctx.Header("Content-Transfer-Encoding", "binary") // 传输二进制编码
ctx.File("media/1.png")
// 方式2 交给前端处理
ctx.Header("fileName", "xxxpng")
ctx.File("media/2.png")
}
中间件
单个路由中间件
- 如果有多个响应, 响应会拼接起来
- 就是连续使用多个视图函数就可以
- 由前往后执行
- Abort跳出, 后面不执行
func TestRouter(router *gin.RouterGroup) {
router.GET("/test", func(ctx *gin.Context) {
fmt.Println("执行中间件1")
ctx.JSON(200, gin.H{"msg": "响应1"})
}, func(ctx *gin.Context) {
fmt.Println("执行中间件2")
ctx.JSON(200, gin.H{"msg": "响应2"})
ctx.Abort() // 直接响应, 后面不走了
}, func(ctx *gin.Context) {
fmt.Println("执行中间件3")
ctx.JSON(200, gin.H{"msg": "响应3"})
})
请求前执行的中间价
- 遇到Next就会跳到下一个视图
执行顺序 1, 2, 3, 4
func TestRouter(router *gin.RouterGroup) {
router.GET("/test", func(ctx *gin.Context) {
fmt.Println("1")
ctx.Next()
fmt.Println("4")
}, func(ctx *gin.Context) {
fmt.Println("2")
ctx.Next()
fmt.Println("3")
})
执行顺序 1, 4
func TestRouter(router *gin.RouterGroup) {
router.GET("/test", func(ctx *gin.Context) {
fmt.Println("1")
ctx.About()
ctx.Next()
fmt.Println("4")
}, func(ctx *gin.Context) {
fmt.Println("2")
ctx.Next()
fmt.Println("3")
})
全局中间价
- 使用use注册
func M1(ctx *gin.Context) {
fmt.Println("执行全局中间价1")
}
func M2(ctx *gin.Context) {
fmt.Println("执行全局中间价2")
}
func TestRouter(router *gin.RouterGroup) {
router.Use(M1, M2)
router.GET("/test", func(ctx *gin.Context) {
fmt.Println("执行视图")
})
}
中间价数值传递
- 上下文参数, 使用Set设置, 使用Get接受
- 由于接受的参数是any, 如果传的是结构体的话, 类型也会变成any, 需要取值的话使用断言 xxx := xxx.(xxx)转换成结构体类型
func M1(ctx *gin.Context) {
fmt.Println("执行全局中间价1")
name, _ := ctx.Set("name", "陈海峰")
}
func TestRouter(router *gin.RouterGroup) {
router.Use(M1)
router.GET("/test", func(ctx *gin.Context) {
fmt.Println("执行视图")
ctx.Get("name")
})
}
企.路由分组
func main() {
router := gin.Default()
authRouter := router.Group("auth") // 组1
mainRouter := router.Group("main") // 组2
authRouter.GET("/xxx", func(ctx *gin.Context) {})
...
}
分组中间价
func MyMiddle(name string) gin.HandlerFunc {
return func(ctx *gin.Context) {
ctx.JSON(200, gin.H{"msg": "权限验证失败", "name": name})
}
}
func InitRouter(app *gin.Engine) {
app.Use(MyMiddle("陈海峰"))
}
八.内置的日志
- os.Stdout 终端输出打印
func main() {
file, _ := os.Create("gin.log")
gin.DefaultWriter = io.MultiWriter(file. os.Stdout)
router := gin.Default()
router.run()
}
```
自定义路由格式
func main() { gin.DebugPrintRouteFunc = func(xxx){ log.Printf( "[ chen ] %s %s %s %d \n, xxx, xxx, xxx, ) } ... }
打印所有路由信息
func main() { router := gin.Default() router.GET("/test", func(xx){xxx}) for _, info := range router.Routers() { fmt.Println(info.Path, info.Method, info.Handler) } }
配置打印信息 隐藏启动项目时的信息
func main() { gin.SetMode(gin.REleaseMode) }