Golang+Vue2从零开始搭建K8S后台管理系统(4)——自动更新资源列表(下)
至此,整一个websocket的前后端调用就完成了。可见的是,这种是比较初级的做法,未来将会看见更优雅的实现。
目录
梗概
上章实现了后端deployment map的实时更新,我们还需要把这部分的更新传送到前端。
本章重点讲述前后端通过websocket进行这部分的数据交互。
基本做法
基本思路是,当前端请求后端列表api时,同时发送另一个http请求到后端,根据websocket的原理,可以将该连接升级成websocket连接。我们将这个客户端存到一个本地缓存维护的map中,当资源发生变动,触发handler,我们同样将更新后的列表发送到前端,前端更新用于列表展示的那部分数据。
实操部分
后端实现
首先定义接口,http升级websocket,并将客户端保存在map里面
func Connect(c *gin.Context) string {
client,_ := websocket.Upgrader{}.Upgrade(c.Writer, c.Request, nil) //升级成websocket
wscore.ClientMap.Store(client)
return "true"
}
其中,ClientMap是定义好的一个全局对象,供所有需要使用websocket的handler使用
var ClientMap *ClientMapStruct
type ClientMapStruct struct {
data sync.Map // key 是客户端IP value 就是 WsClient连接对象
lock sync.Mutex
}
那么,针对前步中升级websocket获得的连接对象,我们需要有以下操作:
1.存入到map中
this.data.Store(conn.RemoteAddr().String(), wsClient)
2.开启协程定时发送消息检测存活,ping一下,出错就从map中删除这个连接,这是比较初级的做法。
func (this *WsClient) Ping(waittime time.Duration) {
for {
time.Sleep(waittime)
func() {
this.Locker.Lock()
defer this.Locker.Unlock()
err := this.conn.WriteMessage(websocket.TextMessage, []byte("ping"))
if err != nil {
ClientMap.Remove(this.conn)
return
}
}()
}
}
这里定义了一个结构体,并加入了sync.Mutex。因为websocket不支持并发写,所以加入互斥锁。
题外话,其实websocket也会存在客户端继续压入数据的情况。对此,需要开启一个协程,反复(死循环)从连接中读取信息,并推入这部分信息到channel中。再另外开启一个协程去处理 总控 循环。这样是比较合理的做法,这与我们要实现的功能无关,所以不做过多的演示。
然后,我们需要实现一个传参为interface{}的方法去接受一个对象,并把这部分的对象数据给发送到所有的客户端,客户端判断数据是否和我需要的类型相匹配,(如果匹配)做对应更新。这同样是比较初级的做法,有如下。
//向所有客户端 发送消息--发送资源列表
func (this *ClientMapStruct) SendAll(v interface{}) {
this.data.Range(func(key, value any) bool {
func() {
c := value.(*WsClient).conn
value.(*WsClient).Locker.Lock()
defer value.(*WsClient).Locker.Unlock()
err := c.WriteJSON(v)
if err != nil {
this.Remove(c)
log.Println(err)
}
}()
return true
})
}
然后在informer-handler中调用该接口,包括了新增、删除、更新,下面是一个示例。
func (this *DepHandler) OnAdd(obj interface{}) {
this.DepMap.Add(obj.(*v1.Deployment))
ns := obj.(*v1.Deployment).Namespace
//向所有的ws客户端发送消息
//然后前端取消息内容里面判断是否需要更新当前页面
wscore.ClientMap.SendAll(gin.H{
"type": "deployment",
"result": gin.H{
"ns": ns,
"data": this.DepService.ListAll(ns),//这里是你从map中获取到对应namespace下的所有deploy
},
})
}
至此,后端的websocket改造已实现。
接下来我们看前端。
前端websocket客户端
首先引入js中websocket客户端的实现:
let lock=false;
let wsClient;
function reConnect() {
if (lock) return;
lock = true;
console.log("正在重连")
setTimeout(function() {
NewClient();
lock = false;
}, 2000);
}
//0 - 表示连接尚未建立 1 - 表示连接已建立,可以进行通信。 2 - 表示连接正在进行关闭 3 - 表示连接已经关闭或者连接不能打开
const GetClient=function () {
if(wsClient!=null && wsClient.readyState===1){
return wsClient
}
NewClient()
return wsClient
}
const NewClient=function () {
wsClient = new WebSocket("ws://localhost:8080/ws");
wsClient.onopen = function(){
console.log("open");
}
wsClient.onclose = function(e){
console.log("close");
reConnect()
}
wsClient.onerror = function(e){
console.log(e);
reConnect()
}
return wsClient
}
export {NewClient}
其中,指定了后端http的接口,后端在接受这个请求后会将连接升级成websocket。
然后在列表组件中引入
import { NewClient } from '@/utils/ws'
接着在create函数中调用websocket客户端:
export default {
data() {
return {
list: null,
listLoading: true,
wsClient: null,
}
},
created() {
this.fetchData()
},
methods: {
fetchData() {
this.listLoading = true
this.wsClient = NewClient()
this.wsClient.onmessage=(e)=>{
if (e.data !== 'ping') {
const obj = JSON.parse(e.data)
if (obj.type === 'deployment') {
this.list = obj.result.data
//列表更新无法重新渲染页面,需要手动更新
this.$forceUpdate()
}
}
}
},
}
}
</script>
其中,消息内容为ping的用于探活的测试数据会被drop掉,判断类型为‘deployment’的数据则会被更新到data中。要注意的是,列表数据的更新必须调用forceUpdate来进行重新渲染。
总结
至此,整一个websocket的前后端调用就完成了。可见的是,这种是比较初级的做法,未来将会看见更优雅的实现。
更多推荐
所有评论(0)