You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

439 lines
10 KiB

  1. # Gin框架的基本使用
  2. ## 下载与安装
  3. > 更改环境变量 GOPATH
  4. >
  5. > 方便将包下载到其他的盘符目录
  6. >
  7. > 参考:[2024年go安装教程(Windows)包括配置GOPATH-CSDN博客](https://blog.csdn.net/weixin_63860405/article/details/139732041)
  8. ### 下载
  9. ```shell
  10. go get -u github.com/gin-gonic/gin
  11. ```
  12. ![image-20250617112721609](assets/Gin框架/image-20250617112721609-17501308421881.png)
  13. ## 基础使用
  14. ### GET请求
  15. ```go
  16. package main
  17. import "github.com/gin-gonic/gin"
  18. func main() {
  19. router := gin.Default()
  20. router.GET("/ping", func(c *gin.Context) {
  21. c.JSON(200, gin.H{
  22. "message": "Hello World!",
  23. })
  24. })
  25. router.Run() // 监听并在 0.0.0.0:8080 上启动服务
  26. // 默认是在8080端口启动服务,如果8080端口被占用,则自动寻找可用端口启动服务,可以指定端口
  27. }
  28. ```
  29. ![image-20250617112936947](assets/Gin框架/image-20250617112936947.png)
  30. ### ANY请求
  31. 根据请求来选择(使用if else)
  32. ```go
  33. // Any 请求
  34. router.Any("/any", func(c *gin.Context) {
  35. if c.Request.Method == "POST" {
  36. c.JSON(200, gin.H{
  37. "message": "POST",
  38. })
  39. } else if c.Request.Method == "GET" {
  40. c.JSON(200, gin.H{
  41. "message": "GET",
  42. })
  43. } else {
  44. c.JSON(200, gin.H{
  45. "message": "Any",
  46. })
  47. }
  48. })
  49. ```
  50. ### POST请求
  51. ```go
  52. // POST 请求
  53. router.POST("/post", func(c *gin.Context) {
  54. c.JSON(200, gin.H{
  55. "message": "POST",
  56. })
  57. })
  58. ```
  59. ![image-20250617114457929](assets/Gin框架/image-20250617114457929.png)
  60. ## 参数获取
  61. ### 获取query string 参数
  62. 格式:URL `?`后携带参数
  63. 例如:/user/searcher?username=xxx&address=xxx
  64. ```go
  65. // 获取参数,query string
  66. router.GET("/user/search", func(c *gin.Context) {
  67. username := c.DefaultQuery("username", "lihuibear")
  68. address := c.Query("address")
  69. // 返回json数据
  70. c.JSON(200, gin.H{
  71. "username": username,
  72. "address": address,
  73. })
  74. })
  75. ```
  76. ![image-20250617115733445](assets/Gin框架/image-20250617115733445.png)
  77. ### 获取 form参数
  78. 当前端请求的数据通过form表单提交时,例如向`/user/search`发送一个POST请求,获取请求数据的方式如下:
  79. ```go
  80. router.POST("/user/search_by_form", func(c *gin.Context) {
  81. // DefaultPostForm取不到值时会返回指定的默认值
  82. //username := c.DefaultPostForm("username", "小王子")
  83. username := c.PostForm("username")
  84. address := c.PostForm("address")
  85. //输出json结果给调用方
  86. c.JSON(http.StatusOK, gin.H{
  87. "message": "ok",
  88. "username": username,
  89. "address": address,
  90. })
  91. })
  92. ```
  93. ### 获取JSON参数
  94. 当前端请求的数据通过JSON提交时,例如向`/json`发送一个JSON格式的POST请求,则获取请求参数的方式如下:
  95. ```go
  96. //获取JSON参数
  97. router.POST("/json", func(c *gin.Context) {
  98. // 注意:下面为了举例子方便,暂时忽略了错误处理
  99. b, _ := c.GetRawData() // 从c.Request.Body读取请求数据
  100. // 定义map或结构体
  101. var m map[string]interface{}
  102. // 反序列化
  103. _ = json.Unmarshal(b, &m)
  104. c.JSON(http.StatusOK, m)
  105. })
  106. ```
  107. ### 获取path参数
  108. 请求的参数通过URL路径传递,例如:`/user/search/小王子/沙河`。 获取请求URL路径中的参数的方式如下:
  109. ```go
  110. // 获取path参数
  111. router.GET("/user/search/:username/:address", func(c *gin.Context) {
  112. username := c.Param("username")
  113. address := c.Param("address")
  114. //输出json结果给调用方
  115. c.JSON(http.StatusOK, gin.H{
  116. "message": "ok",
  117. "username": username,
  118. "address": address,
  119. })
  120. })
  121. ```
  122. ### 参数绑定 ShouldBind()
  123. 为了能够更方便的获取请求相关参数,提高开发效率,我们可以基于请求的`Content-Type`识别请求数据类型并利用反射机制自动提取请求中`QueryString`、`form表单`、`JSON`、`XML`等参数到结构体中。
  124. ```go
  125. // 参数绑定
  126. // 绑定JSON的示例 ({"user": "q1mi", "password": "123456"})
  127. router.POST("/login_by_JSON", func(c *gin.Context) {
  128. var login Login
  129. if err := c.ShouldBind(&login); err == nil {
  130. fmt.Printf("login info:%#v\n", login)
  131. c.JSON(http.StatusOK, gin.H{
  132. "user": login.User,
  133. "password": login.Password,
  134. })
  135. } else {
  136. c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
  137. }
  138. })
  139. // 绑定form表单示例 (user=q1mi&password=123456)
  140. router.POST("/login_by_form", func(c *gin.Context) {
  141. var login Login
  142. // ShouldBind()会根据请求的Content-Type自行选择绑定器
  143. if err := c.ShouldBind(&login); err == nil {
  144. c.JSON(http.StatusOK, gin.H{
  145. "user": login.User,
  146. "password": login.Password,
  147. })
  148. } else {
  149. c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
  150. }
  151. })
  152. // 绑定QueryString示例 (/loginQuery?user=q1mi&password=123456)
  153. router.GET("/login_by_query", func(c *gin.Context) {
  154. var login Login
  155. // ShouldBind()会根据请求的Content-Type自行选择绑定器
  156. if err := c.ShouldBind(&login); err == nil {
  157. c.JSON(http.StatusOK, gin.H{
  158. "user": login.User,
  159. "password": login.Password,
  160. })
  161. } else {
  162. c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
  163. }
  164. })
  165. ```
  166. `ShouldBind`会按照下面的顺序解析请求中的数据完成绑定:
  167. 1. 如果是 `GET` 请求,只使用 `Form` 绑定引擎(`query`)。
  168. 2. 如果是 `POST` 请求,首先检查 `content-type` 是否为 `JSON``XML`,然后再使用 `Form`(`form-data`)。
  169. ## Gin渲染
  170. 我们首先定义一个存放模板文件的`templates`文件夹,然后在其内部按照业务分别定义一个`posts`文件夹和一个`users`文件夹。 `posts/index.html`文件的内容如下:
  171. ```template
  172. <!DOCTYPE html>
  173. <html lang="en">
  174. <head>
  175. <meta charset="UTF-8">
  176. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  177. <meta http-equiv="X-UA-Compatible" content="ie=edge">
  178. <title>posts/index</title>
  179. </head>
  180. <body>
  181. {{.title}}
  182. </body>
  183. </html>
  184. {{end}}
  185. ```
  186. `users/index.html`文件的内容如下:
  187. ```template
  188. {{define "users/index.html"}}
  189. <!DOCTYPE html>
  190. <html lang="en">
  191. <head>
  192. <meta charset="UTF-8">
  193. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  194. <meta http-equiv="X-UA-Compatible" content="ie=edge">
  195. <title>users/index</title>
  196. </head>
  197. <body>
  198. {{.title}}
  199. </body>
  200. </html>
  201. {{end}}
  202. ```
  203. Gin框架中使用`LoadHTMLGlob()`或者`LoadHTMLFiles()`方法进行HTML模板渲染。
  204. ```go
  205. func main() {
  206. r := gin.Default()
  207. r.LoadHTMLGlob("templates/**/*")
  208. //r.LoadHTMLFiles("templates/posts/index.html", "templates/users/index.html")
  209. r.GET("/posts/index", func(c *gin.Context) {
  210. c.HTML(http.StatusOK, "posts/index.html", gin.H{
  211. "title": "posts/index",
  212. })
  213. })
  214. r.GET("users/index", func(c *gin.Context) {
  215. c.HTML(http.StatusOK, "users/index.html", gin.H{
  216. "title": "users/index",
  217. })
  218. })
  219. r.Run(":8080")
  220. }
  221. ```
  222. ## 文件上传
  223. ### 单文件上传
  224. ```go
  225. // 加载模板
  226. router.LoadHTMLFiles("./templates/upload.html")
  227. // 上传文件
  228. router.GET("/upload", func(c *gin.Context) {
  229. c.HTML(http.StatusOK, "upload.html", nil)
  230. })
  231. router.POST("/upload", func(c *gin.Context) {
  232. // 接收用户上传文件的post请求
  233. f, err := c.FormFile("avatar")
  234. if err != nil {
  235. c.JSON(http.StatusInternalServerError, gin.H{
  236. "message": err.Error(),
  237. })
  238. return
  239. }
  240. // 保存到指定目录 dst
  241. dst := "./upload/" + f.Filename
  242. c.SaveUploadedFile(f, dst)
  243. // 返回响应
  244. c.JSON(http.StatusOK, gin.H{
  245. "message": fmt.Sprintf("'%s' uploaded!", f.Filename),
  246. })
  247. })
  248. ```
  249. ### 多文件上传
  250. ```go
  251. // 多文件上传
  252. // 加载模板
  253. router.LoadHTMLFiles("./templates/uploads.html")
  254. // 显示多文件上传页面
  255. router.GET("/multi-upload", func(c *gin.Context) {
  256. c.HTML(http.StatusOK, "uploads.html", nil)
  257. })
  258. // 多文件上传
  259. router.POST("/multi-upload", func(c *gin.Context) {
  260. // Multipart form
  261. form, err := c.MultipartForm()
  262. if err != nil {
  263. c.JSON(http.StatusBadRequest, gin.H{"error": "failed to parse multipart form"})
  264. return
  265. }
  266. files := form.File["file"]
  267. for index, file := range files {
  268. log.Println(file.Filename)
  269. dst := fmt.Sprintf("./upload/%s_%d", file.Filename, index)
  270. if err := c.SaveUploadedFile(file, dst); err != nil {
  271. c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
  272. return
  273. }
  274. }
  275. c.JSON(http.StatusOK, gin.H{
  276. "message": fmt.Sprintf("%d files uploaded!", len(files)),
  277. })
  278. })
  279. ```
  280. ## 重定向
  281. ### http重定向
  282. ```go
  283. // http 重定向
  284. router.GET("/abc", func(c *gin.Context) {
  285. c.Redirect(http.StatusMovedPermanently, "http://www.baidu.com/")
  286. })
  287. ```
  288. ### 路由重定向
  289. ```go
  290. // 路由重定向
  291. router.GET("/def", func(c *gin.Context) {
  292. c.Request.URL.Path = "/ping"
  293. router.HandleContext(c)
  294. })
  295. ```
  296. ## Gin路由
  297. ### 普通路由
  298. ```go
  299. r.GET("/index", func(c *gin.Context) {...})
  300. r.GET("/login", func(c *gin.Context) {...})
  301. r.POST("/login", func(c *gin.Context) {...})
  302. ```
  303. ### any
  304. ```go
  305. r.Any("/test", func(c *gin.Context) {...})
  306. ```
  307. ### 404页面
  308. ```go
  309. r.NoRoute(func(c *gin.Context) {
  310. c.HTML(http.StatusNotFound, "views/404.html", nil)
  311. })
  312. ```
  313. ### 路由组
  314. 我们可以将拥有共同URL前缀的路由划分为一个路由组。习惯性一对`{}`包裹同组的路由,这只是为了看着清晰,你用不用`{}`包裹功能上没什么区别。
  315. ```go
  316. func main() {
  317. r := gin.Default()
  318. userGroup := r.Group("/user")
  319. {
  320. userGroup.GET("/index", func(c *gin.Context) {...})
  321. userGroup.GET("/login", func(c *gin.Context) {...})
  322. userGroup.POST("/login", func(c *gin.Context) {...})
  323. }
  324. shopGroup := r.Group("/shop")
  325. {
  326. shopGroup.GET("/index", func(c *gin.Context) {...})
  327. shopGroup.GET("/cart", func(c *gin.Context) {...})
  328. shopGroup.POST("/checkout", func(c *gin.Context) {...})
  329. }
  330. r.Run()
  331. }
  332. ```
  333. 路由组也是支持嵌套的,例如:
  334. ```go
  335. shopGroup := r.Group("/shop")
  336. {
  337. shopGroup.GET("/index", func(c *gin.Context) {...})
  338. shopGroup.GET("/cart", func(c *gin.Context) {...})
  339. shopGroup.POST("/checkout", func(c *gin.Context) {...})
  340. // 嵌套路由组
  341. xx := shopGroup.Group("xx")
  342. xx.GET("/oo", func(c *gin.Context) {...})
  343. }
  344. ```
  345. 通常我们将路由分组用在划分业务逻辑或划分API版本时。
  346. ### 路由原理
  347. Gin框架中的路由使用的是[httprouter](https://github.com/julienschmidt/httprouter)这个库。
  348. 其基本原理就是构造一个路由地址的前缀树。