重构部分枚举, 让models包不依赖其他项目中的包
This commit is contained in:
@@ -121,3 +121,9 @@
|
|||||||
* 区域主控告警配置接口: `/api/v1/alarm/region-config`
|
* 区域主控告警配置接口: `/api/v1/alarm/region-config`
|
||||||
* 普通设备告警配置接口: `/api/v1/alarm/device-config`
|
* 普通设备告警配置接口: `/api/v1/alarm/device-config`
|
||||||
2. **接口职责**: 接口负责接收前端请求,调用应用服务层的阈值告警服务来完成实际的业务逻辑。
|
2. **接口职责**: 接口负责接收前端请求,调用应用服务层的阈值告警服务来完成实际的业务逻辑。
|
||||||
|
|
||||||
|
|
||||||
|
# 实现记录
|
||||||
|
|
||||||
|
1. 定义告警表和告警历史表
|
||||||
|
2. 重构部分枚举, 让models包不依赖其他项目中的包
|
||||||
@@ -2,8 +2,6 @@ package dto
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||||
|
|
||||||
"go.uber.org/zap/zapcore"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewListNotificationResponse 从模型数据创建通知列表响应 DTO
|
// NewListNotificationResponse 从模型数据创建通知列表响应 DTO
|
||||||
@@ -18,7 +16,7 @@ func NewListNotificationResponse(data []models.Notification, total int64, page,
|
|||||||
UserID: item.UserID,
|
UserID: item.UserID,
|
||||||
Title: item.Title,
|
Title: item.Title,
|
||||||
Message: item.Message,
|
Message: item.Message,
|
||||||
Level: zapcore.Level(item.Level),
|
Level: item.Level,
|
||||||
AlarmTimestamp: item.AlarmTimestamp,
|
AlarmTimestamp: item.AlarmTimestamp,
|
||||||
ToAddress: item.ToAddress,
|
ToAddress: item.ToAddress,
|
||||||
Status: item.Status,
|
Status: item.Status,
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/notify"
|
|
||||||
|
|
||||||
"go.uber.org/zap/zapcore"
|
"go.uber.org/zap/zapcore"
|
||||||
)
|
)
|
||||||
@@ -12,7 +11,7 @@ import (
|
|||||||
// SendTestNotificationRequest 定义了发送测试通知请求的 JSON 结构
|
// SendTestNotificationRequest 定义了发送测试通知请求的 JSON 结构
|
||||||
type SendTestNotificationRequest struct {
|
type SendTestNotificationRequest struct {
|
||||||
// Type 指定要测试的通知渠道
|
// Type 指定要测试的通知渠道
|
||||||
Type notify.NotifierType `json:"type" validate:"required"`
|
Type models.NotifierType `json:"type" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListNotificationRequest 定义了获取通知列表的请求参数
|
// ListNotificationRequest 定义了获取通知列表的请求参数
|
||||||
@@ -20,7 +19,7 @@ type ListNotificationRequest struct {
|
|||||||
Page int `json:"page" query:"page"`
|
Page int `json:"page" query:"page"`
|
||||||
PageSize int `json:"page_size" query:"page_size"`
|
PageSize int `json:"page_size" query:"page_size"`
|
||||||
UserID *uint `json:"user_id" query:"user_id"`
|
UserID *uint `json:"user_id" query:"user_id"`
|
||||||
NotifierType *notify.NotifierType `json:"notifier_type" query:"notifier_type"`
|
NotifierType *models.NotifierType `json:"notifier_type" query:"notifier_type"`
|
||||||
Status *models.NotificationStatus `json:"status" query:"status"`
|
Status *models.NotificationStatus `json:"status" query:"status"`
|
||||||
Level *zapcore.Level `json:"level" query:"level"`
|
Level *zapcore.Level `json:"level" query:"level"`
|
||||||
StartTime *time.Time `json:"start_time" query:"start_time"`
|
StartTime *time.Time `json:"start_time" query:"start_time"`
|
||||||
@@ -33,11 +32,11 @@ type NotificationDTO struct {
|
|||||||
ID uint `json:"id"`
|
ID uint `json:"id"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
NotifierType notify.NotifierType `json:"notifier_type"`
|
NotifierType models.NotifierType `json:"notifier_type"`
|
||||||
UserID uint `json:"user_id"`
|
UserID uint `json:"user_id"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
Level zapcore.Level `json:"level"`
|
Level models.SeverityLevel `json:"level"`
|
||||||
AlarmTimestamp time.Time `json:"alarm_timestamp"`
|
AlarmTimestamp time.Time `json:"alarm_timestamp"`
|
||||||
ToAddress string `json:"to_address"`
|
ToAddress string `json:"to_address"`
|
||||||
Status models.NotificationStatus `json:"status"`
|
Status models.NotificationStatus `json:"status"`
|
||||||
|
|||||||
@@ -353,7 +353,7 @@ func initNotifyService(
|
|||||||
|
|
||||||
// 3. 动态确定首选通知器
|
// 3. 动态确定首选通知器
|
||||||
var primaryNotifier notify.Notifier
|
var primaryNotifier notify.Notifier
|
||||||
primaryNotifierType := notify.NotifierType(cfg.Primary)
|
primaryNotifierType := models.NotifierType(cfg.Primary)
|
||||||
|
|
||||||
// 检查用户指定的主渠道是否已启用
|
// 检查用户指定的主渠道是否已启用
|
||||||
for _, n := range availableNotifiers {
|
for _, n := range availableNotifiers {
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ import (
|
|||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/notify"
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/notify"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Service 定义了通知领域的核心业务逻辑接口
|
// Service 定义了通知领域的核心业务逻辑接口
|
||||||
@@ -24,14 +22,14 @@ type Service interface {
|
|||||||
BroadcastAlarm(ctx context.Context, content notify.AlarmContent) error
|
BroadcastAlarm(ctx context.Context, content notify.AlarmContent) error
|
||||||
|
|
||||||
// SendTestMessage 向指定用户发送一条测试消息,用于手动验证特定通知渠道的配置。
|
// SendTestMessage 向指定用户发送一条测试消息,用于手动验证特定通知渠道的配置。
|
||||||
SendTestMessage(ctx context.Context, userID uint, notifierType notify.NotifierType) error
|
SendTestMessage(ctx context.Context, userID uint, notifierType models.NotifierType) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// failoverService 是 Service 接口的实现,提供了故障转移功能
|
// failoverService 是 Service 接口的实现,提供了故障转移功能
|
||||||
type failoverService struct {
|
type failoverService struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
userRepo repository.UserRepository
|
userRepo repository.UserRepository
|
||||||
notifiers map[notify.NotifierType]notify.Notifier
|
notifiers map[models.NotifierType]notify.Notifier
|
||||||
primaryNotifier notify.Notifier
|
primaryNotifier notify.Notifier
|
||||||
failureThreshold int
|
failureThreshold int
|
||||||
failureCounters *sync.Map // 使用 sync.Map 来安全地并发读写失败计数, key: userID (uint), value: counter (int)
|
failureCounters *sync.Map // 使用 sync.Map 来安全地并发读写失败计数, key: userID (uint), value: counter (int)
|
||||||
@@ -43,11 +41,11 @@ func NewFailoverService(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
userRepo repository.UserRepository,
|
userRepo repository.UserRepository,
|
||||||
notifiers []notify.Notifier,
|
notifiers []notify.Notifier,
|
||||||
primaryNotifierType notify.NotifierType,
|
primaryNotifierType models.NotifierType,
|
||||||
failureThreshold int,
|
failureThreshold int,
|
||||||
notificationRepo repository.NotificationRepository,
|
notificationRepo repository.NotificationRepository,
|
||||||
) (Service, error) {
|
) (Service, error) {
|
||||||
notifierMap := make(map[notify.NotifierType]notify.Notifier)
|
notifierMap := make(map[models.NotifierType]notify.Notifier)
|
||||||
for _, n := range notifiers {
|
for _, n := range notifiers {
|
||||||
notifierMap[n.Type()] = n
|
notifierMap[n.Type()] = n
|
||||||
}
|
}
|
||||||
@@ -189,7 +187,7 @@ func (s *failoverService) sendAlarmToUser(ctx context.Context, userID uint, cont
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SendTestMessage 实现了手动发送测试消息的功能
|
// SendTestMessage 实现了手动发送测试消息的功能
|
||||||
func (s *failoverService) SendTestMessage(ctx context.Context, userID uint, notifierType notify.NotifierType) error {
|
func (s *failoverService) SendTestMessage(ctx context.Context, userID uint, notifierType models.NotifierType) error {
|
||||||
serviceCtx, logger := logs.Trace(ctx, s.ctx, "SendTestMessage")
|
serviceCtx, logger := logs.Trace(ctx, s.ctx, "SendTestMessage")
|
||||||
user, err := s.userRepo.FindByID(serviceCtx, userID)
|
user, err := s.userRepo.FindByID(serviceCtx, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -210,7 +208,7 @@ func (s *failoverService) SendTestMessage(ctx context.Context, userID uint, noti
|
|||||||
s.recordNotificationAttempt(serviceCtx, userID, notifierType, notify.AlarmContent{
|
s.recordNotificationAttempt(serviceCtx, userID, notifierType, notify.AlarmContent{
|
||||||
Title: "通知服务测试",
|
Title: "通知服务测试",
|
||||||
Message: fmt.Sprintf("这是一条来自【%s】渠道的测试消息。如果您收到此消息,说明您的配置正确。", notifierType),
|
Message: fmt.Sprintf("这是一条来自【%s】渠道的测试消息。如果您收到此消息,说明您的配置正确。", notifierType),
|
||||||
Level: zap.InfoLevel,
|
Level: models.InfoLevel,
|
||||||
Timestamp: time.Now(),
|
Timestamp: time.Now(),
|
||||||
}, "", models.NotificationStatusFailed, fmt.Errorf("用户未配置通知方式 '%s' 的地址", notifierType))
|
}, "", models.NotificationStatusFailed, fmt.Errorf("用户未配置通知方式 '%s' 的地址", notifierType))
|
||||||
return fmt.Errorf("用户未配置通知方式 '%s' 的地址", notifierType)
|
return fmt.Errorf("用户未配置通知方式 '%s' 的地址", notifierType)
|
||||||
@@ -219,7 +217,7 @@ func (s *failoverService) SendTestMessage(ctx context.Context, userID uint, noti
|
|||||||
testContent := notify.AlarmContent{
|
testContent := notify.AlarmContent{
|
||||||
Title: "通知服务测试",
|
Title: "通知服务测试",
|
||||||
Message: fmt.Sprintf("这是一条来自【%s】渠道的测试消息。如果您收到此消息,说明您的配置正确。", notifierType),
|
Message: fmt.Sprintf("这是一条来自【%s】渠道的测试消息。如果您收到此消息,说明您的配置正确。", notifierType),
|
||||||
Level: zap.InfoLevel,
|
Level: models.InfoLevel,
|
||||||
Timestamp: time.Now(),
|
Timestamp: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,15 +237,15 @@ func (s *failoverService) SendTestMessage(ctx context.Context, userID uint, noti
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getAddressForNotifier 是一个辅助函数,根据通知器类型从 ContactInfo 中获取对应的地址
|
// getAddressForNotifier 是一个辅助函数,根据通知器类型从 ContactInfo 中获取对应的地址
|
||||||
func getAddressForNotifier(notifierType notify.NotifierType, contact models.ContactInfo) string {
|
func getAddressForNotifier(notifierType models.NotifierType, contact models.ContactInfo) string {
|
||||||
switch notifierType {
|
switch notifierType {
|
||||||
case notify.NotifierTypeSMTP:
|
case models.NotifierTypeSMTP:
|
||||||
return contact.Email
|
return contact.Email
|
||||||
case notify.NotifierTypeWeChat:
|
case models.NotifierTypeWeChat:
|
||||||
return contact.WeChat
|
return contact.WeChat
|
||||||
case notify.NotifierTypeLark:
|
case models.NotifierTypeLark:
|
||||||
return contact.Feishu
|
return contact.Feishu
|
||||||
case notify.NotifierTypeLog:
|
case models.NotifierTypeLog:
|
||||||
return "log" // LogNotifier不需要具体的地址,但为了函数签名一致性,返回一个无意义的非空字符串以绕过配置存在检查
|
return "log" // LogNotifier不需要具体的地址,但为了函数签名一致性,返回一个无意义的非空字符串以绕过配置存在检查
|
||||||
default:
|
default:
|
||||||
return ""
|
return ""
|
||||||
@@ -264,7 +262,7 @@ func getAddressForNotifier(notifierType notify.NotifierType, contact models.Cont
|
|||||||
func (s *failoverService) recordNotificationAttempt(
|
func (s *failoverService) recordNotificationAttempt(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
userID uint,
|
userID uint,
|
||||||
notifierType notify.NotifierType,
|
notifierType models.NotifierType,
|
||||||
content notify.AlarmContent,
|
content notify.AlarmContent,
|
||||||
toAddress string,
|
toAddress string,
|
||||||
status models.NotificationStatus,
|
status models.NotificationStatus,
|
||||||
@@ -281,7 +279,7 @@ func (s *failoverService) recordNotificationAttempt(
|
|||||||
UserID: userID,
|
UserID: userID,
|
||||||
Title: content.Title,
|
Title: content.Title,
|
||||||
Message: content.Message,
|
Message: content.Message,
|
||||||
Level: models.LogLevel(content.Level),
|
Level: content.Level,
|
||||||
AlarmTimestamp: content.Timestamp,
|
AlarmTimestamp: content.Timestamp,
|
||||||
ToAddress: toAddress,
|
ToAddress: toAddress,
|
||||||
Status: status,
|
Status: status,
|
||||||
|
|||||||
@@ -10,14 +10,14 @@ import (
|
|||||||
// 活跃告警会被更新(如确认状态),因此保留 gorm.Model 以包含所有标准字段。
|
// 活跃告警会被更新(如确认状态),因此保留 gorm.Model 以包含所有标准字段。
|
||||||
type ActiveAlarm struct {
|
type ActiveAlarm struct {
|
||||||
gorm.Model
|
gorm.Model
|
||||||
FarmID uint `gorm:"not null;comment:猪场ID" json:"farm_id,omitempty"`
|
FarmID uint `gorm:"not null;comment:猪场ID" json:"farm_id,omitempty"`
|
||||||
DeviceID uint `gorm:"not null;comment:设备ID" json:"device_id,omitempty"`
|
DeviceID uint `gorm:"not null;comment:设备ID" json:"device_id,omitempty"`
|
||||||
AlarmType string `gorm:"comment:告警类型" json:"alarm_type,omitempty"`
|
AlarmType string `gorm:"comment:告警类型" json:"alarm_type,omitempty"`
|
||||||
AlarmLevel string `gorm:"comment:告警级别" json:"alarm_level,omitempty"`
|
AlarmLevel SeverityLevel `gorm:"comment:告警级别" json:"alarm_level,omitempty"`
|
||||||
AlarmContent string `gorm:"comment:告警内容描述" json:"alarm_content,omitempty"`
|
AlarmContent string `gorm:"comment:告警内容描述" json:"alarm_content,omitempty"`
|
||||||
TriggerTime time.Time `gorm:"not null;comment:告警触发时间" json:"trigger_time"`
|
TriggerTime time.Time `gorm:"not null;comment:告警触发时间" json:"trigger_time"`
|
||||||
IsAcknowledged bool `gorm:"default:false;comment:是否已确认" json:"is_acknowledged,omitempty"`
|
IsAcknowledged bool `gorm:"default:false;comment:是否已确认" json:"is_acknowledged,omitempty"`
|
||||||
AcknowledgedBy string `gorm:"comment:确认人" json:"acknowledged_by,omitempty"`
|
AcknowledgedBy string `gorm:"comment:确认人" json:"acknowledged_by,omitempty"`
|
||||||
// 使用指针类型 *time.Time 来表示可为空的确认时间
|
// 使用指针类型 *time.Time 来表示可为空的确认时间
|
||||||
AcknowledgedTime *time.Time `gorm:"comment:确认时间" json:"acknowledged_time,omitempty"`
|
AcknowledgedTime *time.Time `gorm:"comment:确认时间" json:"acknowledged_time,omitempty"`
|
||||||
}
|
}
|
||||||
@@ -32,16 +32,16 @@ func (ActiveAlarm) TableName() string {
|
|||||||
// ID 和 CreatedAt 共同构成联合主键,以满足 TimescaleDB 超表的要求。
|
// ID 和 CreatedAt 共同构成联合主键,以满足 TimescaleDB 超表的要求。
|
||||||
type HistoricalAlarm struct {
|
type HistoricalAlarm struct {
|
||||||
// 手动定义主键,ID 仍然自增
|
// 手动定义主键,ID 仍然自增
|
||||||
ID uint `gorm:"primaryKey;autoIncrement;comment:主键ID" json:"id,omitempty"`
|
ID uint `gorm:"primaryKey;autoIncrement;comment:主键ID" json:"id,omitempty"`
|
||||||
FarmID uint `gorm:"not null;comment:猪场ID" json:"farm_id,omitempty"`
|
FarmID uint `gorm:"not null;comment:猪场ID" json:"farm_id,omitempty"`
|
||||||
DeviceID uint `gorm:"not null;comment:设备ID" json:"device_id,omitempty"`
|
DeviceID uint `gorm:"not null;comment:设备ID" json:"device_id,omitempty"`
|
||||||
AlarmType string `gorm:"comment:告警类型" json:"alarm_type,omitempty"`
|
AlarmType string `gorm:"comment:告警类型" json:"alarm_type,omitempty"`
|
||||||
AlarmLevel string `gorm:"comment:告警级别" json:"alarm_level,omitempty"`
|
AlarmLevel SeverityLevel `gorm:"comment:告警级别" json:"alarm_level,omitempty"`
|
||||||
AlarmContent string `gorm:"comment:告警内容描述" json:"alarm_content,omitempty"`
|
AlarmContent string `gorm:"comment:告警内容描述" json:"alarm_content,omitempty"`
|
||||||
TriggerTime time.Time `gorm:"not null;comment:告警触发时间" json:"trigger_time"`
|
TriggerTime time.Time `gorm:"not null;comment:告警触发时间" json:"trigger_time"`
|
||||||
ResolveTime time.Time `gorm:"not null;comment:告警解决时间" json:"resolve_time"`
|
ResolveTime time.Time `gorm:"not null;comment:告警解决时间" json:"resolve_time"`
|
||||||
ResolveMethod string `gorm:"comment:告警解决方式" json:"resolve_method,omitempty"`
|
ResolveMethod string `gorm:"comment:告警解决方式" json:"resolve_method,omitempty"`
|
||||||
ResolvedBy string `gorm:"comment:告警解决人" json:"resolved_by,omitempty"`
|
ResolvedBy string `gorm:"comment:告警解决人" json:"resolved_by,omitempty"`
|
||||||
// 将 CreatedAt 作为联合主键的一部分,用于 TimescaleDB 分区
|
// 将 CreatedAt 作为联合主键的一部分,用于 TimescaleDB 分区
|
||||||
CreatedAt time.Time `gorm:"primaryKey;comment:创建时间" json:"created_at"`
|
CreatedAt time.Time `gorm:"primaryKey;comment:创建时间" json:"created_at"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,12 +5,41 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/utils/command_generater"
|
|
||||||
|
|
||||||
"gorm.io/datatypes"
|
"gorm.io/datatypes"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ModbusFunctionCode 定义Modbus功能码的枚举类型
|
||||||
|
type ModbusFunctionCode byte
|
||||||
|
|
||||||
|
// 定义常用的Modbus功能码常量及其应用场景
|
||||||
|
const (
|
||||||
|
// ReadCoils 读取线圈状态 (0x01)
|
||||||
|
// 场景: 用于读取数字量输出(DO)或内部标志位的当前状态,这些状态通常是开关量。
|
||||||
|
ReadCoils ModbusFunctionCode = 0x01
|
||||||
|
// ReadDiscreteInputs 读取离散输入状态 (0x02)
|
||||||
|
// 场景: 用于读取数字量输入(DI)的当前状态,这些状态通常是外部传感器的开关量信号。
|
||||||
|
ReadDiscreteInputs ModbusFunctionCode = 0x02
|
||||||
|
// ReadHoldingRegisters 读取保持寄存器 (0x03)
|
||||||
|
// 场景: 用于读取设备内部可读写的参数或数据,例如温度设定值、电机速度等模拟量或配置数据。
|
||||||
|
ReadHoldingRegisters ModbusFunctionCode = 0x03
|
||||||
|
// ReadInputRegisters 读取输入寄存器 (0x04)
|
||||||
|
// 场景: 用于读取设备的模拟量输入(AI)数据,这些数据通常是只读的,例如当前温度、压力、电压等实时测量值。
|
||||||
|
ReadInputRegisters ModbusFunctionCode = 0x04
|
||||||
|
// WriteSingleCoil 写入单个线圈 (0x05)
|
||||||
|
// 场景: 用于控制单个数字量输出(DO),例如打开或关闭一个继电器、指示灯等。
|
||||||
|
WriteSingleCoil ModbusFunctionCode = 0x05
|
||||||
|
// WriteSingleRegister 写入单个保持寄存器 (0x06)
|
||||||
|
// 场景: 用于修改设备内部的单个可写参数,例如设置一个温度控制器的目标温度、调整一个阀门的开度等。
|
||||||
|
WriteSingleRegister ModbusFunctionCode = 0x06
|
||||||
|
// WriteMultipleCoils 写入多个线圈 (0x0F)
|
||||||
|
// 场景: 用于批量控制多个数字量输出(DO),例如同时打开或关闭一组继电器。
|
||||||
|
WriteMultipleCoils ModbusFunctionCode = 0x0F
|
||||||
|
// WriteMultipleRegisters 写入多个保持寄存器 (0x10)
|
||||||
|
// 场景: 用于批量修改设备内部的多个可写参数,例如一次性更新多个配置参数或模拟量输出值。
|
||||||
|
WriteMultipleRegisters ModbusFunctionCode = 0x10
|
||||||
|
)
|
||||||
|
|
||||||
// DeviceCategory 定义了设备模板的宽泛类别
|
// DeviceCategory 定义了设备模板的宽泛类别
|
||||||
type DeviceCategory string
|
type DeviceCategory string
|
||||||
|
|
||||||
@@ -51,7 +80,7 @@ func (sc *SwitchCommands) SelfCheck() error {
|
|||||||
// SensorCommands 定义了传感器读取指令所需的Modbus参数
|
// SensorCommands 定义了传感器读取指令所需的Modbus参数
|
||||||
type SensorCommands struct {
|
type SensorCommands struct {
|
||||||
// ModbusFunctionCode 记录Modbus功能码,例如 ReadHoldingRegisters。(一般是第二字节)
|
// ModbusFunctionCode 记录Modbus功能码,例如 ReadHoldingRegisters。(一般是第二字节)
|
||||||
ModbusFunctionCode command_generater.ModbusFunctionCode `json:"modbus_function_code"`
|
ModbusFunctionCode ModbusFunctionCode `json:"modbus_function_code"`
|
||||||
// ModbusStartAddress 记录Modbus寄存器的起始地址,用于生成指令。(一般是第三到四字节)
|
// ModbusStartAddress 记录Modbus寄存器的起始地址,用于生成指令。(一般是第三到四字节)
|
||||||
ModbusStartAddress uint16 `json:"modbus_start_address"`
|
ModbusStartAddress uint16 `json:"modbus_start_address"`
|
||||||
// ModbusQuantity 记录Modbus寄存器的数量,用于生成指令。(一般是五到六字节)
|
// ModbusQuantity 记录Modbus寄存器的数量,用于生成指令。(一般是五到六字节)
|
||||||
@@ -62,7 +91,7 @@ type SensorCommands struct {
|
|||||||
func (sc *SensorCommands) SelfCheck() error {
|
func (sc *SensorCommands) SelfCheck() error {
|
||||||
// 校验ModbusFunctionCode是否为读取类型
|
// 校验ModbusFunctionCode是否为读取类型
|
||||||
switch sc.ModbusFunctionCode {
|
switch sc.ModbusFunctionCode {
|
||||||
case command_generater.ReadCoils, command_generater.ReadDiscreteInputs, command_generater.ReadHoldingRegisters, command_generater.ReadInputRegisters:
|
case ReadCoils, ReadDiscreteInputs, ReadHoldingRegisters, ReadInputRegisters:
|
||||||
// 支持的读取功能码
|
// 支持的读取功能码
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("'sensor' 指令集 ModbusFunctionCode %X 无效或不是读取类型", sc.ModbusFunctionCode)
|
return fmt.Errorf("'sensor' 指令集 ModbusFunctionCode %X 无效或不是读取类型", sc.ModbusFunctionCode)
|
||||||
|
|||||||
@@ -154,7 +154,6 @@ func (PendingCollection) TableName() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- 用户审计日志 ---
|
// --- 用户审计日志 ---
|
||||||
// TODO 这些变量放这个包合适吗?
|
|
||||||
|
|
||||||
// --- 审计日志状态常量 ---
|
// --- 审计日志状态常量 ---
|
||||||
type AuditStatus string
|
type AuditStatus string
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetAllModels 返回一个包含所有数据库模型实例的切片。
|
// GetAllModels 返回一个包含所有数据库模型实例的切片。
|
||||||
@@ -129,3 +131,68 @@ func (a *UintArray) Scan(src interface{}) error {
|
|||||||
*a = arr
|
*a = arr
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SeverityLevel 定义了系统中告警、通知、日志的统一级别枚举。
|
||||||
|
// 它以中文形式存储在数据库中,提高了可读性。
|
||||||
|
type SeverityLevel string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DebugLevel 调试级别,用于开发和诊断问题。
|
||||||
|
DebugLevel SeverityLevel = "Debug"
|
||||||
|
// InfoLevel 信息级别,用于记录常规操作。
|
||||||
|
InfoLevel SeverityLevel = "Info"
|
||||||
|
// WarnLevel 警告级别,表示出现潜在问题,需要关注。
|
||||||
|
WarnLevel SeverityLevel = "Warn"
|
||||||
|
// ErrorLevel 错误级别,表示发生了需要处理的错误。
|
||||||
|
ErrorLevel SeverityLevel = "Error"
|
||||||
|
// DPanicLevel 开发时崩溃级别,在开发模式下会触发 panic。
|
||||||
|
DPanicLevel SeverityLevel = "DPanic"
|
||||||
|
// PanicLevel 崩溃级别,记录日志后会立即触发 panic。
|
||||||
|
PanicLevel SeverityLevel = "Panic"
|
||||||
|
// FatalLevel 致命级别,记录日志后会调用 os.Exit(1) 退出程序。
|
||||||
|
FatalLevel SeverityLevel = "Fatal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ToZapLevel 将我们的自定义级别转换为 zapcore.Level,以便与日志记录器兼容。
|
||||||
|
func (al SeverityLevel) ToZapLevel() zapcore.Level {
|
||||||
|
switch al {
|
||||||
|
case DebugLevel:
|
||||||
|
return zapcore.DebugLevel
|
||||||
|
case InfoLevel:
|
||||||
|
return zapcore.InfoLevel
|
||||||
|
case WarnLevel:
|
||||||
|
return zapcore.WarnLevel
|
||||||
|
case ErrorLevel:
|
||||||
|
return zapcore.ErrorLevel
|
||||||
|
case DPanicLevel:
|
||||||
|
return zapcore.DPanicLevel
|
||||||
|
case PanicLevel:
|
||||||
|
return zapcore.PanicLevel
|
||||||
|
case FatalLevel:
|
||||||
|
return zapcore.FatalLevel
|
||||||
|
default:
|
||||||
|
// 默认情况下返回 Info 级别,保证程序健壮性
|
||||||
|
return zapcore.InfoLevel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan 实现了 sql.Scanner 接口,GORM 在从数据库读取数据时会调用此方法。
|
||||||
|
func (al *SeverityLevel) Scan(value interface{}) error {
|
||||||
|
bytes, ok := value.([]byte)
|
||||||
|
if !ok {
|
||||||
|
// 尝试处理其他可能的类型,例如字符串
|
||||||
|
s, ok := value.(string)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("无法将值 %v (类型 %T) 扫描为 SeverityLevel", value, value)
|
||||||
|
}
|
||||||
|
*al = SeverityLevel(s)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
*al = SeverityLevel(bytes)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value 实现了 driver.Valuer 接口,GORM 在将数据写入数据库时会调用此方法。
|
||||||
|
func (al SeverityLevel) Value() (driver.Value, error) {
|
||||||
|
return string(al), nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,15 +1,25 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql/driver"
|
|
||||||
"errors"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/notify"
|
|
||||||
"go.uber.org/zap/zapcore"
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// NotifierType 定义了通知器的类型。
|
||||||
|
type NotifierType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// NotifierTypeSMTP 表示 SMTP 邮件通知器。
|
||||||
|
NotifierTypeSMTP NotifierType = "邮件"
|
||||||
|
// NotifierTypeWeChat 表示企业微信通知器。
|
||||||
|
NotifierTypeWeChat NotifierType = "企业微信"
|
||||||
|
// NotifierTypeLark 表示飞书通知器。
|
||||||
|
NotifierTypeLark NotifierType = "飞书"
|
||||||
|
// NotifierTypeLog 表示日志通知器,作为最终的告警记录渠道。
|
||||||
|
NotifierTypeLog NotifierType = "日志"
|
||||||
|
)
|
||||||
|
|
||||||
// NotificationStatus 定义了通知发送尝试的状态枚举。
|
// NotificationStatus 定义了通知发送尝试的状态枚举。
|
||||||
type NotificationStatus string
|
type NotificationStatus string
|
||||||
|
|
||||||
@@ -19,40 +29,12 @@ const (
|
|||||||
NotificationStatusSkipped NotificationStatus = "已跳过" // 通知因某些原因被跳过(例如:用户未配置联系方式)
|
NotificationStatusSkipped NotificationStatus = "已跳过" // 通知因某些原因被跳过(例如:用户未配置联系方式)
|
||||||
)
|
)
|
||||||
|
|
||||||
// LogLevel is a custom type for zapcore.Level to handle database scanning and valuing.
|
|
||||||
type LogLevel zapcore.Level
|
|
||||||
|
|
||||||
// Scan implements the sql.Scanner interface.
|
|
||||||
func (l *LogLevel) Scan(value interface{}) error {
|
|
||||||
var s string
|
|
||||||
switch v := value.(type) {
|
|
||||||
case []byte:
|
|
||||||
s = string(v)
|
|
||||||
case string:
|
|
||||||
s = v
|
|
||||||
default:
|
|
||||||
return errors.New("LogLevel的类型无效")
|
|
||||||
}
|
|
||||||
|
|
||||||
var zl zapcore.Level
|
|
||||||
if err := zl.UnmarshalText([]byte(s)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*l = LogLevel(zl)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Value implements the driver.Valuer interface.
|
|
||||||
func (l LogLevel) Value() (driver.Value, error) {
|
|
||||||
return (zapcore.Level)(l).String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notification 表示已发送或尝试发送的通知记录。
|
// Notification 表示已发送或尝试发送的通知记录。
|
||||||
type Notification struct {
|
type Notification struct {
|
||||||
gorm.Model
|
gorm.Model
|
||||||
|
|
||||||
// NotifierType 通知器类型 (例如:"邮件", "企业微信", "飞书", "日志")
|
// NotifierType 通知器类型 (例如:"邮件", "企业微信", "飞书", "日志")
|
||||||
NotifierType notify.NotifierType `gorm:"type:varchar(20);not null;index" json:"notifier_type"`
|
NotifierType NotifierType `gorm:"type:varchar(20);not null;index" json:"notifier_type"`
|
||||||
// UserID 接收通知的用户ID,用于追溯通知记录到特定用户
|
// UserID 接收通知的用户ID,用于追溯通知记录到特定用户
|
||||||
UserID uint `gorm:"index" json:"user_id"` // 增加 UserID 字段,并添加索引
|
UserID uint `gorm:"index" json:"user_id"` // 增加 UserID 字段,并添加索引
|
||||||
// Title 通知标题
|
// Title 通知标题
|
||||||
@@ -60,7 +42,7 @@ type Notification struct {
|
|||||||
// Message 通知内容
|
// Message 通知内容
|
||||||
Message string `gorm:"type:text;not null" json:"message"`
|
Message string `gorm:"type:text;not null" json:"message"`
|
||||||
// Level 通知级别 (例如:INFO, WARN, ERROR)
|
// Level 通知级别 (例如:INFO, WARN, ERROR)
|
||||||
Level LogLevel `gorm:"type:varchar(10);not null" json:"level"`
|
Level SeverityLevel `gorm:"type:varchar(10);not null" json:"level"`
|
||||||
// AlarmTimestamp 通知内容生成时的时间戳,与 ID 构成复合主键
|
// AlarmTimestamp 通知内容生成时的时间戳,与 ID 构成复合主键
|
||||||
AlarmTimestamp time.Time `gorm:"primaryKey;not null" json:"alarm_timestamp"`
|
AlarmTimestamp time.Time `gorm:"primaryKey;not null" json:"alarm_timestamp"`
|
||||||
// ToAddress 接收地址 (例如:邮箱地址, 企业微信ID, 日志标识符)
|
// ToAddress 接收地址 (例如:邮箱地址, 企业微信ID, 日志标识符)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -65,7 +66,7 @@ func (l *larkNotifier) Send(ctx context.Context, content AlarmContent, toAddr st
|
|||||||
"tag": "lark_md",
|
"tag": "lark_md",
|
||||||
"content": fmt.Sprintf("## %s\n**级别**: %s\n**时间**: %s\n\n%s",
|
"content": fmt.Sprintf("## %s\n**级别**: %s\n**时间**: %s\n\n%s",
|
||||||
content.Title,
|
content.Title,
|
||||||
content.Level.String(),
|
content.Level,
|
||||||
content.Timestamp.Format(DefaultTimeFormat),
|
content.Timestamp.Format(DefaultTimeFormat),
|
||||||
content.Message,
|
content.Message,
|
||||||
),
|
),
|
||||||
@@ -171,8 +172,8 @@ func (l *larkNotifier) getAccessToken(ctx context.Context) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Type 返回通知器的类型
|
// Type 返回通知器的类型
|
||||||
func (l *larkNotifier) Type() NotifierType {
|
func (l *larkNotifier) Type() models.NotifierType {
|
||||||
return NotifierTypeLark
|
return models.NotifierTypeLark
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- API 数据结构 ---
|
// --- API 数据结构 ---
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
|
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
// logNotifier 实现了 Notifier 接口,用于将告警信息记录到日志中。
|
// logNotifier 实现了 Notifier 接口,用于将告警信息记录到日志中。
|
||||||
@@ -24,10 +25,10 @@ func NewLogNotifier(ctx context.Context) Notifier {
|
|||||||
func (l *logNotifier) Send(ctx context.Context, content AlarmContent, toAddr string) error {
|
func (l *logNotifier) Send(ctx context.Context, content AlarmContent, toAddr string) error {
|
||||||
logger := logs.TraceLogger(ctx, l.ctx, "Send")
|
logger := logs.TraceLogger(ctx, l.ctx, "Send")
|
||||||
logger.Infow("告警已记录到日志",
|
logger.Infow("告警已记录到日志",
|
||||||
"notifierType", NotifierTypeLog,
|
"notifierType", models.NotifierTypeLog,
|
||||||
"title", content.Title,
|
"title", content.Title,
|
||||||
"message", content.Message,
|
"message", content.Message,
|
||||||
"level", content.Level.String(),
|
"level", content.Level,
|
||||||
"timestamp", content.Timestamp.Format(DefaultTimeFormat),
|
"timestamp", content.Timestamp.Format(DefaultTimeFormat),
|
||||||
"toAddr", toAddr,
|
"toAddr", toAddr,
|
||||||
)
|
)
|
||||||
@@ -35,6 +36,6 @@ func (l *logNotifier) Send(ctx context.Context, content AlarmContent, toAddr str
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Type 返回通知器的类型。
|
// Type 返回通知器的类型。
|
||||||
func (l *logNotifier) Type() NotifierType {
|
func (l *logNotifier) Type() models.NotifierType {
|
||||||
return NotifierTypeLog
|
return models.NotifierTypeLog
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,26 +4,12 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.uber.org/zap/zapcore"
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultTimeFormat 定义了所有通知中统一使用的时间格式。
|
// DefaultTimeFormat 定义了所有通知中统一使用的时间格式。
|
||||||
const DefaultTimeFormat = "2006-01-02 15:04:05"
|
const DefaultTimeFormat = "2006-01-02 15:04:05"
|
||||||
|
|
||||||
// NotifierType 定义了通知器的类型。
|
|
||||||
type NotifierType string
|
|
||||||
|
|
||||||
const (
|
|
||||||
// NotifierTypeSMTP 表示 SMTP 邮件通知器。
|
|
||||||
NotifierTypeSMTP NotifierType = "邮件"
|
|
||||||
// NotifierTypeWeChat 表示企业微信通知器。
|
|
||||||
NotifierTypeWeChat NotifierType = "企业微信"
|
|
||||||
// NotifierTypeLark 表示飞书通知器。
|
|
||||||
NotifierTypeLark NotifierType = "飞书"
|
|
||||||
// NotifierTypeLog 表示日志通知器,作为最终的告警记录渠道。
|
|
||||||
NotifierTypeLog NotifierType = "日志"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AlarmContent 定义了通知的内容
|
// AlarmContent 定义了通知的内容
|
||||||
type AlarmContent struct {
|
type AlarmContent struct {
|
||||||
// 通知标题
|
// 通知标题
|
||||||
@@ -31,7 +17,7 @@ type AlarmContent struct {
|
|||||||
// 通知信息
|
// 通知信息
|
||||||
Message string
|
Message string
|
||||||
// 通知级别
|
// 通知级别
|
||||||
Level zapcore.Level
|
Level models.SeverityLevel
|
||||||
// 通知时间
|
// 通知时间
|
||||||
Timestamp time.Time
|
Timestamp time.Time
|
||||||
}
|
}
|
||||||
@@ -41,5 +27,5 @@ type Notifier interface {
|
|||||||
// Send 发送通知
|
// Send 发送通知
|
||||||
Send(ctx context.Context, content AlarmContent, toAddr string) error
|
Send(ctx context.Context, content AlarmContent, toAddr string) error
|
||||||
// Type 返回通知器的类型
|
// Type 返回通知器的类型
|
||||||
Type() NotifierType
|
Type() models.NotifierType
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/smtp"
|
"net/smtp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
// smtpNotifier 实现了 Notifier 接口,用于通过 SMTP 发送邮件通知。
|
// smtpNotifier 实现了 Notifier 接口,用于通过 SMTP 发送邮件通知。
|
||||||
@@ -45,7 +47,7 @@ func (s *smtpNotifier) Send(ctx context.Context, content AlarmContent, toAddr st
|
|||||||
|
|
||||||
// 邮件正文
|
// 邮件正文
|
||||||
body := fmt.Sprintf("级别: %s\n时间: %s\n\n%s",
|
body := fmt.Sprintf("级别: %s\n时间: %s\n\n%s",
|
||||||
content.Level.String(),
|
content.Level,
|
||||||
content.Timestamp.Format(DefaultTimeFormat),
|
content.Timestamp.Format(DefaultTimeFormat),
|
||||||
content.Message,
|
content.Message,
|
||||||
)
|
)
|
||||||
@@ -71,6 +73,6 @@ func (s *smtpNotifier) Send(ctx context.Context, content AlarmContent, toAddr st
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Type 返回通知器的类型
|
// Type 返回通知器的类型
|
||||||
func (s *smtpNotifier) Type() NotifierType {
|
func (s *smtpNotifier) Type() models.NotifierType {
|
||||||
return NotifierTypeSMTP
|
return models.NotifierTypeSMTP
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -55,7 +57,7 @@ func (w *wechatNotifier) Send(ctx context.Context, content AlarmContent, toAddr
|
|||||||
// 2. 构建 markdown 内容
|
// 2. 构建 markdown 内容
|
||||||
markdownContent := fmt.Sprintf("## %s\n> 级别: <font color=\"warning\">%s</font>\n> 时间: %s\n\n%s",
|
markdownContent := fmt.Sprintf("## %s\n> 级别: <font color=\"warning\">%s</font>\n> 时间: %s\n\n%s",
|
||||||
content.Title,
|
content.Title,
|
||||||
content.Level.String(),
|
content.Level,
|
||||||
content.Timestamp.Format(DefaultTimeFormat),
|
content.Timestamp.Format(DefaultTimeFormat),
|
||||||
content.Message,
|
content.Message,
|
||||||
)
|
)
|
||||||
@@ -142,8 +144,8 @@ func (w *wechatNotifier) getAccessToken() (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Type 返回通知器的类型
|
// Type 返回通知器的类型
|
||||||
func (w *wechatNotifier) Type() NotifierType {
|
func (w *wechatNotifier) Type() models.NotifierType {
|
||||||
return NotifierTypeWeChat
|
return models.NotifierTypeWeChat
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- API 数据结构 ---
|
// --- API 数据结构 ---
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
|
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/notify"
|
|
||||||
|
|
||||||
"go.uber.org/zap/zapcore"
|
"go.uber.org/zap/zapcore"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
@@ -15,7 +14,7 @@ import (
|
|||||||
// NotificationListOptions 定义了查询通知列表时的可选参数
|
// NotificationListOptions 定义了查询通知列表时的可选参数
|
||||||
type NotificationListOptions struct {
|
type NotificationListOptions struct {
|
||||||
UserID *uint // 按用户ID过滤
|
UserID *uint // 按用户ID过滤
|
||||||
NotifierType *notify.NotifierType // 按通知器类型过滤
|
NotifierType *models.NotifierType // 按通知器类型过滤
|
||||||
Status *models.NotificationStatus // 按通知状态过滤 (例如:"success", "failed")
|
Status *models.NotificationStatus // 按通知状态过滤 (例如:"success", "failed")
|
||||||
Level *zapcore.Level // 按通知等级过滤 (例如:"info", "warning", "error")
|
Level *zapcore.Level // 按通知等级过滤 (例如:"info", "warning", "error")
|
||||||
StartTime *time.Time // 通知内容生成时间范围 - 开始时间 (对应 AlarmTimestamp)
|
StartTime *time.Time // 通知内容生成时间范围 - 开始时间 (对应 AlarmTimestamp)
|
||||||
|
|||||||
@@ -3,37 +3,8 @@ package command_generater
|
|||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
)
|
|
||||||
|
|
||||||
// ModbusFunctionCode 定义Modbus功能码的枚举类型
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||||
type ModbusFunctionCode byte
|
|
||||||
|
|
||||||
// 定义常用的Modbus功能码常量及其应用场景
|
|
||||||
const (
|
|
||||||
// ReadCoils 读取线圈状态 (0x01)
|
|
||||||
// 场景: 用于读取数字量输出(DO)或内部标志位的当前状态,这些状态通常是开关量。
|
|
||||||
ReadCoils ModbusFunctionCode = 0x01
|
|
||||||
// ReadDiscreteInputs 读取离散输入状态 (0x02)
|
|
||||||
// 场景: 用于读取数字量输入(DI)的当前状态,这些状态通常是外部传感器的开关量信号。
|
|
||||||
ReadDiscreteInputs ModbusFunctionCode = 0x02
|
|
||||||
// ReadHoldingRegisters 读取保持寄存器 (0x03)
|
|
||||||
// 场景: 用于读取设备内部可读写的参数或数据,例如温度设定值、电机速度等模拟量或配置数据。
|
|
||||||
ReadHoldingRegisters ModbusFunctionCode = 0x03
|
|
||||||
// ReadInputRegisters 读取输入寄存器 (0x04)
|
|
||||||
// 场景: 用于读取设备的模拟量输入(AI)数据,这些数据通常是只读的,例如当前温度、压力、电压等实时测量值。
|
|
||||||
ReadInputRegisters ModbusFunctionCode = 0x04
|
|
||||||
// WriteSingleCoil 写入单个线圈 (0x05)
|
|
||||||
// 场景: 用于控制单个数字量输出(DO),例如打开或关闭一个继电器、指示灯等。
|
|
||||||
WriteSingleCoil ModbusFunctionCode = 0x05
|
|
||||||
// WriteSingleRegister 写入单个保持寄存器 (0x06)
|
|
||||||
// 场景: 用于修改设备内部的单个可写参数,例如设置一个温度控制器的目标温度、调整一个阀门的开度等。
|
|
||||||
WriteSingleRegister ModbusFunctionCode = 0x06
|
|
||||||
// WriteMultipleCoils 写入多个线圈 (0x0F)
|
|
||||||
// 场景: 用于批量控制多个数字量输出(DO),例如同时打开或关闭一组继电器。
|
|
||||||
WriteMultipleCoils ModbusFunctionCode = 0x0F
|
|
||||||
// WriteMultipleRegisters 写入多个保持寄存器 (0x10)
|
|
||||||
// 场景: 用于批量修改设备内部的多个可写参数,例如一次性更新多个配置参数或模拟量输出值。
|
|
||||||
WriteMultipleRegisters ModbusFunctionCode = 0x10
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// GenerateModbusRTUReadCommand 生成Modbus RTU读取指令
|
// GenerateModbusRTUReadCommand 生成Modbus RTU读取指令
|
||||||
@@ -52,7 +23,7 @@ const (
|
|||||||
//
|
//
|
||||||
// []byte: 完整的Modbus RTU指令字节切片。
|
// []byte: 完整的Modbus RTU指令字节切片。
|
||||||
// error: 如果参数无效或生成过程中出现错误,则返回错误信息。
|
// error: 如果参数无效或生成过程中出现错误,则返回错误信息。
|
||||||
func GenerateModbusRTUReadCommand(slaveAddress uint8, functionCode ModbusFunctionCode, startAddress uint16, quantity uint16) ([]byte, error) {
|
func GenerateModbusRTUReadCommand(slaveAddress uint8, functionCode models.ModbusFunctionCode, startAddress uint16, quantity uint16) ([]byte, error) {
|
||||||
// 1. 校验输入参数
|
// 1. 校验输入参数
|
||||||
if slaveAddress == 0 || slaveAddress > 247 {
|
if slaveAddress == 0 || slaveAddress > 247 {
|
||||||
return nil, fmt.Errorf("从站地址无效: %d, 必须在1-247之间", slaveAddress)
|
return nil, fmt.Errorf("从站地址无效: %d, 必须在1-247之间", slaveAddress)
|
||||||
@@ -60,9 +31,9 @@ func GenerateModbusRTUReadCommand(slaveAddress uint8, functionCode ModbusFunctio
|
|||||||
|
|
||||||
// 校验功能码是否为读取类型
|
// 校验功能码是否为读取类型
|
||||||
switch functionCode {
|
switch functionCode {
|
||||||
case ReadCoils, ReadDiscreteInputs, ReadHoldingRegisters, ReadInputRegisters:
|
case models.ReadCoils, models.ReadDiscreteInputs, models.ReadHoldingRegisters, models.ReadInputRegisters:
|
||||||
// 这些是支持的读取功能码
|
// 这些是支持的读取功能码
|
||||||
case WriteSingleCoil, WriteSingleRegister, WriteMultipleCoils, WriteMultipleRegisters:
|
case models.WriteSingleCoil, models.WriteSingleRegister, models.WriteMultipleCoils, models.WriteMultipleRegisters:
|
||||||
return nil, fmt.Errorf("功能码 %X 是写入操作,请使用 GenerateModbusRTUWriteCoilCommand 或其他写入函数", functionCode)
|
return nil, fmt.Errorf("功能码 %X 是写入操作,请使用 GenerateModbusRTUWriteCoilCommand 或其他写入函数", functionCode)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("不支持的功能码: %X", functionCode)
|
return nil, fmt.Errorf("不支持的功能码: %X", functionCode)
|
||||||
@@ -130,7 +101,7 @@ func GenerateModbusRTUSwitchCommand(slaveAddress uint8, coilAddress uint16, onOf
|
|||||||
// 2. 构建PDU (协议数据单元) for WriteSingleCoil (0x05)
|
// 2. 构建PDU (协议数据单元) for WriteSingleCoil (0x05)
|
||||||
// PDU结构: 功能码 (1字节) + 线圈地址 (2字节) + 写入值 (2字节)
|
// PDU结构: 功能码 (1字节) + 线圈地址 (2字节) + 写入值 (2字节)
|
||||||
pdu := make([]byte, 5)
|
pdu := make([]byte, 5)
|
||||||
pdu[0] = byte(WriteSingleCoil)
|
pdu[0] = byte(models.WriteSingleCoil)
|
||||||
// Modbus协议中,地址和值都是大端字节序 (高位在前)
|
// Modbus协议中,地址和值都是大端字节序 (高位在前)
|
||||||
binary.BigEndian.PutUint16(pdu[1:3], coilAddress)
|
binary.BigEndian.PutUint16(pdu[1:3], coilAddress)
|
||||||
binary.BigEndian.PutUint16(pdu[3:5], writeValue)
|
binary.BigEndian.PutUint16(pdu[3:5], writeValue)
|
||||||
|
|||||||
Reference in New Issue
Block a user