您的当前位置:首页正文

Go语言开发前后端不分离项目详解

2024-11-08 来源:个人技术集锦

正文

现在,前后端分离的概念深入人心,前端、后端应用从代码仓库到发布到运行,完全都是独立的两套系统,互不影响,带来了良好的独立性。然而,我觉得在某些条件下,前后端不分离,也不失为一种很好的解决方案,在软件开发中,没有什么万金油方案,都是要因地制宜。

一般一些中小系统,尤其是管理后台,就比较适合前后端不分离的开发方式,或者是前端同学,意向学习 go 语言,通过这种前后端不分离的方式快速开发和学习;或者是后端同学,独立开发包含前端的项目。

较于前后端分离,用 Go 语言开发前后端不分离的项目有如下优点:

  • 前后端代码最后都打包到一个二进制文件,无论是做容器,还是单独运行,都非常省事。
  • 后端可以利用 go 的模板技术,在不深入学习前端知识的情况下,也能做出效果尚可的设计。
  • 前端用 vue、react、甚至直接写 jQuery 都行,对技术无限制。
  • 静态语言高性能,由于 go 直接编译成机器码,相较于 PHP、Java的 JSP这些也可以做前后端不分离技术的语言,性能会稍微好点。

接下来,以一个实际项目为例,介绍前后端不分离项目的开发过程:

后端使用 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. 前端项目

如果使用 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 技术,我们在变量TemplateFSStaticFS前面加上//go:embed注释,后面跟着一个相对路径(./可以省略,完整写法是:./template),我们把ui/templateui/static下的所有文件都打包到TemplateFSStaticFS,这个FS就是FileSystem的缩写,底层是实现了一个内存文件系统(只读),从程序角度看,似乎跟直接读文件没什么不同。但实际上我们知道,这些文件已经无需在进程外准备,而是直接打包到二进制文件内了。

在 go 程序编译时,编译器监测到文件内的 go:embed 注释,则会读取这些文件,把它们标记好,在最后生成可执行文件时,把文件内容打到 embed.FS 中。所以这个技术不能支持太大的文件,不然内存容量都可能不够。

接下来,在后端 go 程序的代码,运行 gin 时基本上是这样写法:

engine := gin.New()
engine.Run(":8080")
// 后端路由
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)
}

在上面代码中,我们还缺少了一步关键操作,我们通过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 开发前后端不分离项目的全部流程,其中一些代码细节,可以去 这个项目查看,也可以基于这个项目,打造自己的前后端不分离项目。

如果想使用 vue、react 等前端框架技术,也可以看看 parca,它使用了 HTTP 后端、GRPC 后端、React 前端、Yarn 打包等如今比较热门的技术,可以作为参考。

Top