chore: 初始开源发布
iotlcm-app:小程序端 + 用户端服务。 - app/:uni-app 小程序前端(基于 unibest,MIT) - 微信 AppID → YOUR_WX_APPID,uni-app AppID → __UNI__YOURAPPID - 内网后端地址 → admin.iotlcm.example.com - 应用标题与 unibest 模板 README 重写(保留上游致谢与 MIT 声明) - server/:GoFrame 用户端服务(基于 XiuAdmin,Apache-2.0) - module xiuadmin → git.5iot.com/iotlcm-public/iotlcm-app(360 文件) - 支付回调地址 → app.iotlcm.example.com,商户名与应用名泛化 - 真实手机号 → 合法 11 位编造号(保留号段前 3 位 + 后 4 位) - 依赖号码尾部的断言同步更新 - 补 server/LICENSE(Apache-2.0) - 移除内部资产:CI 触发器 - 补根 README,LF 行尾 + .gitattributes 已知:TestSysDept_RefreshDeptAncestors 在源仓同一提交上同样失败,属既有缺陷。
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
// Package websocket
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@xiujie.cn>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"runtime/debug"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/utility/uuid32"
|
||||
|
||||
"github.com/gogf/gf/v2/container/garray"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
const (
|
||||
// 用户连接超时时间
|
||||
heartbeatExpirationTime = 6 * 60
|
||||
)
|
||||
|
||||
// 用户登录
|
||||
type login struct {
|
||||
UserId uint64
|
||||
Client *Client
|
||||
}
|
||||
|
||||
// GetKey 读取客户端数据
|
||||
func (l *login) GetKey() (key string) {
|
||||
key = GetUserKey(l.UserId)
|
||||
return
|
||||
}
|
||||
|
||||
// Client 客户端连接
|
||||
type Client struct {
|
||||
Addr string // 客户端地址
|
||||
ID string // 连接唯一标识
|
||||
Socket *websocket.Conn // 用户连接
|
||||
Send chan *WResponse // 待发送的数据
|
||||
SendClose bool // 发送是否关闭
|
||||
UserId uint64 // 用户ID,用户登录以后才有
|
||||
FirstTime uint64 // 首次连接事件
|
||||
HeartbeatTime uint64 // 用户上次心跳时间
|
||||
LoginTime uint64 // 登录时间 登录以后才有
|
||||
isApp bool // 是否是app
|
||||
tags *garray.StrArray // 标签
|
||||
Ctx context.Context // 上下文
|
||||
closeSignal chan struct{} // 关闭信号
|
||||
}
|
||||
|
||||
// NewClient 初始化
|
||||
func NewClient(addr string, socket *websocket.Conn, firstTime uint64, ctx context.Context) (client *Client) {
|
||||
client = &Client{
|
||||
Addr: addr,
|
||||
ID: uuid32.New(),
|
||||
Socket: socket,
|
||||
Send: make(chan *WResponse, 100),
|
||||
SendClose: false,
|
||||
FirstTime: firstTime,
|
||||
HeartbeatTime: firstTime,
|
||||
Ctx: ctx,
|
||||
closeSignal: make(chan struct{}, 1),
|
||||
tags: garray.NewStrArray(true),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 读取客户端数据
|
||||
func (c *Client) read() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Println("write stop", string(debug.Stack()), r)
|
||||
}
|
||||
}()
|
||||
|
||||
defer func() {
|
||||
c.close()
|
||||
}()
|
||||
|
||||
for {
|
||||
_, message, err := c.Socket.ReadMessage()
|
||||
if err != nil {
|
||||
g.Log().Info(c.Ctx, "websocket Client.read failed.", err)
|
||||
return
|
||||
}
|
||||
// 处理程序
|
||||
// fmt.Println(message)
|
||||
ProcessData(c, message)
|
||||
}
|
||||
}
|
||||
|
||||
// 向客户端写数据
|
||||
func (c *Client) write() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Println("write stop", string(debug.Stack()), r)
|
||||
}
|
||||
}()
|
||||
defer func() {
|
||||
clientManager.Unregister <- c
|
||||
_ = c.Socket.Close()
|
||||
}()
|
||||
for {
|
||||
select {
|
||||
case <-c.closeSignal:
|
||||
g.Log().Infof(mctx, "websocket client exit, user:%+v", c.UserId)
|
||||
return
|
||||
case message, ok := <-c.Send:
|
||||
if !ok {
|
||||
g.Log().Info(c.Ctx, "websocket Client.write failed.")
|
||||
// 发送数据错误 关闭连接
|
||||
return
|
||||
}
|
||||
_ = c.Socket.WriteJSON(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SendMsg 发送数据
|
||||
func (c *Client) SendMsg(msg *WResponse) {
|
||||
if c == nil || c.SendClose {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Println("SendMsg stop:", r, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
c.Send <- msg
|
||||
}
|
||||
|
||||
// Heartbeat 心跳更新
|
||||
func (c *Client) Heartbeat(currentTime uint64) {
|
||||
c.HeartbeatTime = currentTime
|
||||
}
|
||||
|
||||
// IsHeartbeatTimeout 心跳是否超时
|
||||
func (c *Client) IsHeartbeatTimeout(currentTime uint64) (timeout bool) {
|
||||
if c.HeartbeatTime+heartbeatExpirationTime <= currentTime {
|
||||
timeout = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 关闭客户端
|
||||
func (c *Client) close() {
|
||||
g.Log().Info(c.Ctx, "websocket Client.close")
|
||||
if c.SendClose {
|
||||
return
|
||||
}
|
||||
c.SendClose = true
|
||||
c.closeSignal <- struct{}{}
|
||||
}
|
||||
@@ -0,0 +1,384 @@
|
||||
// Package websocket
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@xiujie.cn>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"runtime/debug"
|
||||
"sync"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gcron"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
// ClientManager 客户端管理
|
||||
type ClientManager struct {
|
||||
Clients map[*Client]bool // 全部的连接
|
||||
ClientsLock sync.RWMutex // 读写锁
|
||||
Users map[string]*Client // 登录的用户 // uuid
|
||||
UserLock sync.RWMutex // 读写锁
|
||||
Register chan *Client // 连接连接处理
|
||||
Login chan *login // 用户登录处理
|
||||
Unregister chan *Client // 断开连接处理程序
|
||||
Broadcast chan *WResponse // 广播 向全部成员发送数据
|
||||
ClientBroadcast chan *ClientWResponse // 广播 向某个客户端发送数据
|
||||
TagBroadcast chan *TagWResponse // 广播 向某个标签成员发送数据
|
||||
UserBroadcast chan *UserWResponse // 广播 向某个用户的所有链接发送数据
|
||||
TagCallbackMap map[string]func(client *Client, tag string) // 注册标签回调
|
||||
closeSignal chan struct{} // 关闭信号
|
||||
TagCountMap map[string]int // 标签map
|
||||
TagLock sync.RWMutex // 标签读写锁
|
||||
}
|
||||
|
||||
func NewClientManager() (clientManager *ClientManager) {
|
||||
clientManager = &ClientManager{
|
||||
Clients: make(map[*Client]bool),
|
||||
Users: make(map[string]*Client),
|
||||
Register: make(chan *Client, 1000),
|
||||
Unregister: make(chan *Client, 1000),
|
||||
Broadcast: make(chan *WResponse, 1000),
|
||||
TagBroadcast: make(chan *TagWResponse, 1000),
|
||||
UserBroadcast: make(chan *UserWResponse, 1000),
|
||||
TagCallbackMap: make(map[string]func(client *Client, tag string)),
|
||||
closeSignal: make(chan struct{}, 1),
|
||||
TagCountMap: make(map[string]int),
|
||||
TagLock: sync.RWMutex{},
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetUserKey 获取用户key
|
||||
func GetUserKey(userId uint64) (key string) {
|
||||
key = fmt.Sprintf("%s_%d", "ws", userId)
|
||||
return
|
||||
}
|
||||
|
||||
// InClient 客户端是否存在
|
||||
func (manager *ClientManager) InClient(client *Client) (ok bool) {
|
||||
manager.ClientsLock.RLock()
|
||||
defer manager.ClientsLock.RUnlock()
|
||||
_, ok = manager.Clients[client]
|
||||
return
|
||||
}
|
||||
|
||||
// GetClients 获取所有客户端
|
||||
func (manager *ClientManager) GetClients() (clients map[*Client]bool) {
|
||||
clients = make(map[*Client]bool)
|
||||
manager.ClientsRange(func(client *Client, value bool) (result bool) {
|
||||
clients[client] = value
|
||||
return true
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// ClientsRange 遍历
|
||||
func (manager *ClientManager) ClientsRange(f func(client *Client, value bool) bool) {
|
||||
manager.ClientsLock.RLock()
|
||||
defer func() {
|
||||
manager.ClientsLock.RUnlock()
|
||||
}()
|
||||
for key, value := range manager.Clients {
|
||||
r := f(key, value)
|
||||
if !r {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetClientsLen 获取客户端总数
|
||||
func (manager *ClientManager) GetClientsLen() (clientsLen int) {
|
||||
clientsLen = len(manager.Clients)
|
||||
return
|
||||
}
|
||||
|
||||
// AddClients 添加客户端
|
||||
func (manager *ClientManager) AddClients(client *Client) {
|
||||
manager.ClientsLock.Lock()
|
||||
defer manager.ClientsLock.Unlock()
|
||||
manager.Clients[client] = true
|
||||
}
|
||||
|
||||
// DelClients 删除客户端
|
||||
func (manager *ClientManager) DelClients(client *Client) {
|
||||
manager.ClientsLock.Lock()
|
||||
defer manager.ClientsLock.Unlock()
|
||||
if _, ok := manager.Clients[client]; ok {
|
||||
// 删除标签
|
||||
client.tags.Iterator(func(k int, name string) bool {
|
||||
DelTagCount(name)
|
||||
return true
|
||||
})
|
||||
delete(manager.Clients, client)
|
||||
}
|
||||
}
|
||||
|
||||
// GetUserClient 获取用户的连接
|
||||
func (manager *ClientManager) GetUserClient(userId uint64) (client *Client) {
|
||||
manager.UserLock.RLock()
|
||||
defer manager.UserLock.RUnlock()
|
||||
userKey := GetUserKey(userId)
|
||||
if value, ok := manager.Users[userKey]; ok {
|
||||
client = value
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// AddUsers 添加用户
|
||||
func (manager *ClientManager) AddUsers(key string, client *Client) {
|
||||
manager.UserLock.Lock()
|
||||
defer manager.UserLock.Unlock()
|
||||
manager.Users[key] = client
|
||||
}
|
||||
|
||||
// DelUsers 删除用户
|
||||
func (manager *ClientManager) DelUsers(client *Client) (result bool) {
|
||||
manager.UserLock.Lock()
|
||||
defer manager.UserLock.Unlock()
|
||||
key := GetUserKey(client.UserId)
|
||||
if value, ok := manager.Users[key]; ok {
|
||||
// 判断是否为相同的用户
|
||||
if value.Addr != client.Addr {
|
||||
return
|
||||
}
|
||||
delete(manager.Users, key)
|
||||
result = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetUsersLen 已登录用户数
|
||||
func (manager *ClientManager) GetUsersLen() (userLen int) {
|
||||
userLen = len(manager.Users)
|
||||
return
|
||||
}
|
||||
|
||||
// EventRegister 用户建立连接事件
|
||||
func (manager *ClientManager) EventRegister(client *Client) {
|
||||
manager.AddClients(client)
|
||||
//发送当前客户端标识
|
||||
client.SendMsg(&WResponse{Event: "connected", Data: g.Map{
|
||||
"ID": client.ID,
|
||||
}})
|
||||
if client.UserId > 0 {
|
||||
manager.AddUsers(GetUserKey(client.UserId), client)
|
||||
}
|
||||
g.Log().Info(client.Ctx, "websocket EventRegister.", client.UserId)
|
||||
}
|
||||
|
||||
// EventLogin 用户登录事件
|
||||
func (manager *ClientManager) EventLogin(login *login) {
|
||||
client := login.Client
|
||||
if manager.InClient(client) {
|
||||
userKey := login.GetKey()
|
||||
manager.AddUsers(userKey, login.Client)
|
||||
}
|
||||
}
|
||||
|
||||
// EventUnregister 用户断开连接事件
|
||||
func (manager *ClientManager) EventUnregister(client *Client) {
|
||||
g.Log().Info(client.Ctx, "websocket EventUnregister.", client.UserId)
|
||||
manager.DelClients(client)
|
||||
// 删除用户连接
|
||||
deleteResult := manager.DelUsers(client)
|
||||
if !deleteResult {
|
||||
// 不是当前连接的客户端
|
||||
return
|
||||
}
|
||||
// 关闭 chan
|
||||
// close(client.Send)
|
||||
client.close()
|
||||
}
|
||||
|
||||
// ClearTimeoutConnections 定时清理超时连接
|
||||
func (manager *ClientManager) clearTimeoutConnections() {
|
||||
currentTime := uint64(gtime.Now().Unix())
|
||||
clients := clientManager.GetClients()
|
||||
for client := range clients {
|
||||
if client.IsHeartbeatTimeout(currentTime) {
|
||||
//fmt.Println("心跳时间超时 关闭连接", client.Addr, client.UserId, client.LoginTime, client.HeartbeatTime)
|
||||
_ = client.Socket.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WebsocketPing 心跳处理
|
||||
func (manager *ClientManager) ping(ctx context.Context) {
|
||||
//定时任务,发送心跳包
|
||||
_, _ = gcron.Add(ctx, "0 */1 * * * *", func(ctx context.Context) {
|
||||
res := &WResponse{
|
||||
Event: Ping,
|
||||
Data: g.Map{},
|
||||
}
|
||||
SendToAll(res)
|
||||
})
|
||||
// 定时任务,清理超时连接
|
||||
_, _ = gcron.Add(ctx, "*/30 * * * * *", func(ctx context.Context) {
|
||||
manager.clearTimeoutConnections()
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// 管道处理程序
|
||||
func (manager *ClientManager) start() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
g.Log().Warningf(mctx, "websocket start recover:%+v, stack:%+v", r, string(debug.Stack()))
|
||||
return
|
||||
}
|
||||
}()
|
||||
for {
|
||||
select {
|
||||
case conn := <-manager.Register:
|
||||
// 建立连接事件
|
||||
manager.EventRegister(conn)
|
||||
|
||||
case login := <-manager.Login:
|
||||
// 用户登录
|
||||
manager.EventLogin(login)
|
||||
|
||||
case conn := <-manager.Unregister:
|
||||
// 断开连接事件
|
||||
manager.EventUnregister(conn)
|
||||
|
||||
case message := <-manager.Broadcast:
|
||||
// 全部客户端广播事件
|
||||
clients := manager.GetClients()
|
||||
for conn := range clients {
|
||||
conn.SendMsg(message)
|
||||
}
|
||||
case message := <-manager.TagBroadcast:
|
||||
// 标签广播事件
|
||||
clients := manager.GetClients()
|
||||
// find := false
|
||||
for conn := range clients {
|
||||
if conn.tags.Contains(message.Tag) {
|
||||
conn.SendMsg(message.WResponse)
|
||||
// find = true
|
||||
}
|
||||
}
|
||||
// g.Log().Debugf(context.TODO(), "client_manager.go 标签广播事件 find:%v msg:%v", find, message)
|
||||
case message := <-manager.UserBroadcast:
|
||||
// 用户广播事件
|
||||
clients := manager.GetClients()
|
||||
for conn := range clients {
|
||||
if conn.UserId == message.UserID {
|
||||
conn.SendMsg(message.WResponse)
|
||||
}
|
||||
}
|
||||
case message := <-manager.ClientBroadcast:
|
||||
// 单个客户端广播事件
|
||||
clients := manager.GetClients()
|
||||
for conn := range clients {
|
||||
if conn.ID == message.ID {
|
||||
conn.SendMsg(message.WResponse)
|
||||
}
|
||||
}
|
||||
case <-manager.closeSignal:
|
||||
g.Log().Debug(mctx, "websocket closeSignal exit.")
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// SendToAll 发送全部客户端
|
||||
func SendToAll(response *WResponse) {
|
||||
clientManager.Broadcast <- response
|
||||
}
|
||||
|
||||
// SendToClientID 发送单个客户端
|
||||
func SendToClientID(id string, response *WResponse) {
|
||||
clientRes := &ClientWResponse{
|
||||
ID: id,
|
||||
WResponse: response,
|
||||
}
|
||||
clientManager.ClientBroadcast <- clientRes
|
||||
}
|
||||
|
||||
// SendToUser 发送单个用户
|
||||
func SendToUser(userID uint64, response *WResponse) {
|
||||
userRes := &UserWResponse{
|
||||
UserID: userID,
|
||||
WResponse: response,
|
||||
}
|
||||
clientManager.UserBroadcast <- userRes
|
||||
// g.Log().Debugf(context.Background(), "websocket SendToUser userID:%v response:%+v", userID, response)
|
||||
}
|
||||
|
||||
// SendToTag 发送某个标签
|
||||
func SendToTag(tag string, response *WResponse) {
|
||||
count := GetTagCount(tag)
|
||||
if count == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
tagRes := &TagWResponse{
|
||||
Tag: tag,
|
||||
WResponse: response,
|
||||
}
|
||||
clientManager.TagBroadcast <- tagRes
|
||||
}
|
||||
|
||||
// RegisterTagCallback 注册标签回调
|
||||
func RegisterTagCallback(tag string, callback func(client *Client, tag string)) {
|
||||
clientManager.TagLock.Lock()
|
||||
defer clientManager.TagLock.Unlock()
|
||||
clientManager.TagCallbackMap[tag] = callback
|
||||
}
|
||||
|
||||
// UnregisterTagCallback 注销标签回调
|
||||
func UnregisterTagCallback(tag string) {
|
||||
clientManager.TagLock.Lock()
|
||||
defer clientManager.TagLock.Unlock()
|
||||
delete(clientManager.TagCallbackMap, tag)
|
||||
}
|
||||
|
||||
// 添加标签订阅计数
|
||||
func AddTagCount(tag string) {
|
||||
clientManager.TagLock.Lock()
|
||||
defer clientManager.TagLock.Unlock()
|
||||
if _, ok := clientManager.TagCountMap[tag]; !ok {
|
||||
clientManager.TagCountMap[tag] = 1
|
||||
} else {
|
||||
clientManager.TagCountMap[tag]++
|
||||
}
|
||||
}
|
||||
|
||||
// 删除标签订阅计数
|
||||
func DelTagCount(tag string) {
|
||||
clientManager.TagLock.Lock()
|
||||
defer clientManager.TagLock.Unlock()
|
||||
count, ok := clientManager.TagCountMap[tag]
|
||||
if ok {
|
||||
if count > 0 {
|
||||
clientManager.TagCountMap[tag]--
|
||||
} else {
|
||||
delete(clientManager.TagCountMap, tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetTagCount 获取标签订阅计数
|
||||
func GetTagCount(tag string) (count int) {
|
||||
clientManager.TagLock.RLock()
|
||||
defer clientManager.TagLock.RUnlock()
|
||||
count, ok := clientManager.TagCountMap[tag]
|
||||
if ok {
|
||||
return count
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// 变量Tag Count
|
||||
func RangeTagCount(callback func(tag string, count int)) {
|
||||
clientManager.TagLock.RLock()
|
||||
defer clientManager.TagLock.RUnlock()
|
||||
for tag, count := range clientManager.TagCountMap {
|
||||
callback(tag, count)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
// Package websocket
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@xiujie.cn>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
// LoginController 用户登录
|
||||
func LoginController(client *Client, req *request) {
|
||||
l := &WsLoginDataReq{}
|
||||
gconv.Struct(req.Data, &l)
|
||||
userId := gconv.Uint64(l.UserID)
|
||||
client.UserId = userId
|
||||
// 用户登录
|
||||
login := &login{
|
||||
UserId: userId,
|
||||
Client: client,
|
||||
}
|
||||
clientManager.Login <- login
|
||||
|
||||
client.SendMsg(&WResponse{
|
||||
Event: Login,
|
||||
Data: "success",
|
||||
})
|
||||
}
|
||||
|
||||
func IsAppController(client *Client) {
|
||||
client.isApp = true
|
||||
}
|
||||
|
||||
// JoinController 加入
|
||||
func JoinController(client *Client, req *request) {
|
||||
name := gconv.String(req.Data["name"])
|
||||
|
||||
if !client.tags.Contains(name) {
|
||||
client.tags.Append(name)
|
||||
for k, v := range clientManager.TagCallbackMap {
|
||||
if strings.HasPrefix(name, k) {
|
||||
if v != nil {
|
||||
v(client, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
AddTagCount(name)
|
||||
}
|
||||
client.SendMsg(&WResponse{
|
||||
Event: Join,
|
||||
Data: client.tags.Slice(),
|
||||
})
|
||||
}
|
||||
|
||||
// QuitController 退出
|
||||
func QuitController(client *Client, req *request) {
|
||||
name := gconv.String(req.Data["name"])
|
||||
if client.tags.Contains(name) {
|
||||
client.tags.RemoveValue(name)
|
||||
DelTagCount(name)
|
||||
}
|
||||
client.SendMsg(&WResponse{
|
||||
Event: Quit,
|
||||
Data: client.tags.Slice(),
|
||||
})
|
||||
}
|
||||
|
||||
func PingController(client *Client) {
|
||||
currentTime := uint64(gtime.Now().Unix())
|
||||
client.Heartbeat(currentTime)
|
||||
}
|
||||
|
||||
// JoinsController 加入
|
||||
func JoinsController(client *Client, req *request) {
|
||||
if req == nil || req.Data == nil {
|
||||
fmt.Println("websocket.JoinsController req is nil or req.Data is nil")
|
||||
return
|
||||
}
|
||||
// fmt.Println("websocket.JoinsController req.Data", req.Data)
|
||||
names, ok := req.Data["names"]
|
||||
if !ok || names == nil {
|
||||
fmt.Println("websocket.JoinsController names is nil or names is empty")
|
||||
return
|
||||
}
|
||||
// fmt.Println("websocket.JoinsController names", names)
|
||||
for _, name := range gconv.Strings(names) {
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
if !client.tags.Contains(name) {
|
||||
client.tags.Append(name)
|
||||
for k, v := range clientManager.TagCallbackMap {
|
||||
if strings.HasPrefix(name, k) {
|
||||
if v != nil {
|
||||
v(client, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
AddTagCount(name)
|
||||
}
|
||||
}
|
||||
client.SendMsg(&WResponse{
|
||||
Event: Joins,
|
||||
Data: client.tags.Slice(),
|
||||
})
|
||||
}
|
||||
|
||||
// QuitController 退出
|
||||
func QuitsController(client *Client, req *request) {
|
||||
for _, name := range gconv.Strings(req.Data["names"]) {
|
||||
if client.tags.Contains(name) {
|
||||
client.tags.RemoveValue(name)
|
||||
DelTagCount(name)
|
||||
}
|
||||
}
|
||||
client.SendMsg(&WResponse{
|
||||
Event: Quits,
|
||||
Data: client.tags.Slice(),
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// Package websocket
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@xiujie.cn>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"context"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/contexts"
|
||||
"net/http"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
var (
|
||||
mctx = gctx.GetInitCtx() // 上下文
|
||||
clientManager = NewClientManager() // 管理者
|
||||
)
|
||||
var upGrader = websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
func StartWebSocket(ctx context.Context) {
|
||||
g.Log().Info(ctx, "启动:WebSocket")
|
||||
go clientManager.start()
|
||||
go clientManager.ping(ctx)
|
||||
}
|
||||
|
||||
func StopWebSocket(ctx context.Context) {
|
||||
g.Log().Info(ctx, "停止:WebSocket")
|
||||
clientManager.closeSignal <- struct{}{}
|
||||
}
|
||||
|
||||
func WsPage(r *ghttp.Request) {
|
||||
g.Log().Infof(r.GetCtx(), "websocket.WsPage Begin. UserID:%v ", contexts.GetUserId(r.GetCtx()))
|
||||
// r.Response.Header().Set("Content-Type", "text/event-stream")
|
||||
contexts.SetIsWebSocket(r.GetCtx(), true)
|
||||
conn, err := upGrader.Upgrade(r.Response.Writer, r.Request, nil)
|
||||
if err != nil {
|
||||
r.Response.Write(err.Error())
|
||||
return
|
||||
}
|
||||
currentTime := uint64(gtime.Now().Unix())
|
||||
client := NewClient(conn.RemoteAddr().String(), conn, currentTime, r.GetCtx())
|
||||
|
||||
client.UserId = uint64(contexts.GetUserId(r.GetCtx()))
|
||||
go client.read()
|
||||
go client.write()
|
||||
|
||||
// 用户连接事件
|
||||
clientManager.Register <- client
|
||||
|
||||
g.Log().Infof(r.GetCtx(), "websocket.WsPage End. UserID:%v", client.UserId)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// Package websocket
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@xiujie.cn>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package websocket
|
||||
|
||||
import "github.com/gogf/gf/v2/frame/g"
|
||||
|
||||
// 当前输入对象
|
||||
type request struct {
|
||||
Event string `json:"e"` //事件名称
|
||||
Data g.Map `json:"d"` //数据
|
||||
}
|
||||
|
||||
// WResponse 输出对象
|
||||
type WResponse struct {
|
||||
Event string `json:"e"` //事件名称
|
||||
Data interface{} `json:"d"` //数据
|
||||
}
|
||||
|
||||
type TagWResponse struct {
|
||||
Tag string
|
||||
WResponse *WResponse
|
||||
}
|
||||
|
||||
type UserWResponse struct {
|
||||
UserID uint64
|
||||
WResponse *WResponse
|
||||
}
|
||||
|
||||
type ClientWResponse struct {
|
||||
ID string
|
||||
WResponse *WResponse
|
||||
}
|
||||
|
||||
type WsLoginDataReq struct {
|
||||
UserID uint `json:"userId" `
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// Package websocket
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@xiujie.cn>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package websocket
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
const (
|
||||
Error = "error"
|
||||
Login = "login"
|
||||
Join = "join"
|
||||
Quit = "quit"
|
||||
IsApp = "is_app"
|
||||
Ping = "ping"
|
||||
Joins = "joins"
|
||||
Quits = "quits"
|
||||
)
|
||||
|
||||
// ProcessData 处理数据
|
||||
func ProcessData(client *Client, message []byte) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Println("websocket.ProcessData 处理数据 stop", r, "message=", message)
|
||||
}
|
||||
}()
|
||||
request := &request{}
|
||||
err := gconv.Struct(message, request)
|
||||
if err != nil {
|
||||
fmt.Println("websocket.ProcessData 数据解析失败:", err)
|
||||
return
|
||||
}
|
||||
fmt.Println("websocket.ProcessData request", request)
|
||||
switch request.Event {
|
||||
case Login:
|
||||
LoginController(client, request)
|
||||
break
|
||||
case Join:
|
||||
JoinController(client, request)
|
||||
break
|
||||
case Quit:
|
||||
QuitController(client, request)
|
||||
break
|
||||
case IsApp:
|
||||
IsAppController(client)
|
||||
break
|
||||
case Ping:
|
||||
PingController(client)
|
||||
break
|
||||
case Joins:
|
||||
JoinsController(client, request)
|
||||
break
|
||||
case Quits:
|
||||
QuitsController(client, request)
|
||||
break
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user