From 2796d9bad705ba543225e439220cd36d7dd56c8e Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Fri, 7 Nov 2025 21:39:24 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=E9=83=A8=E5=88=86=E6=9E=9A?= =?UTF-8?q?=E4=B8=BE,=20=E8=AE=A9models=E5=8C=85=E4=B8=8D=E4=BE=9D?= =?UTF-8?q?=E8=B5=96=E5=85=B6=E4=BB=96=E9=A1=B9=E7=9B=AE=E4=B8=AD=E7=9A=84?= =?UTF-8?q?=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design/exceeding-threshold-alarm/index.md | 6 ++ internal/app/dto/notification_converter.go | 4 +- internal/app/dto/notification_dto.go | 9 ++- internal/core/component_initializers.go | 2 +- internal/domain/notify/notify.go | 30 ++++----- internal/infra/models/alarm.go | 36 +++++----- internal/infra/models/device_template.go | 37 ++++++++-- internal/infra/models/execution.go | 1 - internal/infra/models/models.go | 67 +++++++++++++++++++ internal/infra/models/notify.go | 50 +++++--------- internal/infra/notify/lark.go | 7 +- internal/infra/notify/log_notifier.go | 9 +-- internal/infra/notify/notify.go | 20 +----- internal/infra/notify/smtp.go | 8 ++- internal/infra/notify/wechat.go | 8 ++- .../repository/notification_repository.go | 3 +- .../utils/command_generater/modbus_rtu.go | 39 ++--------- 17 files changed, 188 insertions(+), 148 deletions(-) diff --git a/design/exceeding-threshold-alarm/index.md b/design/exceeding-threshold-alarm/index.md index 2792654..48f1016 100644 --- a/design/exceeding-threshold-alarm/index.md +++ b/design/exceeding-threshold-alarm/index.md @@ -121,3 +121,9 @@ * 区域主控告警配置接口: `/api/v1/alarm/region-config` * 普通设备告警配置接口: `/api/v1/alarm/device-config` 2. **接口职责**: 接口负责接收前端请求,调用应用服务层的阈值告警服务来完成实际的业务逻辑。 + + +# 实现记录 + +1. 定义告警表和告警历史表 +2. 重构部分枚举, 让models包不依赖其他项目中的包 \ No newline at end of file diff --git a/internal/app/dto/notification_converter.go b/internal/app/dto/notification_converter.go index fbc49b7..dddc5d2 100644 --- a/internal/app/dto/notification_converter.go +++ b/internal/app/dto/notification_converter.go @@ -2,8 +2,6 @@ package dto import ( "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" - - "go.uber.org/zap/zapcore" ) // NewListNotificationResponse 从模型数据创建通知列表响应 DTO @@ -18,7 +16,7 @@ func NewListNotificationResponse(data []models.Notification, total int64, page, UserID: item.UserID, Title: item.Title, Message: item.Message, - Level: zapcore.Level(item.Level), + Level: item.Level, AlarmTimestamp: item.AlarmTimestamp, ToAddress: item.ToAddress, Status: item.Status, diff --git a/internal/app/dto/notification_dto.go b/internal/app/dto/notification_dto.go index b493d4e..f0d32ac 100644 --- a/internal/app/dto/notification_dto.go +++ b/internal/app/dto/notification_dto.go @@ -4,7 +4,6 @@ import ( "time" "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" ) @@ -12,7 +11,7 @@ import ( // SendTestNotificationRequest 定义了发送测试通知请求的 JSON 结构 type SendTestNotificationRequest struct { // Type 指定要测试的通知渠道 - Type notify.NotifierType `json:"type" validate:"required"` + Type models.NotifierType `json:"type" validate:"required"` } // ListNotificationRequest 定义了获取通知列表的请求参数 @@ -20,7 +19,7 @@ type ListNotificationRequest struct { Page int `json:"page" query:"page"` PageSize int `json:"page_size" query:"page_size"` 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"` Level *zapcore.Level `json:"level" query:"level"` StartTime *time.Time `json:"start_time" query:"start_time"` @@ -33,11 +32,11 @@ type NotificationDTO struct { ID uint `json:"id"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` - NotifierType notify.NotifierType `json:"notifier_type"` + NotifierType models.NotifierType `json:"notifier_type"` UserID uint `json:"user_id"` Title string `json:"title"` Message string `json:"message"` - Level zapcore.Level `json:"level"` + Level models.SeverityLevel `json:"level"` AlarmTimestamp time.Time `json:"alarm_timestamp"` ToAddress string `json:"to_address"` Status models.NotificationStatus `json:"status"` diff --git a/internal/core/component_initializers.go b/internal/core/component_initializers.go index fb94a45..ee8b4cd 100644 --- a/internal/core/component_initializers.go +++ b/internal/core/component_initializers.go @@ -353,7 +353,7 @@ func initNotifyService( // 3. 动态确定首选通知器 var primaryNotifier notify.Notifier - primaryNotifierType := notify.NotifierType(cfg.Primary) + primaryNotifierType := models.NotifierType(cfg.Primary) // 检查用户指定的主渠道是否已启用 for _, n := range availableNotifiers { diff --git a/internal/domain/notify/notify.go b/internal/domain/notify/notify.go index 88f9647..53f1168 100644 --- a/internal/domain/notify/notify.go +++ b/internal/domain/notify/notify.go @@ -11,8 +11,6 @@ import ( "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/repository" - - "go.uber.org/zap" ) // Service 定义了通知领域的核心业务逻辑接口 @@ -24,14 +22,14 @@ type Service interface { BroadcastAlarm(ctx context.Context, content notify.AlarmContent) error // SendTestMessage 向指定用户发送一条测试消息,用于手动验证特定通知渠道的配置。 - SendTestMessage(ctx context.Context, userID uint, notifierType notify.NotifierType) error + SendTestMessage(ctx context.Context, userID uint, notifierType models.NotifierType) error } // failoverService 是 Service 接口的实现,提供了故障转移功能 type failoverService struct { ctx context.Context userRepo repository.UserRepository - notifiers map[notify.NotifierType]notify.Notifier + notifiers map[models.NotifierType]notify.Notifier primaryNotifier notify.Notifier failureThreshold int failureCounters *sync.Map // 使用 sync.Map 来安全地并发读写失败计数, key: userID (uint), value: counter (int) @@ -43,11 +41,11 @@ func NewFailoverService( ctx context.Context, userRepo repository.UserRepository, notifiers []notify.Notifier, - primaryNotifierType notify.NotifierType, + primaryNotifierType models.NotifierType, failureThreshold int, notificationRepo repository.NotificationRepository, ) (Service, error) { - notifierMap := make(map[notify.NotifierType]notify.Notifier) + notifierMap := make(map[models.NotifierType]notify.Notifier) for _, n := range notifiers { notifierMap[n.Type()] = n } @@ -189,7 +187,7 @@ func (s *failoverService) sendAlarmToUser(ctx context.Context, userID uint, cont } // 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") user, err := s.userRepo.FindByID(serviceCtx, userID) if err != nil { @@ -210,7 +208,7 @@ func (s *failoverService) SendTestMessage(ctx context.Context, userID uint, noti s.recordNotificationAttempt(serviceCtx, userID, notifierType, notify.AlarmContent{ Title: "通知服务测试", Message: fmt.Sprintf("这是一条来自【%s】渠道的测试消息。如果您收到此消息,说明您的配置正确。", notifierType), - Level: zap.InfoLevel, + Level: models.InfoLevel, Timestamp: time.Now(), }, "", models.NotificationStatusFailed, 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{ Title: "通知服务测试", Message: fmt.Sprintf("这是一条来自【%s】渠道的测试消息。如果您收到此消息,说明您的配置正确。", notifierType), - Level: zap.InfoLevel, + Level: models.InfoLevel, Timestamp: time.Now(), } @@ -239,15 +237,15 @@ func (s *failoverService) SendTestMessage(ctx context.Context, userID uint, noti } // getAddressForNotifier 是一个辅助函数,根据通知器类型从 ContactInfo 中获取对应的地址 -func getAddressForNotifier(notifierType notify.NotifierType, contact models.ContactInfo) string { +func getAddressForNotifier(notifierType models.NotifierType, contact models.ContactInfo) string { switch notifierType { - case notify.NotifierTypeSMTP: + case models.NotifierTypeSMTP: return contact.Email - case notify.NotifierTypeWeChat: + case models.NotifierTypeWeChat: return contact.WeChat - case notify.NotifierTypeLark: + case models.NotifierTypeLark: return contact.Feishu - case notify.NotifierTypeLog: + case models.NotifierTypeLog: return "log" // LogNotifier不需要具体的地址,但为了函数签名一致性,返回一个无意义的非空字符串以绕过配置存在检查 default: return "" @@ -264,7 +262,7 @@ func getAddressForNotifier(notifierType notify.NotifierType, contact models.Cont func (s *failoverService) recordNotificationAttempt( ctx context.Context, userID uint, - notifierType notify.NotifierType, + notifierType models.NotifierType, content notify.AlarmContent, toAddress string, status models.NotificationStatus, @@ -281,7 +279,7 @@ func (s *failoverService) recordNotificationAttempt( UserID: userID, Title: content.Title, Message: content.Message, - Level: models.LogLevel(content.Level), + Level: content.Level, AlarmTimestamp: content.Timestamp, ToAddress: toAddress, Status: status, diff --git a/internal/infra/models/alarm.go b/internal/infra/models/alarm.go index 2d7256f..33a3425 100644 --- a/internal/infra/models/alarm.go +++ b/internal/infra/models/alarm.go @@ -10,14 +10,14 @@ import ( // 活跃告警会被更新(如确认状态),因此保留 gorm.Model 以包含所有标准字段。 type ActiveAlarm struct { gorm.Model - FarmID uint `gorm:"not null;comment:猪场ID" json:"farm_id,omitempty"` - DeviceID uint `gorm:"not null;comment:设备ID" json:"device_id,omitempty"` - AlarmType string `gorm:"comment:告警类型" json:"alarm_type,omitempty"` - AlarmLevel string `gorm:"comment:告警级别" json:"alarm_level,omitempty"` - AlarmContent string `gorm:"comment:告警内容描述" json:"alarm_content,omitempty"` - TriggerTime time.Time `gorm:"not null;comment:告警触发时间" json:"trigger_time"` - IsAcknowledged bool `gorm:"default:false;comment:是否已确认" json:"is_acknowledged,omitempty"` - AcknowledgedBy string `gorm:"comment:确认人" json:"acknowledged_by,omitempty"` + FarmID uint `gorm:"not null;comment:猪场ID" json:"farm_id,omitempty"` + DeviceID uint `gorm:"not null;comment:设备ID" json:"device_id,omitempty"` + AlarmType string `gorm:"comment:告警类型" json:"alarm_type,omitempty"` + AlarmLevel SeverityLevel `gorm:"comment:告警级别" json:"alarm_level,omitempty"` + AlarmContent string `gorm:"comment:告警内容描述" json:"alarm_content,omitempty"` + TriggerTime time.Time `gorm:"not null;comment:告警触发时间" json:"trigger_time"` + IsAcknowledged bool `gorm:"default:false;comment:是否已确认" json:"is_acknowledged,omitempty"` + AcknowledgedBy string `gorm:"comment:确认人" json:"acknowledged_by,omitempty"` // 使用指针类型 *time.Time 来表示可为空的确认时间 AcknowledgedTime *time.Time `gorm:"comment:确认时间" json:"acknowledged_time,omitempty"` } @@ -32,16 +32,16 @@ func (ActiveAlarm) TableName() string { // ID 和 CreatedAt 共同构成联合主键,以满足 TimescaleDB 超表的要求。 type HistoricalAlarm struct { // 手动定义主键,ID 仍然自增 - ID uint `gorm:"primaryKey;autoIncrement;comment:主键ID" json:"id,omitempty"` - FarmID uint `gorm:"not null;comment:猪场ID" json:"farm_id,omitempty"` - DeviceID uint `gorm:"not null;comment:设备ID" json:"device_id,omitempty"` - AlarmType string `gorm:"comment:告警类型" json:"alarm_type,omitempty"` - AlarmLevel string `gorm:"comment:告警级别" json:"alarm_level,omitempty"` - AlarmContent string `gorm:"comment:告警内容描述" json:"alarm_content,omitempty"` - TriggerTime time.Time `gorm:"not null;comment:告警触发时间" json:"trigger_time"` - ResolveTime time.Time `gorm:"not null;comment:告警解决时间" json:"resolve_time"` - ResolveMethod string `gorm:"comment:告警解决方式" json:"resolve_method,omitempty"` - ResolvedBy string `gorm:"comment:告警解决人" json:"resolved_by,omitempty"` + ID uint `gorm:"primaryKey;autoIncrement;comment:主键ID" json:"id,omitempty"` + FarmID uint `gorm:"not null;comment:猪场ID" json:"farm_id,omitempty"` + DeviceID uint `gorm:"not null;comment:设备ID" json:"device_id,omitempty"` + AlarmType string `gorm:"comment:告警类型" json:"alarm_type,omitempty"` + AlarmLevel SeverityLevel `gorm:"comment:告警级别" json:"alarm_level,omitempty"` + AlarmContent string `gorm:"comment:告警内容描述" json:"alarm_content,omitempty"` + TriggerTime time.Time `gorm:"not null;comment:告警触发时间" json:"trigger_time"` + ResolveTime time.Time `gorm:"not null;comment:告警解决时间" json:"resolve_time"` + ResolveMethod string `gorm:"comment:告警解决方式" json:"resolve_method,omitempty"` + ResolvedBy string `gorm:"comment:告警解决人" json:"resolved_by,omitempty"` // 将 CreatedAt 作为联合主键的一部分,用于 TimescaleDB 分区 CreatedAt time.Time `gorm:"primaryKey;comment:创建时间" json:"created_at"` } diff --git a/internal/infra/models/device_template.go b/internal/infra/models/device_template.go index bbb432b..8960046 100644 --- a/internal/infra/models/device_template.go +++ b/internal/infra/models/device_template.go @@ -5,12 +5,41 @@ import ( "errors" "fmt" - "git.huangwc.com/pig/pig-farm-controller/internal/infra/utils/command_generater" - "gorm.io/datatypes" "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 定义了设备模板的宽泛类别 type DeviceCategory string @@ -51,7 +80,7 @@ func (sc *SwitchCommands) SelfCheck() error { // SensorCommands 定义了传感器读取指令所需的Modbus参数 type SensorCommands struct { // ModbusFunctionCode 记录Modbus功能码,例如 ReadHoldingRegisters。(一般是第二字节) - ModbusFunctionCode command_generater.ModbusFunctionCode `json:"modbus_function_code"` + ModbusFunctionCode ModbusFunctionCode `json:"modbus_function_code"` // ModbusStartAddress 记录Modbus寄存器的起始地址,用于生成指令。(一般是第三到四字节) ModbusStartAddress uint16 `json:"modbus_start_address"` // ModbusQuantity 记录Modbus寄存器的数量,用于生成指令。(一般是五到六字节) @@ -62,7 +91,7 @@ type SensorCommands struct { func (sc *SensorCommands) SelfCheck() error { // 校验ModbusFunctionCode是否为读取类型 switch sc.ModbusFunctionCode { - case command_generater.ReadCoils, command_generater.ReadDiscreteInputs, command_generater.ReadHoldingRegisters, command_generater.ReadInputRegisters: + case ReadCoils, ReadDiscreteInputs, ReadHoldingRegisters, ReadInputRegisters: // 支持的读取功能码 default: return fmt.Errorf("'sensor' 指令集 ModbusFunctionCode %X 无效或不是读取类型", sc.ModbusFunctionCode) diff --git a/internal/infra/models/execution.go b/internal/infra/models/execution.go index 3c20751..e8804d2 100644 --- a/internal/infra/models/execution.go +++ b/internal/infra/models/execution.go @@ -154,7 +154,6 @@ func (PendingCollection) TableName() string { } // --- 用户审计日志 --- -// TODO 这些变量放这个包合适吗? // --- 审计日志状态常量 --- type AuditStatus string diff --git a/internal/infra/models/models.go b/internal/infra/models/models.go index 3d360a4..13c5418 100644 --- a/internal/infra/models/models.go +++ b/internal/infra/models/models.go @@ -6,6 +6,8 @@ import ( "fmt" "strconv" "strings" + + "go.uber.org/zap/zapcore" ) // GetAllModels 返回一个包含所有数据库模型实例的切片。 @@ -129,3 +131,68 @@ func (a *UintArray) Scan(src interface{}) error { *a = arr 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 +} diff --git a/internal/infra/models/notify.go b/internal/infra/models/notify.go index 291ee9b..d63b5cf 100644 --- a/internal/infra/models/notify.go +++ b/internal/infra/models/notify.go @@ -1,15 +1,25 @@ package models import ( - "database/sql/driver" - "errors" "time" - "git.huangwc.com/pig/pig-farm-controller/internal/infra/notify" - "go.uber.org/zap/zapcore" "gorm.io/gorm" ) +// NotifierType 定义了通知器的类型。 +type NotifierType string + +const ( + // NotifierTypeSMTP 表示 SMTP 邮件通知器。 + NotifierTypeSMTP NotifierType = "邮件" + // NotifierTypeWeChat 表示企业微信通知器。 + NotifierTypeWeChat NotifierType = "企业微信" + // NotifierTypeLark 表示飞书通知器。 + NotifierTypeLark NotifierType = "飞书" + // NotifierTypeLog 表示日志通知器,作为最终的告警记录渠道。 + NotifierTypeLog NotifierType = "日志" +) + // NotificationStatus 定义了通知发送尝试的状态枚举。 type NotificationStatus string @@ -19,40 +29,12 @@ const ( 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 表示已发送或尝试发送的通知记录。 type Notification struct { gorm.Model // 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 uint `gorm:"index" json:"user_id"` // 增加 UserID 字段,并添加索引 // Title 通知标题 @@ -60,7 +42,7 @@ type Notification struct { // Message 通知内容 Message string `gorm:"type:text;not null" json:"message"` // 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 time.Time `gorm:"primaryKey;not null" json:"alarm_timestamp"` // ToAddress 接收地址 (例如:邮箱地址, 企业微信ID, 日志标识符) diff --git a/internal/infra/notify/lark.go b/internal/infra/notify/lark.go index c1971ca..f49ff7e 100644 --- a/internal/infra/notify/lark.go +++ b/internal/infra/notify/lark.go @@ -10,6 +10,7 @@ import ( "time" "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" ) const ( @@ -65,7 +66,7 @@ func (l *larkNotifier) Send(ctx context.Context, content AlarmContent, toAddr st "tag": "lark_md", "content": fmt.Sprintf("## %s\n**级别**: %s\n**时间**: %s\n\n%s", content.Title, - content.Level.String(), + content.Level, content.Timestamp.Format(DefaultTimeFormat), content.Message, ), @@ -171,8 +172,8 @@ func (l *larkNotifier) getAccessToken(ctx context.Context) (string, error) { } // Type 返回通知器的类型 -func (l *larkNotifier) Type() NotifierType { - return NotifierTypeLark +func (l *larkNotifier) Type() models.NotifierType { + return models.NotifierTypeLark } // --- API 数据结构 --- diff --git a/internal/infra/notify/log_notifier.go b/internal/infra/notify/log_notifier.go index 001bdb6..f799c6c 100644 --- a/internal/infra/notify/log_notifier.go +++ b/internal/infra/notify/log_notifier.go @@ -4,6 +4,7 @@ import ( "context" "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" ) // logNotifier 实现了 Notifier 接口,用于将告警信息记录到日志中。 @@ -24,10 +25,10 @@ func NewLogNotifier(ctx context.Context) Notifier { func (l *logNotifier) Send(ctx context.Context, content AlarmContent, toAddr string) error { logger := logs.TraceLogger(ctx, l.ctx, "Send") logger.Infow("告警已记录到日志", - "notifierType", NotifierTypeLog, + "notifierType", models.NotifierTypeLog, "title", content.Title, "message", content.Message, - "level", content.Level.String(), + "level", content.Level, "timestamp", content.Timestamp.Format(DefaultTimeFormat), "toAddr", toAddr, ) @@ -35,6 +36,6 @@ func (l *logNotifier) Send(ctx context.Context, content AlarmContent, toAddr str } // Type 返回通知器的类型。 -func (l *logNotifier) Type() NotifierType { - return NotifierTypeLog +func (l *logNotifier) Type() models.NotifierType { + return models.NotifierTypeLog } diff --git a/internal/infra/notify/notify.go b/internal/infra/notify/notify.go index 4f86074..e34db31 100644 --- a/internal/infra/notify/notify.go +++ b/internal/infra/notify/notify.go @@ -4,26 +4,12 @@ import ( "context" "time" - "go.uber.org/zap/zapcore" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" ) // DefaultTimeFormat 定义了所有通知中统一使用的时间格式。 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 定义了通知的内容 type AlarmContent struct { // 通知标题 @@ -31,7 +17,7 @@ type AlarmContent struct { // 通知信息 Message string // 通知级别 - Level zapcore.Level + Level models.SeverityLevel // 通知时间 Timestamp time.Time } @@ -41,5 +27,5 @@ type Notifier interface { // Send 发送通知 Send(ctx context.Context, content AlarmContent, toAddr string) error // Type 返回通知器的类型 - Type() NotifierType + Type() models.NotifierType } diff --git a/internal/infra/notify/smtp.go b/internal/infra/notify/smtp.go index 131e788..31a06bf 100644 --- a/internal/infra/notify/smtp.go +++ b/internal/infra/notify/smtp.go @@ -5,6 +5,8 @@ import ( "fmt" "net/smtp" "strings" + + "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" ) // 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", - content.Level.String(), + content.Level, content.Timestamp.Format(DefaultTimeFormat), content.Message, ) @@ -71,6 +73,6 @@ func (s *smtpNotifier) Send(ctx context.Context, content AlarmContent, toAddr st } // Type 返回通知器的类型 -func (s *smtpNotifier) Type() NotifierType { - return NotifierTypeSMTP +func (s *smtpNotifier) Type() models.NotifierType { + return models.NotifierTypeSMTP } diff --git a/internal/infra/notify/wechat.go b/internal/infra/notify/wechat.go index ec5c33d..7e783b5 100644 --- a/internal/infra/notify/wechat.go +++ b/internal/infra/notify/wechat.go @@ -9,6 +9,8 @@ import ( "strings" "sync" "time" + + "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" ) const ( @@ -55,7 +57,7 @@ func (w *wechatNotifier) Send(ctx context.Context, content AlarmContent, toAddr // 2. 构建 markdown 内容 markdownContent := fmt.Sprintf("## %s\n> 级别: %s\n> 时间: %s\n\n%s", content.Title, - content.Level.String(), + content.Level, content.Timestamp.Format(DefaultTimeFormat), content.Message, ) @@ -142,8 +144,8 @@ func (w *wechatNotifier) getAccessToken() (string, error) { } // Type 返回通知器的类型 -func (w *wechatNotifier) Type() NotifierType { - return NotifierTypeWeChat +func (w *wechatNotifier) Type() models.NotifierType { + return models.NotifierTypeWeChat } // --- API 数据结构 --- diff --git a/internal/infra/repository/notification_repository.go b/internal/infra/repository/notification_repository.go index 4055966..10798fc 100644 --- a/internal/infra/repository/notification_repository.go +++ b/internal/infra/repository/notification_repository.go @@ -6,7 +6,6 @@ import ( "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/notify" "go.uber.org/zap/zapcore" "gorm.io/gorm" @@ -15,7 +14,7 @@ import ( // NotificationListOptions 定义了查询通知列表时的可选参数 type NotificationListOptions struct { UserID *uint // 按用户ID过滤 - NotifierType *notify.NotifierType // 按通知器类型过滤 + NotifierType *models.NotifierType // 按通知器类型过滤 Status *models.NotificationStatus // 按通知状态过滤 (例如:"success", "failed") Level *zapcore.Level // 按通知等级过滤 (例如:"info", "warning", "error") StartTime *time.Time // 通知内容生成时间范围 - 开始时间 (对应 AlarmTimestamp) diff --git a/internal/infra/utils/command_generater/modbus_rtu.go b/internal/infra/utils/command_generater/modbus_rtu.go index f9afbe1..457f095 100644 --- a/internal/infra/utils/command_generater/modbus_rtu.go +++ b/internal/infra/utils/command_generater/modbus_rtu.go @@ -3,37 +3,8 @@ package command_generater import ( "encoding/binary" "fmt" -) -// 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 + "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" ) // GenerateModbusRTUReadCommand 生成Modbus RTU读取指令 @@ -52,7 +23,7 @@ const ( // // []byte: 完整的Modbus RTU指令字节切片。 // 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. 校验输入参数 if slaveAddress == 0 || slaveAddress > 247 { return nil, fmt.Errorf("从站地址无效: %d, 必须在1-247之间", slaveAddress) @@ -60,9 +31,9 @@ func GenerateModbusRTUReadCommand(slaveAddress uint8, functionCode ModbusFunctio // 校验功能码是否为读取类型 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) default: return nil, fmt.Errorf("不支持的功能码: %X", functionCode) @@ -130,7 +101,7 @@ func GenerateModbusRTUSwitchCommand(slaveAddress uint8, coilAddress uint16, onOf // 2. 构建PDU (协议数据单元) for WriteSingleCoil (0x05) // PDU结构: 功能码 (1字节) + 线圈地址 (2字节) + 写入值 (2字节) pdu := make([]byte, 5) - pdu[0] = byte(WriteSingleCoil) + pdu[0] = byte(models.WriteSingleCoil) // Modbus协议中,地址和值都是大端字节序 (高位在前) binary.BigEndian.PutUint16(pdu[1:3], coilAddress) binary.BigEndian.PutUint16(pdu[3:5], writeValue)