日志发送逻辑及测试消息发送接口
This commit is contained in:
93
config.example.yml
Normal file
93
config.example.yml
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
# 应用基础配置
|
||||||
|
app:
|
||||||
|
name: "PigFarmController" # 应用名称
|
||||||
|
version: "1.0.0" # 应用版本
|
||||||
|
jwt_secret: "your_jwt_secret_key_here" # JWT 签名密钥,请务必修改为强密码
|
||||||
|
|
||||||
|
# 服务器配置
|
||||||
|
server:
|
||||||
|
port: 8080 # 服务器监听端口
|
||||||
|
mode: "debug" # 运行模式: debug, release, test
|
||||||
|
|
||||||
|
# 日志配置
|
||||||
|
log:
|
||||||
|
level: "info" # 日志级别: debug, info, warn, error, dpanic, panic, fatal
|
||||||
|
format: "console" # 日志输出格式: console, json
|
||||||
|
enable_file: true # 是否同时输出到文件
|
||||||
|
file_path: "app_logs/pig_farm_controller.log" # 日志文件路径
|
||||||
|
max_size: 100 # 单个日志文件最大大小 (MB)
|
||||||
|
max_backups: 7 # 最多保留的旧日志文件数量
|
||||||
|
max_age: 7 # 最多保留的旧日志文件天数
|
||||||
|
compress: true # 是否压缩旧日志文件
|
||||||
|
|
||||||
|
# 数据库配置
|
||||||
|
database:
|
||||||
|
host: "localhost" # 数据库主机地址
|
||||||
|
port: 5432 # 数据库端口
|
||||||
|
username: "postgres" # 数据库用户名
|
||||||
|
password: "your_db_password" # 数据库密码
|
||||||
|
dbname: "pig_farm_controller_db" # 数据库名称
|
||||||
|
sslmode: "disable" # SSL模式: disable, require, verify-ca, verify-full
|
||||||
|
is_timescaledb: false # 是否为 TimescaleDB
|
||||||
|
max_open_conns: 100 # 最大开放连接数
|
||||||
|
max_idle_conns: 10 # 最大空闲连接数
|
||||||
|
conn_max_lifetime: 300 # 连接最大生命周期 (秒)
|
||||||
|
|
||||||
|
# WebSocket配置
|
||||||
|
websocket:
|
||||||
|
timeout: 60 # WebSocket请求超时时间 (秒)
|
||||||
|
heartbeat_interval: 30 # 心跳检测间隔 (秒)
|
||||||
|
|
||||||
|
# 心跳配置
|
||||||
|
heartbeat:
|
||||||
|
interval: 10 # 心跳间隔 (秒)
|
||||||
|
concurrency: 5 # 请求并发数
|
||||||
|
|
||||||
|
# ChirpStack API 配置
|
||||||
|
chirp_stack:
|
||||||
|
api_host: "http://localhost:8080" # ChirpStack API 主机地址
|
||||||
|
api_token: "your_chirpstack_api_token" # ChirpStack API Token
|
||||||
|
fport: 10 # ChirpStack FPort
|
||||||
|
api_timeout: 5 # API 请求超时时间 (秒)
|
||||||
|
collection_request_timeout: 10 # 采集请求超时时间 (秒)
|
||||||
|
|
||||||
|
# 任务调度配置
|
||||||
|
task:
|
||||||
|
interval: 5 # 任务调度间隔 (秒)
|
||||||
|
num_workers: 5 # 任务执行器并发工作数量
|
||||||
|
|
||||||
|
# Lora 配置
|
||||||
|
lora:
|
||||||
|
mode: "lora_mesh" # Lora 运行模式: lora_wan, lora_mesh
|
||||||
|
|
||||||
|
# Lora Mesh 配置
|
||||||
|
lora_mesh:
|
||||||
|
uart_port: "/dev/ttyUSB0" # UART 串口路径
|
||||||
|
baud_rate: 115200 # 波特率
|
||||||
|
timeout: 5 # 超时时间 (秒)
|
||||||
|
lora_mesh_mode: "transparent" # Lora Mesh 模式: transparent, command
|
||||||
|
max_chunk_size: 200 # 最大数据块大小
|
||||||
|
reassembly_timeout: 10 # 重组超时时间 (秒)
|
||||||
|
|
||||||
|
# 通知服务配置
|
||||||
|
notify:
|
||||||
|
primary: "log" # 首选通知渠道: smtp, wechat, lark, log (如果其他渠道未启用,log 会自动成为首选)
|
||||||
|
failureThreshold: 2 # 连续失败多少次后触发广播模式
|
||||||
|
smtp:
|
||||||
|
enabled: false # 是否启用 SMTP 邮件通知
|
||||||
|
host: "smtp.example.com" # SMTP 服务器地址
|
||||||
|
port: 587 # SMTP 服务器端口
|
||||||
|
username: "your_email@example.com" # 发件人邮箱地址
|
||||||
|
password: "your_email_password" # 发件人邮箱授权码或密码
|
||||||
|
sender: "PigFarm Alarm <no-reply@example.com>" # 发件人名称和地址
|
||||||
|
|
||||||
|
wechat:
|
||||||
|
enabled: false # 是否启用企业微信通知
|
||||||
|
corpID: "wwxxxxxxxxxxxx" # 企业ID (CorpID)
|
||||||
|
agentID: "1000001" # 应用ID (AgentID)
|
||||||
|
secret: "your_wechat_app_secret" # 应用密钥 (Secret)
|
||||||
|
|
||||||
|
lark:
|
||||||
|
enabled: false # 是否启用飞书通知
|
||||||
|
appID: "cli_xxxxxxxxxx" # 应用 ID
|
||||||
|
appSecret: "your_lark_app_secret" # 应用密钥
|
||||||
91
docs/docs.go
91
docs/docs.go
@@ -3923,6 +3923,64 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/users/{id}/notifications/test": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "为指定用户发送一条特定渠道的测试消息,以验证其配置是否正确。",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"用户管理"
|
||||||
|
],
|
||||||
|
"summary": "发送测试通知",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "用户ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "请求体",
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.SendTestNotificationRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "成功响应",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/controller.Response"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"definitions": {
|
"definitions": {
|
||||||
@@ -5591,6 +5649,22 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dto.SendTestNotificationRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"type"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"description": "Type 指定要测试的通知渠道\n@enum(smtp, wechat, lark, log)",
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/notify.NotifierType"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.SensorDataDTO": {
|
"dto.SensorDataDTO": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -6530,6 +6604,23 @@ const docTemplate = `{
|
|||||||
"$ref": "#/definitions/models.SensorType"
|
"$ref": "#/definitions/models.SensorType"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"notify.NotifierType": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"smtp",
|
||||||
|
"wechat",
|
||||||
|
"lark",
|
||||||
|
"sms",
|
||||||
|
"log"
|
||||||
|
],
|
||||||
|
"x-enum-varnames": [
|
||||||
|
"NotifierTypeSMTP",
|
||||||
|
"NotifierTypeWeChat",
|
||||||
|
"NotifierTypeLark",
|
||||||
|
"NotifierTypeSMS",
|
||||||
|
"NotifierTypeLog"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"securityDefinitions": {
|
"securityDefinitions": {
|
||||||
|
|||||||
@@ -3915,6 +3915,64 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/users/{id}/notifications/test": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "为指定用户发送一条特定渠道的测试消息,以验证其配置是否正确。",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"用户管理"
|
||||||
|
],
|
||||||
|
"summary": "发送测试通知",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "用户ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "请求体",
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.SendTestNotificationRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "成功响应",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/controller.Response"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"definitions": {
|
"definitions": {
|
||||||
@@ -5583,6 +5641,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dto.SendTestNotificationRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"type"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"description": "Type 指定要测试的通知渠道\n@enum(smtp, wechat, lark, log)",
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/notify.NotifierType"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.SensorDataDTO": {
|
"dto.SensorDataDTO": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -6522,6 +6596,23 @@
|
|||||||
"$ref": "#/definitions/models.SensorType"
|
"$ref": "#/definitions/models.SensorType"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"notify.NotifierType": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"smtp",
|
||||||
|
"wechat",
|
||||||
|
"lark",
|
||||||
|
"sms",
|
||||||
|
"log"
|
||||||
|
],
|
||||||
|
"x-enum-varnames": [
|
||||||
|
"NotifierTypeSMTP",
|
||||||
|
"NotifierTypeWeChat",
|
||||||
|
"NotifierTypeLark",
|
||||||
|
"NotifierTypeSMS",
|
||||||
|
"NotifierTypeLog"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"securityDefinitions": {
|
"securityDefinitions": {
|
||||||
|
|||||||
@@ -1125,6 +1125,17 @@ definitions:
|
|||||||
- traderName
|
- traderName
|
||||||
- unitPrice
|
- unitPrice
|
||||||
type: object
|
type: object
|
||||||
|
dto.SendTestNotificationRequest:
|
||||||
|
properties:
|
||||||
|
type:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/notify.NotifierType'
|
||||||
|
description: |-
|
||||||
|
Type 指定要测试的通知渠道
|
||||||
|
@enum(smtp, wechat, lark, log)
|
||||||
|
required:
|
||||||
|
- type
|
||||||
|
type: object
|
||||||
dto.SensorDataDTO:
|
dto.SensorDataDTO:
|
||||||
properties:
|
properties:
|
||||||
data:
|
data:
|
||||||
@@ -1816,6 +1827,20 @@ definitions:
|
|||||||
type:
|
type:
|
||||||
$ref: '#/definitions/models.SensorType'
|
$ref: '#/definitions/models.SensorType'
|
||||||
type: object
|
type: object
|
||||||
|
notify.NotifierType:
|
||||||
|
enum:
|
||||||
|
- smtp
|
||||||
|
- wechat
|
||||||
|
- lark
|
||||||
|
- sms
|
||||||
|
- log
|
||||||
|
type: string
|
||||||
|
x-enum-varnames:
|
||||||
|
- NotifierTypeSMTP
|
||||||
|
- NotifierTypeWeChat
|
||||||
|
- NotifierTypeLark
|
||||||
|
- NotifierTypeSMS
|
||||||
|
- NotifierTypeLog
|
||||||
info:
|
info:
|
||||||
contact:
|
contact:
|
||||||
email: divano@example.com
|
email: divano@example.com
|
||||||
@@ -4086,6 +4111,40 @@ paths:
|
|||||||
summary: 获取指定用户的操作历史
|
summary: 获取指定用户的操作历史
|
||||||
tags:
|
tags:
|
||||||
- 用户管理
|
- 用户管理
|
||||||
|
/api/v1/users/{id}/notifications/test:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: 为指定用户发送一条特定渠道的测试消息,以验证其配置是否正确。
|
||||||
|
parameters:
|
||||||
|
- description: 用户ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
- description: 请求体
|
||||||
|
in: body
|
||||||
|
name: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/dto.SendTestNotificationRequest'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: 成功响应
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/controller.Response'
|
||||||
|
- properties:
|
||||||
|
data:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
summary: 发送测试通知
|
||||||
|
tags:
|
||||||
|
- 用户管理
|
||||||
/api/v1/users/login:
|
/api/v1/users/login:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import (
|
|||||||
"git.huangwc.com/pig/pig-farm-controller/internal/app/webhook"
|
"git.huangwc.com/pig/pig-farm-controller/internal/app/webhook"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/domain/audit"
|
"git.huangwc.com/pig/pig-farm-controller/internal/domain/audit"
|
||||||
domain_device "git.huangwc.com/pig/pig-farm-controller/internal/domain/device"
|
domain_device "git.huangwc.com/pig/pig-farm-controller/internal/domain/device"
|
||||||
|
domain_notify "git.huangwc.com/pig/pig-farm-controller/internal/domain/notify"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/domain/task"
|
"git.huangwc.com/pig/pig-farm-controller/internal/domain/task"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/domain/token"
|
"git.huangwc.com/pig/pig-farm-controller/internal/domain/token"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/config"
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/config"
|
||||||
@@ -68,9 +69,9 @@ func NewAPI(cfg config.ServerConfig,
|
|||||||
pigFarmService service.PigFarmService,
|
pigFarmService service.PigFarmService,
|
||||||
pigBatchService service.PigBatchService,
|
pigBatchService service.PigBatchService,
|
||||||
monitorService service.MonitorService,
|
monitorService service.MonitorService,
|
||||||
userActionLogRepository repository.UserActionLogRepository,
|
|
||||||
tokenService token.TokenService,
|
tokenService token.TokenService,
|
||||||
auditService audit.Service,
|
auditService audit.Service,
|
||||||
|
notifyService domain_notify.Service,
|
||||||
deviceService domain_device.Service,
|
deviceService domain_device.Service,
|
||||||
listenHandler webhook.ListenHandler,
|
listenHandler webhook.ListenHandler,
|
||||||
analysisTaskManager *task.AnalysisPlanTaskManager) *API {
|
analysisTaskManager *task.AnalysisPlanTaskManager) *API {
|
||||||
@@ -96,7 +97,7 @@ func NewAPI(cfg config.ServerConfig,
|
|||||||
config: cfg,
|
config: cfg,
|
||||||
listenHandler: listenHandler,
|
listenHandler: listenHandler,
|
||||||
// 在 NewAPI 中初始化用户控制器,并将其作为 API 结构体的成员
|
// 在 NewAPI 中初始化用户控制器,并将其作为 API 结构体的成员
|
||||||
userController: user.NewController(userRepo, monitorService, logger, tokenService),
|
userController: user.NewController(userRepo, monitorService, logger, tokenService, notifyService),
|
||||||
// 在 NewAPI 中初始化设备控制器,并将其作为 API 结构体的成员
|
// 在 NewAPI 中初始化设备控制器,并将其作为 API 结构体的成员
|
||||||
deviceController: device.NewController(deviceRepository, areaControllerRepository, deviceTemplateRepository, deviceService, logger),
|
deviceController: device.NewController(deviceRepository, areaControllerRepository, deviceTemplateRepository, deviceService, logger),
|
||||||
// 在 NewAPI 中初始化计划控制器,并将其作为 API 结构体的成员
|
// 在 NewAPI 中初始化计划控制器,并将其作为 API 结构体的成员
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ func (a *API) setupRoutes() {
|
|||||||
userGroup := authGroup.Group("/users")
|
userGroup := authGroup.Group("/users")
|
||||||
{
|
{
|
||||||
userGroup.GET("/:id/history", a.userController.ListUserHistory) // 获取用户操作历史
|
userGroup.GET("/:id/history", a.userController.ListUserHistory) // 获取用户操作历史
|
||||||
|
userGroup.POST("/:id/notifications/test", a.userController.SendTestNotification)
|
||||||
}
|
}
|
||||||
a.logger.Info("用户相关接口注册成功 (需要认证和审计)")
|
a.logger.Info("用户相关接口注册成功 (需要认证和审计)")
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"git.huangwc.com/pig/pig-farm-controller/internal/app/controller"
|
"git.huangwc.com/pig/pig-farm-controller/internal/app/controller"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/app/dto"
|
"git.huangwc.com/pig/pig-farm-controller/internal/app/dto"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/app/service"
|
"git.huangwc.com/pig/pig-farm-controller/internal/app/service"
|
||||||
|
domain_notify "git.huangwc.com/pig/pig-farm-controller/internal/domain/notify"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/domain/token"
|
"git.huangwc.com/pig/pig-farm-controller/internal/domain/token"
|
||||||
"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"
|
||||||
@@ -19,16 +20,24 @@ import (
|
|||||||
type Controller struct {
|
type Controller struct {
|
||||||
userRepo repository.UserRepository
|
userRepo repository.UserRepository
|
||||||
monitorService service.MonitorService
|
monitorService service.MonitorService
|
||||||
tokenService token.TokenService // 注入 token 服务
|
tokenService token.TokenService
|
||||||
|
notifyService domain_notify.Service
|
||||||
logger *logs.Logger
|
logger *logs.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewController 创建用户控制器实例
|
// NewController 创建用户控制器实例
|
||||||
func NewController(userRepo repository.UserRepository, monitorService service.MonitorService, logger *logs.Logger, tokenService token.TokenService) *Controller {
|
func NewController(
|
||||||
|
userRepo repository.UserRepository,
|
||||||
|
monitorService service.MonitorService,
|
||||||
|
logger *logs.Logger,
|
||||||
|
tokenService token.TokenService,
|
||||||
|
notifyService domain_notify.Service,
|
||||||
|
) *Controller {
|
||||||
return &Controller{
|
return &Controller{
|
||||||
userRepo: userRepo,
|
userRepo: userRepo,
|
||||||
monitorService: monitorService,
|
monitorService: monitorService,
|
||||||
tokenService: tokenService,
|
tokenService: tokenService,
|
||||||
|
notifyService: notifyService,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -192,3 +201,46 @@ func (c *Controller) ListUserHistory(ctx *gin.Context) {
|
|||||||
c.logger.Infof("%s: 成功获取用户 %d 的操作历史, 数量: %d", actionType, userID, len(data))
|
c.logger.Infof("%s: 成功获取用户 %d 的操作历史, 数量: %d", actionType, userID, len(data))
|
||||||
controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取用户操作历史成功", resp, actionType, "获取用户操作历史成功", opts)
|
controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取用户操作历史成功", resp, actionType, "获取用户操作历史成功", opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SendTestNotification godoc
|
||||||
|
// @Summary 发送测试通知
|
||||||
|
// @Description 为指定用户发送一条特定渠道的测试消息,以验证其配置是否正确。
|
||||||
|
// @Tags 用户管理
|
||||||
|
// @Security BearerAuth
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path int true "用户ID"
|
||||||
|
// @Param body body dto.SendTestNotificationRequest true "请求体"
|
||||||
|
// @Success 200 {object} controller.Response{data=string} "成功响应"
|
||||||
|
// @Router /api/v1/users/{id}/notifications/test [post]
|
||||||
|
func (c *Controller) SendTestNotification(ctx *gin.Context) {
|
||||||
|
const actionType = "发送测试通知"
|
||||||
|
|
||||||
|
// 1. 从 URL 中获取用户 ID
|
||||||
|
userID, err := strconv.ParseUint(ctx.Param("id"), 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Errorf("%s: 无效的用户ID格式: %v", actionType, err)
|
||||||
|
controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的用户ID格式", actionType, "无效的用户ID格式", ctx.Param("id"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 从请求体 (JSON Body) 中获取要测试的通知类型
|
||||||
|
var req dto.SendTestNotificationRequest
|
||||||
|
if err := ctx.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err)
|
||||||
|
controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "请求体格式错误或缺少 'type' 字段: "+err.Error(), actionType, "请求体绑定失败", req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 调用领域服务
|
||||||
|
err = c.notifyService.SendTestMessage(uint(userID), req.Type)
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Errorf("%s: 服务层调用失败: %v", actionType, err)
|
||||||
|
controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "发送测试消息失败: "+err.Error(), actionType, "服务层调用失败", gin.H{"userID": userID, "type": req.Type})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 返回成功响应
|
||||||
|
c.logger.Infof("%s: 成功为用户 %d 发送类型为 %s 的测试消息", actionType, userID, req.Type)
|
||||||
|
controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "测试消息已发送,请检查您的接收端。", nil, actionType, "测试消息发送成功", gin.H{"userID": userID, "type": req.Type})
|
||||||
|
}
|
||||||
|
|||||||
10
internal/app/dto/notification_dto.go
Normal file
10
internal/app/dto/notification_dto.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package dto
|
||||||
|
|
||||||
|
import "git.huangwc.com/pig/pig-farm-controller/internal/infra/notify"
|
||||||
|
|
||||||
|
// SendTestNotificationRequest 定义了发送测试通知请求的 JSON 结构
|
||||||
|
type SendTestNotificationRequest struct {
|
||||||
|
// Type 指定要测试的通知渠道
|
||||||
|
// @enum(smtp, wechat, lark, log)
|
||||||
|
Type notify.NotifierType `json:"type" binding:"required"`
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"git.huangwc.com/pig/pig-farm-controller/internal/app/webhook"
|
"git.huangwc.com/pig/pig-farm-controller/internal/app/webhook"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/domain/audit"
|
"git.huangwc.com/pig/pig-farm-controller/internal/domain/audit"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/domain/device"
|
"git.huangwc.com/pig/pig-farm-controller/internal/domain/device"
|
||||||
|
domain_notify "git.huangwc.com/pig/pig-farm-controller/internal/domain/notify"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/domain/pig"
|
"git.huangwc.com/pig/pig-farm-controller/internal/domain/pig"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/domain/task"
|
"git.huangwc.com/pig/pig-farm-controller/internal/domain/task"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/domain/token"
|
"git.huangwc.com/pig/pig-farm-controller/internal/domain/token"
|
||||||
@@ -19,6 +20,7 @@ import (
|
|||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/database"
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/database"
|
||||||
"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"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/transport"
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/transport"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/transport/lora"
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/transport/lora"
|
||||||
@@ -41,6 +43,9 @@ type Application struct {
|
|||||||
|
|
||||||
// Lora Mesh 监听器
|
// Lora Mesh 监听器
|
||||||
loraMeshCommunicator transport.Listener
|
loraMeshCommunicator transport.Listener
|
||||||
|
|
||||||
|
// 通知服务
|
||||||
|
NotifyService domain_notify.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewApplication 创建并初始化一个新的 Application 实例。
|
// NewApplication 创建并初始化一个新的 Application 实例。
|
||||||
@@ -117,6 +122,12 @@ func NewApplication(configPath string) (*Application, error) {
|
|||||||
// 初始化审计服务
|
// 初始化审计服务
|
||||||
auditService := audit.NewService(userActionLogRepo, logger)
|
auditService := audit.NewService(userActionLogRepo, logger)
|
||||||
|
|
||||||
|
// 初始化通知服务
|
||||||
|
notifyService, err := initNotifyService(cfg.Notify, logger, userRepo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("初始化通知服务失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
// --- 初始化 LoRa 相关组件 ---
|
// --- 初始化 LoRa 相关组件 ---
|
||||||
var listenHandler webhook.ListenHandler
|
var listenHandler webhook.ListenHandler
|
||||||
var comm transport.Communicator
|
var comm transport.Communicator
|
||||||
@@ -176,9 +187,9 @@ func NewApplication(configPath string) (*Application, error) {
|
|||||||
pigFarmService,
|
pigFarmService,
|
||||||
pigBatchService,
|
pigBatchService,
|
||||||
monitorService,
|
monitorService,
|
||||||
userActionLogRepo,
|
|
||||||
tokenService,
|
tokenService,
|
||||||
auditService,
|
auditService,
|
||||||
|
notifyService,
|
||||||
generalDeviceService,
|
generalDeviceService,
|
||||||
listenHandler,
|
listenHandler,
|
||||||
analysisPlanTaskManager,
|
analysisPlanTaskManager,
|
||||||
@@ -197,11 +208,92 @@ func NewApplication(configPath string) (*Application, error) {
|
|||||||
pendingCollectionRepo: pendingCollectionRepo,
|
pendingCollectionRepo: pendingCollectionRepo,
|
||||||
analysisPlanTaskManager: analysisPlanTaskManager,
|
analysisPlanTaskManager: analysisPlanTaskManager,
|
||||||
loraMeshCommunicator: loraListener,
|
loraMeshCommunicator: loraListener,
|
||||||
|
NotifyService: notifyService,
|
||||||
}
|
}
|
||||||
|
|
||||||
return app, nil
|
return app, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// initNotifyService 根据配置初始化并返回一个通知领域服务。
|
||||||
|
// 它确保至少有一个 LogNotifier 总是可用,并根据配置启用其他通知器。
|
||||||
|
func initNotifyService(
|
||||||
|
cfg config.NotifyConfig,
|
||||||
|
log *logs.Logger,
|
||||||
|
userRepo repository.UserRepository,
|
||||||
|
) (domain_notify.Service, error) {
|
||||||
|
var availableNotifiers []notify.Notifier
|
||||||
|
|
||||||
|
// 1. 总是创建 LogNotifier 作为所有告警的最终记录渠道
|
||||||
|
logNotifier := notify.NewLogNotifier(log)
|
||||||
|
availableNotifiers = append(availableNotifiers, logNotifier)
|
||||||
|
log.Info("Log通知器已启用 (作为所有告警的最终记录渠道)")
|
||||||
|
|
||||||
|
// 2. 根据配置,按需创建并收集所有启用的其他 Notifier 实例
|
||||||
|
if cfg.SMTP.Enabled {
|
||||||
|
smtpNotifier := notify.NewSMTPNotifier(
|
||||||
|
cfg.SMTP.Host,
|
||||||
|
cfg.SMTP.Port,
|
||||||
|
cfg.SMTP.Username,
|
||||||
|
cfg.SMTP.Password,
|
||||||
|
cfg.SMTP.Sender,
|
||||||
|
)
|
||||||
|
availableNotifiers = append(availableNotifiers, smtpNotifier)
|
||||||
|
log.Info("SMTP通知器已启用")
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.WeChat.Enabled {
|
||||||
|
wechatNotifier := notify.NewWechatNotifier(
|
||||||
|
cfg.WeChat.CorpID,
|
||||||
|
cfg.WeChat.AgentID,
|
||||||
|
cfg.WeChat.Secret,
|
||||||
|
)
|
||||||
|
availableNotifiers = append(availableNotifiers, wechatNotifier)
|
||||||
|
log.Info("企业微信通知器已启用")
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.Lark.Enabled {
|
||||||
|
larkNotifier := notify.NewLarkNotifier(
|
||||||
|
cfg.Lark.AppID,
|
||||||
|
cfg.Lark.AppSecret,
|
||||||
|
)
|
||||||
|
availableNotifiers = append(availableNotifiers, larkNotifier)
|
||||||
|
log.Info("飞书通知器已启用")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 动态确定首选通知器
|
||||||
|
var primaryNotifier notify.Notifier
|
||||||
|
primaryNotifierType := notify.NotifierType(cfg.Primary)
|
||||||
|
|
||||||
|
// 检查用户指定的主渠道是否已启用
|
||||||
|
for _, n := range availableNotifiers {
|
||||||
|
if n.Type() == primaryNotifierType {
|
||||||
|
primaryNotifier = n
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果用户指定的主渠道未启用或未指定,则自动选择第一个可用的 (这将是 LogNotifier,如果其他都未启用)
|
||||||
|
if primaryNotifier == nil {
|
||||||
|
primaryNotifier = availableNotifiers[0] // 确保总能找到一个,因为 LogNotifier 总是存在的
|
||||||
|
log.Warnf("配置的首选渠道 '%s' 未启用或未指定,已自动降级使用 '%s' 作为首选渠道。", cfg.Primary, primaryNotifier.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 使用创建的 Notifier 列表来组装领域服务
|
||||||
|
notifyService, err := domain_notify.NewFailoverService(
|
||||||
|
log,
|
||||||
|
userRepo,
|
||||||
|
availableNotifiers,
|
||||||
|
primaryNotifier.Type(),
|
||||||
|
cfg.FailureThreshold,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("创建故障转移通知服务失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("通知服务初始化成功,首选渠道: %s, 故障阈值: %d", primaryNotifier.Type(), cfg.FailureThreshold)
|
||||||
|
return notifyService, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Start 启动应用的所有组件并阻塞,直到接收到关闭信号。
|
// Start 启动应用的所有组件并阻塞,直到接收到关闭信号。
|
||||||
func (app *Application) Start() error {
|
func (app *Application) Start() error {
|
||||||
app.Logger.Info("应用启动中...")
|
app.Logger.Info("应用启动中...")
|
||||||
|
|||||||
@@ -215,6 +215,8 @@ func getAddressForNotifier(notifierType notify.NotifierType, contact models.Cont
|
|||||||
return contact.WeChat
|
return contact.WeChat
|
||||||
case notify.NotifierTypeLark:
|
case notify.NotifierTypeLark:
|
||||||
return contact.Feishu
|
return contact.Feishu
|
||||||
|
case notify.NotifierTypeLog:
|
||||||
|
return "log" // LogNotifier不需要具体的地址,但为了函数签名一致性,返回一个无意义的非空字符串以绕过配置存在检查
|
||||||
default:
|
default:
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ type Config struct {
|
|||||||
|
|
||||||
// LoraMesh LoraMesh配置
|
// LoraMesh LoraMesh配置
|
||||||
LoraMesh LoraMeshConfig `yaml:"lora_mesh"`
|
LoraMesh LoraMeshConfig `yaml:"lora_mesh"`
|
||||||
|
|
||||||
|
// Notify 通知服务配置
|
||||||
|
Notify NotifyConfig `yaml:"notify"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AppConfig 代表应用基础配置
|
// AppConfig 代表应用基础配置
|
||||||
@@ -158,6 +161,40 @@ type LoraMeshConfig struct {
|
|||||||
ReassemblyTimeout int `yaml:"reassembly_timeout"`
|
ReassemblyTimeout int `yaml:"reassembly_timeout"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NotifyConfig 包含了所有与通知服务相关的配置
|
||||||
|
type NotifyConfig struct {
|
||||||
|
Primary string `yaml:"primary"` // 首选通知渠道 (e.g., "smtp", "wechat", "lark", "log")
|
||||||
|
FailureThreshold int `yaml:"failureThreshold"` // 连续失败多少次后触发广播模式
|
||||||
|
SMTP SMTPConfig `yaml:"smtp"`
|
||||||
|
WeChat WeChatConfig `yaml:"wechat"`
|
||||||
|
Lark LarkConfig `yaml:"lark"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SMTPConfig SMTP邮件配置
|
||||||
|
type SMTPConfig struct {
|
||||||
|
Enabled bool `yaml:"enabled"`
|
||||||
|
Host string `yaml:"host"`
|
||||||
|
Port int `yaml:"port"`
|
||||||
|
Username string `yaml:"username"`
|
||||||
|
Password string `yaml:"password"`
|
||||||
|
Sender string `yaml:"sender"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WeChatConfig 企业微信应用配置
|
||||||
|
type WeChatConfig struct {
|
||||||
|
Enabled bool `yaml:"enabled"`
|
||||||
|
CorpID string `yaml:"corpID"`
|
||||||
|
AgentID string `yaml:"agentID"`
|
||||||
|
Secret string `yaml:"secret"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LarkConfig 飞书应用配置
|
||||||
|
type LarkConfig struct {
|
||||||
|
Enabled bool `yaml:"enabled"`
|
||||||
|
AppID string `yaml:"appID"`
|
||||||
|
AppSecret string `yaml:"appSecret"`
|
||||||
|
}
|
||||||
|
|
||||||
// NewConfig 创建并返回一个新的配置实例
|
// NewConfig 创建并返回一个新的配置实例
|
||||||
func NewConfig() *Config {
|
func NewConfig() *Config {
|
||||||
// 默认值可以在这里设置,但我们优先使用配置文件中的值
|
// 默认值可以在这里设置,但我们优先使用配置文件中的值
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|||||||
37
internal/infra/notify/log_notifier.go
Normal file
37
internal/infra/notify/log_notifier.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package notify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// logNotifier 实现了 Notifier 接口,用于将告警信息记录到日志中。
|
||||||
|
type logNotifier struct {
|
||||||
|
logger *logs.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLogNotifier 创建一个新的 logNotifier 实例。
|
||||||
|
// 它接收一个日志记录器,用于实际的日志输出。
|
||||||
|
func NewLogNotifier(logger *logs.Logger) Notifier {
|
||||||
|
return &logNotifier{
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send 将告警内容以结构化的方式记录到日志中。
|
||||||
|
// toAddr 参数在这里表示告警的预期接收者地址,也会被记录。
|
||||||
|
func (l *logNotifier) Send(content AlarmContent, toAddr string) error {
|
||||||
|
l.logger.Infow("告警已记录到日志",
|
||||||
|
"notifierType", NotifierTypeLog,
|
||||||
|
"title", content.Title,
|
||||||
|
"message", content.Message,
|
||||||
|
"level", content.Level.String(),
|
||||||
|
"timestamp", content.Timestamp.Format(DefaultTimeFormat),
|
||||||
|
"toAddr", toAddr,
|
||||||
|
)
|
||||||
|
return nil // 记录日志操作本身不应失败
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type 返回通知器的类型。
|
||||||
|
func (l *logNotifier) Type() NotifierType {
|
||||||
|
return NotifierTypeLog
|
||||||
|
}
|
||||||
@@ -14,11 +14,13 @@ type NotifierType string
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// NotifierTypeSMTP 表示 SMTP 邮件通知器。
|
// NotifierTypeSMTP 表示 SMTP 邮件通知器。
|
||||||
NotifierTypeSMTP NotifierType = "邮件"
|
NotifierTypeSMTP NotifierType = "smtp"
|
||||||
// NotifierTypeWeChat 表示企业微信通知器。
|
// NotifierTypeWeChat 表示企业微信通知器。
|
||||||
NotifierTypeWeChat NotifierType = "企业微信"
|
NotifierTypeWeChat NotifierType = "wechat"
|
||||||
// NotifierTypeLark 表示飞书通知器。
|
// NotifierTypeLark 表示飞书通知器。
|
||||||
NotifierTypeLark NotifierType = "飞书"
|
NotifierTypeLark NotifierType = "lark"
|
||||||
|
// NotifierTypeLog 表示日志通知器,作为最终的告警记录渠道。
|
||||||
|
NotifierTypeLog NotifierType = "log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AlarmContent 定义了通知的内容
|
// AlarmContent 定义了通知的内容
|
||||||
|
|||||||
Reference in New Issue
Block a user