RESTful API
以前写网站
get /user
post /create_user
post /update_user
post /delete_user
RESTful API
get /user 获取
post /user 新建
put /user 更新
patch /user 更新部分
delete /user 删除
-
REST与技术无关,代表的是一种软件架构风格,只要API程序遵循了REST风格,那就可以称其为
RESTful API
-
REST的含义就是客户端与Web服务器之间进行交互的时候,使用HTTP协议中的4个请求方法代表不同的动作
-
get
-
post
-
put
-
patch
-
delete
-
-
Gin框架支持开发
RESTful API
的开发 -
package main import ( "net/http" "github.com/gin-gonic/gin" ) func main() { // 创建一个服务 ginServer := gin.Default() // 访问地址,处理请求 Request Response ginServer.GET("/hello", func(context *gin.Context) { context.JSON(200, gin.H{"msg": "hello,world"}) }) ginServer.POST("/user", func(ctx *gin.Context) { ctx.JSON(http.StatusOK, gin.H{"msg": "post请求"}) }) ginServer.PUT("/user") ginServer.DELETE("/user") // 服务器端口 err := ginServer.Run(":8082") // http.ListenAndServe(":8082", ginServer) if err != nil { return } }
第一个Gin示例
// cmd go get -u github.com/gin-gonic/gin
package main import ( "github.com/gin-gonic/gin" ) func main() { // gin.SetMode(gin.ReleaseMode) // 切换到生产模式 // 创建一个服务 ginServer := gin.Default() // 访问地址,处理请求 Request Response ginServer.GET("/hello", func(context *gin.Context) { context.JSON(200, gin.H{"msg": "hello,world"}) // http.StatusOK就是请求已经成功的200的状态码 }) // 服务器端口 err := ginServer.Run(":8082") if err != nil { return } }
-
想要更改左上角的图标
-
package main import ( "github.com/gin-gonic/gin" "github.com/thinkerou/favicon" // go get ) func main() { // gin.SetMode(gin.ReleaseMode) // 切换到生产模式 // 创建一个服务 ginServer := gin.Default() ginServer.Use(favicon.New("./favicon.ico")) // 访问地址,处理请求 Request Response ginServer.GET("/hello", func(context *gin.Context) { context.JSON(200, gin.H{"msg": "hello,world"}) }) // 服务器端口 ginServer.Run(":8082") }
-
-
将
IP
变为内网IP
-
package main import ( "net/http" "github.com/gin-gonic/gin" ) func main() { // gin.SetMode(gin.ReleaseMode) // 切换到生产模式 router := gin.Default() router.GET("/index", func(ctx *gin.Context) { ctx.String(200, "Hello") }) // 启动监听,gin会把web服务运行在本机的0.0.0.0:8080端口上 router.Run("0.0.0.0:8082") // 用原生http服务的方式, router.Run本质就是http.ListenAndServe的进一步封装 http.ListenAndServe(":8082", router) }
-
加载静态页面
-
package main import ( "net/http" "github.com/gin-gonic/gin" ) func main() { // gin.SetMode(gin.ReleaseMode) // 切换到生产模式 // 创建一个服务 ginServer := gin.Default() // ginServer.Use(favicon.New("./favicon.ico")) // 加载静态页面 ginServer.LoadHTMLGlob("templates/*") // ginServer.LoadHTMLFiles("templates/index.html") // LoadHTMLGlob是全局加载Files是指定文件 // 响应一个页面给前端 ginServer.GET("/index", func(context *gin.Context) { // context.JSON() json数据 context.HTML(http.StatusOK, "index.html", gin.H{ "msg": "这是go后台传入的数据", }) }) // 服务器端口 err := ginServer.Run(":8082") if err != nil { return } }
-
新建一个文件夹
templates
,在其下面创建index.html
文件 -
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>一个GoWeb页面</title> </head> <body> <h1>感谢大家支持江小年的博客</h1> 获取后端的数据为: {{.msg}} </body> </html>
加载资源包
-
创建
static
文件夹-
在其中创建
css
文件夹-
style.css
-
-
js
文件夹-
common.js
-
-
-
package main import ( "net/http" "github.com/gin-gonic/gin" ) func main() { // gin.SetMode(gin.ReleaseMode) // 切换到生产模式 // 创建一个服务 ginServer := gin.Default() // ginServer.Use(favicon.New("./favicon.ico")) // 加载静态页面 ginServer.LoadHTMLGlob("templates/*") // ginServer.LoadHTMLFiles("templates/index.html") // LoadHTMLGlob是全局加载Files是指定文件 //加载资源文件 ginServer.Static("/static", "./static") // 响应一个页面给前端 ginServer.GET("/index", func(context *gin.Context) { // context.JSON() json数据 context.HTML(http.StatusOK, "index.html", gin.H{ "msg": "这是go后台传入的数据", "title": "你猜" }) }) // 服务器端口 err := ginServer.Run(":8082") if err != nil { return } }
-
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>一个GoWeb页面</title> <link rel="stylesheet" href="/static/css/style.css"> <script src="/static/js/common.js"></script> </head> <body> <h1>感谢大家支持江小年的博客</h1> 获取后端的数据为: {{.msg}} title: {{.title}} </body> </html>
package main import ( "net/http" "github.com/gin-gonic/gin" ) func main() { gin.SetMode(gin.ReleaseMode) ginServer := gin.Default() ginServer.LoadHTMLFiles("static/index.html") ginServer.Static("/css", "static/css") ginServer.Static("/font", "static/font") ginServer.GET("index", func(ctx *gin.Context) { ctx.HTML(http.StatusOK, "index.html", gin.H{ "msg": "jiangxiaonian", }) }) err := ginServer.Run(":8080") if err != nil { return } }
获取参数
获取请求参数
-
// usl?userid=XXX&username=jaingxionian // /user/info/1/jiangxiaonian
-
// main.go package main import ( "net/http" "github.com/gin-gonic/gin" ) func main() { // 创建一个服务 ginServer := gin.Default() // ginServer.Use(favicon.New("./favicon.ico")) // 加载静态页面 ginServer.LoadHTMLGlob("templates/*") // ginServer.LoadHTMLFiles("templates/index.html") // LoadHTMLGlob是全局加载Files是指定文件 //加载资源文件 ginServer.Static("/static", "./static") // 响应一个页面给前端 ginServer.GET("/index", func(context *gin.Context) { // context.JSON() json数据 context.HTML(http.StatusOK, "index.html", gin.H{ "msg": "这是go后台传入的数据", }) }) // usl?userid=XXX&username=jaingxionian ginServer.GET("/user/info", func(context *gin.Context) { userid := context.Query("userid") username := context.Query("username") // fmt.Println(c.QueryArray("userid")) // 拿到多个相同的查询参数 // fmt.Println(c.DefaultQuery("userid", 0)) context.JSON(http.StatusOK, gin.H{ "userid": userid, "username": username, }) }) // /user/info/1/jiangxiaonian // 只要:后名字正确就能匹配上 ginServer.GET("/user/info/:userid/:username", func(context *gin.Context) { userid := context.Param("userid") username := context.Param("username") context.JSON(http.StatusOK, gin.H{ "userid": userid, "username": username, }) }) // 服务器端口 err := ginServer.Run(":8082") if err != nil { return } } // 运行后访问 http://localhost:8082/user/info?userid=1&username=jaingxiaonain
获取前端给后端传递的json
(序列化)参数
// gin 优化过的BindJSON package controllers import "github.com/gin-gonic/gin" type OrderController struct{} func (o OrderController) GetList(c *gin.Context) { m := make(map[string]interface{}) err := c.BindJSON(&m) if err == nil { c.JSON(http.StatusOK, m) return } c.JSON(4001, gin.H{"err": err}) }
// 结构体 package controllers import "github.com/gin-gonic/gin" type OrderController struct{} type Search struct { Name string `json:"name"` Cid int `json:"cid"` } func (o OrderController) GetList(c *gin.Context) { search := &Search{} err := c.BindJSON(&search) if err == nil { ReturnSuccess(c, 0, search.Name, search.Cid, 1) return } ReturnErrer(c, 4001, gin.H{"err": err}) }
// 未优化的*gin.Context.GetRawData() json.Unmarshal() // 前段给后端传JSON ginServer.POST("/json", func(context *gin.Context) { // GetRawData() 从请求体(request.body)里获取对象 b, _ := context.GetRawData() var m map[string]interface{} // 包装为json数据 []byte _ = json.Unmarshal(b, &m) context.JSON(http.StatusOK, m) })
获取表单中的参数
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>一个GoWeb页面</title> <link rel="stylesheet" href="/static/css/style.css"> <script src="/static/js/common.js"></script> </head> <body> <h1>感谢大家支持江小年的博客</h1> <form action="/user/add" method="post"> <p>username: <input type="text" name="username"></p> <p>password: <input type="password" name="password"></p> <button type="submit"> 提交 </button> </form> </body> </html>
// .DefaultPostForm() .PostForm() package main import ( "encoding/json" "net/http" "github.com/gin-gonic/gin" ) func main() { // 创建一个服务 ginServer := gin.Default() // ginServer.Use(favicon.New("./favicon.ico")) // 加载静态页面 ginServer.LoadHTMLGlob("templates/*") // ginServer.LoadHTMLFiles("templates/index.html") // LoadHTMLGlob是全局加载Files是指定文件 //加载资源文件 ginServer.Static("/static", "./static") // 响应一个页面给前端 ginServer.GET("/index", func(context *gin.Context) { // context.JSON() json数据 context.HTML(http.StatusOK, "index.html", gin.H{ "msg": "这是go后台传入的数据", }) }) // 表单 ginServer.POST("/user/add", func(context *gin.Context) { username := context.PostForm("username") password := context.PostForm("password") // password := context.DefaultPostForm("password", 12345)第二个参数为默认值 // 加判断逻辑代码 context.JSON(http.StatusOK, gin.H{ "msg": "ok", "username": username, "password": password, }) }) // 服务器端口 err := ginServer.Run(":8082") if err != nil { return } }
路由
HTTP
重定向
-
package main import ( "encoding/json" "net/http" "github.com/gin-gonic/gin" ) func main() { // 创建一个服务 ginServer := gin.Default() // 响应一个页面给前端 ginServer.GET("/index", func(context *gin.Context) { // context.JSON() json数据 context.HTML(http.StatusOK, "index.html", gin.H{ "msg": "这是go后台传入的数据", }) }) // 路由 ginServer.GET("/test", func(context *gin.Context) { // 重定向 StatusMovedPermanently 301 context.Redirect(http.StatusMovedPermanently, "https://www.baidu.com") }) // 服务器端口 err := ginServer.Run(":8082") if err != nil { return } }
路由重定向
路由重定向,使用HandleContext
:
package main import ( "github.com/gin-gonic/gin" "net/http" ) func main() { r := gin.Default() 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"}) }) // Listen and serve on 0.0.0.0:8080 err := r.Run(":8080") if err != nil { return } }
404 NoRoute
()
-
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>404</title> </head> <body> <h1>江小年的404页面</h1> </body> </html>
-
package main import ( "encoding/json" "net/http" "github.com/gin-gonic/gin" ) func main() { // 创建一个服务 ginServer := gin.Default() // 路由 ginServer.GET("/test", func(context *gin.Context) { // 重定向 StatusMovedPermanently 301 context.Redirect(http.StatusMovedPermanently, "https://www.baidu.com") }) // 404 NoRoute ginServer.NoRoute(func(context *gin.Context) { context.HTML(http.StatusNotFound, "404.html", nil) }) // 服务器端口 err := ginServer.Run(":8082") if err != nil { return } }
路由组
package main import ( "encoding/json" "net/http" "github.com/gin-gonic/gin" ) func main() { // 创建一个服务 ginServer := gin.Default() // ginServer.Use(favicon.New("./favicon.ico")) // 加载静态页面 ginServer.LoadHTMLGlob("templates/*") // ginServer.LoadHTMLFiles("templates/index.html") // LoadHTMLGlob是全局加载Files是指定文件 //加载资源文件 ginServer.Static("/static", "./static") // 响应一个页面给前端 ginServer.GET("/index", func(context *gin.Context) { // context.JSON() json数据 context.HTML(http.StatusOK, "index.html", gin.H{ "msg": "这是go后台传入的数据", }) }) // usl?userid=XXX&username=jaingxionian ginServer.GET("/user/info", func(context *gin.Context) { userid := context.Query("userid") username := context.Query("username") context.JSON(http.StatusOK, gin.H{ "userid": userid, "username": username, }) }) // /user/info/1/jiangxiaonian // 只要:后名字正确就能匹配上 ginServer.GET("/user/info/:userid/:username", func(context *gin.Context) { userid := context.Param("userid") username := context.Param("username") context.JSON(http.StatusOK, gin.H{ "userid": userid, "username": username, }) }) // 前段给后端传JSON ginServer.POST("/json", func(context *gin.Context) { // GetRawData() 从请求体(request.body)里获取对象 b, _ := context.GetRawData() var m map[string]interface{} // 包装为json数据 []byte _ = json.Unmarshal(b, &m) context.JSON(http.StatusOK, m) }) // 表单 ginServer.POST("/user/add", func(context *gin.Context) { username := context.PostForm("username") password := context.PostForm("password") // 加判断逻辑代码 context.JSON(http.StatusOK, gin.H{ "msg": "ok", "username": username, "password": password, }) }) // 路由 ginServer.GET("/test", func(context *gin.Context) { // 重定向 StatusMovedPermanently 301 context.Redirect(http.StatusMovedPermanently, "https://www.baidu.com") }) // 404 NoRoute ginServer.NoRoute(func(context *gin.Context) { context.HTML(http.StatusNotFound, "404.html", nil) }) // 路由组 userGroup := ginServer.Group("/user") { userGroup.GET("/add") // /user/add userGroup.GET("/login") // /user/add userGroup.GET("/logout") // /user/add } orderGroup := ginServer.Group("/order") { orderGroup.GET("add") orderGroup.GET("delte") } // 服务器端口 err := ginServer.Run(":8082") if err != nil { return } }
路由嵌套
shopGroup := r.Group("/shop") { shopGroup.GET("/index", func(c *gin.Context) {...}) shopGroup.GET("/cart", func(c *gin.Context) {...}) shopGroup.POST("/checkout", func(c *gin.Context) {...}) // 嵌套路由组 xx := shopGroup.Group("xx") xx.GET("/oo", func(c *gin.Context) {...}) }
中间件(Java中为拦截器)
package main import ( "encoding/json" "log" "net/http" "github.com/gin-gonic/gin" ) // 自定义Go中间件 拦截器 func myHandler() gin.HandlerFunc { return func(context *gin.Context) { // Set一些值用作全局变量 // 通过自定义的中间件,设置的值,在后续处理只要调用了这个中间件的都可以拿到这里的参数 context.Set("usersession", "userid-1") context.Next() // 放形 /* if XXX { context.Next() // 放形 } context.Abort() // 阻止 // 注册未指定就是全局使用 */ } /* 46行加入中间件 */ } func main() { // 创建一个服务 ginServer := gin.Default() // ginServer.Use(favicon.New("./favicon.ico")) // 加载静态页面 ginServer.LoadHTMLGlob("templates/*") // ginServer.LoadHTMLFiles("templates/index.html") // LoadHTMLGlob是全局加载Files是指定文件 //加载资源文件 ginServer.Static("/static", "./static") // 响应一个页面给前端 ginServer.GET("/index", func(context *gin.Context) { // context.JSON() json数据 context.HTML(http.StatusOK, "index.html", gin.H{ "msg": "这是go后台传入的数据", }) }) // usl?userid=XXX&username=jaingxionian ginServer.GET("/user/info", myHandler(), func(context *gin.Context) { // 取出中间件中的值 usersession := context.MustGet("usersession").(string) log.Println("========>", usersession) // 前端控制台输出 userid := context.Query("userid") username := context.Query("username") context.JSON(http.StatusOK, gin.H{ "userid": userid, "username": username, }) }) // /user/info/1/jiangxiaonian // 只要:后名字正确就能匹配上 ginServer.GET("/user/info/:userid/:username", func(context *gin.Context) { userid := context.Param("userid") username := context.Param("username") context.JSON(http.StatusOK, gin.H{ "userid": userid, "username": username, }) }) // 前段给后端传JSON ginServer.POST("/json", func(context *gin.Context) { // GetRawData() 从请求体(request.body)里获取对象 b, _ := context.GetRawData() var m map[string]interface{} // 包装为json数据 []byte _ = json.Unmarshal(b, &m) context.JSON(http.StatusOK, m) }) // 表单 ginServer.POST("/user/add", func(context *gin.Context) { username := context.PostForm("username") password := context.PostForm("password") // 加判断逻辑代码 context.JSON(http.StatusOK, gin.H{ "msg": "ok", "username": username, "password": password, }) }) // 路由 ginServer.GET("/test", func(context *gin.Context) { // 重定向 StatusMovedPermanently 301 context.Redirect(http.StatusMovedPermanently, "https://www.baidu.com") }) // 404 NoRoute ginServer.NoRoute(func(context *gin.Context) { context.HTML(http.StatusNotFound, "404.html", nil) }) // 路由组 userGroup := ginServer.Group("/user") { userGroup.GET("/add") // /user/add userGroup.GET("/login") // /user/add userGroup.GET("/logout") // /user/add } orderGroup := ginServer.Group("/order") { orderGroup.GET("add") orderGroup.GET("delte") } // 服务器端口 err := ginServer.Run(":8082") if err != nil { return } }
// StatCost 是一个统计耗时请求耗时的中间件 func StatCost() gin.HandlerFunc { return func(c *gin.Context) { start := time.Now() c.Set("name", "wxy") // 可以通过c.Set在请求上下文中设置值,后续的处理函数能够取到该值 // 调用该请求的剩余处理程序 c.Next() // 不调用该请求的剩余处理程序 // c.Abort() // 计算耗时 cost := time.Since(start) log.Println(cost) } }
多个中间件
func m1(c *gin.Context) { fmt.Println("m1 ...in") } func m2(c *gin.Context) { fmt.Println("m2 ...in") } func main() { router := gin.Default() router.GET("/", m1, func(c *gin.Context) { fmt.Println("index ...") c.JSON(200, gin.H{"msg": "响应数据"}) }, m2) router.Run(":8080") }
中间件拦截响应c.Abort()
package main import ( "fmt" "github.com/gin-gonic/gin" ) func m1(c *gin.Context) { fmt.Println("m1 ...in") c.JSON(200, gin.H{"msg": "第一个中间件拦截了"}) c.Abort() } func m2(c *gin.Context) { fmt.Println("m2 ...in") } func main() { router := gin.Default() router.GET("/", m1, func(c *gin.Context) { fmt.Println("index ...") c.JSON(200, gin.H{"msg": "响应数据"}) }, m2) router.Run(":8080") }
中间件放行c.Next()
package main import ( "fmt" "github.com/gin-gonic/gin" ) func m1(c *gin.Context) { fmt.Println("m1 ...in") c.Next() fmt.Println("m1 ...out") } func m2(c *gin.Context) { fmt.Println("m2 ...in") c.Next() fmt.Println("m2 ...out") } func main() { router := gin.Default() router.GET("/", m1, func(c *gin.Context) { fmt.Println("index ...in") c.JSON(200, gin.H{"msg": "响应数据"}) c.Next() fmt.Println("index ...out") }, m2) router.Run(":8080") }
文件上传
单个文件上传
文件上传前端页面代码:
<!DOCTYPE html> <html lang="zh-CN"> <head> <title>上传文件示例</title> </head> <body> <form action="/upload" method="post" enctype="multipart/form-data"> <input type="file" name="f1"> <input type="submit" value="上传"> </form> </body> </html>
后端gin框架部分代码:
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("D:/go_file/%s", file.Filename) // 上传文件到指定的目录 c.SaveUploadedFile(file, dst) c.JSON(http.StatusOK, gin.H{ "message": fmt.Sprintf("'%s' uploaded!", file.Filename), }) }) router.Run(":8082") }
copy
file, _ := c.FormFile("file") log.Println(file.Filename) // 读取文件中的数据,返回文件对象 fileRead, _ := file.Open() dst := "./" + file.Filename // 创建一个文件 out, err := os.Create(dst) if err != nil { fmt.Println(err) } defer out.Close() // 拷贝文件对象到out中 io.Copy(out, fileRead)
读取上传文件
file, _ := c.FormFile("file") // 读取文件中的数据,返回文件对象 fileRead, _ := file.Open() data, _ := io.ReadAll(fileRead) fmt.Println(string(data))
多个文件上传
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("./upload/%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(":8082") }
文件下载
c.Header("Content-Type", "application/octet-stream") // 表示是文件流,唤起浏览器下载,一般设置了这个,就要设置文件名 c.Header("Content-Disposition", "attachment; filename="+"牛逼.png") // 用来指定下载下来的文件名 c.Header("Content-Transfer-Encoding", "binary") // 表示传输过程中的编码形式,乱码问题可能就是因为它 c.File("uploads/12.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对象 })}s
异常捕获
defer func(){ if err:=recover(); err != nil{ fmt.Println("捕获异常:", err) } }() // 因为recover只有在发生panic时才会返回一个非nil的值。如果没有panic发生,recover会返回nil
打印日志
// pkg/util/logger.go package util import ( "log" "os" "path" "time" "github.com/sirupsen/logrus" ) var LogrusObj *logrus.Logger func init() { // init() 特殊函数 在包被导入时自动执行 src, _ := setOutPutFile() if LogrusObj != nil { LogrusObj.Out = src return } // 实例化 logger := logrus.New() logger.Out = src // 设置输出 logger.SetLevel(logrus.DebugLevel) // 设置日志规则 logger.SetFormatter(&logrus.TextFormatter{ TimestampFormat: "2006-01-02 15:04:05", }) LogrusObj = logger } func setOutPutFile() (*os.File, error) { now := time.Now() logFilePath := "" if dir, err := os.Getwd(); err == nil { // os.Getwd()获取当前的工作目录 logFilePath = dir + "/logs/" } _, err := os.Stat(logFilePath) if os.IsNotExist(err) { if err = os.MkdirAll(logFilePath, 0777); err != nil { log.Println(err.Error()) return nil, err } } logFileName := now.Format("2006-01-02") + ".log" // 日志文件 fileName := path.Join(logFilePath, logFileName) _, err = os.Stat(fileName) if os.IsNotExist(err) { if err = os.MkdirAll(fileName, 0777); err != nil { // os.MkdirAll是用来创建目录的,而不是文件。应该使用os.Create或os.OpenFile来创建文件 log.Println(err.Error()) return nil, err } } // 写入文件 src, err := os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY, os.ModeAppend) if err != nil { return nil, err } return src, nil } // util.LogrusObj.Infoln(err)
gin自带日志系统
package main import ( "github.com/gin-gonic/gin" "io" "os" ) func main() { // 输出到文件 f, _ := os.Create("gin.log") //gin.DefaultWriter = io.MultiWriter(f) // 如果需要同时将日志写入文件和控制台,请使用以下代码。 gin.DefaultWriter = io.MultiWriter(f, os.Stdout) router := gin.Default() router.GET("/", func(c *gin.Context) { c.JSON(200, gin.H{"msg": "/"}) }) router.Run() }
// 查看路由 router.Routes() // 它会返回已注册的路由列表 // 环境切换 如果不想看到这些debug日志,那么我们可以改为release模式 gin.SetMode(gin.ReleaseMode) router := gin.Default() // 修改log的显示 package main import ( "fmt" "github.com/gin-gonic/gin" ) func LoggerWithFormatter(params gin.LogFormatterParams) string { return fmt.Sprintf( "[ feng ] %s | %d | \t %s | %s | %s \t %s\n", params.TimeStamp.Format("2006/01/02 - 15:04:05"), params.StatusCode, // 状态码 params.ClientIP, // 客户端ip params.Latency, // 请求耗时 params.Method, // 请求方法 params.Path, // 路径 ) } func main() { router := gin.New() router.Use(gin.LoggerWithFormatter(LoggerWithFormatter)) router.Run() } // -------------------------------- func LoggerWithFormatter(params gin.LogFormatterParams) string { return fmt.Sprintf( "[ feng ] %s | %d | \t %s | %s | %s \t %s\n", params.TimeStamp.Format("2006/01/02 - 15:04:05"), params.StatusCode, params.ClientIP, params.Latency, params.Method, params.Path, ) } func main() { router := gin.New() router.Use( gin.LoggerWithConfig( gin.LoggerConfig{Formatter: LoggerWithFormatter}, ), ) router.Run() } // ------------------ func LoggerWithFormatter(params gin.LogFormatterParams) string { var statusColor, methodColor, resetColor string statusColor = params.StatusCodeColor() methodColor = params.MethodColor() resetColor = params.ResetColor() return fmt.Sprintf( "[ feng ] %s | %s %d %s | \t %s | %s | %s %-7s %s \t %s\n", params.TimeStamp.Format("2006/01/02 - 15:04:05"), statusColor, params.StatusCode, resetColor, params.ClientIP, params.Latency, methodColor, params.Method, resetColor, params.Path, ) }
*Go跨域
跨域问题
出现跨域问题是浏览器行为,是浏览器认为不安全而进行的拦截
当
url
的协议、域名、端口三者任一一个与当前页面url
不同,就是跨域如何解决?
cors:
在响应头加上对应的响应头即可代理
cors
package main import ( "github.com/gin-gonic/gin" "net/http" ) func Cors() gin.HandlerFunc { return func(c *gin.Context) { method := c.Request.Method if c.Request.Header.Get("origin") != "" { c.Header("Access-Control-Allow-Origin", "*") // 可将将 * 替换为指定的域名 c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE") c.Header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization") c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Cache-Control, Content-Language, Content-Type") c.Header("Access-Control-Allow-Credentials", "true") } if method == "OPTIONS" { c.AbortWithStatus(http.StatusNoContent) } c.Next() } } func Index(c *gin.Context) { c.JSON(200, gin.H{ "code": 0, "msg": "成功", "data": gin.H{}, }) return } func main() { r := gin.Default() r.GET("/api/no_cors", Index) r.POST("/api/no_cors", Index) r.GET("/api/cors", Cors(), Index) r.POST("/api/cors", Cors(), Index) r.Run(":8080") }
代理
这种方案是目前最主流的跨域解决方案,它分为两类,一个是开发环境,一个是生产环境
开发环境解决跨域
以vue3
为例,vite
提供了代理功能
import {fileURLToPath, URL} from 'node:url' import {defineConfig, loadEnv} from 'vite' import vue from '@vitejs/plugin-vue' import type {ImportMetaEnv} from "./env"; // https://vitejs.dev/config/ export default defineConfig(({mode}) => { let env: Record<keyof ImportMetaEnv, string> = loadEnv(mode, process.cwd()) const serverUrl = env.VITE_SERVER_URL const wsUrl = serverUrl.replace("http", "ws") return { plugins: [ vue(), ], envDir: "./", resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) } }, server: { host: "0.0.0.0", port: 80, proxy: { "/api": { target: serverUrl, changeOrigin: true, } } } } })
凡是使用代理的情况,
axios
请求的后端路径就不能写死了因为一旦写死了,代理就捕获不到了,相当于还是前端直接请求后端接口,肯定会跨域的
生产环境解决跨域
使用nginx
的反向代理
server { listen 80; server_name blog.fengfengzhidao.com; location / { try_files $uri $uri/ /index.html; root /opt/gvb/web/dist; index index.html index.htm; } location /api/ { # rewrite ^/(api/.*) /$1 break; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header REMOTE-HOST $remote_addr; proxy_pass http://127.0.0.1:8082/api/; } location /uploads/ { alias /opt/gvb/server/uploads/; } access_log /opt/gvb/access.log; error_log /opt/gvb/error.log; }
*http
反向代理(网关)
package main import ( "fmt" "net/http" "net/http/httputil" "net/url" ) type Proxy struct {} func (Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { remote, _ := url.Parse("http://127.0.0.1:20023") // 要转到访问的地址 reverseProxy := httputil.NewSingleHostReverseProxy(remote) reverseProxy.ServeHTTP(w, r) } func main() { addr := "127.0.0.1:9090" // 监听地址 fmt.Println("gateway runserver on %s\n", addr) http.ListenAndServe(addr, Proxy{}) }
<!-- -->
Gin
// cmd go get -u github.com/gin-gonic/gin
项目开始
路由组封装
// main.go package main import "godemo/router" func main() { r := router.Router() r.Run(":9090") }
// router/routers.go package router import ( "net/http" "github.com/gin-gonic/gin" ) func Router() *gin.Engine { r := gin.Default() r.GET("/hello", func(ctx *gin.Context) { ctx.String(http.StatusOK, "Hello World") }) user := r.Group("/user") { user.POST("/list", func(ctx *gin.Context) { ctx.String(http.StatusOK, "user list") }) user.PUT("/add", func(ctx *gin.Context) { ctx.String(http.StatusOK, "user add") }) user.DELETE("/delete", func(ctx *gin.Context) { ctx.String(http.StatusOK, "user delete") }) } return r }
封装JS
// controllers/common.go package controllers import ( "github.com/gin-gonic/gin" ) type JsonStruct struct { Code int `json:"code"` Msg interface{} `json:"msg"` Data interface{} `json:"data"` Count int64 `json:"count"` } func ReturnSuccess(c *gin.Context, code int, msg interface{}, data interface{}, count int64) { json := &JsonStruct{ Code: code, Msg: msg, Data: data, Count: count, } c.JSON(200, json) } func ReturnErrer(c *gin.Context, code int, msg interface{}) { json := &JsonStruct{ Code: code, Msg: msg, } c.JSON(200, json) }
// router/routers.go package router import ( "godemo/controllers" "net/http" "github.com/gin-gonic/gin" ) func Router() *gin.Engine { r := gin.Default() r.GET("/hello", func(ctx *gin.Context) { ctx.String(http.StatusOK, "Hello World") }) user := r.Group("/user") { user.GET("/info", controllers.GetUserInfo) user.POST("/list", func(ctx *gin.Context) { ctx.String(http.StatusOK, "user list") }) user.PUT("/add", func(ctx *gin.Context) { ctx.String(http.StatusOK, "user add") }) user.DELETE("/delete", func(ctx *gin.Context) { ctx.String(http.StatusOK, "user delete") }) } return r }
// controllers/user.go package controllers import ( "github.com/gin-gonic/gin" ) func GetUserInfo(c *gin.Context) { ReturnSuccess(c, 0, "success", "user info", 1) }
-
// router/routers.go package router import ( "godemo/controllers" "net/http" "github.com/gin-gonic/gin" ) func Router() *gin.Engine { r := gin.Default() r.GET("/hello", func(ctx *gin.Context) { ctx.String(http.StatusOK, "Hello World") }) user := r.Group("/user") { user.GET("/info", controllers.GetUserInfo) user.POST("/list", controllers.GetList) user.PUT("/add", func(ctx *gin.Context) { ctx.String(http.StatusOK, "user add") }) user.DELETE("/delete", func(ctx *gin.Context) { ctx.String(http.StatusOK, "user delete") }) } return r }
-
// controllers/user.go package controllers import ( "github.com/gin-gonic/gin" ) func GetUserInfo(c *gin.Context) { ReturnSuccess(c, 0, "success", "user info", 1) } func GetList(c *gin.Context) { ReturnErrer(c, 4004, "没有相关信息") }
-
// controllers/common.go package controllers import ( "github.com/gin-gonic/gin" ) type JsonStruct struct { Code int `json:"code"` Msg interface{} `json:"msg"` Data interface{} `json:"data"` Count int64 `json:"count"` } type JsonErrStruct struct { Code int `json:"code"` Msg interface{} `json:"msg"` } func ReturnSuccess(c *gin.Context, code int, msg interface{}, data interface{}, count int64) { json := &JsonStruct{ Code: code, Msg: msg, Data: data, Count: count, } c.JSON(200, json) } func ReturnErrer(c *gin.Context, code int, msg interface{}) { json := &JsonErrStruct{ Code: code, Msg: msg, } c.JSON(200, json) }
结构体优化
-
在
controllers/user.go
中的函数,因为都在一个包里,所以当新建的order.go
中出现该函数就会报错,这时就可以用结构体方法进行优化
// controllers/order.go func GetList(c *gin.Context) { ReturnErrer(c, 4004, "没有相关信息") }
// controllers/user.go package controllers import ( "github.com/gin-gonic/gin" ) type UserController struct{} func (u UserController)GetUserInfo(c *gin.Context) { ReturnSuccess(c, 0, "success", "user info", 1) } func (u UserController)GetList(c *gin.Context) { ReturnErrer(c, 4004, "没有相关信息list") }
// controllers/order.go package controllers import "github.com/gin-gonic/gin" type OrderContreller struct{} func (o OrderContreller) GetList(c *gin.Context) { ReturnErrer(c, 4004, "没有相关信息") }
// router/routers.go package router import ( "godemo/controllers" "net/http" "github.com/gin-gonic/gin" ) func Router() *gin.Engine { r := gin.Default() r.GET("/hello", func(ctx *gin.Context) { ctx.String(http.StatusOK, "Hello World") }) user := r.Group("/user") { user.GET("/info", controllers.UserController{}.GetUserInfo) user.POST("/list", controllers.UserController{}.GetList) user.PUT("/add", func(ctx *gin.Context) { ctx.String(http.StatusOK, "user add") }) user.DELETE("/delete", func(ctx *gin.Context) { ctx.String(http.StatusOK, "user delete") }) } order := r.Group("/order") { order.GET("list", controllers.OrderContreller{}.GetList) } return r }
获取请求参数
方式一Param
// router/routers.go package router import ( "godemo/controllers" "net/http" "github.com/gin-gonic/gin" ) func Router() *gin.Engine { r := gin.Default() r.GET("/hello", func(ctx *gin.Context) { ctx.String(http.StatusOK, "Hello World") }) user := r.Group("/user") { user.GET("/info/:id/:name", controllers.UserController{}.GetUserInfo) user.POST("/list", controllers.UserController{}.GetList) user.PUT("/add", func(ctx *gin.Context) { ctx.String(http.StatusOK, "user add") }) user.DELETE("/delete", func(ctx *gin.Context) { ctx.String(http.StatusOK, "user delete") }) } order := r.Group("/order") { order.GET("list", controllers.OrderContreller{}.GetList) } return r }
// controllers/user.go package controllers import ( "github.com/gin-gonic/gin" ) type UserController struct{} func (u UserController) GetUserInfo(c *gin.Context) { id := c.Param("id") name := c.Param("name") ReturnSuccess(c, 0, name, id, 1) } func (u UserController) GetList(c *gin.Context) { ReturnErrer(c, 4004, "没有相关信息list") }
方式二获取POST的参数 PostForm
// controllers/order.go package controllers import "github.com/gin-gonic/gin" type OrderController struct{} func (o OrderController) GetList(c *gin.Context) { cid := c.PostForm("cid") name := c.DefaultPostForm("name", "xiaohua") ReturnSuccess(c, 0, name, cid, 1) }
方式三获取JSON参数 BindJSON
_ Map
&&结构体
// controllers/order.go package controllers import "github.com/gin-gonic/gin" type OrderController struct{} func (o OrderController) GetList(c *gin.Context) { param := make(map[string]interface{}) err := c.BindJSON(¶m) if err == nil { ReturnSuccess(c, 0, param["name"], param["cid"], 1) return } ReturnErrer(c, 4001, gin.H{"err": err}) }
// controllers/order.go package controllers import "github.com/gin-gonic/gin" type OrderController struct{} type Search struct { Name string `json:"name"` Cid int `json:"cid"` } func (o OrderController) GetList(c *gin.Context) { search := &Search{} err := c.BindJSON(&search) if err == nil { ReturnSuccess(c, 0, search.Name, search.Cid, 1) return } ReturnErrer(c, 4001, gin.H{"err": err}) }
异常捕获
// controllers/user.go package controllers import ( "fmt" "github.com/gin-gonic/gin" ) type UserController struct{} func (u UserController) GetUserInfo(c *gin.Context) { id := c.Param("id") name := c.Param("name") ReturnSuccess(c, 0, name, id, 1) } func (u UserController) GetList(c *gin.Context) { defer func() { if err := recover(); err != nil { fmt.Println("捕获异常:", err) } }() // 因为recover只有在发生panic时才会返回一个非nil的值。如果没有panic发生,recover会返回nil num1 := 1 num2 := 0 num3 := num1 / num2 ReturnErrer(c, 4004, num3) } // 异常错误输出到日志中
日志收集
// pkg/util/logger.go package util import ( "log" "os" "path" "time" "github.com/sirupsen/logrus" ) var LogrusObj *logrus.Logger func init() { // init() 特殊函数 在包被导入时自动执行 src, _ := setOutPutFile() if LogrusObj != nil { LogrusObj.Out = src return } // 实例化 logger := logrus.New() logger.Out = src // 设置输出 logger.SetLevel(logrus.DebugLevel) // 设置日志规则 logger.SetFormatter(&logrus.TextFormatter{ TimestampFormat: "2006-01-02 15:04:05", }) LogrusObj = logger } func setOutPutFile() (*os.File, error) { now := time.Now() logFilePath := "" if dir, err := os.Getwd(); err == nil { // os.Getwd()获取当前的工作目录 logFilePath = dir + "/logs/" } _, err := os.Stat(logFilePath) if os.IsNotExist(err) { if err = os.MkdirAll(logFilePath, 0777); err != nil { log.Println(err.Error()) return nil, err } } logFileName := now.Format("2006-01-02") + ".log" // 日志文件 fileName := path.Join(logFilePath, logFileName) _, err = os.Stat(fileName) if os.IsNotExist(err) { if err = os.MkdirAll(fileName, 0777); err != nil { // os.MkdirAll是用来创建目录的,而不是文件。应该使用os.Create或os.OpenFile来创建文件 log.Println(err.Error()) return nil, err } } // 写入文件 src, err := os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY, os.ModeAppend) if err != nil { return nil, err } return src, nil } // util.LogrusObj.Infoln(err)
// controllers/user.go package controllers import ( "fmt" pkg "godemo/pkg/logger" "github.com/gin-gonic/gin" ) type UserController struct{} func (u UserController) GetUserInfo(c *gin.Context) { id := c.Param("id") name := c.Param("name") ReturnSuccess(c, 0, name, id, 1) } func (u UserController) GetList(c *gin.Context) { defer func() { if err := recover(); err != nil { fmt.Println("捕获异常:", err) pkg.LogrusObj.Infoln(err) } }() num1 := 1 num2 := 0 num3 := num1 / num2 ReturnErrer(c, 4004, num3) }
引入Gorm框架
go get -u gorm.io/driver/mysql go get -u github.com/jinzhu/gorm net start mysql mysql -u root -p net stop mysql
// config/db.go package config const ( Mysqldb = "root:l20030328@tcp(127.0.0.1:3306)/ranking?charset=utf8" )
// dao/dao.go package dao import ( "godemo/config" pkg "godemo/pkg/logger" "time" "github.com/jinzhu/gorm" _ "gorm.io/driver/mysql" ) var ( Db *gorm.DB err error ) func init() { Db, err = gorm.Open("mysql", config.Mysqldb) if err != nil { pkg.LogrusObj.Error(map[string]interface{}{"mysql conent error": err}) } if Db.Error != nil { pkg.LogrusObj.Error(map[string]interface{}{"datebase conent error": Db.Error}) } Db.DB().SetMaxIdleConns(10) Db.DB().SetMaxOpenConns(100) Db.DB().SetConnMaxLifetime(time.Hour) }
// models/user.go package models import "godemo/dao" type User struct { Id int name string } func (User) TableName() string { return "user" } func GetUserTest(id int) (User, error) { var user User err := dao.Db.Where("id = ?", id).First(&user).Error return user, err }
// controllers/user.go package controllers import ( "fmt" "godemo/models" "strconv" "github.com/gin-gonic/gin" ) type UserController struct{} func (u UserController) GetUserInfo(c *gin.Context) { idStr := c.Param("id") name := c.Param("name") id, _ := strconv.Atoi(idStr) user, _ := models.GetUserTest(id) ReturnSuccess(c, 0, name, user, 1) } func (u UserController) GetList(c *gin.Context) { defer func() { if err := recover(); err != nil { fmt.Println("捕获异常:", err) } }() num1 := 1 num2 := 0 num3 := num1 / num2 ReturnErrer(c, 4004, num3) }
数据库crud的实现
// models/user.go package models import "godemo/dao" type User struct { Id int Username string } func (User) TableName() string { return "user" } func init() { dao.Db.AutoMigrate(&User{}) } func GetUserTest(id int) (User, error) { var user User err := dao.Db.Where("id = ?", id).First(&user).Error return user, err } func GetUserListTest() ([]User, error) { var users []User err := dao.Db.Where("id < ?", 3).Find(&users).Error return users, err } func AddUser(username string) (int, error) { user := User{Username: username} err := dao.Db.Create(&user).Error return user.Id, err } func UpdateUser(id int, username string) { dao.Db.Model(&User{}).Where("id = ?", id).Update("username", username) } func DeleteUser(id int) error { err := dao.Db.Delete(&User{}, id).Error return err }
// controllers/user.go package controllers import ( "fmt" "godemo/models" "strconv" "github.com/gin-gonic/gin" ) type UserController struct{} func (u UserController) GetUserInfo(c *gin.Context) { idStr := c.Param("id") // name := c.Param("name") id, _ := strconv.Atoi(idStr) user, _ := models.GetUserTest(id) ReturnSuccess(c, 0, "name", user, 1) } func (u UserController) AddUser(c *gin.Context) { username := c.DefaultPostForm("username", "") id, err := models.AddUser(username) if err != nil { ReturnErrer(c, 4002, "保存错误") return } ReturnSuccess(c, 0, "保存成功", id, 1) } func (u UserController) UpdateUser(c *gin.Context) { username := c.DefaultPostForm("username", "") idStr := c.DefaultPostForm("id", "") id, _ := strconv.Atoi(idStr) models.UpdateUser(id, username) ReturnSuccess(c, 0, "更新成功", true, 1) } func (u UserController) DeleteUser(c *gin.Context) { idStr := c.DefaultPostForm("id", "") id, _ := strconv.Atoi(idStr) err := models.DeleteUser(id) if err != nil { ReturnErrer(c, 4003, "删除错误") return } ReturnSuccess(c, 0, "删除成功", true, 1) } func (u UserController) GetList(c *gin.Context) { defer func() { if err := recover(); err != nil { fmt.Println("捕获异常:", err) } }() num1 := 1 num2 := 0 num3 := num1 / num2 ReturnErrer(c, 4004, num3) } func (u UserController) GetUserListTest(c *gin.Context) { users, err := models.GetUserListTest() if err != nil { ReturnErrer(c, 4004, "没有相关数据") return } ReturnSuccess(c, 0, "查询成功", users, 1) }
// router/routers.go package router import ( "godemo/controllers" "net/http" "github.com/gin-gonic/gin" ) func Router() *gin.Engine { r := gin.Default() r.GET("/hello", func(ctx *gin.Context) { ctx.String(http.StatusOK, "Hello World") }) user := r.Group("/user") { user.GET("/info/:id", controllers.UserController{}.GetUserInfo) user.POST("/list", controllers.UserController{}.GetList) user.POST("/add", controllers.UserController{}.AddUser) user.POST("/update", controllers.UserController{}.UpdateUser) user.POST("/delete", controllers.UserController{}.DeleteUser) user.POST("/list/test", controllers.UserController{}.GetUserListTest) } order := r.Group("/order") { order.GET("list", controllers.OrderController{}.GetList) } return r }
用户注册登录,以及会话的使用
// controllers/user.go package controllers type UserController struct{}
// models/user.go package models import "godemo/dao" type User struct { Id int Username string } func (User) TableName() string { return "user" } func init() { dao.Db.AutoMigrate(&User{}) }
// router/routers.go package router import ( "github.com/gin-gonic/gin" ) func Router() *gin.Engine { r := gin.Default() user := r.Group("/user") { } return r }
注册
// contrillers/common.go package controllers import ( "crypto/md5" "encoding/hex" "github.com/gin-gonic/gin" ) type JsonStruct struct { Code int `json:"code"` Msg interface{} `json:"msg"` Data interface{} `json:"data"` Count int64 `json:"count"` } type JsonErrStruct struct { Code int `json:"code"` Msg interface{} `json:"msg"` } func ReturnSuccess(c *gin.Context, code int, msg interface{}, data interface{}, count int64) { json := &JsonStruct{ Code: code, Msg: msg, Data: data, Count: count, } c.JSON(200, json) } func ReturnErrer(c *gin.Context, code int, msg interface{}) { json := &JsonErrStruct{ Code: code, Msg: msg, } c.JSON(200, json) } // md5加密 func EncryMd5(s string) string { ctx := md5.New() ctx.Write([]byte(s)) return hex.EncodeToString(ctx.Sum(nil)) }
// controllers/user.go package controllers import ( "godemo/models" "github.com/gin-gonic/gin" ) type UserController struct{} func (u UserController) Register(c *gin.Context) { // 接收用户名,密码,确认密码 username := c.DefaultPostForm("username", "") password := c.DefaultPostForm("password", "") confirmPassword := c.DefaultPostForm("confirmPassword", "") if username == "" || password == "" || confirmPassword == "" { ReturnErrer(c, 4001, "请输入正确信息") return } if password != confirmPassword { ReturnErrer(c, 4001, "密码和确认密码不一致") return } user, _ := models.GetUserInfoByUsername(username) if user.Id != 0 { ReturnErrer(c, 4001, "用户名已存在") return } _, err := models.AddUser(username, EncryMd5(password)) if err != nil { ReturnErrer(c, 4001, "保存失败,请联系管理员") return } ReturnSuccess(c, 1, "注册成功", user.Id, 1) }
// models/user.go package models import ( "godemo/dao" "time" ) type User struct { Id int `json:"id"` Username string `json:"username"` Password string `json:"password"` AddTime int64 `json:"addTime"` UpdateTime int64 `json:"updateTime"` } func (User) TableName() string { return "user" } func init() { dao.Db.AutoMigrate(&User{}) } // 判断用户名是否存在 func GetUserInfoByUsername(username string) (User, error) { var user User err := dao.Db.Where("username = ?", username).First(&user).Error return user, err } // 创建用户 func AddUser(username string, password string) (int, error) { user := User{Username: username, Password: password, AddTime: time.Now().Unix(), UpdateTime: time.Now().Unix(), } err := dao.Db.Create(&user).Error return user.Id, err }
// router/routers.go package router import ( "godemo/controllers" "github.com/gin-gonic/gin" ) func Router() *gin.Engine { r := gin.Default() user := r.Group("/user") { user.POST("/register", controllers.UserController{}.Register) } return r }
登录|会话签发
redis-server.exe --service-start net start mysql net stop mysql redis-server.exe --service-stop
go get github.com/gin-contrib/sessions go get github.com/gin-contrib/sessions/redis
// router/routers.go package router import ( "godemo/config" "godemo/controllers" "github.com/gin-contrib/sessions" sessions_redis "github.com/gin-contrib/sessions/redis" "github.com/gin-gonic/gin" ) func Router() *gin.Engine { r := gin.Default() store, _ := sessions_redis.NewStore(10, "tcp", config.RedisAddress, "", []byte("secret")) // []byte("secret") 是用于加密会话数据的密钥 r.Use(sessions.Sessions("mysession", store)) // 将会话中间件添加到路由,检查是否存在名为 "mysession" 的会话,并在不存在时创建一个 user := r.Group("/user") { user.POST("/register", controllers.UserController{}.Register) user.POST("/login", controllers.UserController{}.Login) } return r }
// config/redis.go package config const ( RedisAddress = "localhost:6379" )
// controllers/user.go package controllers import ( "godemo/models" "strconv" "github.com/gin-contrib/sessions" "github.com/gin-gonic/gin" ) type UserController struct{} // 注册 func (u UserController) Register(c *gin.Context) { // 接收用户名,密码,确认密码 username := c.DefaultPostForm("username", "") password := c.DefaultPostForm("password", "") confirmPassword := c.DefaultPostForm("confirmPassword", "") if username == "" || password == "" || confirmPassword == "" { ReturnErrer(c, 4001, "请输入正确信息") return } if password != confirmPassword { ReturnErrer(c, 4001, "密码和确认密码不一致") return } user, _ := models.GetUserInfoByUsername(username) if user.Id != 0 { ReturnErrer(c, 4001, "用户名已存在") return } _, err := models.AddUser(username, EncryMd5(password)) if err != nil { ReturnErrer(c, 4001, "保存失败,请联系管理员") return } ReturnSuccess(c, 1, "注册成功", user.Id, 1) } type UserApi struct { Id int `json:"id"` Username string `json:"username"` } // 登录 func (u UserController) Login(c *gin.Context) { // 接受用户名和密码 username := c.DefaultPostForm("username", "") password := c.DefaultPostForm("password", "") if username == "" || password == "" { ReturnErrer(c, 4001, "请输入正确的信息") return } user, _ := models.GetUserInfoByUsername(username) if user.Id == 0 { ReturnErrer(c, 4004, "用户名或密码不正确") return } if user.Password != EncryMd5(password) { ReturnErrer(c, 4004, "用户名或密码不正确") return } session := sessions.Default(c) // 从请求上下文中获取默认会话 session.Set("login:"+strconv.Itoa(user.Id), user.Id) // 将会话键设置为 "login:" 后跟用户的 ID session.Save() // 保存会话数据,将数据发送到 Redis 服务器进行存储 data := UserApi{Id: user.Id, Username: username} ReturnSuccess(c, 0, "登录成功", data, 1) }
// router/routers.go package router import ( "godemo/config" "godemo/controllers" "github.com/gin-contrib/sessions" sessions_redis "github.com/gin-contrib/sessions/redis" "github.com/gin-gonic/gin" ) func Router() *gin.Engine { r := gin.Default() store, _ := sessions_redis.NewStore(10, "tcp", config.RedisAddress, "", []byte("secret")) r.Use(sessions.Sessions("mysession", store)) user := r.Group("/user") { user.POST("/register", controllers.UserController{}.Register) user.POST("/login", controllers.UserController{}.Login) } return r }
另一种的token签发
package routes import ( "todo_list/api" "todo_list/middieware" "github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions/cookie" "github.com/gin-gonic/gin" ) func NewRouter() *gin.Engine { r := gin.Default() // 创建gin引擎 store := cookie.NewStore([]byte("something-very-secret")) // 初始化cookie会话存储 r.Use(sessions.Sessions("mysession", store)) // 设置会话中间件 v1 := r.Group("api/v1") // 定义一个路由组v1 { // 用户操作,在路由组内定义路由 v1.POST("user/register", api.UserRegister) v1.POST("user/login", api.UserLogin) authed := v1.Group("/") authed.Use(middieware.JWT()) // 运行时先验证middieware.JWT()这个中间件看有没有这个权限 { authed.POST("task", api.CreateTask) } } return r }
// service/user.go // 密码验证成功后发一个token,为了其他功能需要身份验证所给前端存储的 // 创建一个备忘录,这个功能就要token,不然不知道是谁创建的备忘录 token, err := utils.GenerateToken(user.ID, service.UserName, service.Password) if err != nil { return serializer.Response{ Status: 500, Msg: "Token签发错误", } } return serializer.Response{ Status: 200, Data: serializer.TokenData{User: serializer.BuildUser(user), Token: token}, Msg: "登录成功", } }
package middieware import ( "time" "todo_list/pkg/utils" "github.com/gin-gonic/gin" ) func JWT() gin.HandlerFunc { return func(c *gin.Context) { code := 200 token := c.GetHeader("Authorization") // 从HTTP请求的头部获取名为"Authorization"的值,这通常是JWT存放的地方 if token == "" { code = 404 } else { claim, err := utils.ParseToken(token) if err != nil { code = 403 // 无权限,token是无权的,是假的 } else if time.Now().Unix() > claim.ExpiresAt { code = 401 // Token无效 // JWT解析成功,但当前时间已经超过了claim.ExpiresAt(即token已过期) } } if code != 200 { c.JSON(200, gin.H{ // map[string]interface{}的缩写 "status": code, "msg": "Token解析错误", }) c.Abort() // 终止当前的请求处理流程 return } c.Next() // 将请求传递给后续的中间件或路由处理函数 } }
package utils import ( "time" "github.com/dgrijalva/jwt-go" ) var JwtSecret = []byte("ABAB") type Claims struct { Id uint `json:"id"` UserName string `json:"user_name"` Password string `json:"password"` jwt.StandardClaims } // 签发token func GenerateToken(id uint, username, password string) (string, error) { notTime := time.Now() expireTime := notTime.Add(24 * time.Hour) Claims := Claims{ Id: id, UserName: username, Password: password, StandardClaims: jwt.StandardClaims{ ExpiresAt: expireTime.Unix(), Issuer: "todo_list", }, } tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, Claims) token, err := tokenClaims.SignedString(JwtSecret) return token, err } // 验证token func ParseToken(token string) (*Claims, error) { tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(Token *jwt.Token) (interface{}, error) { return JwtSecret, nil }) if tokenClaims != nil { if Claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid { return Claims, nil } } return nil, err }
投票功能
redis-server.exe --service-start net start mysql net stop mysql redis-server.exe --service-stop
查看player列表
// controllers/player.go // controllers/player.go package controllers import ( "godemo/models" "strconv" "github.com/gin-gonic/gin" ) type PlayerController struct{} func (p PlayerController) GetPlayers(c *gin.Context) { aidStr := c.DefaultPostForm("aid", "0") aid, _ := strconv.Atoi(aidStr) rs, err := models.GetPlayers(aid) if err != nil { ReturnErrer(c, 4004, "没有相关信息") return } ReturnSuccess(c, 0, "success", rs, 1) }
// models/player.go package models import "godemo/dao" type Player struct { Id int `json:"id"` Aid int `json:"aid"` Ref string `json:"ref"` Nickname string `json:"nickname"` Declaration string `json:"declaration"` Avatar string `json:"avatar"` Score int `json:"score"` } func (Player) TableName() string { return "player" } func init() { dao.Db.AutoMigrate(&Player{}) } func GetPlayers(aid int) ([]Player, error) { var players []Player err := dao.Db.Where("aid = ?", aid).Find(&players).Error return players, err }
// router/routers.go package router import ( "godemo/config" "godemo/controllers" "github.com/gin-contrib/sessions" sessions_redis "github.com/gin-contrib/sessions/redis" "github.com/gin-gonic/gin" ) func Router() *gin.Engine { r := gin.Default() store, _ := sessions_redis.NewStore(10, "tcp", config.RedisAddress, "", []byte("secret")) // []byte("secret") 是用于加密会话数据的密钥 r.Use(sessions.Sessions("mysession", store)) // 将会话中间件添加到路由,检查是否存在名为 "mysession" 的会话,并在不存在时创建一个 user := r.Group("/user") { user.POST("/register", controllers.UserController{}.Register) user.POST("/login", controllers.UserController{}.Login) } player := r.Group("/player") { player.POST("/list", controllers.PlayerController{}.GetPlayers) } return r }
投票实现
// models/user.go package models import ( "godemo/dao" "time" ) type User struct { Id int `json:"id"` Username string `json:"username"` Password string `json:"password"` AddTime int64 `json:"addTime"` UpdateTime int64 `json:"updateTime"` } func (User) TableName() string { return "user" } func init() { dao.Db.AutoMigrate(&User{}) } // 判断用户名是否存在 func GetUserInfoByUsername(username string) (User, error) { var user User err := dao.Db.Where("username = ?", username).First(&user).Error return user, err } func GetUserInfo(id int) (User, error) { var user User err := dao.Db.Where("id = ?", id).First(&user).Error return user, err } // 创建用户 func AddUser(username string, password string) (int, error) { user := User{Username: username, Password: password, AddTime: time.Now().Unix(), UpdateTime: time.Now().Unix(), } err := dao.Db.Create(&user).Error return user.Id, err }
// controllers/vote.go // controllers/vote.go package controllers import ( "godemo/models" "strconv" "github.com/gin-gonic/gin" ) type VoteController struct{} func (v VoteController) AddVote(c *gin.Context) { userIdStr := c.DefaultPostForm("userId", "0") playerIdStr := c.DefaultPostForm("playerId", "0") userId, _ := strconv.Atoi(userIdStr) playerId, _ := strconv.Atoi(playerIdStr) if userId == 0 || playerId == 0 { ReturnErrer(c, 4001, "请输入正确的信息") return } user, _ := models.GetUserInfo(userId) if user.Id == 0 { ReturnErrer(c, 4001, "投票用户不存在") return } player, _ := models.GetPlayerInfo(playerId) if player.Id == 0 { ReturnErrer(c, 4001, "参赛选手不存在") return } vote, _ := models.GetVoteInfo(userId, playerId) if vote.Id != 0 { ReturnErrer(c, 4001, "已投票") return } rs, err := models.AddVote(userId, playerId) if err == nil { // 更新参赛选手分数字段,自增1 models.UpdatePlayerScore(playerId) ReturnSuccess(c, 0, "投票成功", rs, 1) return } ReturnErrer(c, 4004, "请联系管理员") return }
// models/player.go // models/player.go package models import ( "godemo/dao" "github.com/jinzhu/gorm" ) type Player struct { Id int `json:"id"` Aid int `json:"aid"` Ref string `json:"ref"` Nickname string `json:"nickname"` Declaration string `json:"declaration"` Avatar string `json:"avatar"` Score int `json:"score"` } func (Pla