Go语言开发前后端不分离项目详解
作者:FengY_HYY
正文
现在,前后端分离的概念深入人心,前端、后端应用从代码仓库到发布到运行,完全都是独立的两套系统,互不影响,带来了良好的独立性。然而,我觉得在某些条件下,前后端不分离,也不失为一种很好的解决方案,在软件开发中,没有什么万金油方案,都是要因地制宜。
一般一些中小系统,尤其是管理后台,就比较适合前后端不分离的开发方式,或者是前端同学,意向学习 go 语言,通过这种前后端不分离的方式快速开发和学习;或者是后端同学,独立开发包含前端的项目。
较于前后端分离,用 Go 语言开发前后端不分离的项目有如下优点:
- 前后端代码最后都打包到一个二进制文件,无论是做容器,还是单独运行,都非常省事。
- 后端可以利用 go 的模板技术,在不深入学习前端知识的情况下,也能做出效果尚可的设计。
- 前端用 vue、react、甚至直接写 jQuery 都行,对技术无限制。
- 静态语言高性能,由于 go 直接编译成机器码,相较于 PHP、Java的 JSP这些也可以做前后端不分离技术的语言,性能会稍微好点。
接下来,以一个实际项目为例,介绍前后端不分离项目的开发过程:
Sail 后端使用 Gin 框架,前端使用 LayUI(基于 jQuery),用到了模板技术。
1. 项目结构
. . ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── deploy │ └── Dockerfile ├── go.mod ├── go.sum ├── internal │ └── service └── ui ├── static ├── template └── ui.go
前端代码都存在于 ui 文件夹,后端代码都存在于 internal 文件夹,其它文件是一些容器打包命令之类。
2. 前端项目
在 ui 文件夹中,static 存着一些 js 库、css 样式、图片之类,template 目录下是 html 模板。这就是 Layui 所需的全部了。我对比过 vue、react 和 layui,最后还是觉得 layui 更适合后端程序员开发前端网页,比较返璞归真,HTML+CSS+JQuery网上的资料也很多,可以专注于开发逻辑,不至于陷入学习知识的漫长过程中。
如果使用 vue、react 等当然也可以,同样是放在 ui 文件夹,无非是用一些打包工具比如 webpack、yarn 等打包成public
或者dist
,实际最后还是一些 js、html、css,然后把它们集成到 go 中。
3. 后端项目
后端项目与一个 gin 的标准示例框架没什么不同,前端来访问也是当成一个正常的接口访问,只是有一点不同的是,由于前后端不分离,前端访问接口无需带域名(或者 ip),毕竟部署也是部署在一起的,直接访问 url 就行。
4. 结合在一起
关键是在前端项目根目录中增加一个.go文件,内部引用前端页面:
package ui import ( "embed" ) //go:embed template var TemplateFs embed.FS //go:embed static var StaticFs embed.FS
利用 go1.16发布的 embed 技术,我们在变量TemplateFS
、StaticFS
前面加上//go:embed
注释,后面跟着一个相对路径(./
可以省略,完整写法是:./template
),我们把ui/template
、ui/static
下的所有文件都打包到TemplateFS
、StaticFS
,这个FS
就是FileSystem
的缩写,底层是实现了一个内存文件系统(只读),从程序角度看,似乎跟直接读文件没什么不同。但实际上我们知道,这些文件已经无需在进程外准备,而是直接打包到二进制文件内了。
在 go 程序编译时,编译器监测到文件内的 go:embed 注释,则会读取这些文件,把它们标记好,在最后生成可执行文件时,把文件内容打到 embed.FS 中。所以这个技术不能支持太大的文件,不然内存容量都可能不够。
接下来,在后端 go 程序的代码,运行 gin 时基本上是这样写法:
engine := gin.New() engine.Run(":8080")
如何把对前端路由的监听插到 engine 中呢?实际上在 gin 的引擎盖下,真正对系统 TCP 连接发起监听的还是 go 本身的 http 包,gin 不过是做了层封装。我们可以不运行Run
,而是把 gin 作为一个HTTPHandler
,代码如下:
// 后端路由 engine := gin.New() // 前端路由 staticEngine := gin.New() templateHTML, err := template.ParseFS(ui.TemplateFs, "template/**/**/*.html") if err != nil { panic(err) } staticEngine.SetHTMLTemplate(templateHTML) fads, err := fs.Sub(ui.StaticFs, "static") if err != nil { panic(err) } staticEngine.StaticFS("/static", http.FS(fads)) // Route s.Route(c, engine) s.RouteHTML(c, staticEngine) // 通过一个 Server 运行,判断应该走前端路由还是后端路由 server := &http.Server{ Handler: http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { // 如果 URL 以 /static 或 /ui 开头,则走前端路由 if strings.HasPrefix(request.URL.Path, "/static") || strings.HasPrefix(request.URL.Path, "/ui") { // 🤔:/ui 是怎么来的? staticEngine.ServeHTTP(writer, request) return } // 否则,走后端路由 engine.ServeHTTP(writer, request) }), } if err = server.ListenAndServe(":8080");err != nil{ panic(err) }
我们知道,go 提供的 http server,当有请求来时,默认会回调到它注册的 handler 上,而 gin 框架的核心也就是提供一个 handler,所以我们设计了一个自己的 handler,判断比如是 /static/xxxxx.css
这种请求,则走前端路由,否则走后端路由。
在上面代码中,我们还缺少了一步关键操作,我们通过SetHTMLTemplate
把 html 模板文件传递给 gin 的staticEngine,却没有为这些 html 文件设置访问入口,我们需要为每个 html 配置访问入口:
// 以 index.html 为例: func (s *Server) RouteHTML(c *Handlers, staticEngine *gin.Engine) { templateGroup := staticEngine.Group("/ui") // /ui 是这里声明的 templateGroup.GET("/index", c.indexHandler.Index) // 这是说,如果访问/ui/index,则回调 Index 函数 } func (h *IndexHandler) Index(c *gin.Context) { // 刚才 SetHTMLTemplate 已经把所有的 html 文件传给 gin 了,gin 已经保存在 map 中,所以这里只需要指明 html 文件名即可。 c.HTML(http.StatusOK, "index.html", gin.H{}) }
在 Index 函数里,我们通过 gin 的 HTML 方法渲染了模板,并返回 html 页面。那么,只要访问/ui/index
即可获取到这个 html 页了。通过模板技术,我们可以把任意数据传递给这个页面。
这就是 Go 开发前后端不分离项目的全部流程,其中一些代码细节,可以去 Sail 这个项目查看,也可以基于这个项目,打造自己的前后端不分离项目。
如果想使用 vue、react 等前端框架技术,也可以看看 parca,它使用了 HTTP 后端、GRPC 后端、React 前端、Yarn 打包等如今比较热门的技术,可以作为参考。
以上就是Go语言开发前后端不分离项目详解的详细内容,更多关于Go前后端不分离项目的资料请关注脚本之家其它相关文章!