7

gin 部署前后端分离项目

 3 years ago
source link: https://blog.cugxuan.cn/2020/05/07/Go/gin-serve-staticfs-in-root/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

gin 部署前后端分离项目

发表于

2020-05-07 更新于 2020-07-08 分类于 Go

阅读次数: 2213 Valine: 0

最近在写一个自己的小项目 GONEList,技术栈是 gin+vue。准备在部署的时候,如何使用 gin 来同时部署前后端?
把前端使用 / 进行挂载,后端监听其他的路由,这样会遇到问题,默认的路由规则不允许同时挂载到 staticfs 到根和监听其他路由
在 google 之后 issue 里面找到 gin 的一个库 gin-contrib/static 来解决,直接调用即可

前端是 Sillywa 使用 vue+iView 写的 gonelist-web,由于 Golang 的跨平台特性,在发布的时候想要尽量减少部署的难度,于是想用 gin 来同时路由前端生成的 dist 文件夹。

根据官方文档上的 router.StaticFS() 来进行的话会遇到报错

package main

import (
"github.com/gin-gonic/gin"
"net/http"
)

func main() {
router := gin.Default()
router.GET("/", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
router.StaticFS("/", http.Dir("my_file_system"))
router.Run(":8080")
}

build-fail

static-middleware

由于 gin 本身的路由会发生冲突,gin-contrib/static 使用中间件的方式判断是否存在该静态文件,使用起来非常简单,下面是例子

package main

import (
"github.com/gin-contrib/static"
"github.com/gin-gonic/gin"
)

func main() {
router := gin.Default()
router.Use(static.Serve("/", static.LocalFile("./dist/", false)))

router.GET("/", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})

router.Run(":8080")
}

fileserver 是一个 Handler,判断 fs 中是否有请求的文件,如果有则使用 fileserver 将文件写入到 gin 的 writer 里面返回,否则就不处理,中间件处理结束之后就会去匹配对应的 api。戳我跳转代码

// Static returns a middleware handler that serves static files in the given directory.
func Serve(urlPrefix string, fs ServeFileSystem) gin.HandlerFunc {
fileserver := http.FileServer(fs)
if urlPrefix != "" {
fileserver = http.StripPrefix(urlPrefix, fileserver)
}
return func(c *gin.Context) {
if fs.Exists(urlPrefix, c.Request.URL.Path) {
fileserver.ServeHTTP(c.Writer, c.Request)
c.Abort()
}
}
}

上面讲到的是对于挂载静态文件的解决方法,主要处理的问题是 gin 默认使用 httprouter 不支持路由的优先级
通过使用中间件来判断不同的 URL 应该走什么样的逻辑,如果你的某条冲突的路由是需要执行自己的某个 api,可以自己写一个中间件来解决问题

假设现在有一个 appV1 是一个 Group,路由为 /apps,在 AppV1 下有两个 GET 接口(注意 GET 和 POST 是不会冲突的

  • /statistics 这个是自己逻辑
  • /:appid/users 获取某个 appid 的 users

此时如果直接这么写就会产生冲突

appV1 := r.Group("/apps")
appV1.GET("/:appid/users", func(c *gin.Context){...})
appV1.GET("/statistics", func(c *gin.Context){...})
// panic: 'statistics' in new path '/apps/statistics' conflicts with existing wildcard ':appid' in existing prefix '/apps/:appid'
// 原因在报错中写的很清楚,如果有一个 appid 的值为 statistics 那么就会出现二义性

我们可以通过写一个中间件来解决,代码如下

package main

import "github.com/gin-gonic/gin"

// 自己写的逻辑函数
func Statistics(c *gin.Context) {
c.JSON(200, gin.H{
"url": c.Request.URL.Path,
})
}

func hand() gin.HandlerFunc {
return func(c *gin.Context) {
if c.Request.URL.Path == "/apps/statistics" {
Statistics(c) // 这里执行 app.Statistics(c)
c.Abort()
}
}
}
func main() {
r := gin.Default()

appV1 := r.Group("/apps")
appV1.Use(hand())
appV1.GET("/:appid/users", func(c *gin.Context) {
appid, _ := c.Get("appid")
c.JSON(200, gin.H{
"appid": appid,
"api": "user",
})
})
r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK