You've already forked VptPassiveAdapter
- 将时间格式从 ISO 8601 修改为标准日期时间格式 - 修复连续性检查中 Gaps 字段的空数组初始化问题 - 重构连续性检查循环逻辑,启动时立即执行一次检查 - 提取连续性检查逻辑到独立的 performContinuityCheck 函数 - 优化检查时间范围的判断逻辑
191 lines
5.5 KiB
Go
191 lines
5.5 KiB
Go
package api
|
|
|
|
import (
|
|
"ZhenTuLocalPassiveAdapter/dto"
|
|
"ZhenTuLocalPassiveAdapter/logger"
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
|
|
"go.opentelemetry.io/otel/attribute"
|
|
"go.opentelemetry.io/otel/codes"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
const (
|
|
// 连续性上报接口地址
|
|
continuityReportURL = "https://zhentuai.com/api/device/video-continuity/report"
|
|
// 间隔阈值(毫秒)
|
|
maxAllowedGapMs = 2000
|
|
)
|
|
|
|
// ReportContinuityCheck 上报连续性检查结果
|
|
func ReportContinuityCheck(ctx context.Context, results []dto.ContinuityCheckResult) error {
|
|
_, span := tracer.Start(ctx, "ReportContinuityCheck")
|
|
defer span.End()
|
|
|
|
span.SetAttributes(attribute.Int("results.count", len(results)))
|
|
|
|
// 统计不连续的设备数量
|
|
discontinuousCount := 0
|
|
for _, r := range results {
|
|
if !r.IsContinuous {
|
|
discontinuousCount++
|
|
}
|
|
}
|
|
span.SetAttributes(attribute.Int("discontinuous.count", discontinuousCount))
|
|
|
|
// 逐个设备上报
|
|
var lastErr error
|
|
for _, result := range results {
|
|
if err := reportSingleDevice(ctx, result); err != nil {
|
|
logger.Error("上报设备连续性检查结果失败",
|
|
zap.String("deviceNo", result.DeviceNo),
|
|
zap.Error(err))
|
|
lastErr = err
|
|
}
|
|
}
|
|
|
|
return lastErr
|
|
}
|
|
|
|
// reportSingleDevice 上报单个设备的连续性检查结果
|
|
func reportSingleDevice(ctx context.Context, result dto.ContinuityCheckResult) error {
|
|
_, span := tracer.Start(ctx, "reportSingleDevice")
|
|
defer span.End()
|
|
|
|
span.SetAttributes(
|
|
attribute.String("device.no", result.DeviceNo),
|
|
attribute.Bool("continuous", result.IsContinuous),
|
|
)
|
|
|
|
// 转换为接口请求格式
|
|
request := convertToReportRequest(result)
|
|
|
|
// 序列化请求体
|
|
jsonData, err := json.Marshal(request)
|
|
if err != nil {
|
|
span.SetAttributes(attribute.String("error", err.Error()))
|
|
span.SetStatus(codes.Error, "序列化JSON失败")
|
|
logger.Error("序列化连续性检查请求失败", zap.Error(err))
|
|
return err
|
|
}
|
|
|
|
span.SetAttributes(attribute.String("request.body", string(jsonData)))
|
|
|
|
// 创建请求
|
|
req, err := http.NewRequestWithContext(ctx, "POST", continuityReportURL, bytes.NewBuffer(jsonData))
|
|
if err != nil {
|
|
span.SetAttributes(attribute.String("error", err.Error()))
|
|
span.SetStatus(codes.Error, "创建请求失败")
|
|
logger.Error("创建连续性检查上报请求失败", zap.Error(err))
|
|
return err
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
span.SetAttributes(
|
|
attribute.String("http.url", continuityReportURL),
|
|
attribute.String("http.method", "POST"),
|
|
)
|
|
|
|
// 发送请求
|
|
resp, err := GetAPIClient().Do(req)
|
|
if err != nil {
|
|
span.SetAttributes(attribute.String("error", err.Error()))
|
|
span.SetStatus(codes.Error, "发送请求失败")
|
|
logger.Error("发送连续性检查上报请求失败", zap.Error(err))
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
span.SetAttributes(
|
|
attribute.String("http.status", resp.Status),
|
|
attribute.Int("http.status_code", resp.StatusCode),
|
|
)
|
|
|
|
// 读取响应体
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
span.SetAttributes(attribute.String("error", err.Error()))
|
|
span.SetStatus(codes.Error, "读取响应体失败")
|
|
logger.Error("读取连续性检查上报响应失败", zap.Error(err))
|
|
return err
|
|
}
|
|
|
|
span.SetAttributes(attribute.String("response.body", string(body)))
|
|
|
|
// 解析响应
|
|
var response dto.ContinuityReportResponse
|
|
if err := json.Unmarshal(body, &response); err != nil {
|
|
span.SetAttributes(attribute.String("error", err.Error()))
|
|
span.SetStatus(codes.Error, "解析响应失败")
|
|
logger.Error("解析连续性检查上报响应失败",
|
|
zap.Error(err),
|
|
zap.String("body", string(body)))
|
|
return err
|
|
}
|
|
|
|
// 检查业务响应码
|
|
if response.Code != 0 {
|
|
errMsg := fmt.Sprintf("上报失败: code=%d, msg=%s", response.Code, response.Msg)
|
|
span.SetAttributes(attribute.String("error", errMsg))
|
|
span.SetStatus(codes.Error, "上报失败")
|
|
logger.Warn("连续性检查上报业务失败",
|
|
zap.String("deviceNo", result.DeviceNo),
|
|
zap.Int("code", response.Code),
|
|
zap.String("msg", response.Msg))
|
|
return fmt.Errorf(errMsg)
|
|
}
|
|
|
|
span.SetStatus(codes.Ok, "上报成功")
|
|
if result.IsContinuous {
|
|
logger.Debug("设备连续性检查上报成功",
|
|
zap.String("deviceNo", result.DeviceNo),
|
|
zap.Int("fileCount", result.FileCount))
|
|
} else {
|
|
logger.Info("设备连续性检查上报成功(不连续)",
|
|
zap.String("deviceNo", result.DeviceNo),
|
|
zap.Int("gapCount", len(result.Gaps)))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// convertToReportRequest 将内部检查结果转换为接口请求格式
|
|
func convertToReportRequest(result dto.ContinuityCheckResult) dto.ContinuityReportRequest {
|
|
// 时间格式
|
|
const timeFormat = "2006-01-02 15:04:05"
|
|
|
|
request := dto.ContinuityReportRequest{
|
|
DeviceNo: result.DeviceNo,
|
|
StartTime: result.RangeStart.Format(timeFormat),
|
|
EndTime: result.RangeEnd.Format(timeFormat),
|
|
Support: true, // 始终支持
|
|
Continuous: result.IsContinuous,
|
|
TotalVideos: result.FileCount,
|
|
TotalDurationMs: result.TotalDurationMs,
|
|
MaxAllowedGapMs: maxAllowedGapMs,
|
|
}
|
|
|
|
// 转换间隙列表
|
|
if len(result.Gaps) > 0 {
|
|
request.Gaps = make([]dto.ContinuityReportGap, 0, len(result.Gaps))
|
|
for _, gap := range result.Gaps {
|
|
request.Gaps = append(request.Gaps, dto.ContinuityReportGap{
|
|
BeforeFileName: gap.PreviousFileName,
|
|
AfterFileName: gap.NextFileName,
|
|
GapMs: int64(gap.GapSeconds * 1000),
|
|
GapStartTime: gap.PreviousEndTime.Format(timeFormat),
|
|
GapEndTime: gap.NextStartTime.Format(timeFormat),
|
|
})
|
|
}
|
|
} else {
|
|
request.Gaps = []dto.ContinuityReportGap{}
|
|
}
|
|
|
|
return request
|
|
}
|