871 lines
31 KiB
Go
871 lines
31 KiB
Go
package reporter
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"sync"
|
|
"time"
|
|
|
|
"FireLeave_tool/connect"
|
|
"FireLeave_tool/logger"
|
|
"FireLeave_tool/video_server"
|
|
)
|
|
|
|
var (
|
|
// mergingAlarms 用于合并报警,避免重复处理
|
|
mergingAlarms sync.Map
|
|
// 事件周期状态
|
|
eventCycleMutex sync.RWMutex
|
|
currentEventCycle EventCycle
|
|
cycleTimeout time.Time
|
|
)
|
|
|
|
// EventCycle 事件周期类型
|
|
type EventCycle string
|
|
|
|
const (
|
|
// EventCycleFireLeave 动火离人上报周期
|
|
EventCycleFireLeave EventCycle = "fire_leave"
|
|
// EventCycleFireCheck 火焰检测上报周期
|
|
EventCycleFireCheck EventCycle = "fire_check"
|
|
// 周期时限缓存文件
|
|
CycleTimeoutFile = "/data/cache/event_cycle_timeout.json"
|
|
)
|
|
|
|
// Reporter 上报管理器
|
|
type Reporter struct {
|
|
wsClient *connect.WSClient
|
|
uploadURL string
|
|
logger *Logger
|
|
}
|
|
|
|
// Logger 上报管理器专用日志
|
|
type Logger struct {
|
|
*log.Logger
|
|
}
|
|
|
|
// NewLogger 创建上报管理器专用日志
|
|
func NewLogger() *Logger {
|
|
return &Logger{
|
|
Logger: logger.GlobalLoggerManager.GetLogger("reporter"),
|
|
}
|
|
}
|
|
|
|
// NewReporter 创建上报管理器
|
|
func NewReporter() *Reporter {
|
|
// 初始化默认事件周期
|
|
eventCycleMutex.Lock()
|
|
currentEventCycle = EventCycleFireLeave
|
|
eventCycleMutex.Unlock()
|
|
|
|
return &Reporter{
|
|
wsClient: connect.GetWSClient(),
|
|
uploadURL: "",
|
|
logger: NewLogger(),
|
|
}
|
|
}
|
|
|
|
// getLogger 获取日志实例,确保不为nil
|
|
func (r *Reporter) getLogger() *log.Logger {
|
|
if r.logger != nil && r.logger.Logger != nil {
|
|
return r.logger.Logger
|
|
}
|
|
// 如果logger未初始化,使用全局Logger
|
|
return logger.Logger
|
|
}
|
|
|
|
// GetCurrentEventCycle 获取当前事件周期
|
|
func GetCurrentEventCycle() EventCycle {
|
|
eventCycleMutex.RLock()
|
|
defer eventCycleMutex.RUnlock()
|
|
return currentEventCycle
|
|
}
|
|
|
|
// IsCycleTimeout 检查事件周期是否超时
|
|
func IsCycleTimeout() bool {
|
|
eventCycleMutex.RLock()
|
|
defer eventCycleMutex.RUnlock()
|
|
return time.Now().After(cycleTimeout)
|
|
}
|
|
|
|
// SwitchEventCycle 切换事件周期
|
|
func SwitchEventCycle(cycle EventCycle) error {
|
|
eventCycleMutex.Lock()
|
|
defer eventCycleMutex.Unlock()
|
|
|
|
// 读取周期时限
|
|
timeout, err := loadCycleTimeout()
|
|
if err != nil {
|
|
// 使用默认值
|
|
timeout = 30 * time.Minute
|
|
}
|
|
|
|
// 更新事件周期和超时时间
|
|
oldCycle := currentEventCycle
|
|
currentEventCycle = cycle
|
|
cycleTimeout = time.Now().Add(timeout)
|
|
|
|
// 记录日志
|
|
if logger := logger.GlobalLoggerManager.GetLogger("reporter"); logger != nil {
|
|
logger.Printf("事件周期切换: %s → %s, 时限: %v", oldCycle, cycle, timeout)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// CycleTimeoutConfig 周期时限配置
|
|
type CycleTimeoutConfig struct {
|
|
FireLeaveTimeout time.Duration `json:"fire_leave_timeout"`
|
|
FireCheckTimeout time.Duration `json:"fire_check_timeout"`
|
|
}
|
|
|
|
// loadCycleTimeout 加载周期时限
|
|
func loadCycleTimeout() (time.Duration, error) {
|
|
data, err := os.ReadFile(CycleTimeoutFile)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
var config CycleTimeoutConfig
|
|
if err := json.Unmarshal(data, &config); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
// 根据当前周期返回对应时限
|
|
switch currentEventCycle {
|
|
case EventCycleFireLeave:
|
|
return config.FireLeaveTimeout, nil
|
|
case EventCycleFireCheck:
|
|
return config.FireCheckTimeout, nil
|
|
default:
|
|
return 30 * time.Minute, nil
|
|
}
|
|
}
|
|
|
|
// SaveCycleTimeout 保存周期时限
|
|
func SaveCycleTimeout(config CycleTimeoutConfig) error {
|
|
data, err := json.Marshal(config)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// 确保目录存在
|
|
if err := os.MkdirAll("/data/cache", 0755); err != nil {
|
|
return err
|
|
}
|
|
|
|
return os.WriteFile(CycleTimeoutFile, data, 0644)
|
|
}
|
|
|
|
// ForbidTimeConfig 禁止时间配置
|
|
var (
|
|
// ForbidTimeFile 禁止时间缓存文件
|
|
ForbidTimeFile = "/data/cache/forbid_time_config.json"
|
|
)
|
|
|
|
// SaveForbidTimeConfig 保存禁止时间配置
|
|
func SaveForbidTimeConfig(minutr string) error {
|
|
config := struct {
|
|
Minutr string `json:"minutr"`
|
|
}{
|
|
Minutr: minutr,
|
|
}
|
|
|
|
data, err := json.Marshal(config)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// 确保目录存在
|
|
if err := os.MkdirAll("/data/cache", 0755); err != nil {
|
|
return err
|
|
}
|
|
|
|
return os.WriteFile(ForbidTimeFile, data, 0644)
|
|
}
|
|
|
|
// SaveEventCycleTimeoutConfig 保存事件周期时限配置
|
|
func SaveEventCycleTimeoutConfig(fireLeaveTimeout, fireCheckTimeout time.Duration) error {
|
|
config := CycleTimeoutConfig{
|
|
FireLeaveTimeout: fireLeaveTimeout,
|
|
FireCheckTimeout: fireCheckTimeout,
|
|
}
|
|
|
|
data, err := json.Marshal(config)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// 确保目录存在
|
|
if err := os.MkdirAll("/data/cache", 0755); err != nil {
|
|
return err
|
|
}
|
|
|
|
return os.WriteFile(CycleTimeoutFile, data, 0644)
|
|
}
|
|
|
|
// GetForbidTimeConfig 获取禁止时间配置
|
|
func GetForbidTimeConfig() string {
|
|
// 读取缓存文件
|
|
data, err := os.ReadFile(ForbidTimeFile)
|
|
if err != nil {
|
|
// 缓存中没有值时,默认返回0
|
|
return "0"
|
|
}
|
|
|
|
var config struct {
|
|
Minutr string `json:"minutr"`
|
|
}
|
|
|
|
if err := json.Unmarshal(data, &config); err != nil {
|
|
// 解析失败时,默认返回0
|
|
return "0"
|
|
}
|
|
|
|
return config.Minutr
|
|
}
|
|
|
|
// ShouldReportBothEvents 判断是否应该同时上报两种事件
|
|
func ShouldReportBothEvents() bool {
|
|
// 注意:此逻辑已被修改,现在只在特定情况下使用
|
|
// 正常情况下,应根据当前事件周期上报对应类型的事件
|
|
return false
|
|
}
|
|
|
|
// jsonString 将任意类型转换为JSON字符串
|
|
func jsonString(v interface{}) string {
|
|
b, err := json.Marshal(v)
|
|
if err != nil {
|
|
if logger := logger.GlobalLoggerManager.GetLogger("reporter"); logger != nil {
|
|
logger.Printf("JSON编码失败: %v", err)
|
|
}
|
|
return ""
|
|
}
|
|
return string(b)
|
|
}
|
|
|
|
// UploadVideoAndReport 上传视频并上报事件
|
|
func (r *Reporter) UploadVideoAndReport(deviceData connect.DeviceData, alarmTime int64, mergedFile string) error {
|
|
// 异步执行视频上传和上报,避免阻塞主线程
|
|
go func() {
|
|
logger := r.getLogger()
|
|
logger.Printf("开始异步处理视频上传和事件上报 (DeviceUUID=%s, 文件=%s)", deviceData.DeviceUUID, mergedFile)
|
|
defer func() {
|
|
if err := recover(); err != nil {
|
|
logger.Printf("上传视频和上报事件发生 panic: %v", err)
|
|
// 确保临时文件被清理
|
|
os.Remove(mergedFile)
|
|
}
|
|
}()
|
|
|
|
// 检查事件周期是否超时
|
|
logger.Printf("检查事件周期状态 (DeviceUUID=%s)", deviceData.DeviceUUID)
|
|
if IsCycleTimeout() {
|
|
// 超时后切换回默认周期
|
|
logger.Printf("事件周期超时,切换回默认周期 (DeviceUUID=%s)", deviceData.DeviceUUID)
|
|
if err := SwitchEventCycle(EventCycleFireLeave); err != nil {
|
|
logger.Printf("事件周期超时切换失败: %v", err)
|
|
}
|
|
}
|
|
|
|
// 获取当前事件周期
|
|
currentCycle := GetCurrentEventCycle()
|
|
logger.Printf("当前事件周期: %s (DeviceUUID=%s)", currentCycle, deviceData.DeviceUUID)
|
|
|
|
// 检查是否应该同时上报两种事件
|
|
if ShouldReportBothEvents() {
|
|
logger.Printf("Forbid time config is 0, will report both event types (DeviceUUID=%s)", deviceData.DeviceUUID)
|
|
|
|
// 复制文件路径,避免在goroutine中使用同一个变量
|
|
fileToClean := mergedFile
|
|
|
|
// 上报fire_leave事件
|
|
logger.Printf("开始上报fire_leave事件 (DeviceUUID=%s)", deviceData.DeviceUUID)
|
|
r.reportEventWithLevel(deviceData, alarmTime, mergedFile, ReportLevelFireLeave)
|
|
|
|
// 上报fire_check事件
|
|
logger.Printf("开始上报fire_check事件 (DeviceUUID=%s)", deviceData.DeviceUUID)
|
|
r.reportEventWithLevel(deviceData, alarmTime, mergedFile, ReportLevelFireCheck)
|
|
|
|
// 延迟清理临时文件,确保上传完成
|
|
go func() {
|
|
// 等待一段时间,确保上传操作有足够时间完成
|
|
time.Sleep(5 * time.Second)
|
|
logger.Printf("清理临时文件: %s (DeviceUUID=%s)", fileToClean, deviceData.DeviceUUID)
|
|
os.Remove(fileToClean)
|
|
}()
|
|
|
|
logger.Printf("双事件上报完成 (DeviceUUID=%s)", deviceData.DeviceUUID)
|
|
return
|
|
}
|
|
|
|
// 根据当前事件周期确定上报级别
|
|
var reportLevel ReportLevel
|
|
switch currentCycle {
|
|
case EventCycleFireLeave:
|
|
reportLevel = ReportLevelFireLeave
|
|
case EventCycleFireCheck:
|
|
reportLevel = ReportLevelFireCheck
|
|
default:
|
|
reportLevel = ReportLevelFireLeave
|
|
}
|
|
logger.Printf("确定上报级别: %s (DeviceUUID=%s)", reportLevel, deviceData.DeviceUUID)
|
|
|
|
// 生成Nginx上传参数
|
|
logger.Printf("生成Nginx上传参数 (DeviceUUID=%s)", deviceData.DeviceUUID)
|
|
nginxParams := r.GenerateNginxParams(deviceData, alarmTime, reportLevel)
|
|
|
|
// 上传视频到Nginx
|
|
logger.Printf("开始上传视频到Nginx (DeviceUUID=%s, 文件=%s, URL=%s)", deviceData.DeviceUUID, mergedFile, deviceData.UploadURL)
|
|
videoURL, err := video_server.UploadToNginx(mergedFile, nginxParams, deviceData.UploadURL)
|
|
videoURLStr := ""
|
|
if err != nil {
|
|
logger.Printf("Nginx上传失败: %v (DeviceUUID=%s)", err, deviceData.DeviceUUID)
|
|
} else {
|
|
videoURLStr = videoURL
|
|
logger.Printf("Nginx上传成功: %s (DeviceUUID=%s)", videoURL, deviceData.DeviceUUID)
|
|
}
|
|
|
|
// 检查WebSocket客户端
|
|
logger.Printf("检查WebSocket客户端连接状态 (DeviceUUID=%s)", deviceData.DeviceUUID)
|
|
if r.wsClient == nil {
|
|
logger.Printf("WebSocket客户端未初始化,尝试重新获取 (DeviceUUID=%s)", deviceData.DeviceUUID)
|
|
// 尝试重新获取WebSocket客户端
|
|
r.wsClient = connect.GetWSClient()
|
|
if r.wsClient == nil {
|
|
logger.Printf("WebSocket客户端仍然为nil,无法上报事件 (DeviceUUID=%s)", deviceData.DeviceUUID)
|
|
os.Remove(mergedFile)
|
|
return
|
|
}
|
|
}
|
|
|
|
if !r.wsClient.IsConnected() {
|
|
logger.Printf("WS未就绪,延迟上报 (DeviceUUID=%s)", deviceData.DeviceUUID)
|
|
os.Remove(mergedFile)
|
|
return
|
|
}
|
|
logger.Printf("WebSocket客户端连接状态正常 (DeviceUUID=%s)", deviceData.DeviceUUID)
|
|
|
|
// 上报事件
|
|
logger.Printf("开始上报事件 (DeviceUUID=%s, Level=%s, VideoURL=%s)", deviceData.DeviceUUID, reportLevel, videoURLStr)
|
|
if err := r.ReportEvent(deviceData, alarmTime, videoURLStr, string(reportLevel)); err != nil {
|
|
logger.Printf("事件上报失败: %v (DeviceUUID=%s)", err, deviceData.DeviceUUID)
|
|
} else {
|
|
logger.Printf("事件上报成功 (DeviceUUID=%s, Level=%s)", deviceData.DeviceUUID, reportLevel)
|
|
}
|
|
|
|
// 上报数据快照
|
|
logger.Printf("开始上报数据快照 (DeviceUUID=%s)", deviceData.DeviceUUID)
|
|
if err := r.ReportDataSnapshot(deviceData, alarmTime); err != nil {
|
|
logger.Printf("数据快照上报失败: %v (DeviceUUID=%s)", err, deviceData.DeviceUUID)
|
|
} else {
|
|
logger.Printf("数据快照上报成功 (DeviceUUID=%s)", deviceData.DeviceUUID)
|
|
}
|
|
|
|
// 清理临时文件
|
|
logger.Printf("清理临时文件: %s (DeviceUUID=%s)", mergedFile, deviceData.DeviceUUID)
|
|
os.Remove(mergedFile)
|
|
logger.Printf("视频上传和事件上报完成 (DeviceUUID=%s)", deviceData.DeviceUUID)
|
|
}()
|
|
|
|
return nil
|
|
}
|
|
|
|
// UploadVideoAndReportFireLeave 上传视频并上报温度报警事件(只上报fire_leave类型)
|
|
func (r *Reporter) UploadVideoAndReportFireLeave(deviceData connect.DeviceData, alarmTime int64, mergedFile string) error {
|
|
// 异步执行视频上传和上报,避免阻塞主线程
|
|
go func() {
|
|
logger := r.getLogger()
|
|
logger.Printf("开始异步处理温度报警视频上传和事件上报 (DeviceUUID=%s, 文件=%s)", deviceData.DeviceUUID, mergedFile)
|
|
defer func() {
|
|
if err := recover(); err != nil {
|
|
logger.Printf("上传温度报警视频和上报事件发生 panic: %v", err)
|
|
// 确保临时文件被清理
|
|
os.Remove(mergedFile)
|
|
}
|
|
}()
|
|
|
|
// 检查事件周期是否超时
|
|
logger.Printf("检查事件周期状态 (DeviceUUID=%s)", deviceData.DeviceUUID)
|
|
if IsCycleTimeout() {
|
|
// 超时后切换回默认周期
|
|
logger.Printf("事件周期超时,切换回默认周期 (DeviceUUID=%s)", deviceData.DeviceUUID)
|
|
if err := SwitchEventCycle(EventCycleFireLeave); err != nil {
|
|
logger.Printf("事件周期超时切换失败: %v", err)
|
|
}
|
|
}
|
|
|
|
// 生成Nginx上传参数
|
|
logger.Printf("生成Nginx上传参数 (DeviceUUID=%s)", deviceData.DeviceUUID)
|
|
nginxParams := r.GenerateNginxParams(deviceData, alarmTime, ReportLevelFireLeave)
|
|
|
|
// 上传视频到Nginx
|
|
logger.Printf("开始上传视频到Nginx (DeviceUUID=%s, 文件=%s, URL=%s)", deviceData.DeviceUUID, mergedFile, deviceData.UploadURL)
|
|
videoURL, err := video_server.UploadToNginx(mergedFile, nginxParams, deviceData.UploadURL)
|
|
videoURLStr := ""
|
|
if err != nil {
|
|
logger.Printf("Nginx上传失败: %v (DeviceUUID=%s)", err, deviceData.DeviceUUID)
|
|
} else {
|
|
videoURLStr = videoURL
|
|
logger.Printf("Nginx上传成功: %s (DeviceUUID=%s)", videoURL, deviceData.DeviceUUID)
|
|
}
|
|
|
|
// 检查WebSocket客户端
|
|
logger.Printf("检查WebSocket客户端连接状态 (DeviceUUID=%s)", deviceData.DeviceUUID)
|
|
if r.wsClient == nil {
|
|
logger.Printf("WebSocket客户端未初始化,尝试重新获取 (DeviceUUID=%s)", deviceData.DeviceUUID)
|
|
// 尝试重新获取WebSocket客户端
|
|
r.wsClient = connect.GetWSClient()
|
|
if r.wsClient == nil {
|
|
logger.Printf("WebSocket客户端仍然为nil,无法上报事件 (DeviceUUID=%s)", deviceData.DeviceUUID)
|
|
os.Remove(mergedFile)
|
|
return
|
|
}
|
|
}
|
|
|
|
if !r.wsClient.IsConnected() {
|
|
logger.Printf("WS未就绪,延迟上报 (DeviceUUID=%s)", deviceData.DeviceUUID)
|
|
os.Remove(mergedFile)
|
|
return
|
|
}
|
|
logger.Printf("WebSocket客户端连接状态正常 (DeviceUUID=%s)", deviceData.DeviceUUID)
|
|
|
|
// 上报事件(只上报fire_leave类型)
|
|
logger.Printf("开始上报温度报警事件 (DeviceUUID=%s, Level=%s, VideoURL=%s)", deviceData.DeviceUUID, ReportLevelFireLeave, videoURLStr)
|
|
if err := r.ReportEvent(deviceData, alarmTime, videoURLStr, string(ReportLevelFireLeave)); err != nil {
|
|
logger.Printf("温度报警事件上报失败: %v (DeviceUUID=%s)", err, deviceData.DeviceUUID)
|
|
} else {
|
|
logger.Printf("温度报警事件上报成功 (DeviceUUID=%s, Level=%s)", deviceData.DeviceUUID, ReportLevelFireLeave)
|
|
}
|
|
|
|
// 上报数据快照
|
|
logger.Printf("开始上报数据快照 (DeviceUUID=%s)", deviceData.DeviceUUID)
|
|
if err := r.ReportDataSnapshot(deviceData, alarmTime); err != nil {
|
|
logger.Printf("数据快照上报失败: %v (DeviceUUID=%s)", err, deviceData.DeviceUUID)
|
|
} else {
|
|
logger.Printf("数据快照上报成功 (DeviceUUID=%s)", deviceData.DeviceUUID)
|
|
}
|
|
|
|
// 清理临时文件
|
|
logger.Printf("清理临时文件: %s (DeviceUUID=%s)", mergedFile, deviceData.DeviceUUID)
|
|
os.Remove(mergedFile)
|
|
logger.Printf("温度报警视频上传和事件上报完成 (DeviceUUID=%s)", deviceData.DeviceUUID)
|
|
}()
|
|
|
|
return nil
|
|
}
|
|
|
|
// UploadVideoAndReportFireCheck 上传视频并上报火焰检测报警事件(只上报fire_check类型)
|
|
func (r *Reporter) UploadVideoAndReportFireCheck(deviceData connect.DeviceData, alarmTime int64, mergedFile string) error {
|
|
// 异步执行视频上传和上报,避免阻塞主线程
|
|
go func() {
|
|
logger := r.getLogger()
|
|
logger.Printf("开始异步处理火焰检测报警视频上传和事件上报 (DeviceUUID=%s, 文件=%s)", deviceData.DeviceUUID, mergedFile)
|
|
defer func() {
|
|
if err := recover(); err != nil {
|
|
logger.Printf("上传火焰检测报警视频和上报事件发生 panic: %v", err)
|
|
// 确保临时文件被清理
|
|
os.Remove(mergedFile)
|
|
}
|
|
}()
|
|
|
|
// 检查事件周期是否超时
|
|
logger.Printf("检查事件周期状态 (DeviceUUID=%s)", deviceData.DeviceUUID)
|
|
if IsCycleTimeout() {
|
|
// 超时后切换到fire_check周期
|
|
logger.Printf("事件周期超时,切换到fire_check周期 (DeviceUUID=%s)", deviceData.DeviceUUID)
|
|
if err := SwitchEventCycle(EventCycleFireCheck); err != nil {
|
|
logger.Printf("事件周期超时切换失败: %v", err)
|
|
}
|
|
}
|
|
|
|
// 生成Nginx上传参数
|
|
logger.Printf("生成Nginx上传参数 (DeviceUUID=%s)", deviceData.DeviceUUID)
|
|
nginxParams := r.GenerateNginxParams(deviceData, alarmTime, ReportLevelFireCheck)
|
|
|
|
// 上传视频到Nginx
|
|
logger.Printf("开始上传视频到Nginx (DeviceUUID=%s, 文件=%s, URL=%s)", deviceData.DeviceUUID, mergedFile, deviceData.UploadURL)
|
|
videoURL, err := video_server.UploadToNginx(mergedFile, nginxParams, deviceData.UploadURL)
|
|
videoURLStr := ""
|
|
if err != nil {
|
|
logger.Printf("Nginx上传失败: %v (DeviceUUID=%s)", err, deviceData.DeviceUUID)
|
|
} else {
|
|
videoURLStr = videoURL
|
|
logger.Printf("Nginx上传成功: %s (DeviceUUID=%s)", videoURL, deviceData.DeviceUUID)
|
|
}
|
|
|
|
// 检查WebSocket客户端
|
|
logger.Printf("检查WebSocket客户端连接状态 (DeviceUUID=%s)", deviceData.DeviceUUID)
|
|
if r.wsClient == nil {
|
|
logger.Printf("WebSocket客户端未初始化,尝试重新获取 (DeviceUUID=%s)", deviceData.DeviceUUID)
|
|
// 尝试重新获取WebSocket客户端
|
|
r.wsClient = connect.GetWSClient()
|
|
if r.wsClient == nil {
|
|
logger.Printf("WebSocket客户端仍然为nil,无法上报事件 (DeviceUUID=%s)", deviceData.DeviceUUID)
|
|
os.Remove(mergedFile)
|
|
return
|
|
}
|
|
}
|
|
|
|
if !r.wsClient.IsConnected() {
|
|
logger.Printf("WS未就绪,延迟上报 (DeviceUUID=%s)", deviceData.DeviceUUID)
|
|
os.Remove(mergedFile)
|
|
return
|
|
}
|
|
logger.Printf("WebSocket客户端连接状态正常 (DeviceUUID=%s)", deviceData.DeviceUUID)
|
|
|
|
// 上报事件(只上报fire_check类型)
|
|
logger.Printf("开始上报火焰检测报警事件 (DeviceUUID=%s, Level=%s, VideoURL=%s)", deviceData.DeviceUUID, ReportLevelFireCheck, videoURLStr)
|
|
if err := r.ReportEvent(deviceData, alarmTime, videoURLStr, string(ReportLevelFireCheck)); err != nil {
|
|
logger.Printf("火焰检测报警事件上报失败: %v (DeviceUUID=%s)", err, deviceData.DeviceUUID)
|
|
} else {
|
|
logger.Printf("火焰检测报警事件上报成功 (DeviceUUID=%s, Level=%s)", deviceData.DeviceUUID, ReportLevelFireCheck)
|
|
}
|
|
|
|
// 上报数据快照
|
|
logger.Printf("开始上报数据快照 (DeviceUUID=%s)", deviceData.DeviceUUID)
|
|
if err := r.ReportDataSnapshot(deviceData, alarmTime); err != nil {
|
|
logger.Printf("数据快照上报失败: %v (DeviceUUID=%s)", err, deviceData.DeviceUUID)
|
|
} else {
|
|
logger.Printf("数据快照上报成功 (DeviceUUID=%s)", deviceData.DeviceUUID)
|
|
}
|
|
|
|
// 清理临时文件
|
|
logger.Printf("清理临时文件: %s (DeviceUUID=%s)", mergedFile, deviceData.DeviceUUID)
|
|
os.Remove(mergedFile)
|
|
logger.Printf("火焰检测报警视频上传和事件上报完成 (DeviceUUID=%s)", deviceData.DeviceUUID)
|
|
}()
|
|
|
|
return nil
|
|
}
|
|
|
|
// reportEventWithLevel 按指定级别上报事件
|
|
func (r *Reporter) reportEventWithLevel(deviceData connect.DeviceData, alarmTime int64, mergedFile string, reportLevel ReportLevel) error {
|
|
// 异步执行视频上传和上报,避免阻塞主线程
|
|
go func() {
|
|
logger := r.getLogger()
|
|
defer func() {
|
|
if err := recover(); err != nil {
|
|
logger.Printf("按指定级别上报事件发生 panic: %v", err)
|
|
}
|
|
}()
|
|
|
|
// 生成Nginx上传参数
|
|
nginxParams := r.GenerateNginxParams(deviceData, alarmTime, reportLevel)
|
|
|
|
// 上传视频到Nginx
|
|
videoURL, err := video_server.UploadToNginx(mergedFile, nginxParams, deviceData.UploadURL)
|
|
videoURLStr := ""
|
|
if err != nil {
|
|
logger.Printf("Nginx上传失败: %v", err)
|
|
} else {
|
|
videoURLStr = videoURL
|
|
logger.Printf("Nginx上传成功: %s", videoURL)
|
|
}
|
|
|
|
// 检查WebSocket客户端
|
|
if r.wsClient == nil || !r.wsClient.IsConnected() {
|
|
logger.Printf("WS未就绪,延迟上报 (DeviceUUID=%s)", deviceData.DeviceUUID)
|
|
return
|
|
}
|
|
|
|
// 上报事件
|
|
if err := r.ReportEvent(deviceData, alarmTime, videoURLStr, string(reportLevel)); err != nil {
|
|
logger.Printf("事件上报失败: %v", err)
|
|
}
|
|
|
|
// 上报数据快照
|
|
if err := r.ReportDataSnapshot(deviceData, alarmTime); err != nil {
|
|
logger.Printf("数据快照上报失败: %v", err)
|
|
}
|
|
|
|
logger.Printf("按指定级别上报事件完成 (DeviceUUID=%s, Level=%s)", deviceData.DeviceUUID, string(reportLevel))
|
|
}()
|
|
|
|
return nil
|
|
}
|
|
|
|
// ReportEvent 上报事件
|
|
func (r *Reporter) ReportEvent(deviceData connect.DeviceData, alarmTime int64, videoURL string, level string) error {
|
|
logger := r.getLogger()
|
|
|
|
// 检查WebSocket客户端
|
|
if r.wsClient == nil {
|
|
logger.Printf("WebSocket客户端未初始化,无法上报事件 (DeviceUUID=%s)", deviceData.DeviceUUID)
|
|
return fmt.Errorf("websocket client not initialized")
|
|
}
|
|
|
|
if !r.wsClient.IsConnected() {
|
|
logger.Printf("WebSocket客户端未连接,无法上报事件 (DeviceUUID=%s)", deviceData.DeviceUUID)
|
|
return fmt.Errorf("websocket client not connected")
|
|
}
|
|
|
|
// 生成事件消息
|
|
eventMsg := r.GenerateEventMessage(deviceData, alarmTime, videoURL, level)
|
|
|
|
// 发送事件消息
|
|
r.wsClient.SendAsync(eventMsg)
|
|
logger.Printf("报警事件已上报 (DeviceUUID=%s, Level=%s)", deviceData.DeviceUUID, level)
|
|
|
|
return nil
|
|
}
|
|
|
|
// ReportDataSnapshot 上报数据快照
|
|
func (r *Reporter) ReportDataSnapshot(deviceData connect.DeviceData, alarmTime int64) error {
|
|
logger := r.getLogger()
|
|
|
|
// 检查WebSocket客户端
|
|
if r.wsClient == nil {
|
|
logger.Printf("WebSocket客户端未初始化,无法上报数据快照 (DeviceUUID=%s)", deviceData.DeviceUUID)
|
|
return fmt.Errorf("websocket client not initialized")
|
|
}
|
|
|
|
if !r.wsClient.IsConnected() {
|
|
logger.Printf("WebSocket客户端未连接,无法上报数据快照 (DeviceUUID=%s)", deviceData.DeviceUUID)
|
|
return fmt.Errorf("websocket client not connected")
|
|
}
|
|
|
|
// 生成数据快照消息
|
|
dataMsg := r.GenerateDataMessage(deviceData, alarmTime)
|
|
|
|
// 发送数据快照消息
|
|
r.wsClient.SendAsync(dataMsg)
|
|
logger.Printf("数据快照已上报 (DeviceUUID=%s)", deviceData.DeviceUUID)
|
|
|
|
return nil
|
|
}
|
|
|
|
// GenerateEventMessage 生成事件消息
|
|
func (r *Reporter) GenerateEventMessage(deviceData connect.DeviceData, alarmTime int64, videoURL string, level string) map[string]interface{} {
|
|
// 获取HostUUID
|
|
hostUUID := r.getHostUUID(deviceData.DeviceUUID)
|
|
|
|
// 生成描述
|
|
description := "无人工作区且温度超阈值报警"
|
|
if level == string(ReportLevelFireCheck) {
|
|
description = "检测到异常火苗"
|
|
}
|
|
|
|
// 构建事件负载
|
|
eventPayload := EventPayload{
|
|
Type: string(ReportTypeEvent),
|
|
Args: map[string]interface{}{
|
|
"level": level,
|
|
"description": description,
|
|
"device_uuid": deviceData.DeviceUUID,
|
|
"host_uuid": hostUUID,
|
|
"video_url": videoURL,
|
|
"alarm_time": alarmTime,
|
|
"address": deviceData.Address,
|
|
},
|
|
}
|
|
|
|
// 构建上报消息
|
|
eventMsg := map[string]interface{}{
|
|
"method": "metric_data",
|
|
"params": map[string]interface{}{
|
|
"route_key": fmt.Sprintf("/dhlr/device/%s/event", deviceData.DeviceUUID),
|
|
"metric": jsonString(eventPayload),
|
|
},
|
|
}
|
|
|
|
return eventMsg
|
|
}
|
|
|
|
// GenerateDataMessage 生成数据快照消息
|
|
func (r *Reporter) GenerateDataMessage(deviceData connect.DeviceData, alarmTime int64) map[string]interface{} {
|
|
// 构建数据快照负载
|
|
dataPayload := DataPayload{
|
|
Type: string(ReportTypeData),
|
|
Args: map[string]interface{}{
|
|
"person_count": deviceData.PersonCount,
|
|
"temperature": deviceData.Temperature,
|
|
"camera_rtsp": deviceData.CameraRTSP,
|
|
"task_id": deviceData.TaskID,
|
|
"camera_ip": deviceData.CameraIP,
|
|
"confidence": deviceData.Confidence,
|
|
"alarm_time": alarmTime,
|
|
"address": deviceData.Address,
|
|
},
|
|
}
|
|
|
|
// 构建上报消息
|
|
dataMsg := map[string]interface{}{
|
|
"method": "metric_data",
|
|
"params": map[string]interface{}{
|
|
"route_key": fmt.Sprintf("/dhlr/device/%s/data", deviceData.DeviceUUID),
|
|
"metric": jsonString(dataPayload),
|
|
},
|
|
}
|
|
|
|
return dataMsg
|
|
}
|
|
|
|
// GenerateNginxParams 生成Nginx上传参数
|
|
func (r *Reporter) GenerateNginxParams(deviceData connect.DeviceData, alarmTime int64, level ReportLevel) map[string]interface{} {
|
|
// 获取HostUUID
|
|
hostUUID := r.getHostUUID(deviceData.DeviceUUID)
|
|
|
|
// 生成描述
|
|
description := "无人工作区且温度超阈值报警"
|
|
if level == ReportLevelFireCheck {
|
|
description = "检测到异常火苗"
|
|
}
|
|
|
|
// 构建参数
|
|
params := map[string]interface{}{
|
|
"alarm_time": alarmTime,
|
|
"description": description,
|
|
"device_UUID": deviceData.DeviceUUID,
|
|
"host_uuid": hostUUID,
|
|
"person_count": deviceData.PersonCount,
|
|
"level": string(level),
|
|
}
|
|
|
|
// 火焰检测不需要温度数据
|
|
if level != ReportLevelFireCheck {
|
|
params["temperature"] = deviceData.Temperature
|
|
}
|
|
|
|
return params
|
|
}
|
|
|
|
// TriggerAlarmWithDelay 延迟触发报警并上报(温度报警)
|
|
func (r *Reporter) TriggerAlarmWithDelay(deviceData connect.DeviceData, alarmTime int64, cooldown int) {
|
|
startTime := time.Now()
|
|
logger := r.getLogger()
|
|
logger.Printf("[%.3f] 开始处理温度报警事件 (DeviceUUID=%s, alarmTime=%d, cooldown=%d)", time.Since(startTime).Seconds(), deviceData.DeviceUUID, alarmTime, cooldown)
|
|
|
|
// 捕获所有异常,确保函数不会因为panic而崩溃
|
|
defer func() {
|
|
if err := recover(); err != nil {
|
|
logger.Printf("[%.3f] 处理温度报警事件发生panic: %v (DeviceUUID=%s)", time.Since(startTime).Seconds(), err, deviceData.DeviceUUID)
|
|
// 确保锁被释放
|
|
key := fmt.Sprintf("%s_%d", deviceData.DeviceUUID, alarmTime)
|
|
mergingAlarms.Delete(key)
|
|
}
|
|
}()
|
|
|
|
// 防止重复处理
|
|
key := fmt.Sprintf("%s_%d", deviceData.DeviceUUID, alarmTime)
|
|
logger.Printf("[%.3f] 检查温度报警事件是否已在处理中 (DeviceUUID=%s, key=%s)", time.Since(startTime).Seconds(), deviceData.DeviceUUID, key)
|
|
if _, loaded := mergingAlarms.LoadOrStore(key, true); loaded {
|
|
logger.Printf("[%.3f] 温度报警事件已在处理中,跳过重复处理 (DeviceUUID=%s, key=%s)", time.Since(startTime).Seconds(), deviceData.DeviceUUID, key)
|
|
return
|
|
}
|
|
defer mergingAlarms.Delete(key)
|
|
logger.Printf("[%.3f] 成功获取处理锁 (DeviceUUID=%s, key=%s)", time.Since(startTime).Seconds(), deviceData.DeviceUUID, key)
|
|
|
|
// 合并视频
|
|
logger.Printf("[%.3f] 开始合并视频 (DeviceUUID=%s, alarmTime=%d)", time.Since(startTime).Seconds(), deviceData.DeviceUUID, alarmTime)
|
|
mergeStartTime := time.Now()
|
|
mergedFile, err := video_server.MergeVideo(deviceData.DeviceUUID, alarmTime)
|
|
if err != nil {
|
|
logger.Printf("[%.3f] 视频合并失败 (DeviceUUID=%s, 耗时:%.3fs): %v", time.Since(startTime).Seconds(), deviceData.DeviceUUID, time.Since(mergeStartTime).Seconds(), err)
|
|
return
|
|
}
|
|
logger.Printf("[%.3f] 视频合并成功,生成文件: %s (DeviceUUID=%s, 耗时:%.3fs)", time.Since(startTime).Seconds(), mergedFile, deviceData.DeviceUUID, time.Since(mergeStartTime).Seconds())
|
|
|
|
// 上传视频并上报(只上报温度报警,即fire_leave类型)
|
|
logger.Printf("[%.3f] 开始上传视频并上报温度报警事件 (DeviceUUID=%s, 文件=%s)", time.Since(startTime).Seconds(), deviceData.DeviceUUID, mergedFile)
|
|
uploadStartTime := time.Now()
|
|
if err := r.UploadVideoAndReportFireLeave(deviceData, alarmTime, mergedFile); err != nil {
|
|
logger.Printf("[%.3f] 温度报警上报失败 (DeviceUUID=%s, 耗时:%.3fs): %v", time.Since(startTime).Seconds(), deviceData.DeviceUUID, time.Since(uploadStartTime).Seconds(), err)
|
|
// 清理临时文件
|
|
if err := os.Remove(mergedFile); err != nil {
|
|
logger.Printf("[%.3f] 清理临时文件失败: %v (DeviceUUID=%s)", time.Since(startTime).Seconds(), err, deviceData.DeviceUUID)
|
|
}
|
|
return
|
|
}
|
|
logger.Printf("[%.3f] 温度报警上报成功 (DeviceUUID=%s, 耗时:%.3fs)", time.Since(startTime).Seconds(), deviceData.DeviceUUID, time.Since(uploadStartTime).Seconds())
|
|
|
|
logger.Printf("[%.3f] 温度报警事件处理完成 (DeviceUUID=%s, 总耗时:%.3fs)", time.Since(startTime).Seconds(), deviceData.DeviceUUID, time.Since(startTime).Seconds())
|
|
}
|
|
|
|
// TriggerFireCheckAlarm 触发火焰检测报警并上报
|
|
func (r *Reporter) TriggerFireCheckAlarm(deviceData connect.DeviceData, alarmTime int64) {
|
|
startTime := time.Now()
|
|
logger := r.getLogger()
|
|
logger.Printf("[%.3f] 开始处理火焰检测报警事件 (DeviceUUID=%s, alarmTime=%d)", time.Since(startTime).Seconds(), deviceData.DeviceUUID, alarmTime)
|
|
|
|
// 捕获所有异常,确保函数不会因为panic而崩溃
|
|
defer func() {
|
|
if err := recover(); err != nil {
|
|
logger.Printf("[%.3f] 处理火焰检测报警事件发生panic: %v (DeviceUUID=%s)", time.Since(startTime).Seconds(), err, deviceData.DeviceUUID)
|
|
// 确保锁被释放
|
|
key := fmt.Sprintf("%s_fire_%d", deviceData.DeviceUUID, alarmTime)
|
|
mergingAlarms.Delete(key)
|
|
}
|
|
}()
|
|
|
|
// 防止重复处理
|
|
key := fmt.Sprintf("%s_fire_%d", deviceData.DeviceUUID, alarmTime)
|
|
logger.Printf("[%.3f] 检查火焰检测报警事件是否已在处理中 (DeviceUUID=%s, key=%s)", time.Since(startTime).Seconds(), deviceData.DeviceUUID, key)
|
|
if _, loaded := mergingAlarms.LoadOrStore(key, true); loaded {
|
|
logger.Printf("[%.3f] 火焰检测报警事件已在处理中,跳过重复处理 (DeviceUUID=%s, key=%s)", time.Since(startTime).Seconds(), deviceData.DeviceUUID, key)
|
|
return
|
|
}
|
|
defer mergingAlarms.Delete(key)
|
|
logger.Printf("[%.3f] 成功获取处理锁 (DeviceUUID=%s, key=%s)", time.Since(startTime).Seconds(), deviceData.DeviceUUID, key)
|
|
|
|
// 合并视频(使用fire_check目录的视频)
|
|
logger.Printf("[%.3f] 开始合并火焰检测视频 (DeviceUUID=%s, alarmTime=%d)", time.Since(startTime).Seconds(), deviceData.DeviceUUID, alarmTime)
|
|
mergeStartTime := time.Now()
|
|
// 这里可以修改MergeVideo函数,使其支持指定目录,或者创建一个新的函数用于合并fire_check视频
|
|
mergedFile, err := video_server.MergeVideo(deviceData.DeviceUUID+"_fire_check", alarmTime)
|
|
if err != nil {
|
|
logger.Printf("[%.3f] 火焰检测视频合并失败 (DeviceUUID=%s, 耗时:%.3fs): %v", time.Since(startTime).Seconds(), deviceData.DeviceUUID, time.Since(mergeStartTime).Seconds(), err)
|
|
return
|
|
}
|
|
logger.Printf("[%.3f] 火焰检测视频合并成功,生成文件: %s (DeviceUUID=%s, 耗时:%.3fs)", time.Since(startTime).Seconds(), mergedFile, deviceData.DeviceUUID, time.Since(mergeStartTime).Seconds())
|
|
|
|
// 上传视频并上报(只上报火焰检测,即fire_check类型)
|
|
logger.Printf("[%.3f] 开始上传视频并上报火焰检测报警事件 (DeviceUUID=%s, 文件=%s)", time.Since(startTime).Seconds(), deviceData.DeviceUUID, mergedFile)
|
|
uploadStartTime := time.Now()
|
|
if err := r.UploadVideoAndReportFireCheck(deviceData, alarmTime, mergedFile); err != nil {
|
|
logger.Printf("[%.3f] 火焰检测报警上报失败 (DeviceUUID=%s, 耗时:%.3fs): %v", time.Since(startTime).Seconds(), deviceData.DeviceUUID, time.Since(uploadStartTime).Seconds(), err)
|
|
// 清理临时文件
|
|
if err := os.Remove(mergedFile); err != nil {
|
|
logger.Printf("[%.3f] 清理临时文件失败: %v (DeviceUUID=%s)", time.Since(startTime).Seconds(), err, deviceData.DeviceUUID)
|
|
}
|
|
return
|
|
}
|
|
logger.Printf("[%.3f] 火焰检测报警上报成功 (DeviceUUID=%s, 耗时:%.3fs)", time.Since(startTime).Seconds(), deviceData.DeviceUUID, time.Since(uploadStartTime).Seconds())
|
|
|
|
logger.Printf("[%.3f] 火焰检测报警事件处理完成 (DeviceUUID=%s, 总耗时:%.3fs)", time.Since(startTime).Seconds(), deviceData.DeviceUUID, time.Since(startTime).Seconds())
|
|
}
|
|
|
|
// getHostUUID 获取设备的HostUUID
|
|
func (r *Reporter) getHostUUID(deviceUUID string) string {
|
|
configs, err := connect.LoadServiceConfig()
|
|
if err != nil {
|
|
r.logger.Printf("加载服务配置失败: %v", err)
|
|
return ""
|
|
}
|
|
|
|
for _, cfg := range configs {
|
|
if cfg.DeviceUUID == deviceUUID {
|
|
return cfg.HostUUID
|
|
}
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
// SetWSClient 设置WebSocket客户端
|
|
func (r *Reporter) SetWSClient(client *connect.WSClient) {
|
|
r.wsClient = client
|
|
}
|
|
|
|
// SetUploadURL 设置上传URL
|
|
func (r *Reporter) SetUploadURL(url string) {
|
|
r.uploadURL = url
|
|
}
|
|
|
|
// IsWSReady 检查WebSocket是否就绪
|
|
func (r *Reporter) IsWSReady() bool {
|
|
return r.wsClient != nil && r.wsClient.IsConnected()
|
|
}
|