|
4 | 4 | "context" |
5 | 5 | "encoding/json" |
6 | 6 | "errors" |
| 7 | + "fmt" |
7 | 8 | "strconv" |
8 | 9 | "sync" |
9 | 10 | "time" |
@@ -457,52 +458,99 @@ func (*TelemetryDataApi) ServeDeviceStatusByWS(c *gin.Context) { |
457 | 458 |
|
458 | 459 | logrus.Infof("WebSocket连接已建立 - 设备ID: %s, 用户ID: %s, 租户ID: %s", deviceID, claims.ID, claims.TenantID) |
459 | 460 |
|
460 | | - // 订阅设备在线状态 |
461 | | - var mu sync.Mutex |
462 | | - logrus.Info("User SubscribeOnlineOffline") |
463 | | - var mqttClient ws_subscribe.WsMqttClient |
464 | | - if err := mqttClient.SubscribeOnlineOffline(deviceID, conn, msgType, &mu); err != nil { |
465 | | - logrus.Error("订阅设备状态失败:", err) |
466 | | - conn.WriteMessage(msgType, []byte("Failed to subscribe to device status")) |
| 461 | + // 查询设备当前状态并立即推送 (保持原有格式: is_online 为整数 0/1) |
| 462 | + currentStatusMap, err := service.GroupApp.Device.GetDeviceOnlineStatus(deviceID) |
| 463 | + if err != nil { |
| 464 | + logrus.WithError(err).Error("查询设备当前状态失败") |
| 465 | + conn.WriteMessage(msgType, []byte("Failed to query device status")) |
467 | 466 | return |
468 | 467 | } |
469 | | - defer mqttClient.Close() |
470 | 468 |
|
471 | | - // 处理心跳 |
| 469 | + // 提取在线状态 (整数格式) |
| 470 | + isOnline := 0 |
| 471 | + if status, ok := currentStatusMap["is_online"]; ok { |
| 472 | + isOnline = status |
| 473 | + } |
| 474 | + |
| 475 | + // 发送当前状态 (简化格式,与原有接口保持一致) |
| 476 | + initialMsg := map[string]interface{}{ |
| 477 | + "is_online": isOnline, |
| 478 | + } |
| 479 | + if data, err := json.Marshal(initialMsg); err == nil { |
| 480 | + conn.WriteMessage(msgType, data) |
| 481 | + } |
| 482 | + |
| 483 | + // 订阅 Redis Pub/Sub 通道 |
| 484 | + ctx, cancel := context.WithCancel(context.Background()) |
| 485 | + defer cancel() |
| 486 | + |
| 487 | + channel := fmt.Sprintf("device:%s:status", deviceID) |
| 488 | + pubsub := global.REDIS.Subscribe(ctx, channel) |
| 489 | + defer pubsub.Close() |
| 490 | + |
| 491 | + logrus.Infof("WebSocket已订阅Redis通道: %s", channel) |
| 492 | + |
| 493 | + // 使用 sync.Mutex 保护 WebSocket 写操作 |
| 494 | + var mu sync.Mutex |
| 495 | + |
| 496 | + // Goroutine 1: Redis消息转发到WebSocket |
| 497 | + go func() { |
| 498 | + ch := pubsub.Channel() |
| 499 | + for { |
| 500 | + select { |
| 501 | + case <-ctx.Done(): |
| 502 | + logrus.Debug("Redis订阅上下文取消") |
| 503 | + return |
| 504 | + case redisMsg, ok := <-ch: |
| 505 | + if !ok { |
| 506 | + logrus.Warn("Redis通道已关闭") |
| 507 | + return |
| 508 | + } |
| 509 | + |
| 510 | + // 转发到WebSocket |
| 511 | + mu.Lock() |
| 512 | + err := conn.WriteMessage(msgType, []byte(redisMsg.Payload)) |
| 513 | + mu.Unlock() |
| 514 | + |
| 515 | + if err != nil { |
| 516 | + logrus.WithError(err).Error("WebSocket写入失败") |
| 517 | + cancel() // 通知主goroutine退出 |
| 518 | + return |
| 519 | + } |
| 520 | + |
| 521 | + logrus.WithField("device_id", deviceID).Debug("状态更新已推送到WebSocket") |
| 522 | + } |
| 523 | + } |
| 524 | + }() |
| 525 | + |
| 526 | + // Goroutine 2 (主): WebSocket心跳处理 |
472 | 527 | for { |
473 | | - _, msg, err := conn.ReadMessage() |
| 528 | + _, wsMsg, err := conn.ReadMessage() |
474 | 529 | if err != nil { |
475 | | - // 记录错误日志 |
476 | | - logrus.Error("WebSocket读取错误:", err) |
| 530 | + logrus.WithError(err).Info("WebSocket连接关闭") |
477 | 531 |
|
478 | | - // 尝试发送错误消息给客户端 |
479 | | - closeMsg := []byte("connection closed due to error") |
480 | | - // 使用 WriteControl 发送关闭消息,设置1秒超时 |
| 532 | + // 发送关闭消息 |
| 533 | + closeMsg := []byte("connection closed") |
481 | 534 | deadline := time.Now().Add(time.Second) |
482 | 535 | conn.WriteControl(websocket.CloseMessage, |
483 | | - websocket.FormatCloseMessage(websocket.CloseInternalServerErr, string(closeMsg)), |
| 536 | + websocket.FormatCloseMessage(websocket.CloseNormalClosure, string(closeMsg)), |
484 | 537 | deadline) |
485 | 538 |
|
486 | | - // 现在可以安全退出了 |
| 539 | + cancel() // 通知Redis订阅goroutine退出 |
487 | 540 | return |
488 | 541 | } |
489 | 542 |
|
490 | 543 | // 处理心跳消息 |
491 | | - if string(msg) == "ping" { |
| 544 | + if string(wsMsg) == "ping" { |
492 | 545 | mu.Lock() |
493 | | - if err := conn.WriteMessage(msgType, []byte("pong")); err != nil { |
494 | | - logrus.Error("发送pong消息失败:", err) |
495 | | - |
496 | | - // 尝试发送错误消息 |
497 | | - deadline := time.Now().Add(time.Second) |
498 | | - conn.WriteControl(websocket.CloseMessage, |
499 | | - websocket.FormatCloseMessage(websocket.CloseInternalServerErr, "failed to send pong"), |
500 | | - deadline) |
| 546 | + err := conn.WriteMessage(msgType, []byte("pong")) |
| 547 | + mu.Unlock() |
501 | 548 |
|
502 | | - mu.Unlock() |
| 549 | + if err != nil { |
| 550 | + logrus.WithError(err).Error("发送pong失败") |
| 551 | + cancel() |
503 | 552 | return |
504 | 553 | } |
505 | | - mu.Unlock() |
506 | 554 | } |
507 | 555 | } |
508 | 556 | } |
|
0 commit comments