这个问题已经困扰了我将近一年了,去年刚开始学习golang的时候,就想过能不能通过golang的静态资源服务器来部署vue打包后的资源呢?这样我就可以一个端口部署前后端分离的应用了,而且还能完美解决跨域问题。但试了好几次,发现只是单页面的应用,这里指的是不带路由的vue应用,是可以直接显示出来的,跟普通的html文件是一样的,但使用了路由之后,打开index.html之后就会显示空白页面。
下面先演示一下:
我们只需要关注dist目录和public目录,dist目录是vue打包后的产物,它的子目录static下存放的是一系列的js和css文件,而public目录就是我们后端的一个静态资源目录,其实两个都是静态资源目录,只不过这里我把两个目录分开,后面起两个静态资源服务器,更好区分。
下面来分析代码,下面这段代码很简单,创建了两个静态资源服务器:
一个是路径前缀为“/assets/”的资源服务器,用于获取public目录下的文件(例如图片,文档等等);另一个是直接为根路径的静态资源服务器,便是这次的重点,用于部署vue打包后的资源的。
(这里需要注意的就是,我刻意用了"/assets/"这个路径而不用"/static/"是为了与dist目录下的static进行一个区分,不然容易发生冲突)
package main
import (
"net/http"
"project02/routers"
)
func main() {
http.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir("public"))))
http.Handle("/", routers.StaticFileServe())
http.ListenAndServe(":6166", nil)
}
接下来我们来看看StaticFileServe里面做了什么,下面这段代码做的事情也很简单:
前面的path.Clean()的功能就是把传进去的路径变得干净一点,例如:
Clean("a/c") = "a/c"
Clean("a//c") = "a/c"
Clean("a/c/.") = "a/c"
Clean("a/c/b/..") = "a/c"
Clean("/../a/c") = "/a/c"
接下来的两个判断都是判断文件是否存在,rootFS就是以docRoot(dist目录)为静态资源目录的文件服务器,下面的http处理方法中:
使用 strings.HasPrefix
函数检查请求路径是否以 /
开头,如果不是,则将其加上 /
。然后,我们使用 path.Clean
函数结合目录根路径 docRoot
和请求路径 rPath
,生成最终文件的完整路径 dstFileName
。
之后,我们使用 os.Stat
函数检查文件是否存在,如果不存在,则将请求路径修改为 /index.html
,并重新设置到 r.URL.Path
中。最后,我们调用 rootFS.ServeHTTP
方法,使用 http.ResponseWriter
将文件内容返回给客户端。
var docRoot = "dist"
func StaticFileServe() (fs http.Handler) {
docRoot = path.Clean(docRoot)
_, err := os.Stat(docRoot)
if err != nil {
fmt.Println(fmt.Sprintf("配置项docRoot:%s不存在或无权限,请修正", docRoot))
os.Exit(-1)
return
}
idxFile := docRoot + "/index.html"
_, err = os.Stat(idxFile)
if os.IsNotExist(err) {
fmt.Println(fmt.Sprintf("配置项docRoot:%s不存在,请修正", idxFile))
os.Exit(-1)
return
}
rootFS := http.FileServer(http.Dir(docRoot))
fs = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if w == nil || r == nil {
fmt.Println("w or r is nil", idxFile)
os.Exit(-1)
return
}
rPath := r.URL.Path
if !strings.HasPrefix(rPath, "/") {
rPath = "/" + rPath
r.URL.Path = rPath
}
dstFileName := path.Clean(docRoot + rPath)
_, err = os.Stat(dstFileName)
if os.IsNotExist(err) {
//重定向至首页
r.URL.Path = "/index.html"
}
rootFS.ServeHTTP(w, r)
})
return fs
}
此时基本的部署已经完成,很完美!!但当我打开首页的时候,能不能被显示出来呢?
很遗憾,还是空白页面,那么问题到底出在了哪里?
我打开了浏览器的控制台检查了一番,发现报错了,这个错误提示是因为浏览器在加载 Javascript 模块时,判断服务器返回的 MIME 类型不是 application/javascript
时,就会报出这个错误。
那么我是不是只要将响应头的Content-Type的类型改为正确的类型是不是就可以解决这个问题了呢?不哔哔,我们再修改一下代码:
只需要在刚才的StaticFileServe中添加一段代码:
//对js文件的Content-Type设置正确的类型
if strings.HasSuffix(dstFileName, ".js") {
w.Header().Set("Content-Type", "application/javascript")
}
我们再来看看添加了这段代码后,能不能正常显示页面
芜湖,完美解决啦,同时vue也能正常处理路由,可以看到我这里直接调整到了login页面上了,同时这时候的类型也修改为了正常的MIME类型了,证明之前确实是因为这个问题导致显示空白页面的。
在网上摸索了挺久的,也使用过gin,但gin的静态文件资源服务器好像有点问题,我同样修改了响应头的Content-Type但最后获取到的类型依然还是text/plain,具体原因不大清除,希望有知道的小伙伴可以评论区告知一下!!
本次使用版本:
Golang1.17
Vue3+Vite构建工具