fresh包可以实现预加载
预定义函数
预定义的全局函数,用在html文件中
and |
函数返回它的第一个empty参数或者最后一个参数就是说"and x y"等价于"if x then y else x":所有参数都会执行 |
or |
返回第一个非empty参数或者最后一个参数亦"or x y"等价于"if x then x else y":所有参数都会执行 |
not |
返回它的单个参数的整数类型长度 |
len |
返回它的参数的整数类型长度 |
index |
执行结果为第一个参数以剩下的参数为索引/键指向的值 |
自定义模板函数
| |
| func UnixToTime(timestamp int) string { |
| t := time.Unix(int64(timestamp), 0) |
| return t.Format("2006-01-01 15:04:05") |
| } |
| |
| func main() { |
| |
| r := gin.Default() |
| |
| r.SetFuncMap(template.FuncMap{ |
| "UnixToTime": UnixToTime, |
| }) |
| |
| r.LoadHTMLGlob("templates/**/*") |
| } |
在对应的html文件中
模板嵌套
在一个html里面嵌套另外一个html文件
比如这个page_header:
| {{define "public/page_header.html" }} |
| <h1>我是一个公共的标题</h1> |
| {{end}} |
把它添加到另一个index.html里面
| |
| {{define "test/index.html"}} |
| <!doctype html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" |
| content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> |
| <meta http-equiv="X-UA-Compatible" content="ie=edge"> |
| <title>Document</title> |
| </head> |
| <body> |
| |
| {{template "public/page_header.html" .}} |
| <header> |
| |
| {{$t := .title}} |
| <h4>这里测试变量标题:{{$t}}</h4> |
| 测试gin的html模板文件{{.title}} |
| 新闻内容{{.news.Title}} |
| 我是新的模板页面!!~~~ |
| </header> |
| </body> |
| </html> |
| {{end}} |
静态文件服务
| |
| r := gin.Default() |
| |
| r.SetFuncMap(template.FuncMap{ |
| "UnixToTime": UnixToTime, |
| }) |
| |
| r.LoadHTMLGlob("templates/**/*") |
| |
| r.Static("/static", "././static") |
接口传值
GET post以及动态路由传值、Get Post数据解析到结构体、Post xml数据解析到结构体
GET传值
| r.GET("/", func(c *gin.Context) { |
| username := c.Query("username") |
| age := c.Query("age") |
| page := c.DefaultQuery("page", "1") |
| c.JSON(http.StatusOK, gin.H{ |
| "username": username, |
| "age": age, |
| "page": page, |
| }) |
| }) |
POST传值
| r.POST("/add", func(c *gin.Context) { |
| username := c.PostForm("username") |
| password := c.PostForm("password") |
| age := c.DefaultPostForm("age", "20") |
| c.JSON(http.StatusOK, gin.H{ |
| "username": username, |
| "password": password, |
| "age": age, |
| }) |
| }) |
获取GET、POST传递的数据绑定到结构体
要求传过来的值和结构体tag里面form对应的值对应上
/?username=zhangsan&password=123456
| |
| type Userinfo struct { |
| Username string `form:"username" json:"user"` |
| Password string `form:"password" json:"password"` |
| } |
| |
| r.GET("/getUser", func(c *gin.Context) { |
| user := &Userinfo{} |
| if err := c.ShouldBind(&user); err == nil { |
| fmt.Printf("%#v", user) |
| c.JSON(http.StatusOK, user) |
| } else { |
| c.JSON(http.StatusOK, gin.H{ |
| "err": err.Error(), |
| }) |
| } |
| }) |
| |
XML绑定结构体
| <?xml.version="1.@"encoding="UTF-8"?> |
| <article> |
| <content type="string">我是张=</content> |
| <title type="string">张=</title> |
| </article> |
| type Article struct { |
| Title string `form:"title" xml:"title"` |
| Content string `form:"content" xml:"content"` |
| } |
| |
| func main() { |
| |
| r.POST("/xml", func(c *gin.Context) { |
| article := &Article{} |
| xmlSliceData, _ := c.GetRawData() |
| if err := xml.Unmarshal(xmlSliceData, &article); err == nil { |
| c.JSON(http.StatusOK, article) |
| fmt.Printf("%#v", article) |
| } else { |
| c.JSON(http.StatusBadRequest, gin.H{ |
| "err": err.Error(), |
| }) |
| } |
| }) |
| } |
动态路由传值
| |
| r.GET("/list/:cid", func(c *gin.Context) { |
| cid := c.Param("cid") |
| c.String(200, "%v", cid) |
| }) |
路由分组
| apiRouters := r.Group("/api") |
| { |
| apiRouters.GET("/", func(c *gin.Context) { |
| c.String(200, "我是一个api接口") |
| }) |
| apiRouters.GET("/userlist", func(c *gin.Context) { |
| c.String(200, "我是一个api接口-userlist") |
| }) |
| r.GET("/plist", func(c *gin.Context) { |
| c.String(200, "我是一个api接口-plist") |
| }) |
| } |
自定义控制器
可以拆分成router路由文件和controller文件,一个处理路由,一个处理路由对应的响应,做到一个解耦
| package api |
| |
| import "github.com/gin-gonic/gin" |
| |
| type UserController struct { |
| } |
| |
| func (con UserController) List(c *gin.Context) { |
| c.String(200, "我是一个api接口-userlist,单独放在api文件夹下") |
| } |
中间件
Gin 框架允许开发者在处理请求的过程中,加入用户自己的钩子 (Hook) 函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等。
通俗理解:就是匹配路由前和匹配路由完成后执行的一系列操作
路由中间件
| func initMiddleware(c *gin.Context) { |
| fmt.Println("aaa") |
| } |
| func main() { |
| |
| r := gin.Default() |
| |
| r.SetFuncMap(template.FuncMap{ |
| "UnixToTime": UnixToTime, |
| }) |
| |
| r.LoadHTMLGlob("templates/**/*") |
| |
| r.Static("/static", "././static") |
| |
| |
| r.GET("/", initMiddleware, func(c *gin.Context) { |
| c.String(200, "gin首页") |
| }) |
| } |
| func initMiddleware(c *gin.Context) { |
| start := time.Now().UnixNano() |
| fmt.Println("1我是中间件") |
| |
| c.Next() |
| fmt.Println("2我是一个中间件") |
| end := time.Now().UnixNano() |
| fmt.Println(end - start) |
| } |
| |
| func main() { |
| |
| r := gin.Default() |
| |
| r.SetFuncMap(template.FuncMap{ |
| "UnixToTime": UnixToTime, |
| }) |
| |
| r.LoadHTMLGlob("templates/**/*") |
| |
| r.Static("/static", "././static") |
| |
| |
| r.GET("/", initMiddleware, func(c *gin.Context) { |
| fmt.Println("这是一个首页") |
| c.String(200, "gin首页") |
| }) |
| } |
| |
| |
| |
| |
| |
| |
c.Next()
会跳过这个中间件执行后面路由的处理程序,执行完之后再回到这个函数里面执行c.Next()后面的语句
c.Abort()
表示终止调用该请求的剩余处理程序
| func initMiddleware(c *gin.Context) { |
| start := time.Now().UnixNano() |
| fmt.Println("1我是中间件") |
| c.Abort() |
| fmt.Println("2我是一个中间件") |
| end := time.Now().UnixNano() |
| fmt.Println(end - start) |
| } |
| |
| |
| |
| |
| |
多个中间件
| func initMiddlewareOne(c *gin.Context) { |
| fmt.Println("我是中间件One") |
| |
| c.Next() |
| fmt.Println("我是一个中间件One") |
| } |
| |
| func initMiddlewareTwo(c *gin.Context) { |
| fmt.Println("我是中间件Two") |
| |
| c.Next() |
| fmt.Println("我是一个中间件Two") |
| } |
| |
| func main() { |
| |
| |
| r.GET("/", initMiddlewareOne, initMiddlewareTwo, func(c *gin.Context) { |
| fmt.Println("这是一个首页") |
| c.String(200, "gin首页") |
| }) |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
全局中间件
| |
| r.Use(initMiddlewareOne, initMiddlewareTwo) |
在路由分组中配置中间件
为路由组注册中间件有两种写法:
写法1:
| apiRouters := r.Group("/api",xxxx) |
写法2:
| apiRouters := r.Group("/api") |
| apiRouters.Use(xxxx) |
可以把中间件单独写在一个文件中,再别的地方引入,如写法1和2中所示
| package middlewares |
| |
| import ( |
| "fmt" |
| "github.com/gin-gonic/gin" |
| "time" |
| ) |
| |
| func InitMiddleware(c *gin.Context) { |
| |
| fmt.Println(time.Now()) |
| fmt.Println(c.Request.URL) |
| } |
中间件和对应控制器之间共享数据
设置值
ctx.Set("username","张三")
获取值
username, _ := ctx.Get("username")
gin默认中间件
gin.Default默认使用了Logger和Recovery中间件,其中:
- Logger中间件将日志写入
gin.DefaultWriter
,即使配置了GIN_MODE=release
- Recovery中间件会recover任何panic,如果有panic的话,会写入500响应码
如果不想使用上面两个默认的中间件,可以使用gin.New()
新建一个没有任何默认中间件的路由。
但是推荐default
gin中间件中使用goroutine
当在中间件或 handler 中启动新的 goroutine 时,不能使用原始的上下文 c *gin.Context
,必须使用其只读副本c.Copy()
Gin中自定义Model
我们的应用非常简单的话,我们可以在Controller中处理常见的业务逻辑,但是如果我们有一个功能想在多个控制器、或者多个模板里面复用的话,那么我们就可以把公共的功能单独抽取出来作为一个模块(Model)。Model是逐步抽象的过程,一般我们会在Model里面封装一些公共的方法让不同的Controller使用,也可以在Model中实现和数据库打交道
新建一个models文件夹,tools.go
| package models |
| |
| import ( |
| "fmt" |
| "time" |
| ) |
| |
| |
| func UnixToTime(timestamp int) string { |
| fmt.Println(timestamp) |
| t := time.Unix(int64(timestamp), 0) |
| return t.Format("2006-01-02 15:04:05") |
| } |
在mian.go里面注册
| |
| r := gin.Default() |
| |
| r.SetFuncMap(template.FuncMap{ |
| "UnixToTime": models.UnixToTime, |
| }) |
在对应的index.html
文件中可以调用这个方法
| <body> |
| |
| {{UnixToTime .t}} |
| </body> |
如果某个功能既在模板中使用又在控制器中使用,可以写在定义的Model里面
方便多人分组开发
单文件和多文件上传
| <form action="/admin/user/doUpload" method="post" enctype="multipart/form-data"> |
| 用户名:<input type="text" name="username" placeholder="用户名"><br/> |
| 头像:<input type="file" name="face"><br/> |
| <input type="submit" value="提交"> |
| </form> |
在controller里面配置静态文件,访问这个某个路由的时候,加载包含上面代码的HTML文件
enctype="multipart/form-data"
必须加上
单文件上传
| func (con UserController) DoUpload(c *gin.Context) { |
| username := c.PostForm("username") |
| file, err := c.FormFile("face") |
| |
| dst := path.Join("/static/upload", file.Filename) |
| if err == nil { |
| c.SaveUploadedFile(file, dst) |
| } |
| |
| c.JSON(http.StatusOK, gin.H{ |
| "success": true, |
| "username": username, |
| "dst": dst, |
| }) |
| } |
多文件上传
| <form action="/admin/user/doEdit" method="post" enctype="multipart/form-data"> |
| 用户名:<input type="text" name="username" placeholder="用户名"><br/> |
| 头像1:<input type="file" name="face1"><br/> |
| 头像2:<input type="file" name="face2"><br/> |
| <input type="submit" value="提交"> |
| </form> |
| func (con UserController) DoEdit(c *gin.Context) { |
| username := c.PostForm("username") |
| file1, err1 := c.FormFile("face1") |
| |
| if err1 == nil { |
| dst := path.Join("/static/upload", file1.Filename) |
| c.SaveUploadedFile(file1, dst) |
| } |
| |
| file2, err2 := c.FormFile("face2") |
| |
| if err2 == nil { |
| dst := path.Join("/static/upload", file2.Filename) |
| c.SaveUploadedFile(file2, dst) |
| } |
| |
| |
| c.JSON(http.StatusOK, gin.H{ |
| "success": true, |
| "username": username, |
| }) |
| } |
相同名字的多个文件上传
| <form action="/admin/user/doUpload" method="post" enctype="multipart/form-data"> |
| 用户名:<input type="text" name="username" placeholder="用户名"><br/> |
| 头像1:<input type="file" name="face[]"><br/> |
| 头像2:<input type="file" name="face[]"><br/> |
| 头像3:<input type="file" name="face[]"><br/> |
| <input type="submit" value="提交"> |
| </form> |
| func (con UserController) DoUpload(c *gin.Context) { |
| username := c.PostForm("username") |
| form,_ := c.MultipartForm() |
| files := form.File["face[]"] |
| |
| for _, file := range files{ |
| dst := path.Join("/static/upload", file.Filename) |
| |
| c.SaveUploadedFile(file, dst) |
| } |
| |
| c.JSON(http.StatusOK, gin.H{ |
| "success": true, |
| "username": username, |
| }) |
| } |
Gin按照日期存储图片
| |
| func GetDay() string { |
| template := "20060102" |
| return time.Now().Format(template) |
| } |
| |
| |
| func GetUnix() int64 { |
| return time.Now().Unix() |
| } |
| |
| |
| |
| |
| |
| |
| |
| func (con UserController) DoUpload(c *gin.Context) { |
| username := c.PostForm("username") |
| |
| file, err := c.FormFile("face") |
| |
| if err == nil { |
| |
| extName := path.Ext(file.Filename) |
| allowExtMap := map[string]bool{ |
| ".jpg": true, |
| ".png": true, |
| ".gif": true, |
| ".jpeg": true, |
| } |
| if _, ok := allowExtMap[extName]; !ok { |
| c.String(200, "上传的文件类型不合法") |
| return |
| } |
| |
| day := models.GetDay() |
| dir := "./static/upload/" + day |
| os.MkdirAll(dir, 0666) |
| if err != nil { |
| fmt.Println(err) |
| c.String(200, "MkdirAll失败") |
| return |
| } |
| |
| fileName := strconv.FormatInt(models.GetUnix(), 10) + extName |
| |
| dst := path.Join(dir, fileName) |
| c.SaveUploadedFile(file, dst) |
| } |
| c.JSON(http.StatusOK, gin.H{ |
| "success": true, |
| "username": username, |
| }) |
| } |
Gin中的Cookie以及多个域名共享Cookie
cookie保存在客户端浏览器中
可以实现的功能:
- 保持用户登录状态
- 保存用户浏览的历史记录
- 猜你喜欢,智能推荐
- 电商网站的加入购物车
在访问一个页面的时候,可以用c.Set
和c.Get
来交换数据,但如果是不同的页面,则需要cookie
设置Cookie
| c.SetCookie(name, value string, maxAge int,path,domain string, secure, httpOnly bool) |
- 第一个参数 key
- 第二个参数 value
- 第三个参数 过期时间,如果只想设置Cookie的保存路径而不想设置存活时间,可以在第三个参数中传递nil
- 第四个参数 cookie的路径
- 第五个参数 cookie的路径Domain作用域,本地调式配置成localhost,正式上线配置成域名
- 第六个参数 secure,当secure值为true时,cookie在HTTP中是无效的,在HTTPS中才有效
- 第七个参数 httpOnly,是微软对Cookie做的扩展,如果在Cookie中设置了"httpOnly"属性,则通过程序(JS脚本、applet等)将无法读取到Cookie的信息,防止XSS攻击产生
| |
| func (con DefaultController) Index(c *gin.Context) { |
| c.SetCookie("username", "张三", 3600, "/", "localhost", false, false) |
| |
| c.SetCookie("hobby", "吃饭", 5, "/", "localhost", false, false) |
| } |
| |
| func (con DefaultController) News(c *gin.Context) { |
| username, _ := c.Cookie("username") |
| c.String(200, "cookie"+username) |
| } |
| |
| func (con DefaultController) DeleteCookie(c *gin.Context) { |
| |
| c.SetCookie("username", "张三", -1, "/", "localhost", false, true) |
| c.String(200, "删除成功") |
| } |
多个二级域名共享cookie
a.test.com
和b.test.com
| func (con DefaultController) Index(c *gin.Context) { |
| c.SetCookie("username", "张三", 3600, "/", ".test.com", false, false) |
| } |
Gin中Session设置获取以及分布式Session
session保存在服务器上
sessionId会放在cookie里面保存在客户端
Gin官方没有提供Session相关的文档,我们可以使用第三方的Session中间件来实现
https://github.com/gin-contrib/sessions
git-contrib/sessions
中间件支持的存储引擎
- cookie
- memstore
- redis
- memcached
- mongodb
引入插件之后在mian.go里面配置session
| |
| |
| store := cookie.NewStore([]byte("secret111")) |
| |
| r.Use(sessions.Sessions("mysession", store)) |
在controller里面使用的时候
| func (con UserController) Index(c *gin.Context) { |
| |
| session := sessions.Default(c) |
| session.Set("username", "张三111") |
| session.Save() |
| } |
| func (con UserController) GetSession(c *gin.Context) { |
| |
| session := sessions.Default(c) |
| username := session.Get("username") |
| c.String(200, "username=%v", username) |
| } |
如果负载均衡,那么session可以存放在redis
想要存储在redis,需要下启动redis,然后重新配置存储引擎
| store, _ := redis.NewStore(10, "tcp", "localhost:6379", "", []byte("secret11111")) |
| r.Use(sessions.Sessions("mysession", store)) |
如果想要设置session过期时间
| session := sessions.Default(c) |
| session.Options(sessions.Options{ |
| MaxAge: 3600 * 6, |
| }) |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
2021-04-19 从浏览器地址栏输入url到显示页面的步骤
2021-04-19 AJAX
2019-04-19 1017 A除以B
2019-04-19 strlen( )和 sizeof( )的区别