網(wǎng)站首頁 編程語言 正文
正文
現(xiàn)在,前后端分離的概念深入人心,前端、后端應(yīng)用從代碼倉庫到發(fā)布到運(yùn)行,完全都是獨(dú)立的兩套系統(tǒng),互不影響,帶來了良好的獨(dú)立性。然而,我覺得在某些條件下,前后端不分離,也不失為一種很好的解決方案,在軟件開發(fā)中,沒有什么萬金油方案,都是要因地制宜。
一般一些中小系統(tǒng),尤其是管理后臺,就比較適合前后端不分離的開發(fā)方式,或者是前端同學(xué),意向?qū)W習(xí) go 語言,通過這種前后端不分離的方式快速開發(fā)和學(xué)習(xí);或者是后端同學(xué),獨(dú)立開發(fā)包含前端的項(xiàng)目。
較于前后端分離,用 Go 語言開發(fā)前后端不分離的項(xiàng)目有如下優(yōu)點(diǎn):
- 前后端代碼最后都打包到一個二進(jìn)制文件,無論是做容器,還是單獨(dú)運(yùn)行,都非常省事。
- 后端可以利用 go 的模板技術(shù),在不深入學(xué)習(xí)前端知識的情況下,也能做出效果尚可的設(shè)計(jì)。
- 前端用 vue、react、甚至直接寫 jQuery 都行,對技術(shù)無限制。
- 靜態(tài)語言高性能,由于 go 直接編譯成機(jī)器碼,相較于 PHP、Java的 JSP這些也可以做前后端不分離技術(shù)的語言,性能會稍微好點(diǎn)。
接下來,以一個實(shí)際項(xiàng)目為例,介紹前后端不分離項(xiàng)目的開發(fā)過程:
Sail 后端使用 Gin 框架,前端使用 LayUI(基于 jQuery),用到了模板技術(shù)。
1. 項(xiàng)目結(jié)構(gòu)
. . ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── deploy │ └── Dockerfile ├── go.mod ├── go.sum ├── internal │ └── service └── ui ├── static ├── template └── ui.go
前端代碼都存在于 ui 文件夾,后端代碼都存在于 internal 文件夾,其它文件是一些容器打包命令之類。
2. 前端項(xiàng)目
在 ui 文件夾中,static 存著一些 js 庫、css 樣式、圖片之類,template 目錄下是 html 模板。這就是 Layui 所需的全部了。我對比過 vue、react 和 layui,最后還是覺得 layui 更適合后端程序員開發(fā)前端網(wǎng)頁,比較返璞歸真,HTML+CSS+JQuery網(wǎng)上的資料也很多,可以專注于開發(fā)邏輯,不至于陷入學(xué)習(xí)知識的漫長過程中。
如果使用 vue、react 等當(dāng)然也可以,同樣是放在 ui 文件夾,無非是用一些打包工具比如 webpack、yarn 等打包成public
或者dist
,實(shí)際最后還是一些 js、html、css,然后把它們集成到 go 中。
3. 后端項(xiàng)目
后端項(xiàng)目與一個 gin 的標(biāo)準(zhǔn)示例框架沒什么不同,前端來訪問也是當(dāng)成一個正常的接口訪問,只是有一點(diǎn)不同的是,由于前后端不分離,前端訪問接口無需帶域名(或者 ip),畢竟部署也是部署在一起的,直接訪問 url 就行。
4. 結(jié)合在一起
關(guān)鍵是在前端項(xiàng)目根目錄中增加一個.go文件,內(nèi)部引用前端頁面:
package ui import ( "embed" ) //go:embed template var TemplateFs embed.FS //go:embed static var StaticFs embed.FS
利用 go1.16發(fā)布的 embed 技術(shù),我們在變量TemplateFS
、StaticFS
前面加上//go:embed
注釋,后面跟著一個相對路徑(./
可以省略,完整寫法是:./template
),我們把ui/template
、ui/static
下的所有文件都打包到TemplateFS
、StaticFS
,這個FS
就是FileSystem
的縮寫,底層是實(shí)現(xiàn)了一個內(nèi)存文件系統(tǒng)(只讀),從程序角度看,似乎跟直接讀文件沒什么不同。但實(shí)際上我們知道,這些文件已經(jīng)無需在進(jìn)程外準(zhǔn)備,而是直接打包到二進(jìn)制文件內(nèi)了。
在 go 程序編譯時,編譯器監(jiān)測到文件內(nèi)的 go:embed 注釋,則會讀取這些文件,把它們標(biāo)記好,在最后生成可執(zhí)行文件時,把文件內(nèi)容打到 embed.FS 中。所以這個技術(shù)不能支持太大的文件,不然內(nèi)存容量都可能不夠。
接下來,在后端 go 程序的代碼,運(yùn)行 gin 時基本上是這樣寫法:
engine := gin.New() engine.Run(":8080")
如何把對前端路由的監(jiān)聽插到 engine 中呢?實(shí)際上在 gin 的引擎蓋下,真正對系統(tǒng) TCP 連接發(fā)起監(jiān)聽的還是 go 本身的 http 包,gin 不過是做了層封裝。我們可以不運(yùn)行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 運(yùn)行,判斷應(yīng)該走前端路由還是后端路由 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,當(dāng)有請求來時,默認(rèn)會回調(diào)到它注冊的 handler 上,而 gin 框架的核心也就是提供一個 handler,所以我們設(shè)計(jì)了一個自己的 handler,判斷比如是 /static/xxxxx.css
這種請求,則走前端路由,否則走后端路由。
在上面代碼中,我們還缺少了一步關(guān)鍵操作,我們通過SetHTMLTemplate
把 html 模板文件傳遞給 gin 的staticEngine,卻沒有為這些 html 文件設(shè)置訪問入口,我們需要為每個 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,則回調(diào) Index 函數(shù) } func (h *IndexHandler) Index(c *gin.Context) { // 剛才 SetHTMLTemplate 已經(jīng)把所有的 html 文件傳給 gin 了,gin 已經(jīng)保存在 map 中,所以這里只需要指明 html 文件名即可。 c.HTML(http.StatusOK, "index.html", gin.H{}) }
在 Index 函數(shù)里,我們通過 gin 的 HTML 方法渲染了模板,并返回 html 頁面。那么,只要訪問/ui/index
即可獲取到這個 html 頁了。通過模板技術(shù),我們可以把任意數(shù)據(jù)傳遞給這個頁面。
這就是 Go 開發(fā)前后端不分離項(xiàng)目的全部流程,其中一些代碼細(xì)節(jié),可以去 Sail 這個項(xiàng)目查看,也可以基于這個項(xiàng)目,打造自己的前后端不分離項(xiàng)目。
如果想使用 vue、react 等前端框架技術(shù),也可以看看 parca,它使用了 HTTP 后端、GRPC 后端、React 前端、Yarn 打包等如今比較熱門的技術(shù),可以作為參考。
原文鏈接:https://juejin.cn/post/7165898640002580488
相關(guān)推薦
- 2022-11-05 解決使用pip安裝報(bào)錯:Microsoft?Visual?C++?14.0?is?required.
- 2023-06-04 C#中+=是什么意思及+=的用法_C#教程
- 2022-03-27 c++模擬實(shí)現(xiàn)string類詳情_C 語言
- 2022-10-02 jQuery?編程之jQuery?屬性選擇器_jquery
- 2023-12-08 maven中mybatis-generator插件執(zhí)行報(bào)錯:Cannot resolve class
- 2023-02-12 JetpackCompose?Navigation導(dǎo)航實(shí)現(xiàn)流程_Android
- 2022-03-28 一篇文章帶你學(xué)習(xí)python的函數(shù)與類_python
- 2022-06-30 Python使用Pillow添加水印_python
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支