增加心跳
This commit is contained in:
@@ -25,4 +25,9 @@ database:
|
|||||||
# WebSocket配置
|
# WebSocket配置
|
||||||
websocket:
|
websocket:
|
||||||
# WebSocket请求超时时间(秒)
|
# WebSocket请求超时时间(秒)
|
||||||
timeout: 5
|
timeout: 5
|
||||||
|
|
||||||
|
# 心跳配置
|
||||||
|
heartbeat:
|
||||||
|
# 心跳间隔(秒)
|
||||||
|
interval: 30
|
||||||
@@ -56,6 +56,9 @@ type API struct {
|
|||||||
// websocketService WebSocket服务
|
// websocketService WebSocket服务
|
||||||
websocketService *service.WebSocketService
|
websocketService *service.WebSocketService
|
||||||
|
|
||||||
|
// heartbeatService 心跳服务
|
||||||
|
heartbeatService *service.HeartbeatService
|
||||||
|
|
||||||
// deviceStatusPool 设备状态池
|
// deviceStatusPool 设备状态池
|
||||||
deviceStatusPool *service.DeviceStatusPool
|
deviceStatusPool *service.DeviceStatusPool
|
||||||
|
|
||||||
@@ -65,7 +68,7 @@ type API struct {
|
|||||||
|
|
||||||
// NewAPI 创建并返回一个新的API实例
|
// NewAPI 创建并返回一个新的API实例
|
||||||
// 初始化Gin引擎和相关配置
|
// 初始化Gin引擎和相关配置
|
||||||
func NewAPI(cfg *config.Config, userRepo repository.UserRepo, operationHistoryRepo repository.OperationHistoryRepo, deviceControlRepo repository.DeviceControlRepo, deviceRepo repository.DeviceRepo, websocketService *service.WebSocketService, deviceStatusPool *service.DeviceStatusPool) *API {
|
func NewAPI(cfg *config.Config, userRepo repository.UserRepo, operationHistoryRepo repository.OperationHistoryRepo, deviceControlRepo repository.DeviceControlRepo, deviceRepo repository.DeviceRepo, websocketService *service.WebSocketService, heartbeatService *service.HeartbeatService, deviceStatusPool *service.DeviceStatusPool) *API {
|
||||||
// 设置Gin为发布模式
|
// 设置Gin为发布模式
|
||||||
gin.SetMode(gin.DebugMode)
|
gin.SetMode(gin.DebugMode)
|
||||||
|
|
||||||
@@ -96,7 +99,7 @@ func NewAPI(cfg *config.Config, userRepo repository.UserRepo, operationHistoryRe
|
|||||||
operationController := operation.NewController(operationHistoryRepo)
|
operationController := operation.NewController(operationHistoryRepo)
|
||||||
|
|
||||||
// 创建设备控制控制器
|
// 创建设备控制控制器
|
||||||
deviceController := device.NewController(deviceControlRepo, deviceRepo, websocketService, deviceStatusPool)
|
deviceController := device.NewController(deviceControlRepo, deviceRepo, websocketService, heartbeatService, deviceStatusPool)
|
||||||
|
|
||||||
// 创建WebSocket管理器
|
// 创建WebSocket管理器
|
||||||
websocketManager := websocket.NewManager(websocketService, deviceRepo)
|
websocketManager := websocket.NewManager(websocketService, deviceRepo)
|
||||||
@@ -117,6 +120,7 @@ func NewAPI(cfg *config.Config, userRepo repository.UserRepo, operationHistoryRe
|
|||||||
authMiddleware: authMiddleware,
|
authMiddleware: authMiddleware,
|
||||||
websocketManager: websocketManager,
|
websocketManager: websocketManager,
|
||||||
websocketService: websocketService,
|
websocketService: websocketService,
|
||||||
|
heartbeatService: heartbeatService,
|
||||||
deviceStatusPool: deviceStatusPool,
|
deviceStatusPool: deviceStatusPool,
|
||||||
logger: logs.NewLogger(),
|
logger: logs.NewLogger(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ type Config struct {
|
|||||||
|
|
||||||
// WebSocket WebSocket配置
|
// WebSocket WebSocket配置
|
||||||
WebSocket WebSocketConfig `yaml:"websocket"`
|
WebSocket WebSocketConfig `yaml:"websocket"`
|
||||||
|
|
||||||
|
// Heartbeat 心跳配置
|
||||||
|
Heartbeat HeartbeatConfig `yaml:"heartbeat"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServerConfig 代表服务器配置
|
// ServerConfig 代表服务器配置
|
||||||
@@ -76,12 +79,21 @@ type WebSocketConfig struct {
|
|||||||
Timeout int `yaml:"timeout"`
|
Timeout int `yaml:"timeout"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HeartbeatConfig 代表心跳配置
|
||||||
|
type HeartbeatConfig struct {
|
||||||
|
// Interval 心跳间隔(秒)
|
||||||
|
Interval int `yaml:"interval"`
|
||||||
|
}
|
||||||
|
|
||||||
// NewConfig 创建并返回一个新的配置实例
|
// NewConfig 创建并返回一个新的配置实例
|
||||||
func NewConfig() *Config {
|
func NewConfig() *Config {
|
||||||
return &Config{
|
return &Config{
|
||||||
WebSocket: WebSocketConfig{
|
WebSocket: WebSocketConfig{
|
||||||
Timeout: 5, // 默认5秒超时
|
Timeout: 5, // 默认5秒超时
|
||||||
},
|
},
|
||||||
|
Heartbeat: HeartbeatConfig{
|
||||||
|
Interval: 30, // 默认30秒心跳间隔
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,3 +134,11 @@ func (c *Config) GetWebSocketTimeout() int {
|
|||||||
}
|
}
|
||||||
return c.WebSocket.Timeout
|
return c.WebSocket.Timeout
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetHeartbeatInterval 获取心跳间隔(秒)
|
||||||
|
func (c *Config) GetHeartbeatInterval() int {
|
||||||
|
if c.Heartbeat.Interval <= 0 {
|
||||||
|
return 30 // 默认30秒心跳间隔
|
||||||
|
}
|
||||||
|
return c.Heartbeat.Interval
|
||||||
|
}
|
||||||
|
|||||||
@@ -122,16 +122,18 @@ type Controller struct {
|
|||||||
deviceControlRepo repository.DeviceControlRepo
|
deviceControlRepo repository.DeviceControlRepo
|
||||||
deviceRepo repository.DeviceRepo
|
deviceRepo repository.DeviceRepo
|
||||||
websocketService *service.WebSocketService
|
websocketService *service.WebSocketService
|
||||||
|
heartbeatService *service.HeartbeatService
|
||||||
deviceStatusPool *service.DeviceStatusPool
|
deviceStatusPool *service.DeviceStatusPool
|
||||||
logger *logs.Logger
|
logger *logs.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewController 创建设备控制控制器实例
|
// NewController 创建设备控制控制器实例
|
||||||
func NewController(deviceControlRepo repository.DeviceControlRepo, deviceRepo repository.DeviceRepo, websocketService *service.WebSocketService, deviceStatusPool *service.DeviceStatusPool) *Controller {
|
func NewController(deviceControlRepo repository.DeviceControlRepo, deviceRepo repository.DeviceRepo, websocketService *service.WebSocketService, heartbeatService *service.HeartbeatService, deviceStatusPool *service.DeviceStatusPool) *Controller {
|
||||||
return &Controller{
|
return &Controller{
|
||||||
deviceControlRepo: deviceControlRepo,
|
deviceControlRepo: deviceControlRepo,
|
||||||
deviceRepo: deviceRepo,
|
deviceRepo: deviceRepo,
|
||||||
websocketService: websocketService,
|
websocketService: websocketService,
|
||||||
|
heartbeatService: heartbeatService,
|
||||||
deviceStatusPool: deviceStatusPool,
|
deviceStatusPool: deviceStatusPool,
|
||||||
logger: logs.NewLogger(),
|
logger: logs.NewLogger(),
|
||||||
}
|
}
|
||||||
@@ -199,6 +201,9 @@ func (c *Controller) Create(ctx *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 刷新设备状态
|
||||||
|
c.heartbeatService.TriggerManualHeartbeatAsync()
|
||||||
|
|
||||||
controller.SendSuccessResponse(ctx, "创建设备成功", device)
|
controller.SendSuccessResponse(ctx, "创建设备成功", device)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,9 +258,6 @@ func (c *Controller) Update(ctx *gin.Context) {
|
|||||||
req.BusNumber != nil && req.DeviceAddress != nil {
|
req.BusNumber != nil && req.DeviceAddress != nil {
|
||||||
device.Set485Address(*req.BusNumber, *req.DeviceAddress)
|
device.Set485Address(*req.BusNumber, *req.DeviceAddress)
|
||||||
}
|
}
|
||||||
// TODO: 设备状态应该由系统自动获取,而不是由用户指定
|
|
||||||
// 这里保持设备原有状态,后续需要实现自动状态检测
|
|
||||||
// 设备状态现在只在内存中维护,不持久化到数据库
|
|
||||||
|
|
||||||
if err := c.deviceRepo.Update(device); err != nil {
|
if err := c.deviceRepo.Update(device); err != nil {
|
||||||
c.logger.Error("更新设备失败: " + err.Error())
|
c.logger.Error("更新设备失败: " + err.Error())
|
||||||
@@ -263,6 +265,9 @@ func (c *Controller) Update(ctx *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 刷新设备状态
|
||||||
|
c.heartbeatService.TriggerManualHeartbeatAsync()
|
||||||
|
|
||||||
controller.SendSuccessResponse(ctx, "更新设备成功", device)
|
controller.SendSuccessResponse(ctx, "更新设备成功", device)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,6 +296,9 @@ func (c *Controller) Delete(ctx *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 刷新设备状态
|
||||||
|
c.heartbeatService.TriggerManualHeartbeatAsync()
|
||||||
|
|
||||||
controller.SendSuccessResponse(ctx, "删除设备成功", nil)
|
controller.SendSuccessResponse(ctx, "删除设备成功", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -397,6 +405,9 @@ func (c *Controller) Switch(ctx *gin.Context) {
|
|||||||
Message: message,
|
Message: message,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 刷新设备状态
|
||||||
|
c.heartbeatService.TriggerManualHeartbeatAsync()
|
||||||
|
|
||||||
controller.SendSuccessResponse(ctx, "设备控制成功", data)
|
controller.SendSuccessResponse(ctx, "设备控制成功", data)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -474,6 +485,8 @@ func (c *Controller) GetDeviceStatus(ctx *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO 需要刷新设备状态吗? 刷新的话这个接口可能会很慢
|
||||||
|
|
||||||
// 从设备状态池中获取设备状态
|
// 从设备状态池中获取设备状态
|
||||||
status, exists := c.deviceStatusPool.GetStatus(deviceID)
|
status, exists := c.deviceStatusPool.GetStatus(deviceID)
|
||||||
if !exists {
|
if !exists {
|
||||||
|
|||||||
@@ -45,6 +45,9 @@ type Application struct {
|
|||||||
// DeviceStatusPool 设备状态池实例
|
// DeviceStatusPool 设备状态池实例
|
||||||
DeviceStatusPool *service.DeviceStatusPool
|
DeviceStatusPool *service.DeviceStatusPool
|
||||||
|
|
||||||
|
// HeartbeatService 心跳服务实例
|
||||||
|
HeartbeatService *service.HeartbeatService
|
||||||
|
|
||||||
// Config 应用配置
|
// Config 应用配置
|
||||||
Config *config.Config
|
Config *config.Config
|
||||||
|
|
||||||
@@ -103,8 +106,11 @@ func (app *Application) Start() error {
|
|||||||
// 设置WebSocket超时时间
|
// 设置WebSocket超时时间
|
||||||
app.WebSocketService.SetDefaultTimeout(app.Config.GetWebSocketTimeout())
|
app.WebSocketService.SetDefaultTimeout(app.Config.GetWebSocketTimeout())
|
||||||
|
|
||||||
|
// 初始化心跳服务
|
||||||
|
app.HeartbeatService = service.NewHeartbeatService(app.WebSocketService, app.DeviceStatusPool, app.DeviceRepo, app.Config)
|
||||||
|
|
||||||
// 初始化API组件
|
// 初始化API组件
|
||||||
app.API = api.NewAPI(app.Config, app.UserRepo, app.OperationHistoryRepo, app.DeviceControlRepo, app.DeviceRepo, app.WebSocketService, app.DeviceStatusPool)
|
app.API = api.NewAPI(app.Config, app.UserRepo, app.OperationHistoryRepo, app.DeviceControlRepo, app.DeviceRepo, app.WebSocketService, app.HeartbeatService, app.DeviceStatusPool)
|
||||||
|
|
||||||
// 初始化任务执行器组件(使用5个工作协程)
|
// 初始化任务执行器组件(使用5个工作协程)
|
||||||
app.TaskExecutor = task.NewExecutor(5)
|
app.TaskExecutor = task.NewExecutor(5)
|
||||||
@@ -119,6 +125,9 @@ func (app *Application) Start() error {
|
|||||||
app.logger.Info("启动任务执行器")
|
app.logger.Info("启动任务执行器")
|
||||||
app.TaskExecutor.Start()
|
app.TaskExecutor.Start()
|
||||||
|
|
||||||
|
// 启动心跳服务
|
||||||
|
app.logger.Info("启动心跳服务")
|
||||||
|
app.HeartbeatService.Start()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,6 +143,10 @@ func (app *Application) Stop() error {
|
|||||||
app.logger.Info("停止任务执行器")
|
app.logger.Info("停止任务执行器")
|
||||||
app.TaskExecutor.Stop()
|
app.TaskExecutor.Stop()
|
||||||
|
|
||||||
|
// 停止心跳服务
|
||||||
|
app.logger.Info("停止心跳服务")
|
||||||
|
app.HeartbeatService.Stop()
|
||||||
|
|
||||||
// 停止存储组件
|
// 停止存储组件
|
||||||
if err := app.Storage.Disconnect(); err != nil {
|
if err := app.Storage.Disconnect(); err != nil {
|
||||||
return fmt.Errorf("存储断开连接失败: %v", err)
|
return fmt.Errorf("存储断开连接失败: %v", err)
|
||||||
|
|||||||
148
internal/service/heartbeat.go
Normal file
148
internal/service/heartbeat.go
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
// Package service 提供各种业务服务功能
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/config"
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/logs"
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/storage/repository"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HeartbeatService 心跳服务,负责管理设备的心跳检测
|
||||||
|
type HeartbeatService struct {
|
||||||
|
// websocketService WebSocket服务
|
||||||
|
websocketService *WebSocketService
|
||||||
|
|
||||||
|
// deviceStatusPool 设备状态池
|
||||||
|
deviceStatusPool *DeviceStatusPool
|
||||||
|
|
||||||
|
// deviceRepo 设备仓库
|
||||||
|
deviceRepo repository.DeviceRepo
|
||||||
|
|
||||||
|
// logger 日志记录器
|
||||||
|
logger *logs.Logger
|
||||||
|
|
||||||
|
// 心跳间隔
|
||||||
|
heartbeatInterval time.Duration
|
||||||
|
|
||||||
|
// 手动心跳触发器
|
||||||
|
triggerChan chan struct{}
|
||||||
|
|
||||||
|
// ticker 心跳定时器
|
||||||
|
ticker *time.Ticker
|
||||||
|
|
||||||
|
// ctx 上下文
|
||||||
|
ctx context.Context
|
||||||
|
|
||||||
|
// cancel 取消函数
|
||||||
|
cancel context.CancelFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHeartbeatService 创建心跳服务实例
|
||||||
|
func NewHeartbeatService(websocketService *WebSocketService, deviceStatusPool *DeviceStatusPool, deviceRepo repository.DeviceRepo, config *config.Config) *HeartbeatService {
|
||||||
|
return &HeartbeatService{
|
||||||
|
websocketService: websocketService,
|
||||||
|
deviceStatusPool: deviceStatusPool,
|
||||||
|
deviceRepo: deviceRepo,
|
||||||
|
logger: logs.NewLogger(),
|
||||||
|
heartbeatInterval: time.Duration(config.GetHeartbeatInterval()) * time.Second,
|
||||||
|
triggerChan: make(chan struct{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start 启动心跳服务
|
||||||
|
func (hs *HeartbeatService) Start() {
|
||||||
|
// 创建上下文
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
hs.cancel = cancel
|
||||||
|
|
||||||
|
// 创建定时器
|
||||||
|
hs.logger.Info(fmt.Sprintf("设置心跳间隔为 %d 秒", int(hs.heartbeatInterval.Seconds())))
|
||||||
|
hs.ticker = time.NewTicker(hs.heartbeatInterval)
|
||||||
|
|
||||||
|
// 启动心跳goroutine
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-hs.ticker.C:
|
||||||
|
hs.handleHeartbeat()
|
||||||
|
case <-hs.triggerChan:
|
||||||
|
hs.handleHeartbeat()
|
||||||
|
case <-ctx.Done():
|
||||||
|
hs.logger.Info("心跳服务已停止")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
hs.logger.Info("心跳服务已启动")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop 停止心跳服务
|
||||||
|
func (hs *HeartbeatService) Stop() {
|
||||||
|
if hs == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if hs.ticker != nil {
|
||||||
|
hs.ticker.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
if hs.cancel != nil {
|
||||||
|
hs.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
hs.logger.Info("[Heartbeat] 心跳任务停止指令已发送")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TriggerManualHeartbeat 手动触发心跳检测
|
||||||
|
func (hs *HeartbeatService) TriggerManualHeartbeat() {
|
||||||
|
hs.logger.Info("收到手动触发心跳检测请求")
|
||||||
|
hs.triggerChan <- struct{}{}
|
||||||
|
hs.logger.Info("手动心跳检测完成")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TriggerManualHeartbeatAsync 手动触发心跳检测且不等待检测结果
|
||||||
|
func (hs *HeartbeatService) TriggerManualHeartbeatAsync() {
|
||||||
|
hs.logger.Info("收到手动触发异步心跳检测请求")
|
||||||
|
go func() {
|
||||||
|
hs.triggerChan <- struct{}{}
|
||||||
|
hs.logger.Info("手动心跳检测完成")
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendHeartbeat 发送心跳包到所有中继设备
|
||||||
|
func (hs *HeartbeatService) handleHeartbeat() {
|
||||||
|
// 记录心跳开始日志
|
||||||
|
hs.logger.Debug("开始发送心跳包")
|
||||||
|
|
||||||
|
// 获取所有已连接的设备
|
||||||
|
connectedDevices := hs.websocketService.GetConnectedDevices()
|
||||||
|
|
||||||
|
// 遍历所有连接的设备并发送心跳包
|
||||||
|
for _, deviceID := range connectedDevices {
|
||||||
|
// 发送心跳包到设备
|
||||||
|
response, err := hs.websocketService.SendCommandAndWait(deviceID, "heartbeat", nil, 0)
|
||||||
|
if err != nil {
|
||||||
|
hs.logger.Error(fmt.Sprintf("向设备 %s 发送心跳包失败: %v", deviceID, err))
|
||||||
|
// 更新设备状态为离线
|
||||||
|
hs.deviceStatusPool.SetStatus(deviceID, &DeviceStatus{
|
||||||
|
Active: false,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录收到心跳响应
|
||||||
|
hs.logger.Debug(fmt.Sprintf("收到来自设备 %s 的心跳响应: %+v", deviceID, response))
|
||||||
|
|
||||||
|
// 更新设备状态为在线
|
||||||
|
hs.deviceStatusPool.SetStatus(deviceID, &DeviceStatus{
|
||||||
|
Active: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
hs.logger.Debug("心跳包发送完成")
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user