测试工具:
gin官网文档: https://gin-gonic.com/zh-cn/docs/
fresh安装 : go get github.com/pilu/fresh
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
// gin 程序的热加载, gin路由 get post put delete
func main() {
//创建一个默认的路由引擎
r := gin.Default()
//配置路由
r.GET("/", func(c *gin.Context) {
c.String(200, "你好gin")
})
r.GET("/news", func(c *gin.Context) {
//http.StatusOK go 自带 http包中的常量 表示200 状态码
c.String(http.StatusOK, "这是%v", "新闻页面")
})
//post 请求
r.POST("/post", func(c *gin.Context) {
c.String(200, "这是一个post请求")
})
//put请求
r.PUT("/put", func(c *gin.Context) {
c.String(200, "这是一个put数据主要用于编辑数据")
})
r.DELETE("/delete", func(c *gin.Context) {
c.String(200, "这是一个delete请求主要用于删除数据")
})
//启动 服务 , 默认8080端口
r.Run()
}
注意: 配置模板再goland 中默认的 是从项目根路径开始查找的
go 代码
// gin 路由响应数据 string, json , jsonp , html
func ginDemo02() {
type Article struct {
//`json:"title"` 将前面大写的自动替换为后面小写的
Title string `json:"title"`
Desc string `json:"desc"`
Content string `json:"content"`
}
//创建路由引擎
r := gin.Default()
//加载模板 注意: 默认是以 项目 为根目录,
r.LoadHTMLGlob("templates/*")
r.GET("/json", func(c *gin.Context) {
c.JSON(200, map[string]interface{}{
"success": true,
"message": "你好gin",
})
})
r.GET("/json2", func(c *gin.Context) {
//gin.H 等价于 type H map[string]any
c.JSON(200, gin.H{
"success": true,
"message": "你好gin gin.H",
})
})
//返回自定义结构体
r.GET("/json3", func(c *gin.Context) {
//gin.H 等价于 type H map[string]any
a := &Article{
Title: "这是一个json请求",
Desc: "描述",
Content: "内容",
}
c.JSON(200, a)
})
//jsonp 请求 主要用于跨域请求
//加上回调函数callback: http://127.0.0.1:8080/jsonp?callback=xxx
//xxx({"title":"这是一个jsonp请求","desc":"描述","content":"内容"});
r.GET("/jsonp", func(c *gin.Context) {
//gin.H 等价于 type H map[string]any
a := &Article{
Title: "这是一个jsonp请求",
Desc: "描述",
Content: "内容",
}
c.JSONP(200, a)
})
//返回XML数据
r.GET("/xml", func(c *gin.Context) {
//gin.H 等价于 type H map[string]any
a := &Article{
Title: "这是一个xml请求",
Desc: "描述",
Content: "内容",
}
c.XML(200, a)
})
//响应 html数据
r.GET("/news", func(c *gin.Context) {
c.HTML(200, "goods.html", gin.H{
"title": "这是商品页面",
})
})
r.Run()
}
html模板文件
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>goodstitle>
head>
<body>
<h2>{{.title}}h2>
body>
html>
go 代码
// gin html 模板渲染
func gindemo03() {
r := gin.Default()
//** 表示文件夹
r.LoadHTMLGlob("templates/**/*")
//注意页面的名字 为 模板文件 define 的名字
//{{ define "admin/news.html" }}
r.GET("/", func(c *gin.Context) {
c.HTML(200, "default/index.html", gin.H{
"title": "hello gin",
"score": 92,
"hobby": []string{"吃饭", "睡觉", "coding"},
"testEmptySlice": []string{},
"news": &Article{
Title: "新闻标题",
Desc: "描述",
Content: "with结构结构体使用",
},
})
})
r.GET("/news", func(c *gin.Context) {
c.HTML(http.StatusOK, "default/news.html", gin.H{
"title": "hello 前台新闻页面",
})
})
r.GET("/admin", func(c *gin.Context) {
c.HTML(http.StatusOK, "admin/index.html", gin.H{
"title": "hello admin",
})
})
r.GET("/admin/news", func(c *gin.Context) {
c.HTML(http.StatusOK, "admin/news.html", gin.H{
"title": "hello admin news ",
})
})
r.Run()
}
html模板文件
{{ define "admin/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>Documenttitle>
head>
<body>
{{ .title }}
<h2>这是后台首页h2>
body>
html>
{{ end }}
演示go代码
r.GET("/", func(c *gin.Context) {
c.HTML(200, "default/index.html", gin.H{
"title": "hello gin",
"score": 92,
"hobby": []string{"吃饭", "睡觉", "coding"},
"testEmptySlice": []string{},
"news": &Article{
Title: "新闻标题",
Desc: "描述",
Content: "with结构结构体使用",
},
})
})
{{}} 输出数据
{{.title}}
变量
{{ $t := .title }}
<h4> {{$t}}h4>
比较函数
条件判断
注意 {{end}} 结尾
range 遍历
<ul>
{{range $key, $value := .hobby}}
<li>{{$key}} --- {{$value}}li>
{{else}}
<li>列表为空li>
{{end}}
ul>
with 解构结构体
{{ with .news}}
{{.Title}}
{{.Desc}}
{{.Content}}
{{end}}
模板自带 函数
自定义模板函数
在utils 啊包中定义一个时间戳转换函数
// 将时间戳转换为 常见时间格式
func UnixToTime(timestamp int) string {
t := time.Unix(int64(timestamp), 0)
return t.Format("2006-01-02 15:04:05")
}
注入自定义函数
//自定义模板函数, 注入自己定义的函数, 注意这个 需要在加载模板之前
r.SetFuncMap(template.FuncMap{
"UnixToTime": utils.UnixToTime,
})
// 加载模板文件 ** 表示文件夹
r.LoadHTMLGlob("templates/**/*")
使用
r.GET("/", func(c *gin.Context) {
c.HTML(200, "default/index.html", gin.H{
"title": "hello gin",
"score": 92,
"hobby": []string{"吃饭", "睡觉", "coding"},
"testEmptySlice": []string{},
"timeStamp": 2953130242,
})
})
在html文件中调用函数
{{ len .title}}
{{ UnixToTime .timeStamp }}
模板文件嵌套其他模板文件
注意后面需要有一个点 ,才能将当前 页面的数据传递到引入 的模板中
{{ template "public/page_header.html" .}}
gin 中 html 加载 css js 图片 等静态文件不能直接加载, 需要先配置静态文件目录
// 当访问 /static/*这个 url 的时候。 就会引入这个 ./static 文件夹内的内容, 这样才能正常加载 static 文件夹内的文件
r.Static("/static", "./static")
可以正常加载对应文件
-- 访问了 /static/* 这个 url 就会触发 从而自动导入文件夹内的内容,
<link rel="stylesheet" href="/static/css/base.css">
<img src="/static/images/node.jpg">
不能正常加载对应文件
<link rel="stylesheet" href="/aaa/css/base.css">
即使 文件夹内存在 对应文件也不能正常加载,需要配置 , 这样访问 aaa开头的url, 就会加载 当前 aaa 目录内的文件
r.Static("/aaa", "./aaa")
//get请求传值
r.GET("/get", func(c *gin.Context) {
//获取参数的值
username := c.Query("u")
age := c.Query("age")
//没有接收到参数 使用默认值 1
page := c.DefaultQuery("page", "1")
c.JSON(http.StatusOK, gin.H{
"username": username,
"age": age,
"page": page,
})
})
将接收的值绑定到结构体上
// form 表示 接收form 表单中的数据
type UserInfo struct {
Username string `json:"username" form:"username"`
Password string `json:"password" form:"password"`
}
//将传入的数据绑定到结构体上
r.GET("/getUser", func(c *gin.Context) {
user := &UserInfo{}
//将数据解析到结构体
err := c.ShouldBind(user)
if err == nil {
c.JSON(http.StatusOK, user)
} else {
c.JSON(http.StatusBadRequest, gin.H{
"err": err.Error(),
})
}
})
// 接收传递的数据
r.POST("/doAddUser", func(c *gin.Context) {
//接收数据
username := c.PostForm("username")
password := c.PostForm("pwd")
//没有传入 使用默认值
age := c.DefaultPostForm("age", "12")
c.JSON(http.StatusOK, gin.H{
"code": true,
"message": "成功获取数据",
"u": username,
"p": password,
"age": age,
})
})
// 将xml中的数据解析到 结构体中
type News struct {
Title string `json:"title" xml:"title"`
Content string `json:"content" xml:"content"`
}
//获取post xml 数据
r.POST("/xml", func(c *gin.Context) {
news := &News{}
//获取数据, 返回一个 []byte
xmlSliceData, _ := c.GetRawData()
//将数据 解析到 结构体中
err := xml.Unmarshal(xmlSliceData, news)
if err == nil {
c.JSON(http.StatusOK, news)
} else {
c.JSON(http.StatusBadRequest, gin.H{
"err": err.Error(),
})
}
})
xml测试数据
<news>
<content type="string">张三content>
<title type="string">姓名title>
news>
//动态路由传值
//访问 list/xxx 会替换 id变成 list/xxx 可以用于传值
r.GET("/list/:id", func(c *gin.Context) {
//获取传入的值
param := c.Param("id")
c.String(200, "%v", param)
})
将不同 功能 的 模块进行分组 抽离成单独的go文件, 方便管理和 协同开发
defaultRoutes.go
package routes
import "github.com/gin-gonic/gin"
func DefaultRoutesInit(r *gin.Engine) {
//进行路由分组,下面设置的url都是 这个的子层级
defaultRoutes := r.Group("/")
{
defaultRoutes.GET("/", func(c *gin.Context) {
c.String(200, "首页主页面")
})
defaultRoutes.GET("/news", func(c *gin.Context) {
c.String(200, "首页新闻页面")
})
}
}
apiRoutes.go
package routes
import "github.com/gin-gonic/gin"
func ApiRoutesInit(r *gin.Engine) {
//进行路由分组,下面设置的url都是 这个的子层级
apiRoutes := r.Group("/api")
{
apiRoutes.GET("/", func(c *gin.Context) {
c.String(200, "api首页面")
})
apiRoutes.GET("/news", func(c *gin.Context) {
c.String(200, "api新闻页面")
})
}
}
adminRoutes.go
package routes
import "github.com/gin-gonic/gin"
func AdminRoutesInit(r *gin.Engine) {
//进行路由分组,下面设置的url都是 这个的子层级
adminRoutes := r.Group("/admin")
{
adminRoutes.GET("/", func(c *gin.Context) {
c.String(200, "后台首页面")
})
adminRoutes.GET("/news", func(c *gin.Context) {
c.String(200, "后天新闻页面")
})
}
}
在main 文件中调用
// 路由文件抽离
func ginDemo05() {
r := gin.Default()
//自定义模板函数, 注入自己定义的函数, 注意这个 需要在加载模板之前
//自定义模板函数, 注入自己定义的函数, 注意这个 需要在加载模板之前
r.SetFuncMap(template.FuncMap{
"UnixToTime": utils.UnixToTime,
})
//加载模板
r.LoadHTMLGlob("templates/**/*")
//加载路由页面
routes.DefaultRoutesInit(r)
routes.ApiRoutesInit(r)
routes.AdminRoutesInit(r)
r.Run()
}
路由只负责配置路由, 业务代码由控制器实现
UserController.go
package admin
import "github.com/gin-gonic/gin"
// 将其 挂载到结构体上 可以实现继承关系
type UserController struct {
}
func (conn UserController) Index(c *gin.Context) {
c.String(200, "后台管理用户首页 -- index")
}
func (conn UserController) News(c *gin.Context) {
c.String(200, "后天管理新闻页面 -- news")
}
进行路由处理
package routes
import (
"GinProject/src/demo01/controllers/admin"
"github.com/gin-gonic/gin"
)
func AdminRoutesInit(r *gin.Engine) {
//进行路由分组,下面设置的url都是 这个的子层级
adminRoutes := r.Group("/admin")
{
adminRoutes.GET("/", admin.UserController{}.Index)
adminRoutes.GET("/news", admin.UserController{}.News)
}
}
中间件: 匹配路由前和匹配路由完成后 执行的一系列操作
func MiddlewareOne(c *gin.Context) {
fmt.Println("1-我是一个中间件 -- One")
}
//func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes
//可以传入 多可回调函数, 中间的回调函数可以理解为一个简单的中间件
// 从前往后开始执行 回调函数
r.GET("/mid", MiddlewareOne, func(c *gin.Context) {
fmt.Println("这是一个首页...")
c.String(200, "这是一个首页")
})
gin 启动引擎默认也是 使用了 中间件的
//Default() 默认使用了中间件 engine.Use(Logger(), Recovery())
//gin.New(): 不使用任何中间件
r := gin.Default()
c.Next() : 跳转执行后续的回调方法, 其他回调函数执行完毕,再继续向后执行
func MiddlewareOne(c *gin.Context) {
//执行后续的 回调方法
c.Next()
fmt.Println("1-我是一个中间件 -- One")
}
** c.Abort()** : 中止后续回调函数的执行
func MiddlewareTwo(c *gin.Context) {
fmt.Println("1-我是一个中间件(全局) -- Two")
c.Abort()
}
// 计算程序运行时间中间件
func TimeMiddleware(c *gin.Context) {
start := time.Now().UnixNano()
fmt.Println("我是一个 计算执行时间的中间件")
c.Next()
end := time.Now().UnixNano()
fmt.Println("执行时间", end-start)
}
// 应用
r.GET("/time", TimeMiddleware, func(c *gin.Context) {
time.Sleep(1 * time.Second)
c.String(200, "执行时间")
})
全局中间件 会配置到 每一个路由上
//配置全局中间件, 可以配置多个
r.Use(MiddlewareTwo)
//法一:
adminRoutes := r.Group("/admin", middlewares.Initmiddlewares)
//法二
adminRoutes.Use(middlewares.Initmiddlewares)
在 中间件中存储数据
//在中间件中将数据存储到 域中
c.Set("username", "ggbo")
在控制器存储数据
注意:取出数据的类型转换
// 在控制器 取出数据
func (conn UserController) Index(c *gin.Context) {
//中间价和 控制器之间传递数据
// 返回 any : type any = interface{} 是空接口的类型 需要转换成 具体的类型
username, _ := c.Get("username")
fmt.Println(username)
//类型转换
s, ok := username.(string)
if ok {
c.String(200, "用户:"+s)
} else {
c.String(200, "用户获取失败")
}
}
注意不能直接 在 go 程中使用原有的 Context操作
func Initmiddlewares(c *gin.Context) {
//使用协程 不能直接使用 原有 Context, 需要复制,然后操作复制的Context
Copycontext := c.Copy()
//使用 go 程统计日志
go func() {
time.Sleep(5 * time.Second)
fmt.Println("Done!!!" + Copycontext.Request.URL.Path)
}()
}
关于Model
只是不同 的命名而已, 就是将公共的方法 抽离 出来组成一个工具 库
form表单
<form action="/fileupload" method="post" enctype="multipart/form-data">
选择文件1: <input type="file" name="face">
<input type="submit" value="提交">
form>
go 代码
r.POST("/fileupload", func(c *gin.Context) {
file, err := c.FormFile("face")
if err == nil {
dst := path.Join("./static/upload", file.Filename)
//保存文件
c.SaveUploadedFile(file, dst)
c.String(200, "success")
} else {
c.String(200, "error")
}
})
html
<form action="/filesupload" method="post" enctype="multipart/form-data">
选择文件1: <input type="file" name="face1">
选择文件2: <input type="file" name="face2">
<input type="submit" value="提交">
form>
go 代码
//参考 单文件上传, 其他的相同, 分别获取 不同的文件即可
file1, err := c.FormFile("face1")
file2, err := c.FormFile("face2")
html
<form action="/filesupload" method="post" enctype="multipart/form-data">
选择文件1: <input type="file" name="face[]">
选择文件2: <input type="file" name="face[]">
<input type="submit" value="提交">
form>
go
//多文件上传
r.POST("/filesupload", func(c *gin.Context) {
form, _ := c.MultipartForm()
files := form.File["face[]"]
//遍历保存文件
for _, file := range files {
dst := path.Join("./static/upload", file.Filename)
c.SaveUploadedFile(file, dst)
}
c.String(200, "success uploadFiles")
})
将 上传的文件自动保存到 以当前日期命名的文件夹内
go 工具类
import "time"
// 获取时间戳
func GetUnix() int64 {
return time.Now().Unix()
}
// 获取当前日期
func GetNowDate() string {
date := "2006-01-01 15:04:05"
return time.Now().Format(date)
}
// 获取年月日
func GetDay() string {
template := "20060102"
return time.Now().Format(template)
}
逻辑代码
//按照日期保存文件
r.POST("/fileupload3", func(c *gin.Context) {
//1.获取上传的文件
file, err := c.FormFile("face")
if err == nil {
//2.创建当前日期的文件目录
// 获取当前 日期
day := models.GetDay()
dir := "./static/upload/" + day
err := os.MkdirAll(dir, 0666)
if err != nil {
fmt.Println(err)
c.String(200, "创建文件失败")
return
}
// 3.对文件使用时间戳格式命名
// 获取当前时间戳
date := models.GetUnix()
//注意文件名不能有空格, 否则不能正常保存
filename := strconv.FormatInt(date, 10) + path.Ext(file.Filename)
dst := path.Join(dir, filename)
fmt.Println(dst)
c.SaveUploadedFile(file, dst)
c.String(200, "success")
} else {
c.String(200, "error")
}
})
设置cookie 字段解析
SameSite详细解析
http.SetCookie(c.Writer, &http.Cookie{
Name: name,
Value: url.QueryEscape(value),
MaxAge: maxAge, // 最大存活时间,单位秒小于0 的时候浏览器将会删除该cookie
Path: path, // 路径
Domain: domain, // 域名
SameSite: c.sameSite, // 限制第三方 Cookie,从而减少安全风险。
Secure: secure, // 是否只能在https 下操作
HttpOnly: httpOnly, // 是否允许前端访问
}
cookie 使用
//设置cookie
c.SetCookie("username", "张三", 120, "/", "localhost", false, true)
//获取cookie
c.Cookie("username")
假设域名: ddac.com
二级域名 a.ddac.com
setcookie域名: a.ddac.com
c.SetCookie("username", "张三", 120, "a.ddac.com", ".", false, true)
多个 域名共享 cookie
setcookie域名: .ddac.com
[xxx].ddac.com 类型的域名都能够共享cookie
c.SetCookie("username", "张三", 120, "/", ".ddac.com", false, true)
session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而session保存在服务器上。
session工作流程
当客户端浏览器第–次访问服务器并发送请求时,服务器端会创建一一个session对象,生成一个类似于key,value 的键值对,然后将value保存到服务器将key(cookie)返回到浏览器(客户)端。浏览器下次访问时会携带key(cookie),找到对应的session(value)。
安装
go get github.com/gin-contrib/sessions
**官方地址: **
//配置session 中间件
//创建基于cookie 的存储引擎, secret1111: 适用于加密的秘钥
//store := cookie.NewStore([]byte("secret1111"))
store, _ := redis.NewStore(10, "tcp", "43.139.17.97:13654", "45928ddacQWE", []byte("secret1111"))
//store 存储引擎
r.Use(sessions.Sessions("hello", store))
//将数据保存到 redis
//设置/获取sessions
r.GET("/hello", func(c *gin.Context) {
//设置session
session := sessions.Default(c)
//设置session过期时间
session.Options(sessions.Options{
MaxAge: 3600*6, // 单位秒
})
if session.Get("hello") != "world" {
session.Set("hello", "redis")
//注意需要保存
session.Save()
}
c.JSON(200, gin.H{"hello": session.Get("hello")})
})
//配置session 中间件
//创建基于cookie 的存储引擎, secret1111: 适用于加密的秘钥
store := cookie.NewStore([]byte("secret1111"))
//store 存储引擎
r.Use(sessions.Sessions("hello", store))
//将数据保存到 redis
//设置/获取sessions
r.GET("/hello", func(c *gin.Context) {
//设置session
session := sessions.Default(c)
//设置session过期时间
session.Options(sessions.Options{
MaxAge: 3600 * 6, // 单位秒
})
if session.Get("hello") != "world" {
session.Set("hello", "redis")
//注意需要保存
session.Save()
}
c.JSON(200, gin.H{"hello": session.Get("hello")})
})
将session 保存到redis中
将 store := cookie.NewStore([]byte(“secret1111”)) 替换为
store, _ := redis.NewStore(10, "tcp", "43.139.17.97:13654", "45928ddacQWE", []byte("secret1111"))