具体流程
需求: 每次前端包更新后,无需用户刷新页面后生效, 实现热更新。
这边前端用
vue
,后端用gin
,作为例子。
1、客户端 - vue
- 开启websocket, 建立
message
监听
WebSocket.vue
js
<template>
<div>
<h1>WebSocket Component version-9</h1>
</div>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'
import { debounce } from 'lodash'
let socket = null
const connectWebSocket = () => {
// Connect to WebSocket -> 这里跟后端的ws地址相对应
socket = new WebSocket('ws://localhost:8888/hero-ranking/ws')
// Listen for WebSocket connection success event
socket.addEventListener('open', (event) => {
console.log('WebSocket connected:', event)
})
// Listen for WebSocket received message event
socket.addEventListener(
'message',
debounce((event) => {
const message = event.data
console.log('Received message-----:', message)
location.reload()
}, 2000),
)
// Listen for WebSocket close event
socket.addEventListener('close', (event) => {
console.log('WebSocket closed:', event)
})
// Listen for WebSocket error event
socket.addEventListener('error', (event) => {
console.error('WebSocket error:', event)
})
}
const closeWebSocket = () => {
// Close WebSocket connection before component is unmounted
if (socket) {
socket.close()
}
}
onMounted(() => {
connectWebSocket()
})
onBeforeUnmount(() => {
closeWebSocket()
})
</script>
2、客户端 - gin
2-1 创建Websocket
包
- 创建包
Websocket.go
, 封装下webpack
go
package Websocket
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"net/http"
)
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
}
// ?连接对象全局定义
var Conn *websocket.Conn
func Connect(c *gin.Context) {
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
fmt.Println(err)
return
}
// defer conn.Close()
Conn = conn
}
// 发送消息函数
func SendClientMsg(msg string) {
Conn.WriteMessage(websocket.TextMessage, []byte(msg))
}
2-2 创建监听文件库FSNotify
- 创建包
FSNotify.go
, 利用fsnotify
监听前端打包文件dist, 文件变化后通过websoket
发送消息。 - 这段代码参考了 这里
go
package FSNotify
import (
"fmt"
"fuck-go/src/main/Websocket"
"github.com/fsnotify/fsnotify"
"os"
"path/filepath"
)
type NotifyFile struct {
watch *fsnotify.Watcher
}
func NewNotifyFile() *NotifyFile {
w := new(NotifyFile)
w.watch, _ = fsnotify.NewWatcher()
return w
}
// 监控目录
func (this *NotifyFile) WatchDir(dir string) {
//通过Walk来遍历目录下的所有子目录
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
//判断是否为目录,监控目录,目录下文件也在监控范围内,不需要加
if info.IsDir() {
path, err := filepath.Abs(path)
if err != nil {
return err
}
err = this.watch.Add(path)
if err != nil {
return err
}
fmt.Println("监控 : ", path)
}
return nil
})
go this.WatchEvent() //协程
}
func (this *NotifyFile) WatchEvent() {
for {
select {
case ev := <-this.watch.Events:
fmt.Println("file update")
// 监听到文件变化,通过Websocket发送消息
Websocket.SendClientMsg("file update")
// 如果是目录的创建操作,则添加监控
if ev.Op&fsnotify.Create == fsnotify.Create {
file, err := os.Stat(ev.Name)
if err == nil && file.IsDir() {
this.watch.Add(ev.Name)
fmt.Println("添加监控 : ", ev.Name)
}
}
case err := <-this.watch.Errors:
fmt.Println("error:", err)
}
}
return
for {
select {
case ev := <-this.watch.Events:
{
/*fmt.Println("文件进行变化了~")
os.Stat(ev.Name)
return*/
if ev.Op&fsnotify.Create == fsnotify.Create {
fmt.Println("创建文件 : ", ev.Name)
//获取新创建文件的信息,如果是目录,则加入监控中
file, err := os.Stat(ev.Name)
if err == nil && file.IsDir() {
this.watch.Add(ev.Name)
fmt.Println("添加监控 : ", ev.Name)
}
}
if ev.Op&fsnotify.Write == fsnotify.Write {
//fmt.Println("写入文件 : ", ev.Name)
}
if ev.Op&fsnotify.Remove == fsnotify.Remove {
fmt.Println("删除文件 : ", ev.Name)
//如果删除文件是目录,则移除监控
fi, err := os.Stat(ev.Name)
if err == nil && fi.IsDir() {
this.watch.Remove(ev.Name)
fmt.Println("删除监控 : ", ev.Name)
}
}
if ev.Op&fsnotify.Rename == fsnotify.Rename {
//如果重命名文件是目录,则移除监控 ,注意这里无法使用os.Stat来判断是否是目录了
//因为重命名后,go已经无法找到原文件来获取信息了,所以简单粗爆直接remove
fmt.Println("重命名文件 : ", ev.Name)
this.watch.Remove(ev.Name)
}
if ev.Op&fsnotify.Chmod == fsnotify.Chmod {
fmt.Println("修改权限 : ", ev.Name)
}
fmt.Println("文件进行变化了~")
}
case err := <-this.watch.Errors:
{
fmt.Println("error : ", err)
return
}
}
}
}
2-3 创建路由包Routers
Routers.go
go
package Routers
import (
"fmt"
"fuck-go/src/main/FSNotify"
"fuck-go/src/main/Websocket"
"github.com/gin-gonic/gin"
"net/http"
"strings"
)
func CreateRouter() {
// 1.创建路由
var r = gin.Default()
// 设置路由组(统一前缀地址)
apiGroup := r.Group("/hero-ranking")
// 加载路由
loadRoute(apiGroup)
// 触发监听文件
go watchFile()
r.Run(":8888")
}
func loadRoute(apiGroup *gin.RouterGroup) {
// ?websocket
apiGroup.GET("/ws", func(c *gin.Context) {
Websocket.Connect(c)
})
}
func watchFile() {
watch := FSNotify.NewNotifyFile()
// 监听你服务端前端包地址
watch.WatchDir("/Users/tog/Documents/work-space/personal/hero-ranking/dist")
select {}
}
2-4 执行 main.go
- 然后启动go服务就好了
go
package main
import (
"fuck-go/src/main/Routers"
)
func main() {
Routers.CreateRouter()
}
3、看看实际效果
我们这里本地测启动下生产包
看下效果
题外话
websocket
是双向的,按照热更新的逻辑,其实只需要服务端
推送到客户端
.- 经过同事科普,单向的话 Server-sent events 应该也可以实现需求。