Gin框架
- gin框架路由使用前缀树,路由注册的过程是构造前缀树的过程,路由匹配的过程就是查找前缀树的过程。
- gin框架的中间件函数和处理函数是以切片形式的调用链条存在的,我们可以顺序调用也可以借助c.Next()方法实现嵌套调用。
- 借助c.Set()和c.Get()方法我们能够在不同的中间件函数中传递数据。
- 使用前需要调用gin.Default()生成gin引擎
- 调用r.Run()结尾,可以指定端口
安装
go get -u github.com/gin-gonic/gin
静态文件解析
- 一般放在statics文件夹下分类存放
//静态文件的处理:静态文件指html页面用到的css\js\图片
r.Static("/relativepath", "./statics") //必须在模板解析之前;前面指定相对路径(html模板中使用,后面指定静态文件位置)
//使用
<link rel="stylesheet" href="/relativepath/demo.css">
<script src="/relativepath/vendor/jquery/jquery.min.js"></script>
HTML渲染
- HTML一般放在templates文件夹中;可以通过define 来给模板命名来定位;
- 模板文件后缀可以是.html也可以是.tmpl
//例如
{{define "post/index.tmpl"}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>posts/index</title>
</head>
<body>
{{.title}}
</body>
</html>
{{end}}
//模板加载的两种方式
r.LoadHTMLFiles("templates/post/index.tmpl", "templates/user/index.tmpl")//便于解析单个文件
r.LoadHTMLGlob("templates/**/*") //便于解析多个文件 **表示目录 *表示文件
//GET提交
r.GET("/post/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "post/index.tmpl", gin.H{ //第二个参数与模板中define 的文件名保持一致
"title": "post/index渲染",
})
})
- 自定义模板
// 自定义模板函数 必须写在模板解析之前
r.SetFuncMap(template.FuncMap{
"safe": func(s string) template.HTML {
return template.HTML(s)
},
})
//使用:
<body>
<h1> {{.title}}</h1>
<div> {{.setFuncMap|safe}}</div>
</body>
- 模板继承(调用"github.com/gin-contrib/multitemplate"库实现)
/*
示例;
假设home.tmpl和index.tmpl继承base.tmpl
templates
├── includes
│ ├── home.tmpl
│ └── index.tmpl
├── layouts
│ └── base.tmpl
└── scripts.tmpl
*/
func loadTemplates(templatesDir string) multitemplate.Renderer {
r := multitemplate.NewRenderer()
layouts, err := filepath.Glob(templatesDir + "/layouts/*.tmpl")
if err != nil {
panic(err.Error())
}
includes, err := filepath.Glob(templatesDir + "/includes/*.tmpl")
if err != nil {
panic(err.Error())
}
// 为layouts/和includes/目录生成 templates map
for _, include := range includes {
layoutCopy := make([]string, len(layouts))
copy(layoutCopy, layouts)
files := append(layoutCopy, include)
r.AddFromFiles(filepath.Base(include), files...)
}
return r
}
func main(){
r := gin.Default()
r.HTMLRender = loadTemplates("./templates")
r.GET("/index", func (c *gin.Context){
c.HTML(http.StatusOK, "index.tmpl", nil)
})
r.GET("/home", func (c *gin.Context){
c.HTML(http.StatusOK, "home.tmpl", nil)
})
r.Run()
}
JSON渲染
//方式1
r.GET("/hello", func(c *gin.Context) {
// c.JSON:返回JSON格式的数据
c.JSON(200, gin.H{
"message": "Hello world!",
})
})
//方式2
r.GET("/helloJSON2", func(c *gin.Context) {
type msg struct {
Name string `json:"name"`
Age int
Hobby string
}
var dataObj = msg{
"wuzb",
12,
"soccer",
}
c.JSON(http.StatusOK, dataObj)
})
获取请求参数
//获取URL中querystring参数参数
//www.xxx.com/user/search?id=2&page=1
func main() {
r := gin.Default()
r.GET("/user/search", func(c *gin.Context) {
userid := c.DefaultQuery("id ", "1")
//userid := c.Query("id")
page:= c.Query("page")
//输出json结果给调用方
c.JSON(http.StatusOK, gin.H{
"message": "ok",
"userid ": userid ,
"page": page,
})
})
r.Run()
}
//获取form参数
func main() {
r := gin.Default()
r.POST("/user/search", func(c *gin.Context) {
// DefaultPostForm取不到值时会返回指定的默认值
//username := c.DefaultPostForm("username", "zs")
username := c.PostForm("username")
address := c.PostForm("address")
//输出json结果给调用方
c.JSON(http.StatusOK, gin.H{
"message": "ok",
"username": username,
"address": address,
})
})
r.Run(":8080")
}
//获取path参数
func main() {
r := gin.Default()
r.GET("/user/search/:username/:address", func(c *gin.Context) {
username := c.Param("username")
address := c.Param("address")
c.JSON(http.StatusOK, gin.H{
"message": "ok",
"username": username,
"address": address,
})
})
r.Run(":8080")
}
//通过bind(基于反射)来自动绑定参数到结构体
type Login struct {
Username string `form:"name"json:"name" binding:"required,NameValid"` //NameValid是自定义验证方法
Password string `form:"pwd"json:"pwd"`
}
//shouldBind() 能够基于请求自动提取JSON、form表单和QueryString类型的数据,并把值绑定到指定的结构体对象。
//绑定JSON的示例 ({"name": "wz", "pwd": "123456"})
r.GET("/shudBind", func(c *gin.Context) {
var u Login
if err := c.ShouldBind(&u); err == nil {
c.JSON(http.StatusOK, gin.H{
"name": u.Username,
"pwd": u.Password,
})
} else {
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
})
}
})
关于binging中的参数既可以自定义也可以使用框架自带的参数;
- 框架自带的参数比如required,gt=x,lt=y(大于x小于y)等;
- 自定义参数的话,验证使用到gopkg.in/go-playground/validator.v8;例如:
//首先写一个验证方法
func NameValid(
v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value,
field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string,
) bool {
if s, ok := field.Interface().(string); ok {
if s == "admin" {
return false
}
}
return true
}
// 在路由中绑定绑定验证器
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
v.RegisterValidation("NameValid", NameValid)
}
重定向
//HTTP重定向
r.GET("/test", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "http://www.sogo.com/")
})
//路由重定向
r.GET("/test", func(c *gin.Context) {
// 指定重定向的URL
c.Request.URL.Path = "/test2"
r.HandleContext(c)
})
r.GET("/test2", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"hello": "world"})
})
路由
//get
r.GET("/get", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", gin.H{})
})
//post
r.POST("/post", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", gin.H{})
})
//any可以匹配所有请求方法
r.Any("/any", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", gin.H{})
})
//NoRoute:用于处理非注册的路由,报404页面
r.NoRoute(func(c *gin.Context) {
c.HTML(http.StatusNotFound, "404.html", nil)
})
//路由组
ginRoute := gin.Default()
prodGroup := ginRoute.Group("/prod")
{
prodGroup.POST("/list", func(c *gin.Context) {c.HTML(http.StatusOK,"index.html", gin.H{})})
prodGroup.GET("/detail/:pid",func(c *gin.Context){c.HTML(http.StatusOK,"index.html", gin.H{})})
}
return ginRoute
中间件
//中间件函数(统计请求处理函数的耗时)
func m1() gin.HandlerFunc {
//连接数据库或者其他的一些操作
return func(c *gin.Context) {
//存放具体的逻辑
start := time.Now()
c.Next() //调用后续的函数
// c.Abort() //阻止后续的函数
//c.Set()//往上下文中存值;通过c.Get()取值;实现中间件的值传递
cost := time.Since(start)
fmt.Printf("cost %d times", cost)
}
}
//中间件使用
//1.单个路由注册
r.GET("/middleware", m1(), func(c *gin.Context) { //某个路由单独注册中间体
c.JSON(http.StatusOK, gin.H{
"msg": "middleware",
})
})
//2.注册一个全局中间件
r.Use(m1())
//3.组注册中间件
r.Use(m1()){
把组路由给包裹进来
}
//或者
prodGroup := ginRoute.Group("/prod",m1())
{
prodGroup.POST("/list", func(c *gin.Context) {c.HTML(http.StatusOK,"index.html", gin.H{})})
prodGroup.GET("/detail/:pid",func(c *gin.Context){c.HTML(http.StatusOK,"index.html", gin.H{})})
}
文件上传
//html
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="f1">
<input type="submit" value="上传">
</form>
//go
//单文件上传处理
func main() {
router := gin.Default()
// 处理multipart forms提交文件时默认的内存限制是32 MiB
// 可以通过下面的方式修改
// router.MaxMultipartMemory = 8 << 20 // 8 MiB
router.POST("/upload", func(c *gin.Context) {
// 单个文件
file, err := c.FormFile("f1")
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"message": err.Error(),
})
return
}
log.Println(file.Filename)
dst := fmt.Sprintf("C:/tmp/%s", file.Filename)
// 上传文件到指定的目录
c.SaveUploadedFile(file, dst)
c.JSON(http.StatusOK, gin.H{
"message": fmt.Sprintf("'%s' uploaded!", file.Filename),
})
})
router.Run()
}
//多文件上传处理
func main() {
router := gin.Default()
// 处理multipart forms提交文件时默认的内存限制是32 MiB
// 可以通过下面的方式修改
// router.MaxMultipartMemory = 8 << 20 // 8 MiB
router.POST("/upload", func(c *gin.Context) {
// Multipart form
form, _ := c.MultipartForm()
files := form.File["file"]
for index, file := range files {
log.Println(file.Filename)
dst := fmt.Sprintf("C:/tmp/%s_%d", file.Filename, index)
// 上传文件到指定的目录
c.SaveUploadedFile(file, dst)
}
c.JSON(http.StatusOK, gin.H{
"message": fmt.Sprintf("%d files uploaded!", len(files)),
})
})
router.Run()
}
注意:
//异步(无法使用他的原始上下文,必须使用他的只读副本)
r.GET("/request-async", func(c *gin.Context) {
copyC := c.Copy()
go func() {
time.Sleep(3 * time.Second)
fmt.Println("异步请求:", copyC)
}()
})
/*
gin.Default()默认使用了Logger和Recovery中间件:
Logger中间件将日志写入gin.DefaultWriter,即使配置了GIN_MODE=release。
Recovery中间件会recover任何panic。如果有panic的话,会写入500响应码。
*/
//需要建立个空白中间件的gin引擎的话可以使用
gin.New()