如何在 Golang 程序中实现对 Vue.js 单页应用路由支持
前言
在构建现代 Web 应用时,Vue.js 等流行的前端框架为我们提供了构建单页应用(SPA)的能力。 这些 SPA 应用通常依赖于前端路由来实现页面的无缝切换。
前端路由主要有两种模式:Hash 模式和 History 模式。
Hash 模式的 URL 格式如 https://example.com/admin#/abc/abc
History 模式的 URL 格式则为 https://example.com/admin/abc/abc
。
尽管 Hash 模式简单易用,但 History 模式提供了更清晰的 URL 结构,更符合 SEO 优化和用户体验的需求。
然而,当 SPA 应用与 Golang 后端服务结合时,我们可能会遇到一个问题:SPA单页应用的前端路由如果使用的是 History模式 ,在浏览器端刷新页面后会报 404,这个的原因是因为SPA应用的路由只是注册在前端的项目的,当浏览器端刷新页面了,请求被发到了服务端来了,然后服务端没有这个页面,所以返回的是404页面。 那么常见的解决方法就是只能在前端的SPA应用中把路由模式改成 Hash 模式。
解决方法
这个问题如何解决呢?解决方法还是很多的,可以从服务器程序层解决,也可以从Nginx等反向代理程序端解决。
这里我们就是要在 Golang 端解决这个问题,在 Golang 端来解决这个前端路由的 url 重写的,把SPA的路由 url 全部重新回来入口的 index.html 文件即可.
这里我使用的是 go-gin 框架,既然前端是路由的问题,那么同理的我们要兼容也是在路由层解决。
假设我们的接口的前缀是 /api/v1/xxxx
,前端管理入口的uri前缀是:/admin
。
那么在 Go-Gin 框架 中有一个 NoRoute
方法,假设我们的后台入口地址是:https://example.com/admin
r := gin.New()
//后台的入口
r.GET("/admin", func(ctx *gin.Context) {
ctx.HTML(200, "admin", nil)
})
//兼容处理,把指定前缀的url做重定向后台回来到入口来
r.NoRoute(func(ctx *gin.Context) {
if strings.Contains(ctx.Request.URL.Path, "/admin") {
file, _ := os.ReadFile("template/admin/index.html")
etag := fmt.Sprintf("%x", md5.Sum(file))
ctx.Header("ETag", etag)
ctx.Header("Cache-Control", "no-cache")
// 如果客户端已经缓存了文件,则返回 304 不修改状态
if match := ctx.GetHeader("If-None-Match"); match != "" {
if strings.Contains(match, etag) {
ctx.Status(http.StatusNotModified)
return
}
}
ctx.Data(http.StatusOK, "text/html; charset=utf-8", file)
return
}
// 如果不是指定前缀的 URL,则返回 404 页面
ctx.HTML(200, "notfound", nil)
})
如果是其他的框架呢?该怎么处理?
这样也好处理,有类型 NoRoute
这种空路由方法的可以直接使用。如果没有的,那么中间件总有吧?
我们直接在中间件层来判断 uri 的前缀,如果是 /admin
前缀的,我们之间输出入口的代码
其他方式
当然,我们也不必完全拘泥于 Golang端,这个东西在 http 服务程序端解决也一样的 如服务端接口和前端分开部署: 部署2套代码,2个请求地址 服务端接口:https://api.example.com/api/v1 前端页面:https://admin.example.com/ 然后在前端的部署的项目中,配置一个 Url 重写规则,也能解决。
nginx 的配置:
location / {
try_files $uri $uri/ /index.html;
}
Apache的配置:
<IfModule mod_negotiation.c>
Options -MultiViews
</IfModule>
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
</IfModule>