亲宝软件园·资讯

展开

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

FengY_HYY 人气:0

正文

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

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

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

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

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 技术,我们在变量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 的引擎盖下,真正对系统 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") { // 

加载全部内容

相关教程
猜你喜欢
用户评论