diff --git a/gb28181-wvp-proxy-service/src/main/java/cn/skcks/docking/gb28181/wvp/config/ReportConfig.java b/gb28181-wvp-proxy-service/src/main/java/cn/skcks/docking/gb28181/wvp/config/ReportConfig.java new file mode 100644 index 0000000..5ad0a6d --- /dev/null +++ b/gb28181-wvp-proxy-service/src/main/java/cn/skcks/docking/gb28181/wvp/config/ReportConfig.java @@ -0,0 +1,17 @@ +package cn.skcks.docking.gb28181.wvp.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import java.util.HashMap; +import java.util.Map; + +@Data +@Configuration +@ConfigurationProperties(prefix = "report") +public class ReportConfig { + private Boolean enabled = false; + private String url; + private Map customHeaders = new HashMap<>(); +} diff --git a/gb28181-wvp-proxy-service/src/main/java/cn/skcks/docking/gb28181/wvp/dto/report/ReportReq.java b/gb28181-wvp-proxy-service/src/main/java/cn/skcks/docking/gb28181/wvp/dto/report/ReportReq.java new file mode 100644 index 0000000..c23a27b --- /dev/null +++ b/gb28181-wvp-proxy-service/src/main/java/cn/skcks/docking/gb28181/wvp/dto/report/ReportReq.java @@ -0,0 +1,46 @@ +package cn.skcks.docking.gb28181.wvp.dto.report; + +import cn.hutool.core.date.DatePattern; +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.format.annotation.DateTimeFormat; + +import java.util.Date; + +@AllArgsConstructor +@NoArgsConstructor +@Data +@Schema(description = "上报信息") +public class ReportReq { + @Schema(description = "上报消息id") + private String id; + @Schema(description = "设备编码") + private String deviceCode; + @Schema(description = "设备id/通道id") + private String deviceId; + @Schema(description = "点播时长") + private String durationTime; + @Schema(description = "点播时长") + private TimeRange timeRange; + + @DateTimeFormat(pattern= DatePattern.NORM_DATETIME_PATTERN) + @JsonFormat(pattern = DatePattern.NORM_DATETIME_PATTERN) + @Schema(description = "日志记录时间") + private Date logTime; + + @AllArgsConstructor + @NoArgsConstructor + @Data + public static class TimeRange { + @Schema(description = "开始时间") + private Date startTime; + @Schema(description = "结束时间") + private Date endTime; + } + + @Schema(description = "文件大小, 未知大小为 -1") + private String fileSize; +} diff --git a/gb28181-wvp-proxy-service/src/main/java/cn/skcks/docking/gb28181/wvp/service/gb28181/Gb28181DownloadService.java b/gb28181-wvp-proxy-service/src/main/java/cn/skcks/docking/gb28181/wvp/service/gb28181/Gb28181DownloadService.java index eeca1cb..a158038 100644 --- a/gb28181-wvp-proxy-service/src/main/java/cn/skcks/docking/gb28181/wvp/service/gb28181/Gb28181DownloadService.java +++ b/gb28181-wvp-proxy-service/src/main/java/cn/skcks/docking/gb28181/wvp/service/gb28181/Gb28181DownloadService.java @@ -231,7 +231,7 @@ public class Gb28181DownloadService { writeErrorToResponse(asyncResponse, JsonResponse.error("下载失败")); } else if (wvpProxyConfig.getUseFfmpeg()) { log.info("开始 ffmpeg 录制, deviceCode {}, startTime {}, endTime {}", deviceCode, DateUtil.formatDateTime(startTime), DateUtil.formatDateTime(endTime)); - videoService.ffmpegRecord(asyncResponse, videoInfo.getUrl(), DateUtil.between(startTime, endTime, DateUnit.SECOND), videoInfo.getDevice(), videoInfo.getCallId()); + videoService.ffmpegRecord(request, asyncResponse, videoInfo.getUrl(), DateUtil.between(startTime, endTime, DateUnit.SECOND), videoInfo.getDevice(), videoInfo.getCallId()); DateTime end = DateUtil.date(); asyncContext.complete(); log.info("下载总耗时: {}, deviceCode {}, startTime {}, endTime {}", DateUtil.between(start, end, DateUnit.SECOND), deviceCode, DateUtil.formatDateTime(startTime), DateUtil.formatDateTime(endTime)); @@ -376,7 +376,7 @@ public class Gb28181DownloadService { writeErrorToResponse(asyncResponse, JsonResponse.error("下载失败")); } else if(wvpProxyConfig.getUseFfmpeg()){ log.info("开始 ffmpeg 录制, deviceCode {}, startTime {}, endTime {}", deviceCode, DateUtil.formatDateTime(startTime), DateUtil.formatDateTime(endTime)); - videoService.ffmpegRecord(asyncResponse, videoInfo.getUrl(), DateUtil.between(startTime, endTime, DateUnit.SECOND), videoInfo.getDevice(), videoInfo.getCallId()); + videoService.ffmpegRecord(request, asyncResponse, videoInfo.getUrl(), DateUtil.between(startTime, endTime, DateUnit.SECOND), videoInfo.getDevice(), videoInfo.getCallId()); DateTime end = DateUtil.date(); asyncContext.complete(); log.info("下载总耗时: {}, deviceCode {}, startTime {}, endTime {}", DateUtil.between(start, end, DateUnit.SECOND), deviceCode, DateUtil.formatDateTime(startTime), DateUtil.formatDateTime(endTime)); diff --git a/gb28181-wvp-proxy-service/src/main/java/cn/skcks/docking/gb28181/wvp/service/report/ReportApi.java b/gb28181-wvp-proxy-service/src/main/java/cn/skcks/docking/gb28181/wvp/service/report/ReportApi.java new file mode 100644 index 0000000..4ceeff8 --- /dev/null +++ b/gb28181-wvp-proxy-service/src/main/java/cn/skcks/docking/gb28181/wvp/service/report/ReportApi.java @@ -0,0 +1,20 @@ +package cn.skcks.docking.gb28181.wvp.service.report; + +import cn.skcks.docking.gb28181.common.json.JsonResponse; +import cn.skcks.docking.gb28181.media.feign.IgnoreSSLFeignClientConfig; +import cn.skcks.docking.gb28181.wvp.dto.report.ReportReq; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.util.MultiValueMap; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; + +@FeignClient( + name = "ReportServiceProxy", + url = "${report.url}", + configuration = {IgnoreSSLFeignClientConfig.class} +) +public interface ReportApi { + @PostMapping + JsonResponse report(@RequestHeader MultiValueMap headers, @RequestBody ReportReq body); +} diff --git a/gb28181-wvp-proxy-service/src/main/java/cn/skcks/docking/gb28181/wvp/service/report/ReportService.java b/gb28181-wvp-proxy-service/src/main/java/cn/skcks/docking/gb28181/wvp/service/report/ReportService.java new file mode 100644 index 0000000..1d19b97 --- /dev/null +++ b/gb28181-wvp-proxy-service/src/main/java/cn/skcks/docking/gb28181/wvp/service/report/ReportService.java @@ -0,0 +1,46 @@ +package cn.skcks.docking.gb28181.wvp.service.report; + +import cn.hutool.core.date.BetweenFormatter; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.io.unit.DataSizeUtil; +import cn.hutool.core.util.IdUtil; +import cn.skcks.docking.gb28181.wvp.config.ReportConfig; +import cn.skcks.docking.gb28181.wvp.dto.report.ReportReq; +import cn.skcks.docking.gb28181.wvp.orm.mybatis.dynamic.model.WvpProxyDevice; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.util.LinkedMultiValueMap; + +import java.util.Date; + +@Slf4j +@Service +@RequiredArgsConstructor +public class ReportService { + private final ReportApi reportApi; + private final ReportConfig reportConfig; + + public void report(HttpServletRequest request, WvpProxyDevice device, Date startTime, Date endTime, long fileSize) { + if(!reportConfig.getEnabled()){ + return; + } + + ReportReq reportReq = new ReportReq(IdUtil.fastUUID(), + device.getDeviceCode(), + device.getGbDeviceChannelId(), + DateUtil.formatBetween(startTime, endTime, BetweenFormatter.Level.SECOND), + new ReportReq.TimeRange(startTime, endTime), + DateUtil.date(), + DataSizeUtil.format(fileSize)); + LinkedMultiValueMap headers = new LinkedMultiValueMap<>(); + reportConfig.getCustomHeaders().forEach(headers::add); + request.getHeaderNames().asIterator().forEachRemaining(headerKey -> { + String header = request.getHeader(headerKey); + headers.add(headerKey, header); + }); + log.info("上报调用信息 {}", reportReq); + reportApi.report(headers, reportReq); + } +} diff --git a/gb28181-wvp-proxy-service/src/main/java/cn/skcks/docking/gb28181/wvp/service/video/VideoService.java b/gb28181-wvp-proxy-service/src/main/java/cn/skcks/docking/gb28181/wvp/service/video/VideoService.java index fa0d57d..fec5225 100644 --- a/gb28181-wvp-proxy-service/src/main/java/cn/skcks/docking/gb28181/wvp/service/video/VideoService.java +++ b/gb28181-wvp-proxy-service/src/main/java/cn/skcks/docking/gb28181/wvp/service/video/VideoService.java @@ -12,6 +12,7 @@ import cn.skcks.docking.gb28181.wvp.orm.mybatis.dynamic.model.WvpProxyDevice; import cn.skcks.docking.gb28181.wvp.orm.mybatis.dynamic.model.WvpProxyDocking; import cn.skcks.docking.gb28181.wvp.service.docking.DockingService; import cn.skcks.docking.gb28181.wvp.service.ffmpeg.FfmpegSupportService; +import cn.skcks.docking.gb28181.wvp.service.report.ReportService; import cn.skcks.docking.gb28181.wvp.sip.request.SipRequestBuilder; import cn.skcks.docking.gb28181.wvp.sip.sender.SipSender; import jakarta.servlet.AsyncContext; @@ -51,6 +52,7 @@ public class VideoService { private final DockingService dockingService; private final SipSender sender; private final FfmpegConfig ffmpegConfig; + private final ReportService reportService; private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); /** @@ -231,7 +233,7 @@ public class VideoService { * @param time 录制时长 (单位: 秒) */ @SneakyThrows - public void ffmpegRecord(ServletResponse response, String url, long time, WvpProxyDevice device,String callId){ + public void ffmpegRecord(HttpServletRequest request, ServletResponse response, String url, long time, WvpProxyDevice device,String callId){ String tmpDir = ffmpegConfig.getTmpDir(); String fileName = callId + ".mp4"; File file = new File(tmpDir, fileName); @@ -274,7 +276,9 @@ public class VideoService { log.info("临时文件 {}(大小 {})", file.getAbsolutePath(), file.length()); IoUtil.copy(new FileInputStream(file), servletOutputStream); response.flushBuffer(); + reportService.report(request, device, startTime, endTime, file.length()); } catch (Exception e){ + reportService.report(request, device, startTime, endTime, -1); log.error("写入 http 响应异常: {}", e.getMessage()); } finally { System.gc(); @@ -284,6 +288,8 @@ public class VideoService { } } + + /** * 录制视频 并写入 异步响应 * @param response AsyncContext.getResponse 异步响应 diff --git a/gb28181-wvp-proxy-service/src/main/java/cn/skcks/docking/gb28181/wvp/service/wvp/WvpService.java b/gb28181-wvp-proxy-service/src/main/java/cn/skcks/docking/gb28181/wvp/service/wvp/WvpService.java index 14a7172..2b18a4a 100644 --- a/gb28181-wvp-proxy-service/src/main/java/cn/skcks/docking/gb28181/wvp/service/wvp/WvpService.java +++ b/gb28181-wvp-proxy-service/src/main/java/cn/skcks/docking/gb28181/wvp/service/wvp/WvpService.java @@ -28,6 +28,7 @@ import cn.skcks.docking.gb28181.wvp.proxy.WvpProxyClient; import cn.skcks.docking.gb28181.wvp.service.device.DeviceService; import cn.skcks.docking.gb28181.wvp.service.docking.DockingService; import cn.skcks.docking.gb28181.wvp.service.download.DownloadService; +import cn.skcks.docking.gb28181.wvp.service.report.ReportService; import cn.skcks.docking.gb28181.wvp.service.video.VideoService; import cn.skcks.docking.gb28181.wvp.utils.RetryUtil; import com.github.rholder.retry.*; @@ -65,6 +66,8 @@ public class WvpService { private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); private final ConcurrentMap> playing = new ConcurrentHashMap<>(); + private final ReportService reportService; + public void header(HttpServletResponse response) { response.setContentType("video/mp4"); response.setHeader("Accept-Ranges", "none"); @@ -113,6 +116,7 @@ public class WvpService { String reason = MessageFormat.format("调用 wvp api 查询设备({0})历史失败, 异常: {1}", deviceCode, e.getMessage()); writeErrorToResponse(asyncResponse, JsonResponse.error(reason)); } finally { + reportService.report(request,wvpProxyDevice, startTime, endTime,-1); log.info("asyncContext 结束"); asyncContext.complete(); } diff --git a/gb28181-wvp-proxy-starter/src/main/java/cn/skcks/docking/gb28181/wvp/Gb28181WvpProxyStarter.java b/gb28181-wvp-proxy-starter/src/main/java/cn/skcks/docking/gb28181/wvp/Gb28181WvpProxyStarter.java index 4a52ae8..5508110 100644 --- a/gb28181-wvp-proxy-starter/src/main/java/cn/skcks/docking/gb28181/wvp/Gb28181WvpProxyStarter.java +++ b/gb28181-wvp-proxy-starter/src/main/java/cn/skcks/docking/gb28181/wvp/Gb28181WvpProxyStarter.java @@ -9,7 +9,8 @@ import org.springframework.scheduling.annotation.EnableAsync; @EnableFeignClients(basePackages = { "cn.skcks.docking.gb28181.media", - "cn.skcks.docking.gb28181.wvp.proxy" + "cn.skcks.docking.gb28181.wvp.proxy", + "cn.skcks.docking.gb28181.wvp.service.report" }) @SpringBootApplication @ComponentScan(basePackages = { diff --git a/gb28181-wvp-proxy-starter/src/main/resources/application-local.yml b/gb28181-wvp-proxy-starter/src/main/resources/application-local.yml index 5bb3d7f..90b99be 100644 --- a/gb28181-wvp-proxy-starter/src/main/resources/application-local.yml +++ b/gb28181-wvp-proxy-starter/src/main/resources/application-local.yml @@ -105,3 +105,9 @@ ffmpeg-support: # [可选] 日志配置, 一般不需要改 logging: config: classpath:logback.xml + +report: + enabled: false + url: http://127.0.0.1:8080/api/report + custom-headers: + agent: gb28181-proxy \ No newline at end of file diff --git a/gb28181-wvp-proxy-starter/src/main/resources/application.yml b/gb28181-wvp-proxy-starter/src/main/resources/application.yml index 6cf30f4..37957ff 100644 --- a/gb28181-wvp-proxy-starter/src/main/resources/application.yml +++ b/gb28181-wvp-proxy-starter/src/main/resources/application.yml @@ -99,3 +99,10 @@ ffmpeg-support: # [可选] 日志配置, 一般不需要改 logging: config: classpath:logback.xml + + +report: + enabled: false + url: http://127.0.0.1:8080/api/report + custom-headers: + agent: gb28181-proxy