213 lines
4.8 KiB
Go
213 lines
4.8 KiB
Go
package gateway
|
||
|
||
import (
|
||
errors "ai_scheduler/internal/data/error"
|
||
"ai_scheduler/internal/data/model"
|
||
"context"
|
||
"log"
|
||
"math/rand"
|
||
"sync"
|
||
"time"
|
||
|
||
"github.com/google/uuid"
|
||
|
||
"github.com/gofiber/websocket/v2"
|
||
)
|
||
|
||
var (
|
||
ErrConnClosed = errors.SysErrf("连接不存在或已关闭")
|
||
rng = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||
idBuf = make([]byte, 20)
|
||
)
|
||
|
||
type Client struct {
|
||
id string // 客户端唯一ID
|
||
conn *websocket.Conn // WebSocket 连接
|
||
session string // 会话ID
|
||
key string // 应用密钥
|
||
auth string // 用户凭证token
|
||
codes []string // 用户权限code
|
||
sysInfo *model.AiSy // 系统信息
|
||
tasks []model.AiTask // 任务列表
|
||
sysCode string // 系统编码
|
||
Ctx context.Context
|
||
Cancel context.CancelFunc
|
||
LastActive time.Time
|
||
mu sync.Mutex
|
||
}
|
||
|
||
func NewClient(conn *websocket.Conn, ctx context.Context, cancel context.CancelFunc) *Client {
|
||
|
||
return &Client{
|
||
id: generateClientID(),
|
||
conn: conn,
|
||
Ctx: ctx,
|
||
Cancel: cancel,
|
||
mu: sync.Mutex{},
|
||
}
|
||
}
|
||
|
||
// GetID 获取客户端的唯一ID
|
||
func (c *Client) GetID() string {
|
||
return c.id
|
||
}
|
||
|
||
// GetConn 获取客户端的 WebSocket 连接
|
||
func (c *Client) GetConn() *websocket.Conn {
|
||
return c.conn
|
||
}
|
||
|
||
// GetSession 获取会话ID
|
||
func (c *Client) GetSession() string {
|
||
return c.session
|
||
}
|
||
|
||
// GetKey 获取应用密钥
|
||
func (c *Client) GetKey() string {
|
||
return c.key
|
||
}
|
||
|
||
// GetAuth 获取用户凭证token
|
||
func (c *Client) GetAuth() string {
|
||
return c.auth
|
||
}
|
||
|
||
// GetCodes 获取用户权限code
|
||
func (c *Client) GetCodes() []string {
|
||
return c.codes
|
||
}
|
||
|
||
// 获取系统编码
|
||
func (c *Client) GetSysCode() string {
|
||
return c.sysCode
|
||
}
|
||
|
||
// GetSysInfo 获取系统信息
|
||
func (c *Client) GetSysInfo() *model.AiSy {
|
||
return c.sysInfo
|
||
}
|
||
|
||
// SetSysInfo 设置系统信息
|
||
func (c *Client) SetSysInfo(sysInfo *model.AiSy) {
|
||
c.sysInfo = sysInfo
|
||
}
|
||
|
||
// GetTasks 获取任务列表
|
||
func (c *Client) GetTasks() []model.AiTask {
|
||
return c.tasks
|
||
}
|
||
|
||
// SetTasks 设置任务列表
|
||
func (c *Client) SetTasks(tasks []model.AiTask) {
|
||
c.tasks = tasks
|
||
}
|
||
|
||
// 设置用户权限code
|
||
func (c *Client) SetCodes(codes []string) {
|
||
c.codes = codes
|
||
}
|
||
|
||
// Close 关闭客户端连接
|
||
func (c *Client) Close() {
|
||
//c.mu.Lock()
|
||
//defer c.mu.Unlock()
|
||
if c.conn != nil {
|
||
_ = c.conn.Close()
|
||
c.conn = nil
|
||
}
|
||
}
|
||
|
||
// SendFunc 发送消息到客户端
|
||
func (c *Client) SendFunc(msg []byte) error {
|
||
return c.SendMessage(websocket.TextMessage, msg)
|
||
}
|
||
|
||
// 在Client结构体中添加更详细的日志
|
||
func (c *Client) SendMessage(msgType int, msg []byte) error {
|
||
c.mu.Lock()
|
||
defer c.mu.Unlock()
|
||
|
||
if c.conn == nil {
|
||
return ErrConnClosed
|
||
}
|
||
|
||
err := c.conn.WriteMessage(msgType, msg)
|
||
if err != nil {
|
||
log.Printf("发送消息失败: %v, 客户端ID: %s, 消息类型: %d",
|
||
err, c.id, msgType)
|
||
}
|
||
return err
|
||
}
|
||
|
||
// 生成唯一的客户端ID
|
||
func generateClientID() string {
|
||
return uuid.New().String()
|
||
//// 1. 时间戳
|
||
//timestamp := time.Now().UnixNano()
|
||
//binary.BigEndian.PutUint64(idBuf[:8], uint64(timestamp))
|
||
//
|
||
//// 2. 随机数(4字节)
|
||
//binary.BigEndian.PutUint32(idBuf[8:12], rng.Uint32())
|
||
//
|
||
//// 3. 十六进制编码
|
||
//n := pkg.HexEncode(idBuf[:12], idBuf[12:])
|
||
//return string(idBuf[12 : 12+n])
|
||
}
|
||
|
||
// 连接数据验证和收集
|
||
func (c *Client) DataAuth() (err error) {
|
||
c.session = c.conn.Query("x-session", "")
|
||
if len(c.session) == 0 {
|
||
err = errors.SessionNotFound
|
||
return
|
||
}
|
||
c.auth = c.conn.Query("x-authorization", "")
|
||
if len(c.auth) == 0 {
|
||
err = errors.AuthNotFound
|
||
return
|
||
}
|
||
c.key = c.conn.Query("x-app-key", "")
|
||
if len(c.key) == 0 {
|
||
err = errors.KeyNotFound
|
||
return
|
||
}
|
||
// 系统编码
|
||
c.sysCode = c.conn.Query("x-sys-code", "")
|
||
if len(c.sysCode) == 0 {
|
||
err = errors.SysCodeNotFound
|
||
return
|
||
}
|
||
return
|
||
}
|
||
|
||
// 总结:目前绝大多数浏览器不支持直接发送WebSocket Ping帧,因此在实际开发中,应该实现应用层ping机制作为主要心跳检测方案,todo 同时保留对未来可能的原生支持的兼容检测。
|
||
func (c *Client) InitHeartbeat(timeoutSecond time.Duration) {
|
||
ticker := time.NewTicker(timeoutSecond * time.Second)
|
||
defer ticker.Stop()
|
||
for {
|
||
select {
|
||
case <-ticker.C:
|
||
//*2是防止丢包,连续丢包两次,再加5s网络延迟容错
|
||
if time.Since(c.LastActive) > (timeoutSecond*2*time.Second + 5) { // 5秒容错
|
||
log.Println("心跳超时", "clientId", c.id)
|
||
err := c.SendMessage(websocket.CloseMessage, []byte("Heartbeat timeout"))
|
||
if err != nil {
|
||
log.Println("发送心跳超时消息失败", err)
|
||
}
|
||
c.Close()
|
||
return
|
||
}
|
||
case <-c.Ctx.Done():
|
||
return
|
||
}
|
||
}
|
||
}
|
||
|
||
// 在Client结构体中添加ReadMessage方法
|
||
func (c *Client) ReadMessage() (messageType int, message []byte, err error) {
|
||
if c.conn == nil {
|
||
return 0, nil, ErrConnClosed
|
||
}
|
||
return c.conn.ReadMessage()
|
||
}
|