Compare commits
	
		
			6 Commits
		
	
	
		
			bd8729d473
			...
			43c1839345
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 43c1839345 | |||
| f62cc1c4a9 | |||
| f6d2069e1a | |||
| f33e14f60f | |||
| d6f275b2d1 | |||
| d8de5a68eb | 
							
								
								
									
										259
									
								
								docs/docs.go
									
									
									
									
									
								
							
							
						
						
									
										259
									
								
								docs/docs.go
									
									
									
									
									
								
							| @@ -975,6 +975,149 @@ const docTemplate = `{ | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         "/api/v1/monitor/notifications": { | ||||
|             "get": { | ||||
|                 "security": [ | ||||
|                     { | ||||
|                         "BearerAuth": [] | ||||
|                     } | ||||
|                 ], | ||||
|                 "description": "根据提供的过滤条件,分页获取通知列表", | ||||
|                 "produces": [ | ||||
|                     "application/json" | ||||
|                 ], | ||||
|                 "tags": [ | ||||
|                     "数据监控" | ||||
|                 ], | ||||
|                 "summary": "批量查询通知", | ||||
|                 "parameters": [ | ||||
|                     { | ||||
|                         "type": "string", | ||||
|                         "name": "end_time", | ||||
|                         "in": "query" | ||||
|                     }, | ||||
|                     { | ||||
|                         "enum": [ | ||||
|                             7, | ||||
|                             -1, | ||||
|                             0, | ||||
|                             1, | ||||
|                             2, | ||||
|                             3, | ||||
|                             4, | ||||
|                             5, | ||||
|                             -1, | ||||
|                             5, | ||||
|                             6 | ||||
|                         ], | ||||
|                         "type": "integer", | ||||
|                         "format": "int32", | ||||
|                         "x-enum-varnames": [ | ||||
|                             "_numLevels", | ||||
|                             "DebugLevel", | ||||
|                             "InfoLevel", | ||||
|                             "WarnLevel", | ||||
|                             "ErrorLevel", | ||||
|                             "DPanicLevel", | ||||
|                             "PanicLevel", | ||||
|                             "FatalLevel", | ||||
|                             "_minLevel", | ||||
|                             "_maxLevel", | ||||
|                             "InvalidLevel" | ||||
|                         ], | ||||
|                         "name": "level", | ||||
|                         "in": "query" | ||||
|                     }, | ||||
|                     { | ||||
|                         "enum": [ | ||||
|                             "邮件", | ||||
|                             "企业微信", | ||||
|                             "飞书", | ||||
|                             "日志" | ||||
|                         ], | ||||
|                         "type": "string", | ||||
|                         "x-enum-varnames": [ | ||||
|                             "NotifierTypeSMTP", | ||||
|                             "NotifierTypeWeChat", | ||||
|                             "NotifierTypeLark", | ||||
|                             "NotifierTypeLog" | ||||
|                         ], | ||||
|                         "name": "notifier_type", | ||||
|                         "in": "query" | ||||
|                     }, | ||||
|                     { | ||||
|                         "type": "string", | ||||
|                         "name": "order_by", | ||||
|                         "in": "query" | ||||
|                     }, | ||||
|                     { | ||||
|                         "type": "integer", | ||||
|                         "name": "page", | ||||
|                         "in": "query" | ||||
|                     }, | ||||
|                     { | ||||
|                         "type": "integer", | ||||
|                         "name": "pageSize", | ||||
|                         "in": "query" | ||||
|                     }, | ||||
|                     { | ||||
|                         "type": "string", | ||||
|                         "name": "start_time", | ||||
|                         "in": "query" | ||||
|                     }, | ||||
|                     { | ||||
|                         "enum": [ | ||||
|                             "发送成功", | ||||
|                             "发送失败", | ||||
|                             "已跳过" | ||||
|                         ], | ||||
|                         "type": "string", | ||||
|                         "x-enum-comments": { | ||||
|                             "NotificationStatusFailed": "通知发送失败", | ||||
|                             "NotificationStatusSkipped": "通知因某些原因被跳过(例如:用户未配置联系方式)", | ||||
|                             "NotificationStatusSuccess": "通知已成功发送" | ||||
|                         }, | ||||
|                         "x-enum-descriptions": [ | ||||
|                             "通知已成功发送", | ||||
|                             "通知发送失败", | ||||
|                             "通知因某些原因被跳过(例如:用户未配置联系方式)" | ||||
|                         ], | ||||
|                         "x-enum-varnames": [ | ||||
|                             "NotificationStatusSuccess", | ||||
|                             "NotificationStatusFailed", | ||||
|                             "NotificationStatusSkipped" | ||||
|                         ], | ||||
|                         "name": "status", | ||||
|                         "in": "query" | ||||
|                     }, | ||||
|                     { | ||||
|                         "type": "integer", | ||||
|                         "name": "user_id", | ||||
|                         "in": "query" | ||||
|                     } | ||||
|                 ], | ||||
|                 "responses": { | ||||
|                     "200": { | ||||
|                         "description": "OK", | ||||
|                         "schema": { | ||||
|                             "allOf": [ | ||||
|                                 { | ||||
|                                     "$ref": "#/definitions/controller.Response" | ||||
|                                 }, | ||||
|                                 { | ||||
|                                     "type": "object", | ||||
|                                     "properties": { | ||||
|                                         "data": { | ||||
|                                             "$ref": "#/definitions/dto.ListNotificationResponse" | ||||
|                                         } | ||||
|                                     } | ||||
|                                 } | ||||
|                             ] | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         "/api/v1/monitor/pending-collections": { | ||||
|             "get": { | ||||
|                 "security": [ | ||||
| @@ -4489,6 +4632,20 @@ const docTemplate = `{ | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         "dto.ListNotificationResponse": { | ||||
|             "type": "object", | ||||
|             "properties": { | ||||
|                 "list": { | ||||
|                     "type": "array", | ||||
|                     "items": { | ||||
|                         "$ref": "#/definitions/dto.NotificationDTO" | ||||
|                     } | ||||
|                 }, | ||||
|                 "pagination": { | ||||
|                     "$ref": "#/definitions/dto.PaginationDTO" | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         "dto.ListPendingCollectionResponse": { | ||||
|             "type": "object", | ||||
|             "properties": { | ||||
| @@ -4797,6 +4954,47 @@ const docTemplate = `{ | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         "dto.NotificationDTO": { | ||||
|             "type": "object", | ||||
|             "properties": { | ||||
|                 "alarm_timestamp": { | ||||
|                     "type": "string" | ||||
|                 }, | ||||
|                 "created_at": { | ||||
|                     "type": "string" | ||||
|                 }, | ||||
|                 "error_message": { | ||||
|                     "type": "string" | ||||
|                 }, | ||||
|                 "id": { | ||||
|                     "type": "integer" | ||||
|                 }, | ||||
|                 "level": { | ||||
|                     "$ref": "#/definitions/zapcore.Level" | ||||
|                 }, | ||||
|                 "message": { | ||||
|                     "type": "string" | ||||
|                 }, | ||||
|                 "notifier_type": { | ||||
|                     "$ref": "#/definitions/notify.NotifierType" | ||||
|                 }, | ||||
|                 "status": { | ||||
|                     "$ref": "#/definitions/models.NotificationStatus" | ||||
|                 }, | ||||
|                 "title": { | ||||
|                     "type": "string" | ||||
|                 }, | ||||
|                 "to_address": { | ||||
|                     "type": "string" | ||||
|                 }, | ||||
|                 "updated_at": { | ||||
|                     "type": "string" | ||||
|                 }, | ||||
|                 "user_id": { | ||||
|                     "type": "integer" | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         "dto.PaginationDTO": { | ||||
|             "type": "object", | ||||
|             "properties": { | ||||
| @@ -6275,6 +6473,29 @@ const docTemplate = `{ | ||||
|                 "ReasonTypeHealthCare" | ||||
|             ] | ||||
|         }, | ||||
|         "models.NotificationStatus": { | ||||
|             "type": "string", | ||||
|             "enum": [ | ||||
|                 "发送成功", | ||||
|                 "发送失败", | ||||
|                 "已跳过" | ||||
|             ], | ||||
|             "x-enum-comments": { | ||||
|                 "NotificationStatusFailed": "通知发送失败", | ||||
|                 "NotificationStatusSkipped": "通知因某些原因被跳过(例如:用户未配置联系方式)", | ||||
|                 "NotificationStatusSuccess": "通知已成功发送" | ||||
|             }, | ||||
|             "x-enum-descriptions": [ | ||||
|                 "通知已成功发送", | ||||
|                 "通知发送失败", | ||||
|                 "通知因某些原因被跳过(例如:用户未配置联系方式)" | ||||
|             ], | ||||
|             "x-enum-varnames": [ | ||||
|                 "NotificationStatusSuccess", | ||||
|                 "NotificationStatusFailed", | ||||
|                 "NotificationStatusSkipped" | ||||
|             ] | ||||
|         }, | ||||
|         "models.PenStatus": { | ||||
|             "type": "string", | ||||
|             "enum": [ | ||||
| @@ -6608,10 +6829,10 @@ const docTemplate = `{ | ||||
|         "notify.NotifierType": { | ||||
|             "type": "string", | ||||
|             "enum": [ | ||||
|                 "smtp", | ||||
|                 "wechat", | ||||
|                 "lark", | ||||
|                 "log" | ||||
|                 "邮件", | ||||
|                 "企业微信", | ||||
|                 "飞书", | ||||
|                 "日志" | ||||
|             ], | ||||
|             "x-enum-varnames": [ | ||||
|                 "NotifierTypeSMTP", | ||||
| @@ -6619,6 +6840,36 @@ const docTemplate = `{ | ||||
|                 "NotifierTypeLark", | ||||
|                 "NotifierTypeLog" | ||||
|             ] | ||||
|         }, | ||||
|         "zapcore.Level": { | ||||
|             "type": "integer", | ||||
|             "format": "int32", | ||||
|             "enum": [ | ||||
|                 7, | ||||
|                 -1, | ||||
|                 0, | ||||
|                 1, | ||||
|                 2, | ||||
|                 3, | ||||
|                 4, | ||||
|                 5, | ||||
|                 -1, | ||||
|                 5, | ||||
|                 6 | ||||
|             ], | ||||
|             "x-enum-varnames": [ | ||||
|                 "_numLevels", | ||||
|                 "DebugLevel", | ||||
|                 "InfoLevel", | ||||
|                 "WarnLevel", | ||||
|                 "ErrorLevel", | ||||
|                 "DPanicLevel", | ||||
|                 "PanicLevel", | ||||
|                 "FatalLevel", | ||||
|                 "_minLevel", | ||||
|                 "_maxLevel", | ||||
|                 "InvalidLevel" | ||||
|             ] | ||||
|         } | ||||
|     }, | ||||
|     "securityDefinitions": { | ||||
|   | ||||
| @@ -967,6 +967,149 @@ | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         "/api/v1/monitor/notifications": { | ||||
|             "get": { | ||||
|                 "security": [ | ||||
|                     { | ||||
|                         "BearerAuth": [] | ||||
|                     } | ||||
|                 ], | ||||
|                 "description": "根据提供的过滤条件,分页获取通知列表", | ||||
|                 "produces": [ | ||||
|                     "application/json" | ||||
|                 ], | ||||
|                 "tags": [ | ||||
|                     "数据监控" | ||||
|                 ], | ||||
|                 "summary": "批量查询通知", | ||||
|                 "parameters": [ | ||||
|                     { | ||||
|                         "type": "string", | ||||
|                         "name": "end_time", | ||||
|                         "in": "query" | ||||
|                     }, | ||||
|                     { | ||||
|                         "enum": [ | ||||
|                             7, | ||||
|                             -1, | ||||
|                             0, | ||||
|                             1, | ||||
|                             2, | ||||
|                             3, | ||||
|                             4, | ||||
|                             5, | ||||
|                             -1, | ||||
|                             5, | ||||
|                             6 | ||||
|                         ], | ||||
|                         "type": "integer", | ||||
|                         "format": "int32", | ||||
|                         "x-enum-varnames": [ | ||||
|                             "_numLevels", | ||||
|                             "DebugLevel", | ||||
|                             "InfoLevel", | ||||
|                             "WarnLevel", | ||||
|                             "ErrorLevel", | ||||
|                             "DPanicLevel", | ||||
|                             "PanicLevel", | ||||
|                             "FatalLevel", | ||||
|                             "_minLevel", | ||||
|                             "_maxLevel", | ||||
|                             "InvalidLevel" | ||||
|                         ], | ||||
|                         "name": "level", | ||||
|                         "in": "query" | ||||
|                     }, | ||||
|                     { | ||||
|                         "enum": [ | ||||
|                             "邮件", | ||||
|                             "企业微信", | ||||
|                             "飞书", | ||||
|                             "日志" | ||||
|                         ], | ||||
|                         "type": "string", | ||||
|                         "x-enum-varnames": [ | ||||
|                             "NotifierTypeSMTP", | ||||
|                             "NotifierTypeWeChat", | ||||
|                             "NotifierTypeLark", | ||||
|                             "NotifierTypeLog" | ||||
|                         ], | ||||
|                         "name": "notifier_type", | ||||
|                         "in": "query" | ||||
|                     }, | ||||
|                     { | ||||
|                         "type": "string", | ||||
|                         "name": "order_by", | ||||
|                         "in": "query" | ||||
|                     }, | ||||
|                     { | ||||
|                         "type": "integer", | ||||
|                         "name": "page", | ||||
|                         "in": "query" | ||||
|                     }, | ||||
|                     { | ||||
|                         "type": "integer", | ||||
|                         "name": "pageSize", | ||||
|                         "in": "query" | ||||
|                     }, | ||||
|                     { | ||||
|                         "type": "string", | ||||
|                         "name": "start_time", | ||||
|                         "in": "query" | ||||
|                     }, | ||||
|                     { | ||||
|                         "enum": [ | ||||
|                             "发送成功", | ||||
|                             "发送失败", | ||||
|                             "已跳过" | ||||
|                         ], | ||||
|                         "type": "string", | ||||
|                         "x-enum-comments": { | ||||
|                             "NotificationStatusFailed": "通知发送失败", | ||||
|                             "NotificationStatusSkipped": "通知因某些原因被跳过(例如:用户未配置联系方式)", | ||||
|                             "NotificationStatusSuccess": "通知已成功发送" | ||||
|                         }, | ||||
|                         "x-enum-descriptions": [ | ||||
|                             "通知已成功发送", | ||||
|                             "通知发送失败", | ||||
|                             "通知因某些原因被跳过(例如:用户未配置联系方式)" | ||||
|                         ], | ||||
|                         "x-enum-varnames": [ | ||||
|                             "NotificationStatusSuccess", | ||||
|                             "NotificationStatusFailed", | ||||
|                             "NotificationStatusSkipped" | ||||
|                         ], | ||||
|                         "name": "status", | ||||
|                         "in": "query" | ||||
|                     }, | ||||
|                     { | ||||
|                         "type": "integer", | ||||
|                         "name": "user_id", | ||||
|                         "in": "query" | ||||
|                     } | ||||
|                 ], | ||||
|                 "responses": { | ||||
|                     "200": { | ||||
|                         "description": "OK", | ||||
|                         "schema": { | ||||
|                             "allOf": [ | ||||
|                                 { | ||||
|                                     "$ref": "#/definitions/controller.Response" | ||||
|                                 }, | ||||
|                                 { | ||||
|                                     "type": "object", | ||||
|                                     "properties": { | ||||
|                                         "data": { | ||||
|                                             "$ref": "#/definitions/dto.ListNotificationResponse" | ||||
|                                         } | ||||
|                                     } | ||||
|                                 } | ||||
|                             ] | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         "/api/v1/monitor/pending-collections": { | ||||
|             "get": { | ||||
|                 "security": [ | ||||
| @@ -4481,6 +4624,20 @@ | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         "dto.ListNotificationResponse": { | ||||
|             "type": "object", | ||||
|             "properties": { | ||||
|                 "list": { | ||||
|                     "type": "array", | ||||
|                     "items": { | ||||
|                         "$ref": "#/definitions/dto.NotificationDTO" | ||||
|                     } | ||||
|                 }, | ||||
|                 "pagination": { | ||||
|                     "$ref": "#/definitions/dto.PaginationDTO" | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         "dto.ListPendingCollectionResponse": { | ||||
|             "type": "object", | ||||
|             "properties": { | ||||
| @@ -4789,6 +4946,47 @@ | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         "dto.NotificationDTO": { | ||||
|             "type": "object", | ||||
|             "properties": { | ||||
|                 "alarm_timestamp": { | ||||
|                     "type": "string" | ||||
|                 }, | ||||
|                 "created_at": { | ||||
|                     "type": "string" | ||||
|                 }, | ||||
|                 "error_message": { | ||||
|                     "type": "string" | ||||
|                 }, | ||||
|                 "id": { | ||||
|                     "type": "integer" | ||||
|                 }, | ||||
|                 "level": { | ||||
|                     "$ref": "#/definitions/zapcore.Level" | ||||
|                 }, | ||||
|                 "message": { | ||||
|                     "type": "string" | ||||
|                 }, | ||||
|                 "notifier_type": { | ||||
|                     "$ref": "#/definitions/notify.NotifierType" | ||||
|                 }, | ||||
|                 "status": { | ||||
|                     "$ref": "#/definitions/models.NotificationStatus" | ||||
|                 }, | ||||
|                 "title": { | ||||
|                     "type": "string" | ||||
|                 }, | ||||
|                 "to_address": { | ||||
|                     "type": "string" | ||||
|                 }, | ||||
|                 "updated_at": { | ||||
|                     "type": "string" | ||||
|                 }, | ||||
|                 "user_id": { | ||||
|                     "type": "integer" | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         "dto.PaginationDTO": { | ||||
|             "type": "object", | ||||
|             "properties": { | ||||
| @@ -6267,6 +6465,29 @@ | ||||
|                 "ReasonTypeHealthCare" | ||||
|             ] | ||||
|         }, | ||||
|         "models.NotificationStatus": { | ||||
|             "type": "string", | ||||
|             "enum": [ | ||||
|                 "发送成功", | ||||
|                 "发送失败", | ||||
|                 "已跳过" | ||||
|             ], | ||||
|             "x-enum-comments": { | ||||
|                 "NotificationStatusFailed": "通知发送失败", | ||||
|                 "NotificationStatusSkipped": "通知因某些原因被跳过(例如:用户未配置联系方式)", | ||||
|                 "NotificationStatusSuccess": "通知已成功发送" | ||||
|             }, | ||||
|             "x-enum-descriptions": [ | ||||
|                 "通知已成功发送", | ||||
|                 "通知发送失败", | ||||
|                 "通知因某些原因被跳过(例如:用户未配置联系方式)" | ||||
|             ], | ||||
|             "x-enum-varnames": [ | ||||
|                 "NotificationStatusSuccess", | ||||
|                 "NotificationStatusFailed", | ||||
|                 "NotificationStatusSkipped" | ||||
|             ] | ||||
|         }, | ||||
|         "models.PenStatus": { | ||||
|             "type": "string", | ||||
|             "enum": [ | ||||
| @@ -6600,10 +6821,10 @@ | ||||
|         "notify.NotifierType": { | ||||
|             "type": "string", | ||||
|             "enum": [ | ||||
|                 "smtp", | ||||
|                 "wechat", | ||||
|                 "lark", | ||||
|                 "log" | ||||
|                 "邮件", | ||||
|                 "企业微信", | ||||
|                 "飞书", | ||||
|                 "日志" | ||||
|             ], | ||||
|             "x-enum-varnames": [ | ||||
|                 "NotifierTypeSMTP", | ||||
| @@ -6611,6 +6832,36 @@ | ||||
|                 "NotifierTypeLark", | ||||
|                 "NotifierTypeLog" | ||||
|             ] | ||||
|         }, | ||||
|         "zapcore.Level": { | ||||
|             "type": "integer", | ||||
|             "format": "int32", | ||||
|             "enum": [ | ||||
|                 7, | ||||
|                 -1, | ||||
|                 0, | ||||
|                 1, | ||||
|                 2, | ||||
|                 3, | ||||
|                 4, | ||||
|                 5, | ||||
|                 -1, | ||||
|                 5, | ||||
|                 6 | ||||
|             ], | ||||
|             "x-enum-varnames": [ | ||||
|                 "_numLevels", | ||||
|                 "DebugLevel", | ||||
|                 "InfoLevel", | ||||
|                 "WarnLevel", | ||||
|                 "ErrorLevel", | ||||
|                 "DPanicLevel", | ||||
|                 "PanicLevel", | ||||
|                 "FatalLevel", | ||||
|                 "_minLevel", | ||||
|                 "_maxLevel", | ||||
|                 "InvalidLevel" | ||||
|             ] | ||||
|         } | ||||
|     }, | ||||
|     "securityDefinitions": { | ||||
|   | ||||
| @@ -349,6 +349,15 @@ definitions: | ||||
|       pagination: | ||||
|         $ref: '#/definitions/dto.PaginationDTO' | ||||
|     type: object | ||||
|   dto.ListNotificationResponse: | ||||
|     properties: | ||||
|       list: | ||||
|         items: | ||||
|           $ref: '#/definitions/dto.NotificationDTO' | ||||
|         type: array | ||||
|       pagination: | ||||
|         $ref: '#/definitions/dto.PaginationDTO' | ||||
|     type: object | ||||
|   dto.ListPendingCollectionResponse: | ||||
|     properties: | ||||
|       list: | ||||
| @@ -552,6 +561,33 @@ definitions: | ||||
|     - quantity | ||||
|     - toPenID | ||||
|     type: object | ||||
|   dto.NotificationDTO: | ||||
|     properties: | ||||
|       alarm_timestamp: | ||||
|         type: string | ||||
|       created_at: | ||||
|         type: string | ||||
|       error_message: | ||||
|         type: string | ||||
|       id: | ||||
|         type: integer | ||||
|       level: | ||||
|         $ref: '#/definitions/zapcore.Level' | ||||
|       message: | ||||
|         type: string | ||||
|       notifier_type: | ||||
|         $ref: '#/definitions/notify.NotifierType' | ||||
|       status: | ||||
|         $ref: '#/definitions/models.NotificationStatus' | ||||
|       title: | ||||
|         type: string | ||||
|       to_address: | ||||
|         type: string | ||||
|       updated_at: | ||||
|         type: string | ||||
|       user_id: | ||||
|         type: integer | ||||
|     type: object | ||||
|   dto.PaginationDTO: | ||||
|     properties: | ||||
|       page: | ||||
| @@ -1557,6 +1593,24 @@ definitions: | ||||
|     - ReasonTypePreventive | ||||
|     - ReasonTypeTreatment | ||||
|     - ReasonTypeHealthCare | ||||
|   models.NotificationStatus: | ||||
|     enum: | ||||
|     - 发送成功 | ||||
|     - 发送失败 | ||||
|     - 已跳过 | ||||
|     type: string | ||||
|     x-enum-comments: | ||||
|       NotificationStatusFailed: 通知发送失败 | ||||
|       NotificationStatusSkipped: 通知因某些原因被跳过(例如:用户未配置联系方式) | ||||
|       NotificationStatusSuccess: 通知已成功发送 | ||||
|     x-enum-descriptions: | ||||
|     - 通知已成功发送 | ||||
|     - 通知发送失败 | ||||
|     - 通知因某些原因被跳过(例如:用户未配置联系方式) | ||||
|     x-enum-varnames: | ||||
|     - NotificationStatusSuccess | ||||
|     - NotificationStatusFailed | ||||
|     - NotificationStatusSkipped | ||||
|   models.PenStatus: | ||||
|     enum: | ||||
|     - 空闲 | ||||
| @@ -1827,16 +1881,43 @@ definitions: | ||||
|     type: object | ||||
|   notify.NotifierType: | ||||
|     enum: | ||||
|     - smtp | ||||
|     - wechat | ||||
|     - lark | ||||
|     - log | ||||
|     - 邮件 | ||||
|     - 企业微信 | ||||
|     - 飞书 | ||||
|     - 日志 | ||||
|     type: string | ||||
|     x-enum-varnames: | ||||
|     - NotifierTypeSMTP | ||||
|     - NotifierTypeWeChat | ||||
|     - NotifierTypeLark | ||||
|     - NotifierTypeLog | ||||
|   zapcore.Level: | ||||
|     enum: | ||||
|     - 7 | ||||
|     - -1 | ||||
|     - 0 | ||||
|     - 1 | ||||
|     - 2 | ||||
|     - 3 | ||||
|     - 4 | ||||
|     - 5 | ||||
|     - -1 | ||||
|     - 5 | ||||
|     - 6 | ||||
|     format: int32 | ||||
|     type: integer | ||||
|     x-enum-varnames: | ||||
|     - _numLevels | ||||
|     - DebugLevel | ||||
|     - InfoLevel | ||||
|     - WarnLevel | ||||
|     - ErrorLevel | ||||
|     - DPanicLevel | ||||
|     - PanicLevel | ||||
|     - FatalLevel | ||||
|     - _minLevel | ||||
|     - _maxLevel | ||||
|     - InvalidLevel | ||||
| info: | ||||
|   contact: | ||||
|     email: divano@example.com | ||||
| @@ -2400,6 +2481,105 @@ paths: | ||||
|       summary: 获取用药记录列表 | ||||
|       tags: | ||||
|       - 数据监控 | ||||
|   /api/v1/monitor/notifications: | ||||
|     get: | ||||
|       description: 根据提供的过滤条件,分页获取通知列表 | ||||
|       parameters: | ||||
|       - in: query | ||||
|         name: end_time | ||||
|         type: string | ||||
|       - enum: | ||||
|         - 7 | ||||
|         - -1 | ||||
|         - 0 | ||||
|         - 1 | ||||
|         - 2 | ||||
|         - 3 | ||||
|         - 4 | ||||
|         - 5 | ||||
|         - -1 | ||||
|         - 5 | ||||
|         - 6 | ||||
|         format: int32 | ||||
|         in: query | ||||
|         name: level | ||||
|         type: integer | ||||
|         x-enum-varnames: | ||||
|         - _numLevels | ||||
|         - DebugLevel | ||||
|         - InfoLevel | ||||
|         - WarnLevel | ||||
|         - ErrorLevel | ||||
|         - DPanicLevel | ||||
|         - PanicLevel | ||||
|         - FatalLevel | ||||
|         - _minLevel | ||||
|         - _maxLevel | ||||
|         - InvalidLevel | ||||
|       - enum: | ||||
|         - 邮件 | ||||
|         - 企业微信 | ||||
|         - 飞书 | ||||
|         - 日志 | ||||
|         in: query | ||||
|         name: notifier_type | ||||
|         type: string | ||||
|         x-enum-varnames: | ||||
|         - NotifierTypeSMTP | ||||
|         - NotifierTypeWeChat | ||||
|         - NotifierTypeLark | ||||
|         - NotifierTypeLog | ||||
|       - in: query | ||||
|         name: order_by | ||||
|         type: string | ||||
|       - in: query | ||||
|         name: page | ||||
|         type: integer | ||||
|       - in: query | ||||
|         name: pageSize | ||||
|         type: integer | ||||
|       - in: query | ||||
|         name: start_time | ||||
|         type: string | ||||
|       - enum: | ||||
|         - 发送成功 | ||||
|         - 发送失败 | ||||
|         - 已跳过 | ||||
|         in: query | ||||
|         name: status | ||||
|         type: string | ||||
|         x-enum-comments: | ||||
|           NotificationStatusFailed: 通知发送失败 | ||||
|           NotificationStatusSkipped: 通知因某些原因被跳过(例如:用户未配置联系方式) | ||||
|           NotificationStatusSuccess: 通知已成功发送 | ||||
|         x-enum-descriptions: | ||||
|         - 通知已成功发送 | ||||
|         - 通知发送失败 | ||||
|         - 通知因某些原因被跳过(例如:用户未配置联系方式) | ||||
|         x-enum-varnames: | ||||
|         - NotificationStatusSuccess | ||||
|         - NotificationStatusFailed | ||||
|         - NotificationStatusSkipped | ||||
|       - in: query | ||||
|         name: user_id | ||||
|         type: integer | ||||
|       produces: | ||||
|       - application/json | ||||
|       responses: | ||||
|         "200": | ||||
|           description: OK | ||||
|           schema: | ||||
|             allOf: | ||||
|             - $ref: '#/definitions/controller.Response' | ||||
|             - properties: | ||||
|                 data: | ||||
|                   $ref: '#/definitions/dto.ListNotificationResponse' | ||||
|               type: object | ||||
|       security: | ||||
|       - BearerAuth: [] | ||||
|       summary: 批量查询通知 | ||||
|       tags: | ||||
|       - 数据监控 | ||||
|   /api/v1/monitor/pending-collections: | ||||
|     get: | ||||
|       description: 根据提供的过滤条件,分页获取待采集请求 | ||||
|   | ||||
| @@ -176,6 +176,7 @@ func (a *API) setupRoutes() { | ||||
| 			monitorGroup.GET("/pig-sick-logs", a.monitorController.ListPigSickLogs) | ||||
| 			monitorGroup.GET("/pig-purchases", a.monitorController.ListPigPurchases) | ||||
| 			monitorGroup.GET("/pig-sales", a.monitorController.ListPigSales) | ||||
| 			monitorGroup.GET("/notifications", a.monitorController.ListNotifications) | ||||
| 		} | ||||
| 		a.logger.Info("数据监控相关接口注册成功 (需要认证和审计)") | ||||
| 	} | ||||
|   | ||||
| @@ -839,3 +839,50 @@ func (c *Controller) ListPigSales(ctx *gin.Context) { | ||||
| 	c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(data), total) | ||||
| 	controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取猪只售卖记录成功", resp, actionType, "获取猪只售卖记录成功", req) | ||||
| } | ||||
|  | ||||
| // ListNotifications godoc | ||||
| // @Summary      批量查询通知 | ||||
| // @Description  根据提供的过滤条件,分页获取通知列表 | ||||
| // @Tags         数据监控 | ||||
| // @Security     BearerAuth | ||||
| // @Produce      json | ||||
| // @Param        query query dto.ListNotificationRequest true "查询参数" | ||||
| // @Success      200 {object} controller.Response{data=dto.ListNotificationResponse} | ||||
| // @Router       /api/v1/monitor/notifications [get] | ||||
| func (c *Controller) ListNotifications(ctx *gin.Context) { | ||||
| 	const actionType = "批量查询通知" | ||||
|  | ||||
| 	var req dto.ListNotificationRequest | ||||
| 	if err := ctx.ShouldBindQuery(&req); err != nil { | ||||
| 		c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) | ||||
| 		controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	opts := repository.NotificationListOptions{ | ||||
| 		UserID:       req.UserID, | ||||
| 		NotifierType: req.NotifierType, | ||||
| 		Level:        req.Level, | ||||
| 		StartTime:    req.StartTime, | ||||
| 		EndTime:      req.EndTime, | ||||
| 		OrderBy:      req.OrderBy, | ||||
| 		Status:       req.Status, | ||||
| 	} | ||||
|  | ||||
| 	data, total, err := c.monitorService.ListNotifications(opts, req.Page, req.PageSize) | ||||
| 	if err != nil { | ||||
| 		if errors.Is(err, repository.ErrInvalidPagination) { | ||||
| 			c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err) | ||||
| 			controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的分页参数: "+err.Error(), actionType, "无效分页参数", req) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		c.logger.Errorf("%s: 服务层查询失败: %v", actionType, err) | ||||
| 		controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "批量查询通知失败: "+err.Error(), actionType, "服务层查询失败", req) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	resp := dto.NewListNotificationResponse(data, total, req.Page, req.PageSize) | ||||
| 	c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(data), total) | ||||
| 	controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "批量查询通知成功", resp, actionType, "批量查询通知成功", req) | ||||
| } | ||||
|   | ||||
							
								
								
									
										35
									
								
								internal/app/dto/notification_converter.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								internal/app/dto/notification_converter.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| package dto | ||||
|  | ||||
| import ( | ||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/infra/models" | ||||
| ) | ||||
|  | ||||
| // NewListNotificationResponse 从模型数据创建通知列表响应 DTO | ||||
| func NewListNotificationResponse(data []models.Notification, total int64, page, pageSize int) *ListNotificationResponse { | ||||
| 	dtos := make([]NotificationDTO, len(data)) | ||||
| 	for i, item := range data { | ||||
| 		dtos[i] = NotificationDTO{ | ||||
| 			ID:             item.ID, | ||||
| 			CreatedAt:      item.CreatedAt, | ||||
| 			UpdatedAt:      item.UpdatedAt, | ||||
| 			NotifierType:   item.NotifierType, | ||||
| 			UserID:         item.UserID, | ||||
| 			Title:          item.Title, | ||||
| 			Message:        item.Message, | ||||
| 			Level:          item.Level, | ||||
| 			AlarmTimestamp: item.AlarmTimestamp, | ||||
| 			ToAddress:      item.ToAddress, | ||||
| 			Status:         item.Status, | ||||
| 			ErrorMessage:   item.ErrorMessage, | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return &ListNotificationResponse{ | ||||
| 		List: dtos, | ||||
| 		Pagination: PaginationDTO{ | ||||
| 			Total:    total, | ||||
| 			Page:     page, | ||||
| 			PageSize: pageSize, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
| @@ -1,9 +1,50 @@ | ||||
| package dto | ||||
|  | ||||
| import "git.huangwc.com/pig/pig-farm-controller/internal/infra/notify" | ||||
| 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" | ||||
| ) | ||||
|  | ||||
| // SendTestNotificationRequest 定义了发送测试通知请求的 JSON 结构 | ||||
| type SendTestNotificationRequest struct { | ||||
| 	// Type 指定要测试的通知渠道 | ||||
| 	Type notify.NotifierType `json:"type" binding:"required"` | ||||
| } | ||||
|  | ||||
| // ListNotificationRequest 定义了获取通知列表的请求参数 | ||||
| type ListNotificationRequest struct { | ||||
| 	Page         int                        `form:"page,default=1"` | ||||
| 	PageSize     int                        `form:"pageSize,default=10"` | ||||
| 	UserID       *uint                      `form:"user_id"` | ||||
| 	NotifierType *notify.NotifierType       `form:"notifier_type"` | ||||
| 	Status       *models.NotificationStatus `form:"status"` | ||||
| 	Level        *zapcore.Level             `form:"level"` | ||||
| 	StartTime    *time.Time                 `form:"start_time"` | ||||
| 	EndTime      *time.Time                 `form:"end_time"` | ||||
| 	OrderBy      string                     `form:"order_by"` | ||||
| } | ||||
|  | ||||
| // NotificationDTO 是用于API响应的通知结构 | ||||
| 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"` | ||||
| 	UserID         uint                      `json:"user_id"` | ||||
| 	Title          string                    `json:"title"` | ||||
| 	Message        string                    `json:"message"` | ||||
| 	Level          zapcore.Level             `json:"level"` | ||||
| 	AlarmTimestamp time.Time                 `json:"alarm_timestamp"` | ||||
| 	ToAddress      string                    `json:"to_address"` | ||||
| 	Status         models.NotificationStatus `json:"status"` | ||||
| 	ErrorMessage   string                    `json:"error_message"` | ||||
| } | ||||
|  | ||||
| // ListNotificationResponse 是获取通知列表的响应结构 | ||||
| type ListNotificationResponse struct { | ||||
| 	List       []NotificationDTO `json:"list"` | ||||
| 	Pagination PaginationDTO     `json:"pagination"` | ||||
| } | ||||
|   | ||||
| @@ -24,6 +24,7 @@ type MonitorService interface { | ||||
| 	ListPigSickLogs(opts repository.PigSickLogListOptions, page, pageSize int) ([]models.PigSickLog, int64, error) | ||||
| 	ListPigPurchases(opts repository.PigPurchaseListOptions, page, pageSize int) ([]models.PigPurchase, int64, error) | ||||
| 	ListPigSales(opts repository.PigSaleListOptions, page, pageSize int) ([]models.PigSale, int64, error) | ||||
| 	ListNotifications(opts repository.NotificationListOptions, page, pageSize int) ([]models.Notification, int64, error) | ||||
| } | ||||
|  | ||||
| // monitorService 是 MonitorService 接口的具体实现 | ||||
| @@ -40,6 +41,7 @@ type monitorService struct { | ||||
| 	pigTransferLogRepo    repository.PigTransferLogRepository | ||||
| 	pigSickLogRepo        repository.PigSickLogRepository | ||||
| 	pigTradeRepo          repository.PigTradeRepository | ||||
| 	notificationRepo      repository.NotificationRepository | ||||
| } | ||||
|  | ||||
| // NewMonitorService 创建一个新的 MonitorService 实例 | ||||
| @@ -56,6 +58,7 @@ func NewMonitorService( | ||||
| 	pigTransferLogRepo repository.PigTransferLogRepository, | ||||
| 	pigSickLogRepo repository.PigSickLogRepository, | ||||
| 	pigTradeRepo repository.PigTradeRepository, | ||||
| 	notificationRepo repository.NotificationRepository, | ||||
| ) MonitorService { | ||||
| 	return &monitorService{ | ||||
| 		sensorDataRepo:        sensorDataRepo, | ||||
| @@ -70,6 +73,7 @@ func NewMonitorService( | ||||
| 		pigTransferLogRepo:    pigTransferLogRepo, | ||||
| 		pigSickLogRepo:        pigSickLogRepo, | ||||
| 		pigTradeRepo:          pigTradeRepo, | ||||
| 		notificationRepo:      notificationRepo, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -157,3 +161,8 @@ func (s *monitorService) ListPigPurchases(opts repository.PigPurchaseListOptions | ||||
| func (s *monitorService) ListPigSales(opts repository.PigSaleListOptions, page, pageSize int) ([]models.PigSale, int64, error) { | ||||
| 	return s.pigTradeRepo.ListPigSales(opts, page, pageSize) | ||||
| } | ||||
|  | ||||
| // ListNotifications 负责处理查询通知列表的业务逻辑 | ||||
| func (s *monitorService) ListNotifications(opts repository.NotificationListOptions, page, pageSize int) ([]models.Notification, int64, error) { | ||||
| 	return s.notificationRepo.List(opts, page, pageSize) | ||||
| } | ||||
|   | ||||
| @@ -90,6 +90,7 @@ func NewApplication(configPath string) (*Application, error) { | ||||
| 	pigSickPigLogRepo := repository.NewGormPigSickLogRepository(storage.GetDB()) | ||||
| 	medicationLogRepo := repository.NewGormMedicationLogRepository(storage.GetDB()) | ||||
| 	rawMaterialRepo := repository.NewGormRawMaterialRepository(storage.GetDB()) | ||||
| 	notificationRepo := repository.NewGormNotificationRepository(storage.GetDB()) | ||||
|  | ||||
| 	// 初始化事务管理器 | ||||
| 	unitOfWork := repository.NewGormUnitOfWork(storage.GetDB(), logger) | ||||
| @@ -117,13 +118,14 @@ func NewApplication(configPath string) (*Application, error) { | ||||
| 		pigTransferLogRepo, | ||||
| 		pigSickPigLogRepo, | ||||
| 		pigTradeRepo, | ||||
| 		notificationRepo, | ||||
| 	) | ||||
|  | ||||
| 	// 初始化审计服务 | ||||
| 	auditService := audit.NewService(userActionLogRepo, logger) | ||||
|  | ||||
| 	// 初始化通知服务 | ||||
| 	notifyService, err := initNotifyService(cfg.Notify, logger, userRepo) | ||||
| 	notifyService, err := initNotifyService(cfg.Notify, logger, userRepo, notificationRepo) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("初始化通知服务失败: %w", err) | ||||
| 	} | ||||
| @@ -220,6 +222,7 @@ func initNotifyService( | ||||
| 	cfg config.NotifyConfig, | ||||
| 	log *logs.Logger, | ||||
| 	userRepo repository.UserRepository, | ||||
| 	notificationRepo repository.NotificationRepository, | ||||
| ) (domain_notify.Service, error) { | ||||
| 	var availableNotifiers []notify.Notifier | ||||
|  | ||||
| @@ -278,13 +281,14 @@ func initNotifyService( | ||||
| 		log.Warnf("配置的首选渠道 '%s' 未启用或未指定,已自动降级使用 '%s' 作为首选渠道。", cfg.Primary, primaryNotifier.Type()) | ||||
| 	} | ||||
|  | ||||
| 	// 4. 使用创建的 Notifier 列表来组装领域服务 | ||||
| 	// 4. 使用创建的 Notifier 列表和 notificationRepo 来组装领域服务 | ||||
| 	notifyService, err := domain_notify.NewFailoverService( | ||||
| 		log, | ||||
| 		userRepo, | ||||
| 		availableNotifiers, | ||||
| 		primaryNotifier.Type(), | ||||
| 		cfg.FailureThreshold, | ||||
| 		notificationRepo, | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("创建故障转移通知服务失败: %w", err) | ||||
|   | ||||
| @@ -33,6 +33,7 @@ type failoverService struct { | ||||
| 	primaryNotifier  notify.Notifier | ||||
| 	failureThreshold int | ||||
| 	failureCounters  *sync.Map // 使用 sync.Map 来安全地并发读写失败计数, key: userID (uint), value: counter (int) | ||||
| 	notificationRepo repository.NotificationRepository | ||||
| } | ||||
|  | ||||
| // NewFailoverService 创建一个新的故障转移通知服务 | ||||
| @@ -42,6 +43,7 @@ func NewFailoverService( | ||||
| 	notifiers []notify.Notifier, | ||||
| 	primaryNotifierType notify.NotifierType, | ||||
| 	failureThreshold int, | ||||
| 	notificationRepo repository.NotificationRepository, | ||||
| ) (Service, error) { | ||||
| 	notifierMap := make(map[notify.NotifierType]notify.Notifier) | ||||
| 	for _, n := range notifiers { | ||||
| @@ -60,6 +62,7 @@ func NewFailoverService( | ||||
| 		primaryNotifier:  primaryNotifier, | ||||
| 		failureThreshold: failureThreshold, | ||||
| 		failureCounters:  &sync.Map{}, | ||||
| 		notificationRepo: notificationRepo, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| @@ -128,11 +131,15 @@ func (s *failoverService) sendAlarmToUser(userID uint, content notify.AlarmConte | ||||
| 		primaryType := s.primaryNotifier.Type() | ||||
| 		addr := getAddressForNotifier(primaryType, user.Contact) | ||||
| 		if addr == "" { | ||||
| 			// 记录跳过通知 | ||||
| 			s.recordNotificationAttempt(userID, primaryType, content, "", models.NotificationStatusSkipped, fmt.Errorf("用户未配置首选通知方式 '%s' 的地址", primaryType)) | ||||
| 			return fmt.Errorf("用户未配置首选通知方式 '%s' 的地址", primaryType) | ||||
| 		} | ||||
|  | ||||
| 		err = s.primaryNotifier.Send(content, addr) | ||||
| 		if err == nil { | ||||
| 			// 记录成功通知 | ||||
| 			s.recordNotificationAttempt(userID, primaryType, content, addr, models.NotificationStatusSuccess, nil) | ||||
| 			if failureCount > 0 { | ||||
| 				s.log.Infow("首选渠道发送恢复正常", "userID", userID, "notifierType", primaryType) | ||||
| 				s.failureCounters.Store(userID, 0) | ||||
| @@ -140,6 +147,8 @@ func (s *failoverService) sendAlarmToUser(userID uint, content notify.AlarmConte | ||||
| 			return nil | ||||
| 		} | ||||
|  | ||||
| 		// 记录失败通知 | ||||
| 		s.recordNotificationAttempt(userID, primaryType, content, addr, models.NotificationStatusFailed, err) | ||||
| 		newFailureCount := failureCount + 1 | ||||
| 		s.failureCounters.Store(userID, newFailureCount) | ||||
| 		s.log.Warnw("首选渠道发送失败", "userID", userID, "notifierType", primaryType, "error", err, "failureCount", newFailureCount) | ||||
| @@ -152,13 +161,19 @@ func (s *failoverService) sendAlarmToUser(userID uint, content notify.AlarmConte | ||||
| 		for _, notifier := range s.notifiers { | ||||
| 			addr := getAddressForNotifier(notifier.Type(), user.Contact) | ||||
| 			if addr == "" { | ||||
| 				// 记录跳过通知 | ||||
| 				s.recordNotificationAttempt(userID, notifier.Type(), content, "", models.NotificationStatusSkipped, fmt.Errorf("用户未配置通知方式 '%s' 的地址", notifier.Type())) | ||||
| 				continue | ||||
| 			} | ||||
| 			if err := notifier.Send(content, addr); err == nil { | ||||
| 				// 记录成功通知 | ||||
| 				s.recordNotificationAttempt(userID, notifier.Type(), content, addr, models.NotificationStatusSuccess, nil) | ||||
| 				s.log.Infow("广播通知成功", "userID", userID, "notifierType", notifier.Type()) | ||||
| 				s.failureCounters.Store(userID, 0) | ||||
| 				return nil | ||||
| 			} | ||||
| 			// 记录失败通知 | ||||
| 			s.recordNotificationAttempt(userID, notifier.Type(), content, addr, models.NotificationStatusFailed, err) | ||||
| 			lastErr = err | ||||
| 			s.log.Warnw("广播通知:渠道发送失败", "userID", userID, "notifierType", notifier.Type(), "error", err) | ||||
| 		} | ||||
| @@ -185,6 +200,13 @@ func (s *failoverService) SendTestMessage(userID uint, notifierType notify.Notif | ||||
| 	addr := getAddressForNotifier(notifierType, user.Contact) | ||||
| 	if addr == "" { | ||||
| 		s.log.Warnw("发送测试消息失败:缺少地址", "userID", userID, "notifierType", notifierType) | ||||
| 		// 记录跳过通知 | ||||
| 		s.recordNotificationAttempt(userID, notifierType, notify.AlarmContent{ | ||||
| 			Title:     "通知服务测试", | ||||
| 			Message:   fmt.Sprintf("这是一条来自【%s】渠道的测试消息。如果您收到此消息,说明您的配置正确。", notifierType), | ||||
| 			Level:     zap.InfoLevel, | ||||
| 			Timestamp: time.Now(), | ||||
| 		}, "", models.NotificationStatusFailed, fmt.Errorf("用户未配置通知方式 '%s' 的地址", notifierType)) | ||||
| 		return fmt.Errorf("用户未配置通知方式 '%s' 的地址", notifierType) | ||||
| 	} | ||||
|  | ||||
| @@ -199,10 +221,14 @@ func (s *failoverService) SendTestMessage(userID uint, notifierType notify.Notif | ||||
| 	err = notifier.Send(testContent, addr) | ||||
| 	if err != nil { | ||||
| 		s.log.Errorw("发送测试消息失败", "userID", userID, "notifierType", notifierType, "error", err) | ||||
| 		// 记录失败通知 | ||||
| 		s.recordNotificationAttempt(userID, notifierType, testContent, addr, models.NotificationStatusFailed, err) | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	s.log.Infow("发送测试消息成功", "userID", userID, "notifierType", notifierType) | ||||
| 	// 记录成功通知 | ||||
| 	s.recordNotificationAttempt(userID, notifierType, testContent, addr, models.NotificationStatusSuccess, nil) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| @@ -221,3 +247,46 @@ func getAddressForNotifier(notifierType notify.NotifierType, contact models.Cont | ||||
| 		return "" | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // recordNotificationAttempt 记录一次通知发送尝试的结果 | ||||
| // userID: 接收通知的用户ID | ||||
| // notifierType: 使用的通知器类型 | ||||
| // content: 通知内容 | ||||
| // toAddress: 实际发送到的地址 | ||||
| // status: 发送尝试的状态 (成功、失败、跳过) | ||||
| // err: 如果发送失败,记录的错误信息 | ||||
| func (s *failoverService) recordNotificationAttempt( | ||||
| 	userID uint, | ||||
| 	notifierType notify.NotifierType, | ||||
| 	content notify.AlarmContent, | ||||
| 	toAddress string, | ||||
| 	status models.NotificationStatus, | ||||
| 	err error, | ||||
| ) { | ||||
| 	errorMessage := "" | ||||
| 	if err != nil { | ||||
| 		errorMessage = err.Error() | ||||
| 	} | ||||
|  | ||||
| 	notification := &models.Notification{ | ||||
| 		NotifierType:   notifierType, | ||||
| 		UserID:         userID, | ||||
| 		Title:          content.Title, | ||||
| 		Message:        content.Message, | ||||
| 		Level:          content.Level, | ||||
| 		AlarmTimestamp: content.Timestamp, | ||||
| 		ToAddress:      toAddress, | ||||
| 		Status:         status, | ||||
| 		ErrorMessage:   errorMessage, | ||||
| 	} | ||||
|  | ||||
| 	if saveErr := s.notificationRepo.Create(notification); saveErr != nil { | ||||
| 		s.log.Errorw("无法保存通知发送记录到数据库", | ||||
| 			"userID", userID, | ||||
| 			"notifierType", notifierType, | ||||
| 			"status", status, | ||||
| 			"originalError", errorMessage, | ||||
| 			"saveError", saveErr, | ||||
| 		) | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -171,6 +171,7 @@ func (ps *PostgresStorage) creatingHyperTable() error { | ||||
| 		{models.PigSickLog{}, "happened_at"}, | ||||
| 		{models.PigPurchase{}, "purchase_date"}, | ||||
| 		{models.PigSale{}, "sale_date"}, | ||||
| 		{models.Notification{}, "alarm_timestamp"}, | ||||
| 	} | ||||
|  | ||||
| 	for _, table := range tablesToConvert { | ||||
| @@ -211,6 +212,7 @@ func (ps *PostgresStorage) applyCompressionPolicies() error { | ||||
| 		{models.PigSickLog{}, "pig_batch_id"}, | ||||
| 		{models.PigPurchase{}, "pig_batch_id"}, | ||||
| 		{models.PigSale{}, "pig_batch_id"}, | ||||
| 		{models.Notification{}, "user_id"}, | ||||
| 	} | ||||
|  | ||||
| 	for _, policy := range policies { | ||||
|   | ||||
| @@ -59,6 +59,9 @@ func GetAllModels() []interface{} { | ||||
| 		// Medication Models | ||||
| 		&Medication{}, | ||||
| 		&MedicationLog{}, | ||||
|  | ||||
| 		// Notification Models | ||||
| 		&Notification{}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										47
									
								
								internal/infra/models/notify.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								internal/infra/models/notify.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | ||||
| package models | ||||
|  | ||||
| import ( | ||||
| 	"time" | ||||
|  | ||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/infra/notify" | ||||
| 	"go.uber.org/zap/zapcore" | ||||
| 	"gorm.io/gorm" | ||||
| ) | ||||
|  | ||||
| // NotificationStatus 定义了通知发送尝试的状态枚举。 | ||||
| type NotificationStatus string | ||||
|  | ||||
| const ( | ||||
| 	NotificationStatusSuccess NotificationStatus = "发送成功" // 通知已成功发送 | ||||
| 	NotificationStatusFailed  NotificationStatus = "发送失败" // 通知发送失败 | ||||
| 	NotificationStatusSkipped NotificationStatus = "已跳过"  // 通知因某些原因被跳过(例如:用户未配置联系方式) | ||||
| ) | ||||
|  | ||||
| // Notification 表示已发送或尝试发送的通知记录。 | ||||
| type Notification struct { | ||||
| 	gorm.Model | ||||
|  | ||||
| 	// NotifierType 通知器类型 (例如:"邮件", "企业微信", "飞书", "日志") | ||||
| 	NotifierType notify.NotifierType `gorm:"type:varchar(20);not null;index" json:"notifier_type"` | ||||
| 	// UserID 接收通知的用户ID,用于追溯通知记录到特定用户 | ||||
| 	UserID uint `gorm:"index" json:"user_id"` // 增加 UserID 字段,并添加索引 | ||||
| 	// Title 通知标题 | ||||
| 	Title string `gorm:"type:varchar(255);not null" json:"title"` | ||||
| 	// Message 通知内容 | ||||
| 	Message string `gorm:"type:text;not null" json:"message"` | ||||
| 	// Level 通知级别 (例如:INFO, WARN, ERROR) | ||||
| 	Level zapcore.Level `gorm:"type:varchar(10);not null" json:"level"` | ||||
| 	// AlarmTimestamp 通知内容生成时的时间戳,与 ID 构成复合主键 | ||||
| 	AlarmTimestamp time.Time `gorm:"primaryKey;not null" json:"alarm_timestamp"` | ||||
| 	// ToAddress 接收地址 (例如:邮箱地址, 企业微信ID, 日志标识符) | ||||
| 	ToAddress string `gorm:"type:varchar(255);not null" json:"to_address"` | ||||
| 	// Status 通知发送尝试的状态 (例如:"待发送", "发送成功", "发送失败", "已跳过") | ||||
| 	Status NotificationStatus `gorm:"type:varchar(20);not null;default:'待发送'" json:"status"` | ||||
| 	// ErrorMessage 如果通知发送失败,此字段存储错误信息 | ||||
| 	ErrorMessage string `gorm:"type:text" json:"error_message"` | ||||
| } | ||||
|  | ||||
| // TableName 指定 Notification 模型的表名。 | ||||
| func (Notification) TableName() string { | ||||
| 	return "notifications" | ||||
| } | ||||
							
								
								
									
										111
									
								
								internal/infra/repository/notification_repository.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								internal/infra/repository/notification_repository.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,111 @@ | ||||
| package repository | ||||
|  | ||||
| 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" | ||||
| 	"gorm.io/gorm" | ||||
| ) | ||||
|  | ||||
| // NotificationListOptions 定义了查询通知列表时的可选参数 | ||||
| type NotificationListOptions struct { | ||||
| 	UserID       *uint                      // 按用户ID过滤 | ||||
| 	NotifierType *notify.NotifierType       // 按通知器类型过滤 | ||||
| 	Status       *models.NotificationStatus // 按通知状态过滤 (例如:"success", "failed") | ||||
| 	Level        *zapcore.Level             // 按通知等级过滤 (例如:"info", "warning", "error") | ||||
| 	StartTime    *time.Time                 // 通知内容生成时间范围 - 开始时间 (对应 AlarmTimestamp) | ||||
| 	EndTime      *time.Time                 // 通知内容生成时间范围 - 结束时间 (对应 AlarmTimestamp) | ||||
| 	OrderBy      string                     // 排序字段,例如 "alarm_timestamp DESC" | ||||
| } | ||||
|  | ||||
| // NotificationRepository 定义了与通知记录相关的数据库操作接口。 | ||||
| type NotificationRepository interface { | ||||
| 	// Create 将一条新的通知记录插入数据库。 | ||||
| 	Create(notification *models.Notification) error | ||||
| 	// CreateInTx 在给定的事务中插入一条新的通知记录。 | ||||
| 	CreateInTx(tx *gorm.DB, notification *models.Notification) error | ||||
| 	// BatchCreate 批量插入多条通知记录。 | ||||
| 	BatchCreate(notifications []*models.Notification) error | ||||
| 	// List 支持分页和过滤的通知列表查询。 | ||||
| 	// 返回通知列表、总记录数和错误。 | ||||
| 	List(opts NotificationListOptions, page, pageSize int) ([]models.Notification, int64, error) | ||||
| } | ||||
|  | ||||
| // gormNotificationRepository 是 NotificationRepository 的 GORM 实现。 | ||||
| type gormNotificationRepository struct { | ||||
| 	db *gorm.DB | ||||
| } | ||||
|  | ||||
| // NewGormNotificationRepository 创建一个新的 NotificationRepository GORM 实现实例。 | ||||
| func NewGormNotificationRepository(db *gorm.DB) NotificationRepository { | ||||
| 	return &gormNotificationRepository{db: db} | ||||
| } | ||||
|  | ||||
| // Create 将一条新的通知记录插入数据库。 | ||||
| func (r *gormNotificationRepository) Create(notification *models.Notification) error { | ||||
| 	return r.db.Create(notification).Error | ||||
| } | ||||
|  | ||||
| // CreateInTx 在给定的事务中插入一条新的通知记录。 | ||||
| func (r *gormNotificationRepository) CreateInTx(tx *gorm.DB, notification *models.Notification) error { | ||||
| 	return tx.Create(notification).Error | ||||
| } | ||||
|  | ||||
| // BatchCreate 批量插入多条通知记录。 | ||||
| func (r *gormNotificationRepository) BatchCreate(notifications []*models.Notification) error { | ||||
| 	// GORM 的 Create 方法在传入切片时会自动进行批量插入 | ||||
| 	return r.db.Create(¬ifications).Error | ||||
| } | ||||
|  | ||||
| // List 实现了分页和过滤查询通知记录的功能 | ||||
| func (r *gormNotificationRepository) List(opts NotificationListOptions, page, pageSize int) ([]models.Notification, int64, error) { | ||||
| 	// --- 校验分页参数 --- | ||||
| 	if page <= 0 || pageSize <= 0 { | ||||
| 		return nil, 0, ErrInvalidPagination // 复用已定义的错误 | ||||
| 	} | ||||
|  | ||||
| 	var results []models.Notification | ||||
| 	var total int64 | ||||
|  | ||||
| 	query := r.db.Model(&models.Notification{}) | ||||
|  | ||||
| 	// --- 应用过滤条件 --- | ||||
| 	if opts.UserID != nil { | ||||
| 		query = query.Where("user_id = ?", *opts.UserID) | ||||
| 	} | ||||
| 	if opts.NotifierType != nil { | ||||
| 		query = query.Where("notifier_type = ?", *opts.NotifierType) | ||||
| 	} | ||||
| 	if opts.Status != nil { | ||||
| 		query = query.Where("status = ?", *opts.Status) | ||||
| 	} | ||||
| 	if opts.Level != nil { | ||||
| 		query = query.Where("level = ?", opts.Level.String()) | ||||
| 	} | ||||
| 	if opts.StartTime != nil { | ||||
| 		query = query.Where("alarm_timestamp >= ?", *opts.StartTime) | ||||
| 	} | ||||
| 	if opts.EndTime != nil { | ||||
| 		query = query.Where("alarm_timestamp <= ?", *opts.EndTime) | ||||
| 	} | ||||
|  | ||||
| 	// --- 计算总数 --- | ||||
| 	if err := query.Count(&total).Error; err != nil { | ||||
| 		return nil, 0, err | ||||
| 	} | ||||
|  | ||||
| 	// --- 应用排序条件 --- | ||||
| 	orderBy := "alarm_timestamp DESC" // 默认按时间倒序 | ||||
| 	if opts.OrderBy != "" { | ||||
| 		orderBy = opts.OrderBy | ||||
| 	} | ||||
| 	query = query.Order(orderBy) | ||||
|  | ||||
| 	// --- 分页 --- | ||||
| 	offset := (page - 1) * pageSize | ||||
| 	err := query.Limit(pageSize).Offset(offset).Find(&results).Error | ||||
|  | ||||
| 	return results, total, err | ||||
| } | ||||
							
								
								
									
										6
									
								
								internal/infra/repository/repository.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								internal/infra/repository/repository.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| package repository | ||||
|  | ||||
| import "errors" | ||||
|  | ||||
| // ErrInvalidPagination 表示分页参数无效 | ||||
| var ErrInvalidPagination = errors.New("无效的分页参数:page和pageSize必须为大于0") | ||||
| @@ -1,16 +1,12 @@ | ||||
| package repository | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"time" | ||||
|  | ||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/infra/models" | ||||
| 	"gorm.io/gorm" | ||||
| ) | ||||
|  | ||||
| // ErrInvalidPagination 表示分页参数无效 | ||||
| var ErrInvalidPagination = errors.New("无效的分页参数:page和pageSize必须为大于0") | ||||
|  | ||||
| // SensorDataListOptions 定义了查询传感器数据列表时的可选参数 | ||||
| type SensorDataListOptions struct { | ||||
| 	DeviceID   *uint | ||||
|   | ||||
		Reference in New Issue
	
	Block a user