refactor(wvp): wvp有安全风险,暂不开源

This commit is contained in:
Join
2025-12-29 09:02:42 +08:00
parent d434ac7b0e
commit 902bed1830
31 changed files with 0 additions and 4230 deletions
@@ -1,52 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>cn-universal-protocol</artifactId>
<groupId>cn.universal.iot</groupId>
<version>1.5-SNAPSHOT</version>
</parent>
<artifactId>cn-wvp-protocol</artifactId>
<name>cn-wvp-protocol</name>
<description>WVP 视频平台协议下发转换模块</description>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-persistence</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-dm</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -1,765 +0,0 @@
/*
*
* Copyright (c) 2025, NexIoT. All Rights Reserved.
*
* @Description: WVP平台适配器实现
* @Author: gitee.com/NexIoT
*
*/
package cn.wvp.protocol.adapter;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.universal.common.domain.R;
import cn.universal.dm.device.service.action.IoTDeviceActionAfterService;
import cn.universal.dm.video.VideoPlatformAdapter;
import cn.universal.dm.video.VideoPlatformInstanceAdapter;
import cn.universal.persistence.entity.VideoPlatformInstance;
import cn.universal.persistence.mapper.VideoPlatformInstanceMapper;
import cn.wvp.protocol.entity.WvpDownRequest;
import cn.wvp.protocol.service.WvpDeviceService;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;
/** WVP平台适配器实现 将WvpDeviceService的能力适配到VideoPlatformAdapter统一接口 */
@Slf4j
@Component("wvpPlatformAdapter")
@RequiredArgsConstructor
public class WvpPlatformAdapter implements VideoPlatformAdapter, VideoPlatformInstanceAdapter {
private final WvpDeviceService wvpDeviceService;
private final IoTDeviceActionAfterService ioTDeviceActionAfterService;
private final VideoPlatformInstanceMapper videoPlatformInstanceMapper;
@Override
public String getSupportedPlatformType() {
return "wvp";
}
/** 构建通用filters参数 */
private Map<String, Object> buildFilters(VideoPlatformInstance instance) {
Map<String, Object> filters = new HashMap<>();
filters.put("endpoint", instance.getEndpoint());
// 从auth字段中提取token
if (instance.getAuth() != null && !instance.getAuth().isEmpty()) {
try {
JSONObject auth = new JSONObject(instance.getAuth());
if (auth.containsKey("token")) {
filters.put("token", auth.getStr("token"));
} else if (auth.containsKey("apiKey")) {
filters.put("token", auth.getStr("apiKey"));
}
} catch (Exception e) {
log.warn("解析WVP auth配置失败: {}", e.getMessage());
}
}
return filters;
}
/** 构建WvpDownRequest */
private WvpDownRequest buildDownRequest(
VideoPlatformInstance instance, String deviceId, JSONObject data) {
WvpDownRequest request = new WvpDownRequest();
request.setDeviceId(deviceId);
request.setData(data);
request.setWvpRequestData(buildFilters(instance));
return request;
}
@Override
public Map<String, Object> testConnection(VideoPlatformInstance instance) {
Map<String, Object> result = new HashMap<>();
long start = System.currentTimeMillis();
try {
Map<String, Object> filters = buildFilters(instance);
filters.put("page", 1);
filters.put("count", 1);
R<?> response = wvpDeviceService.listDevices(instance.getInstanceKey(), filters);
long latency = System.currentTimeMillis() - start;
if (response.isSuccess()) {
result.put("reachable", true);
result.put("latencyMs", latency);
result.put("message", "WVP连接正常");
} else {
result.put("reachable", false);
result.put("latencyMs", latency);
result.put("message", "WVP响应异常:" + response.getMsg());
}
} catch (Exception e) {
long latency = System.currentTimeMillis() - start;
log.error("WVP连接测试失败:{}", e.getMessage());
result.put("reachable", false);
result.put("latencyMs", latency);
result.put("message", "连接失败:" + e.getMessage());
}
return result;
}
@Override
public List<Map<String, Object>> listDevices(
VideoPlatformInstance instance, Map<String, Object> filters) {
List<Map<String, Object>> devices = new ArrayList<>();
try {
Map<String, Object> mergedFilters = buildFilters(instance);
if (filters != null) {
mergedFilters.putAll(filters);
}
// 如果没有指定分页参数,使用默认值
if (!mergedFilters.containsKey("page")) {
mergedFilters.put("page", 1);
}
if (!mergedFilters.containsKey("count")) {
mergedFilters.put("count", 100);
}
R<?> response = wvpDeviceService.listDevices(instance.getInstanceKey(), mergedFilters);
if (response.isSuccess() && response.getData() != null) {
// 转换响应数据格式
Object data = response.getData();
if (data instanceof Map) {
JSONObject dataJson = new JSONObject(data);
if (dataJson.containsKey("list")) {
JSONArray list = dataJson.getJSONArray("list");
for (int i = 0; i < list.size(); i++) {
JSONObject item = list.getJSONObject(i);
Map<String, Object> device = new HashMap<>();
// 主表公共字段
String deviceId = item.getStr("deviceId");
device.put("deviceId", deviceId);
device.put("name", item.getStr("name", ""));
device.put("manufacturer", item.getStr("manufacturer", ""));
device.put("model", item.getStr("model", ""));
// 根据onLine字段设置状态
Boolean onLine = item.getBool("onLine", false);
String status = onLine ? "online" : "offline";
device.put("status", status);
device.put("channelCount", item.getInt("channelCount", 0));
device.put("ip", item.getStr("ip", ""));
device.put("port", item.getInt("port", 0));
// WVP扩展表特有字段
device.put("charset", item.getStr("charset", ""));
device.put("transport", item.getStr("transport", ""));
device.put("streamMode", item.getStr("streamMode", ""));
device.put("hostAddress", item.getStr("hostAddress", ""));
device.put("registerTime", item.getStr("registerTime", ""));
device.put("keepaliveTime", item.getStr("keepaliveTime", ""));
device.put("expires", item.getInt("expires", 3600));
device.put("mediaServerId", item.getStr("mediaServerId", ""));
// 只在forceSync时查询通道列表(用于同步写入数据库)
// 常规查询应该从video_platform_channel表读取
boolean isForceSync =
mergedFilters.containsKey("forceSync")
&& "true".equalsIgnoreCase(String.valueOf(mergedFilters.get("forceSync")));
if (isForceSync) {
List<Map<String, Object>> channelList = getDeviceChannels(instance, deviceId);
device.put("channelList", channelList);
}
devices.add(device);
}
}
}
} else {
log.warn("WVP设备列表查询失败:{}", response.getMsg());
}
} catch (Exception e) {
log.error("WVP设备列表查询异常:{}", e.getMessage(), e);
}
return devices;
}
@Override
public Map<String, String> getStreamUrl(
VideoPlatformInstance instance, String deviceId, String channelId, String streamType) {
Map<String, String> urls = new HashMap<>();
try {
JSONObject data = new JSONObject();
data.set("channelId", channelId != null ? channelId : deviceId);
data.set("protocol", "flv"); // 默认FLV
WvpDownRequest request = buildDownRequest(instance, deviceId, data);
R<?> response = wvpDeviceService.startPreview(request);
log.info("WVP预览响应: success={}, data={}", response.isSuccess(), response.getData());
if (response.isSuccess() && response.getData() != null) {
JSONObject result = new JSONObject(response.getData());
log.info("WVP预览结果: {}", result.toString());
if (result.containsKey("urls")) {
JSONObject urlsObj = result.getJSONObject("urls");
// 遍历所有返回的流地址
for (String key : urlsObj.keySet()) {
Object value = urlsObj.get(key);
// 过滤掉null值
if (value != null && !"null".equals(String.valueOf(value))) {
urls.put(key, String.valueOf(value));
}
}
} else {
// 如果没有urls字段,直接使用根对象的所有流地址字段
// 常见的流地址字段
String[] streamKeys = {
"flv",
"https_flv",
"ws_flv",
"wss_flv",
"fmp4",
"https_fmp4",
"ws_fmp4",
"wss_fmp4",
"hls",
"https_hls",
"ws_hls",
"wss_hls",
"ts",
"https_ts",
"ws_ts",
"wss_ts",
"rtmp",
"rtmps",
"rtsp",
"rtsps",
"rtc",
"rtcs",
"webrtc"
};
for (String key : streamKeys) {
if (result.containsKey(key)) {
Object value = result.get(key);
// 过滤掉null值
if (value != null && !"null".equals(String.valueOf(value))) {
urls.put(key, String.valueOf(value));
}
}
}
}
log.info("WVP解析后的URLs: {}", urls);
}
} catch (Exception e) {
log.error("WVP播放地址获取异常:{}", e.getMessage(), e);
}
ioTDeviceActionAfterService.online(instance.getInstanceKey(), deviceId);
return urls;
}
@Override
public void controlPTZ(
VideoPlatformInstance instance,
String extDeviceId,
String channelId,
String command,
int speed) {
try {
JSONObject data = new JSONObject();
data.set("channelId", channelId);
data.set("command", command);
data.set("speed", speed);
WvpDownRequest request = buildDownRequest(instance, extDeviceId, data);
R<?> response = wvpDeviceService.ptzControl(request);
if (!response.isSuccess()) {
log.error("WVP PTZ控制失败:{}", response.getMsg());
}
} catch (Exception e) {
log.error("WVP PTZ控制异常:{}", e.getMessage(), e);
}
}
@Override
public List<Map<String, Object>> queryRecords(
VideoPlatformInstance instance,
String extDeviceId,
String channelId,
long startTime,
long endTime) {
List<Map<String, Object>> records = new ArrayList<>();
try {
// WVP要求时间格式为 yyyy-MM-dd HH:mm:ss
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String reqStartTime = sdf.format(new java.util.Date(startTime));
String reqEndTime = sdf.format(new java.util.Date(endTime));
JSONObject data = new JSONObject();
data.set("channelId", channelId);
data.set("startTime", reqStartTime);
data.set("endTime", reqEndTime);
WvpDownRequest request = buildDownRequest(instance, extDeviceId, data);
R<?> response = wvpDeviceService.queryGBRecords(request);
if (response.isSuccess() && response.getData() != null) {
// 转换响应数据格式
Object dataObj = response.getData();
if (dataObj instanceof Map) {
JSONObject dataJson = new JSONObject(dataObj);
if (dataJson.containsKey("recordList")) {
// WVP返回的录像列表在recordList字段中
JSONArray list = dataJson.getJSONArray("recordList");
for (int i = 0; i < list.size(); i++) {
JSONObject item = list.getJSONObject(i);
Map<String, Object> record = new HashMap<>();
// 生成唯一ID
record.put("id", i + 1);
// 将WVP的时间字符串转为时间戳(毫秒)
try {
String respStartTime = item.getStr("startTime", "");
String respEndTime = item.getStr("endTime", "");
if (!respStartTime.isEmpty()) {
record.put("startTime", sdf.parse(respStartTime).getTime());
}
if (!respEndTime.isEmpty()) {
record.put("endTime", sdf.parse(respEndTime).getTime());
}
} catch (Exception e) {
log.warn("录像时间解析失败: {}", e.getMessage());
}
// 文件大小转换为可读格式
long fileSize = item.getLong("fileSize", 0L);
record.put("fileSize", formatFileSize(fileSize));
record.put("fileSizeBytes", fileSize);
// 其他字段
record.put("name", item.getStr("name", ""));
record.put("filePath", item.getStr("filePath", ""));
record.put("address", item.getStr("address", ""));
record.put("secrecy", item.getInt("secrecy", 0));
record.put("type", item.getStr("type", "time"));
records.add(record);
}
}
}
}
} catch (Exception e) {
log.error("WVP录像查询异常:{}", e.getMessage(), e);
}
return records;
}
@Override
public List<Map<String, Object>> listOrganizations(
VideoPlatformInstance instance, Map<String, Object> filters) {
// WVP无组织体系,返回空列表
return List.of();
}
@Override
public void subscribe(VideoPlatformInstance instance, List<String> topics, String callback) {
// WVP订阅暂不实现,通常通过Webhook配置
log.info(
"WVP订阅:instanceKey={}, topics={}, callback={}",
instance.getInstanceKey(),
topics,
callback);
}
@Override
public void unsubscribe(VideoPlatformInstance instance, List<String> topics) {
log.info("WVP取消订阅:instanceKey={}, topics={}", instance.getInstanceKey(), topics);
}
@Override
public Map<String, Object> getPlaybackUrl(
VideoPlatformInstance instance,
String deviceId,
String channelId,
long startTime,
long endTime) {
Map<String, Object> result = new HashMap<>();
try {
JSONObject data = new JSONObject();
data.set("channelId", channelId != null ? channelId : deviceId);
data.set("startTime", startTime);
data.set("endTime", endTime);
WvpDownRequest request = buildDownRequest(instance, deviceId, data);
R<?> response = wvpDeviceService.startPlayback(request);
log.info("WVP回放响应: success={}, data={}", response.isSuccess(), response.getData());
if (response.isSuccess() && response.getData() != null) {
// 解析响应数据
Object responseData = response.getData();
if (responseData instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> dataMap = (Map<String, Object>) responseData;
// 直接使用返回的所有字段(包括流地址和其他字段)
result.putAll(dataMap);
log.info("WVP解析后的回放数据: {}", result);
} else {
log.warn("WVP回放响应数据格式异常: {}", responseData);
}
} else {
log.error("WVP回放失败: {}", response.getMsg());
throw new RuntimeException("WVP回放失败: " + response.getMsg());
}
} catch (Exception e) {
log.error("WVP回放地址获取异常:{}", e.getMessage(), e);
throw new RuntimeException("获取回放地址失败: " + e.getMessage(), e);
}
return result;
}
/** 获取设备的通道列表 */
private List<Map<String, Object>> getDeviceChannels(
VideoPlatformInstance instance, String deviceId) {
List<Map<String, Object>> channels = new ArrayList<>();
try {
Map<String, Object> channelFilters = buildFilters(instance);
channelFilters.put("page", 1);
channelFilters.put("count", 100); // 通道一般不会太多
R<?> response = wvpDeviceService.listChannels(deviceId, channelFilters);
if (response.isSuccess() && response.getData() != null) {
Object data = response.getData();
if (data instanceof Map) {
JSONObject dataJson = new JSONObject(data);
if (dataJson.containsKey("list")) {
JSONArray list = dataJson.getJSONArray("list");
for (int i = 0; i < list.size(); i++) {
JSONObject item = list.getJSONObject(i);
Map<String, Object> channel = new HashMap<>();
// 通道公共字段
channel.put("channelId", item.getStr("deviceId")); // WVP中通道的deviceId就是通道ID
channel.put("name", item.getStr("name", ""));
channel.put("manufacturer", item.getStr("manufacturer", ""));
channel.put("model", item.getStr("model", ""));
channel.put("owner", item.getStr("owner", ""));
channel.put("civilCode", item.getStr("civilCode", ""));
channel.put("address", item.getStr("address", ""));
channel.put("parental", item.getInt("parental", 0));
channel.put("parentId", item.getStr("parentId", ""));
channel.put("safetyWay", item.getInt("safetyWay"));
channel.put("registerWay", item.getInt("registerWay"));
channel.put("secrecy", item.getInt("secrecy", 0));
channel.put("ipAddress", item.getStr("ipAddress", ""));
channel.put("port", item.getInt("port"));
channel.put("status", item.getStr("status", "OFF"));
channel.put("longitude", item.getStr("longitude"));
channel.put("latitude", item.getStr("latitude"));
channel.put("ptzType", item.getInt("ptzType", 0));
channel.put("positionType", item.getInt("positionType"));
// WVP通道级特有字段
channel.put("streamId", item.getStr("streamId", ""));
channel.put("hasAudio", item.getBool("hasAudio", false));
channel.put("subCount", item.getInt("subCount", 0));
channel.put("streamIdentification", item.getStr("streamIdentification", ""));
channel.put("channelType", item.getInt("channelType", 0));
channels.add(channel);
}
}
}
}
} catch (Exception e) {
log.error("WVP通道列表查询异常:deviceId={}, error={}", deviceId, e.getMessage());
}
return channels;
}
/** 格式化文件大小 */
private String formatFileSize(long bytes) {
if (bytes < 1024) {
return bytes + " B";
} else if (bytes < 1024 * 1024) {
return String.format("%.2f KB", bytes / 1024.0);
} else if (bytes < 1024 * 1024 * 1024) {
return String.format("%.2f MB", bytes / (1024.0 * 1024));
} else {
return String.format("%.2f GB", bytes / (1024.0 * 1024 * 1024));
}
}
// ==================== 国标录像下载公开方法 ====================
/** 开始国标录像下载 */
public R<Map<String, Object>> startGBRecordDownload(
VideoPlatformInstance instance, String deviceId, String channelId, Map<String, Object> data) {
try {
JSONObject requestData = new JSONObject();
requestData.set("channelId", channelId);
// 从 data 中获取参数
if (data.containsKey("startTime")) {
requestData.set("startTime", String.valueOf(data.get("startTime")));
}
if (data.containsKey("endTime")) {
requestData.set("endTime", String.valueOf(data.get("endTime")));
}
if (data.containsKey("downloadSpeed")) {
requestData.set("downloadSpeed", data.get("downloadSpeed"));
}
WvpDownRequest request = buildDownRequest(instance, deviceId, requestData);
R<?> response = wvpDeviceService.startGBRecordDownload(request);
if (response.isSuccess() && response.getData() != null) {
@SuppressWarnings("unchecked")
Map<String, Object> result = (Map<String, Object>) response.getData();
return R.ok(result);
}
return R.error(response.getMsg(), null);
} catch (Exception e) {
log.error("WVP国标录像下载失败:{}", e.getMessage(), e);
return R.error("下载失败:" + e.getMessage(), null);
}
}
/** 停止国标录像下载 */
public R<Void> stopGBRecordDownload(
VideoPlatformInstance instance, String deviceId, String channelId, Map<String, Object> data) {
try {
JSONObject requestData = new JSONObject();
requestData.set("channelId", channelId);
if (data.containsKey("stream")) {
requestData.set("stream", String.valueOf(data.get("stream")));
}
WvpDownRequest request = buildDownRequest(instance, deviceId, requestData);
R<?> response = wvpDeviceService.stopGBRecordDownload(request);
if (response.isSuccess()) {
return R.ok();
}
return R.error(response.getMsg());
} catch (Exception e) {
log.error("WVP停止录像下载失败:{}", e.getMessage(), e);
return R.error("停止下载失败:" + e.getMessage());
}
}
/** 获取国标录像下载进度 */
public R<Map<String, Object>> getGBRecordDownloadProgress(
VideoPlatformInstance instance, String deviceId, String channelId, String stream) {
try {
JSONObject requestData = new JSONObject();
requestData.set("channelId", channelId);
requestData.set("stream", stream);
WvpDownRequest request = buildDownRequest(instance, deviceId, requestData);
R<?> response = wvpDeviceService.getGBRecordDownloadProgress(request);
if (response.isSuccess() && response.getData() != null) {
@SuppressWarnings("unchecked")
Map<String, Object> result = (Map<String, Object>) response.getData();
return R.ok(result);
}
return R.error(response.getMsg(), null);
} catch (Exception e) {
log.error("WVP获取下载进度失败:{}", e.getMessage(), e);
return R.error("获取进度失败:" + e.getMessage(), null);
}
}
// ==================== 云端录像公开方法 ====================
/** 查询云端录像日期列表 */
public R<List<String>> queryCloudRecordDates(
VideoPlatformInstance instance,
String deviceId,
String channelId,
Map<String, String> params) {
try {
JSONObject requestData = new JSONObject();
requestData.set("app", params.getOrDefault("app", "rtp"));
requestData.set("stream", deviceId + "_" + channelId);
if (params.containsKey("year")) {
requestData.set("year", Integer.parseInt(params.get("year")));
}
if (params.containsKey("month")) {
requestData.set("month", Integer.parseInt(params.get("month")));
}
WvpDownRequest request = buildDownRequest(instance, deviceId, requestData);
R<?> response = wvpDeviceService.queryCloudRecordDates(request);
if (response.isSuccess() && response.getData() != null) {
@SuppressWarnings("unchecked")
List<String> dates = (List<String>) response.getData();
return R.ok(dates);
}
return R.error(response.getMsg(), null);
} catch (Exception e) {
log.error("WVP云端录像日期查询失败:{}", e.getMessage(), e);
return R.error("查询失败:" + e.getMessage(), null);
}
}
/** 查询云端录像列表 */
public R<Map<String, Object>> queryCloudRecords(
VideoPlatformInstance instance,
String deviceId,
String channelId,
Map<String, String> params) {
try {
JSONObject requestData = new JSONObject();
requestData.set("app", params.getOrDefault("app", "rtp"));
requestData.set("stream", deviceId + "_" + channelId);
requestData.set("page", Integer.parseInt(params.getOrDefault("page", "1")));
requestData.set("count", Integer.parseInt(params.getOrDefault("count", "20")));
if (params.containsKey("startTime")) {
requestData.set("startTime", params.get("startTime"));
}
if (params.containsKey("endTime")) {
requestData.set("endTime", params.get("endTime"));
}
WvpDownRequest request = buildDownRequest(instance, deviceId, requestData);
R<?> response = wvpDeviceService.queryCloudRecords(request);
if (response.isSuccess() && response.getData() != null) {
@SuppressWarnings("unchecked")
Map<String, Object> result = (Map<String, Object>) response.getData();
return R.ok(result);
}
return R.error(response.getMsg(), null);
} catch (Exception e) {
log.error("WVP云端录像查询失败:{}", e.getMessage(), e);
return R.error("查询失败:" + e.getMessage(), null);
}
}
/** 加载云端录像文件 */
public R<Map<String, Object>> loadCloudRecord(
VideoPlatformInstance instance, String deviceId, String channelId, Map<String, Object> data) {
try {
JSONObject requestData = new JSONObject();
requestData.set("app", data.getOrDefault("app", "rtp"));
requestData.set("stream", deviceId + "_" + channelId);
if (data.containsKey("cloudRecordId")) {
requestData.set("cloudRecordId", data.get("cloudRecordId"));
}
WvpDownRequest request = buildDownRequest(instance, deviceId, requestData);
R<?> response = wvpDeviceService.loadCloudRecord(request);
if (response.isSuccess() && response.getData() != null) {
@SuppressWarnings("unchecked")
Map<String, Object> result = (Map<String, Object>) response.getData();
return R.ok(result);
}
return R.error(response.getMsg(), null);
} catch (Exception e) {
log.error("WVP云端录像加载失败:{}", e.getMessage(), e);
return R.error("加载失败:" + e.getMessage(), null);
}
}
/** 云端录像定位 */
public R<Void> seekCloudRecord(
VideoPlatformInstance instance, String deviceId, String channelId, Map<String, Object> data) {
try {
JSONObject requestData = new JSONObject();
requestData.putAll(data);
WvpDownRequest request = buildDownRequest(instance, deviceId, requestData);
R<?> response = wvpDeviceService.seekCloudRecord(request);
if (response.isSuccess()) {
return R.ok();
}
return R.error(response.getMsg());
} catch (Exception e) {
log.error("WVP云端录像定位失败:{}", e.getMessage(), e);
return R.error("定位失败:" + e.getMessage());
}
}
/** 设置云端录像倍速 */
public R<Void> setCloudRecordSpeed(
VideoPlatformInstance instance, String deviceId, String channelId, Map<String, Object> data) {
try {
JSONObject requestData = new JSONObject();
requestData.putAll(data);
WvpDownRequest request = buildDownRequest(instance, deviceId, requestData);
R<?> response = wvpDeviceService.setCloudRecordSpeed(request);
if (response.isSuccess()) {
return R.ok();
}
return R.error(response.getMsg());
} catch (Exception e) {
log.error("WVP云端录像倍速设置失败:{}", e.getMessage(), e);
return R.error("设置倍速失败:" + e.getMessage());
}
}
/** 获取云端录像下载地址 */
public R<Map<String, Object>> getCloudRecordPlayPath(
VideoPlatformInstance instance, Integer recordId) {
try {
JSONObject requestData = new JSONObject();
requestData.set("recordId", recordId);
WvpDownRequest request = buildDownRequest(instance, null, requestData);
R<?> response = wvpDeviceService.getCloudRecordPlayPath(request);
if (response.isSuccess() && response.getData() != null) {
@SuppressWarnings("unchecked")
Map<String, Object> result = (Map<String, Object>) response.getData();
return R.ok(result);
}
return R.error(response.getMsg(), null);
} catch (Exception e) {
log.error("WVP获取云端录像下载地址失败:{}", e.getMessage(), e);
return R.error("获取下载地址失败:" + e.getMessage(), null);
}
}
@Override
@Cacheable(
cacheNames = "getVideoPlatformInstance",
key = "#p0",
unless = "#result == null")
public VideoPlatformInstance getVideoPlatformInstance(String instanceKey) {
if (StrUtil.isBlank(instanceKey)) {
log.warn("wvp query instanceKey is blank");
return null;
}
VideoPlatformInstance instance = new VideoPlatformInstance();
instance.setInstanceKey(instanceKey);
instance = videoPlatformInstanceMapper.selectOne(instance);
return instance;
}
}
@@ -1,65 +0,0 @@
/*
*
* Copyright (c) 2025, NexIoT. All Rights Reserved.
*
* @Description: 本文件由 gitee.com/NexIoT 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
* @Author: gitee.com/NexIoT
* @Email: wo8335224@gmail.com
* @Wechat: outlookFil
*
*
*/
package cn.wvp.protocol.config;
import cn.universal.core.protocol.ProtocolModuleRuntimeRegistry;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* WVP 协议模块自动配置类
*
* <p>当 wvp.protocol.enabled=true 时,自动扫描包并创建所有WVP相关的Bean
*
* <p>通过 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 实现 Spring Boot 自动配置
*
* @version 2.0
* @Author gitee.com/NexIoT
* @since 2025/1/8
*/
@Configuration
@ConditionalOnProperty(name = "wvp.protocol.enabled", havingValue = "true", matchIfMissing = false)
@ComponentScan(basePackages = "cn.wvp.protocol")
@EnableConfigurationProperties(WvpProperties.class)
@Slf4j
public class WvpAutoConfiguration {
@Autowired(required = false)
private WvpModuleInfo moduleInfo;
public WvpAutoConfiguration() {
log.info("[WVP协议] WVP协议模块自动配置已启用");
}
@PostConstruct
public void registerProtocol() {
if (moduleInfo != null) {
ProtocolModuleRuntimeRegistry.registerProtocol(moduleInfo);
log.info("[WVP自动配置] 协议模块已注册到运行时注册表");
}
}
@PreDestroy
public void unregisterProtocol() {
if (moduleInfo != null) {
ProtocolModuleRuntimeRegistry.unregisterProtocol(moduleInfo.getCode());
log.info("[WVP自动配置] 协议模块已从运行时注册表注销");
}
}
}
@@ -1,62 +0,0 @@
/*
*
* Copyright (c) 2025, NexIoT. All Rights Reserved.
*
* @Description: 本文件由 gitee.com/NexIoT 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
* @Author: gitee.com/NexIoT
* @Email: wo8335224@gmail.com
* @Wechat: outlookFil
*
*
*/
package cn.wvp.protocol.config;
import cn.universal.core.protocol.ProtocolModuleInfo;
import org.springframework.stereotype.Component;
/**
* WVP协议模块信息
*
* @version 2.0
* @Author gitee.com/NexIoT
* @since 2025/1/8
*/
@Component
public class WvpModuleInfo implements ProtocolModuleInfo {
@Override
public String getCode() {
return "wvp";
}
@Override
public String getName() {
return "WVP国标视频平台";
}
@Override
public String getVersion() {
return "2.0";
}
@Override
public String getDescription() {
return "WVP(Web Video Platform)国标视频平台协议适配器";
}
@Override
public ProtocolCategory getCategory() {
return ProtocolCategory.PLATFORM;
}
@Override
public boolean isManualCreatable() {
return false;
}
@Override
public String getNotCreatableReason() {
return "视频平台产品采用懒创建策略,请在视频中心中勾选设备时自动创建";
}
}
@@ -1,31 +0,0 @@
/*
*
* Copyright (c) 2025, NexIoT. All Rights Reserved.
*
* @Description: 本文件由 gitee.com/NexIoT 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
* @Author: gitee.com/NexIoT
* @Email: wo8335224@gmail.com
* @Wechat: outlookFil
*
*
*/
package cn.wvp.protocol.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* WVP 协议配置属性
*
* @version 2.0
* @Author gitee.com/NexIoT
* @since 2025/1/8
*/
@ConfigurationProperties(prefix = "wvp.protocol")
@Data
public class WvpProperties {
/** 是否启用WVP协议模块 */
private boolean enabled = true;
}
@@ -1,90 +0,0 @@
/*
* Copyright (c) 2025, NexIoT. All Rights Reserved.
*/
package cn.wvp.protocol.constant;
/**
* WVP平台API路径常量
* 基于wvp-GB28181-pro标准接口
*/
public interface WvpApiConstants {
/** 设备查询接口 */
String API_DEVICE_QUERY = "/api/device/query/devices";
/** 通道查询接口 */
String API_DEVICE_CHANNELS = "/api/device/query/devices/%s/channels";
/** 云台控制接口 (方向+缩放) */
String API_PTZ_CONTROL = "/api/front-end/ptz/%s/%s";
/** 光圈控制接口 */
String API_IRIS_CONTROL = "/api/front-end/fi/iris/%s/%s";
/** 聚焦控制接口 */
String API_FOCUS_CONTROL = "/api/front-end/fi/focus/%s/%s";
/** 预置位查询接口 */
String API_PRESET_QUERY = "/api/front-end/preset/query/%s/%s";
/** 抓拍接口 */
String API_SNAPSHOT = "/api/play/snap";
// ==================== 国标录像相关接口 ====================
/** 国标录像查询接口 */
String API_GB_RECORD_QUERY = "/api/gb_record/query/%s/%s";
/** 国标录像下载开始 */
String API_GB_RECORD_DOWNLOAD_START = "/api/gb_record/download/start/%s/%s";
/** 国标录像下载停止 */
String API_GB_RECORD_DOWNLOAD_STOP = "/api/gb_record/download/stop/%s/%s/%s";
/** 国标录像下载进度 */
String API_GB_RECORD_DOWNLOAD_PROGRESS = "/api/gb_record/download/progress/%s/%s/%s";
// ==================== 云端录像相关接口 ====================
/** 云端录像日期列表 */
String API_CLOUD_RECORD_DATE_LIST = "/api/cloud/record/date/list";
/** 云端录像列表查询 */
String API_CLOUD_RECORD_LIST = "/api/cloud/record/list";
/** 加载云端录像文件 */
String API_CLOUD_RECORD_LOAD = "/api/cloud/record/loadRecord";
/** 云端录像定位 */
String API_CLOUD_RECORD_SEEK = "/api/cloud/record/seek";
/** 云端录像倍速 */
String API_CLOUD_RECORD_SPEED = "/api/cloud/record/speed";
/** 云端录像删除 */
String API_CLOUD_RECORD_DELETE = "/api/cloud/record/delete";
/** 云端录像收藏添加 */
String API_CLOUD_RECORD_COLLECT_ADD = "/api/cloud/record/collect/add";
/** 云端录像收藏删除 */
String API_CLOUD_RECORD_COLLECT_DELETE = "/api/cloud/record/collect/delete";
/** 直播开始接口 */
String API_PLAY_START = "/api/play/start/%s/%s";
/** 直播停止接口 */
String API_PLAY_STOP = "/api/play/stop/%s/%s";
/** 回放开始接口 */
String API_PLAYBACK_START = "/api/playback/start/%s/%s";
/** 回放停止接口 */
String API_PLAYBACK_STOP = "/api/playback/stop/%s/%s";
/** 云端录像下载地址获取 */
String API_CLOUD_RECORD_PLAY_PATH = "/api/cloud/record/play/path";
/** Token请求头名称 */
String HEADER_ACCESS_TOKEN = "Access-Token";
}
@@ -1,194 +0,0 @@
/*
*
* Copyright (c) 2025, NexIoT. All Rights Reserved.
*
* @Description: 本文件由 gitee.com/NexIoT 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
* @Author: gitee.com/NexIoT
* @Email: wo8335224@gmail.com
* @Wechat: outlookFil
*
*
*/
package cn.wvp.protocol.converter;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import cn.universal.common.constant.IoTConstant.DownCmd;
import cn.universal.core.downlink.DownlinkContext;
import cn.universal.core.downlink.converter.DownRequestConverter;
import cn.universal.core.message.UnifiedDownlinkCommand;
import cn.universal.dm.device.service.AbstratIoTService;
import cn.universal.dm.video.VideoPlatformInstanceAdapter;
import cn.universal.persistence.dto.IoTDeviceDTO;
import cn.universal.persistence.entity.IoTProduct;
import cn.universal.persistence.entity.VideoPlatformInstance;
import cn.universal.persistence.query.IoTDeviceQuery;
import cn.wvp.protocol.config.WvpModuleInfo;
import cn.wvp.protocol.entity.WvpDownRequest;
import jakarta.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
/**
* WVP协议转换器 将UnifiedDownlinkCommand转换为WvpDownRequest
*
* @version 1.0
* @since 2025/11/09
*/
@Slf4j
@Component("wvpConverter")
public class WvpDownRequestConverter extends AbstratIoTService
implements DownRequestConverter<WvpDownRequest> {
@Autowired
@Qualifier("wvpPlatformAdapter")
private VideoPlatformInstanceAdapter videoPlatformInstanceAdapter;
@Resource private WvpModuleInfo wvpModuleInfo;
@Override
public WvpDownRequest convert(UnifiedDownlinkCommand command, DownlinkContext<?> context) {
try {
// 1. 加载产品信息
IoTProduct ioTProduct = getProduct(command.getProductKey());
if (ioTProduct == null) {
throw new IllegalArgumentException("产品不存在: " + command.getProductKey());
}
// 2. 创建WVP下行请求
WvpDownRequest request = new WvpDownRequest();
BeanUtil.copyProperties(command, request, "extensions", "metadata");
// 3. 设置产品信息
request.setIoTProduct(ioTProduct);
// 4. 加载设备信息(对于DEV_ADD命令,设备可能不存在)
IoTDeviceDTO ioTDeviceDTO = null;
if (command.getCmd() != DownCmd.DEV_ADD && command.getCmd() != DownCmd.DEV_ADDS) {
ioTDeviceDTO =
getIoTDeviceDTO(
IoTDeviceQuery.builder()
.productKey(command.getProductKey())
.deviceId(command.getDeviceId())
.iotId(command.getIotId())
.build());
if (ioTDeviceDTO == null) {
throw new IllegalArgumentException(
"设备不存在: productKey="
+ command.getProductKey()
+ ", deviceId="
+ command.getDeviceId());
}
}
request.setIoTDeviceDTO(ioTDeviceDTO);
VideoPlatformInstance videoPlatformInstance =
videoPlatformInstanceAdapter.getVideoPlatformInstance(command.getProductKey());
if (videoPlatformInstance == null) {
throw new IllegalArgumentException("视频实例不存在: " + command.getProductKey());
}
buildEndpointAuth(request, videoPlatformInstance);
// 5. 处理扩展字段
if (command.getExtensions() != null && !command.getExtensions().isEmpty()) {
Map<String, Object> wvpRequestData = request.getWvpRequestData();
wvpRequestData.putAll(command.getExtensions());
request.setWvpRequestData(wvpRequestData);
}
// 6. 设置消息ID
if (StrUtil.isBlank(request.getMsgId())) {
request.setMsgId(generateMsgId());
}
if (command.getFunction() != null && !command.getFunction().isEmpty()) {
request.setFunction(command.getFunction());
if (command.getFunction().containsKey("data")) {
request.setData(JSONUtil.parseObj(command.getFunction().get("data")));
}
}
log.debug(
"[WVP转换器] 转换完成: productKey={}, deviceId={}, cmd={}",
request.getProductKey(),
request.getDeviceId(),
request.getCmd() != null ? request.getCmd().getValue() : null);
return request;
} catch (IllegalArgumentException e) {
log.error("[WVP转换器] 参数验证失败: {}", e.getMessage());
throw e;
} catch (Exception e) {
log.error("[WVP转换器] 转换失败", e);
throw new IllegalStateException("WVP请求转换失败: " + e.getMessage(), e);
}
}
/** 生成消息ID */
private String generateMsgId() {
return String.valueOf(System.currentTimeMillis());
}
@Override
public String supportedProtocol() {
return wvpModuleInfo.getCode();
}
@Override
public int getPriority() {
return 10;
}
@Override
public boolean supports(UnifiedDownlinkCommand command) {
return command.getCmd() != null;
}
@Override
public void preConvert(UnifiedDownlinkCommand command, DownlinkContext<?> context) {
if (StrUtil.isBlank(command.getProductKey())) {
throw new IllegalArgumentException("productKey不能为空");
}
log.debug(
"[WVP转换器] 开始转换: productKey={}, deviceId={}, cmd={}",
command.getProductKey(),
command.getDeviceId(),
command.getCmd());
}
@Override
public void postConvert(
UnifiedDownlinkCommand command, WvpDownRequest request, DownlinkContext<?> context) {
context.setAttribute("convertTime", System.currentTimeMillis());
context.setAttribute("protocolType", "WVP");
if (request.getIoTDeviceDTO() != null) {
context.setAttribute("deviceName", request.getIoTDeviceDTO().getDeviceName());
context.setAttribute("deviceStatus", request.getIoTDeviceDTO().getState());
}
log.debug("[WVP转换器] 转换后处理完成: msgId={}", request.getMsgId());
}
private void buildEndpointAuth(WvpDownRequest request, VideoPlatformInstance instance) {
Map<String, Object> wvpRequestData = request.getWvpRequestData();
if (wvpRequestData == null) {
wvpRequestData = new HashMap<>();
}
String auth = instance.getAuth();
if (JSONUtil.isTypeJSON(auth)) {
wvpRequestData.putAll(JSONUtil.toBean(auth, Map.class));
}
String endpoint = instance.getEndpoint();
if (StrUtil.isNotBlank(endpoint)) {
wvpRequestData.put("endpoint", endpoint);
}
request.setWvpRequestData(wvpRequestData);
}
}
@@ -1,46 +0,0 @@
package cn.wvp.protocol.entity;
import cn.universal.common.constant.IoTConstant.DownCmd;
import java.util.Map;
/**
* WVP 下行请求封装
*/
public class WvpDownRequest extends cn.universal.persistence.base.BaseDownRequest {
private DownCmd cmd;
private String productKey;
private String deviceId;
private Map<String, Object> wvpRequestData;
public DownCmd getCmd() {
return cmd;
}
public void setCmd(DownCmd cmd) {
this.cmd = cmd;
}
public String getProductKey() {
return productKey;
}
public void setProductKey(String productKey) {
this.productKey = productKey;
}
public String getDeviceId() {
return deviceId;
}
public void setDeviceId(String deviceId) {
this.deviceId = deviceId;
}
public Map<String, Object> getWvpRequestData() {
return wvpRequestData;
}
public void setWvpRequestData(Map<String, Object> wvpRequestData) {
this.wvpRequestData = wvpRequestData;
}
}
@@ -1,53 +0,0 @@
package cn.wvp.protocol.handle;
import cn.universal.common.constant.IoTConstant.DownCmd;
import cn.universal.common.domain.R;
import cn.universal.persistence.base.IoTDownAdapter;
import cn.wvp.protocol.entity.WvpDownRequest;
import cn.wvp.protocol.handler.WvpCommandHandlerManager;
import jakarta.annotation.Resource;
import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
* WVP 下行实际处理入口(统一命令分发模式)
*/
@Component
public class WvpDownHandle extends IoTDownAdapter<WvpDownRequest> {
private static final Logger log = LoggerFactory.getLogger(WvpDownHandle.class);
@Resource private WvpCommandHandlerManager commandHandlerManager;
public R<?> down(WvpDownRequest downRequest) {
if (downRequest == null || downRequest.getCmd() == null) {
log.warn("WVP设备处理下行对象为空,不处理={}", downRequest);
return R.error("WVP设备处理下行对象为空");
}
R<?> preResult =
beforeDownAction(downRequest.getIoTProduct(), downRequest.getData(), downRequest);
if (Objects.nonNull(preResult)) {
return preResult;
}
return dispatchCommand(downRequest);
}
private R<?> dispatchCommand(WvpDownRequest downRequest) {
DownCmd cmd = downRequest.getCmd();
switch (cmd) {
case DEV_ADD:
case DEV_DEL:
case DEV_UPDATE:
return commandHandlerManager.handleCommand(downRequest);
case DEV_FUNCTION:
return commandHandlerManager.handleCommand(downRequest);
default:
log.info("WVP设备处理下行未匹配到方法: {}", cmd);
return R.error("未支持的命令类型: " + cmd);
}
}
}
@@ -1,9 +0,0 @@
package cn.wvp.protocol.handler;
import cn.universal.common.domain.R;
import cn.wvp.protocol.entity.WvpDownRequest;
public interface WvpCommandHandler {
String getSupportedCommand();
R<?> handle(WvpDownRequest request);
}
@@ -1,108 +0,0 @@
package cn.wvp.protocol.handler;
import cn.hutool.core.map.MapUtil;
import cn.universal.common.domain.R;
import cn.wvp.protocol.entity.WvpDownRequest;
import java.util.HashMap;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* WVP命令处理器管理器
*/
@Slf4j
@Component
public class WvpCommandHandlerManager implements ApplicationContextAware {
private Map<String, WvpCommandHandler> handlerMap = new HashMap<>();
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Map<String, WvpCommandHandler> current =
applicationContext.getBeansOfType(WvpCommandHandler.class);
if (current != null && MapUtil.isNotEmpty(current)) {
current.forEach(
(key, handler) -> {
String command = handler.getSupportedCommand();
if (command != null && !command.isEmpty()) {
handlerMap.put(command.toLowerCase(), handler);
log.info("注册WVP命令处理器: {} -> {}", command, handler.getClass().getSimpleName());
}
});
}
}
/**
* 处理命令
*
* @param request 下行请求
* @return 处理结果
*/
public R<?> handleCommand(WvpDownRequest request) {
String commandKey = getCommandKey(request);
if (commandKey == null) {
log.warn("WVP设备处理功能下行无法确定命令类型,不处理={}", request);
return R.error("无法确定命令类型");
}
WvpCommandHandler handler = handlerMap.get(commandKey);
if (handler == null) {
log.info("WVP设备处理功能下行未匹配到处理器: {}", commandKey);
return R.error("未找到对应的处理器: " + commandKey);
}
try {
return handler.handle(request);
} catch (Exception e) {
log.error("处理命令时发生异常: {}", commandKey, e);
return R.error("处理命令时发生异常: " + e.getMessage());
}
}
/**
* 获取命令键值
* 兼容两种命令格式:
* 1. DEV_FUNCTION 类型:从 function.function 字段或 data.function 字段获取
* 2. DEV_ADD/DEV_DEL/DEV_UPDATE 类型:从 cmd 字段获取
*
* @param request 下行请求
* @return 命令键值
*/
private String getCommandKey(WvpDownRequest request) {
// 首先尝试从 function.function 字段获取(DEV_FUNCTION 类型,新方式)
if (request.getFunction() != null) {
String function = (String) request.getFunction().get("function");
if (function != null && !function.isEmpty()) {
return function.toLowerCase();
}
}
// 其次尝试从 data.function 字段获取(DEV_FUNCTION 类型,兼容旧方式)
if (request.getData() != null) {
String function = request.getData().getStr("function");
if (function != null && !function.isEmpty()) {
return function.toLowerCase();
}
}
// 如果 function 为空,则从 cmd 字段获取(DEV_ADD/DEV_DEL/DEV_UPDATE 类型)
if (request.getCmd() != null) {
return request.getCmd().name().toLowerCase();
}
return null;
}
/**
* 获取所有支持的命令
*
* @return 支持的命令列表
*/
public String[] getSupportedCommands() {
return handlerMap.keySet().toArray(new String[0]);
}
}
@@ -1,66 +0,0 @@
package cn.wvp.protocol.handler.impl;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import cn.universal.common.constant.IoTConstant.DeviceStatus;
import cn.universal.common.constant.IoTConstant.DownCmd;
import cn.universal.common.domain.R;
import cn.universal.persistence.base.IoTDeviceLifeCycle;
import cn.universal.persistence.dto.IoTDeviceDTO;
import cn.universal.persistence.entity.IoTDevice;
import cn.universal.persistence.mapper.IoTDeviceMapper;
import cn.wvp.protocol.entity.WvpDownRequest;
import cn.wvp.protocol.handler.WvpCommandHandler;
import jakarta.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
import org.springframework.stereotype.Component;
@Component
public class WvpDeviceAddHandler implements WvpCommandHandler {
@Resource(name = "ioTDeviceActionAfterService")
private IoTDeviceLifeCycle ioTDeviceLifeCycle;
@Resource private IoTDeviceMapper ioTDeviceMapper;
public R<?> handle(WvpDownRequest downRequest) {
// 幂等检查:productKey + deviceId
Map<String,Object> query = new HashMap<>();
query.put("iotId", downRequest.getProductKey() + downRequest.getDeviceId());
IoTDeviceDTO exist = ioTDeviceMapper.selectIoTDeviceBO(query);
if (exist != null) {
return R.error("设备已存在");
}
// TODO: 接入WVP级联/GB注册绑定逻辑
IoTDevice ioTDevice = IoTDevice.builder()
.deviceId(downRequest.getDeviceId())
.createTime(System.currentTimeMillis() / 1000)
.deviceName(StrUtil.emptyToDefault(downRequest.getDetail(), downRequest.getDeviceId()))
.state(DeviceStatus.offline.getCode())
.iotId(downRequest.getProductKey() + downRequest.getDeviceId())
.creatorId(downRequest.getAppUnionId())
.productName(downRequest.getIoTProduct().getName())
.application(downRequest.getApplicationId())
.detail(downRequest.getDetail())
.productKey(downRequest.getProductKey())
.build();
Map<String, Object> config = new HashMap<>();
config.put("discardValue", 0);
ioTDevice.setConfiguration(JSONUtil.toJsonStr(config));
ioTDeviceMapper.insertUseGeneratedKeys(ioTDevice);
ioTDeviceLifeCycle.create(downRequest.getProductKey(), downRequest.getDeviceId(), downRequest);
Map<String, Object> result = new HashMap<>();
result.put("iotId", ioTDevice.getIotId());
if (StrUtil.isNotBlank(downRequest.getIoTProduct().getMetadata())) {
result.put("metadata", JSONUtil.parseObj(downRequest.getIoTProduct().getMetadata()));
}
result.put("productKey", downRequest.getProductKey());
result.put("deviceNode", downRequest.getIoTProduct().getDeviceNode());
return R.ok(result);
}
public String getSupportedCommand() {
return DownCmd.DEV_ADD.name().toLowerCase();
}
}
@@ -1,41 +0,0 @@
package cn.wvp.protocol.handler.impl;
import cn.universal.common.constant.IoTConstant.DownCmd;
import cn.universal.common.domain.R;
import cn.universal.dm.device.service.impl.IoTDeviceService;
import cn.universal.persistence.base.IoTDeviceLifeCycle;
import cn.universal.persistence.dto.IoTDeviceDTO;
import cn.universal.persistence.mapper.IoTDeviceMapper;
import cn.wvp.protocol.entity.WvpDownRequest;
import cn.wvp.protocol.handler.WvpCommandHandler;
import jakarta.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
import org.springframework.stereotype.Component;
@Component
public class WvpDeviceDeleteHandler implements WvpCommandHandler {
@Resource(name = "ioTDeviceActionAfterService")
private IoTDeviceLifeCycle ioTDeviceLifeCycle;
@Resource private IoTDeviceMapper ioTDeviceMapper;
@Resource private IoTDeviceService iotDeviceService;
public R<?> handle(WvpDownRequest downRequest) {
// 幂等检查
Map<String,Object> param = new HashMap<>();
param.put("iotId", downRequest.getProductKey() + downRequest.getDeviceId());
IoTDeviceDTO dto = ioTDeviceMapper.selectIoTDeviceBO(param);
if (dto == null) {
return R.error("设备不存在");
}
// TODO: 接入WVP解绑逻辑,若未绑定则直接删除本地
ioTDeviceLifeCycle.delete(dto, downRequest);
iotDeviceService.delDevInstance(dto.getIotId());
return R.ok();
}
public String getSupportedCommand() {
return DownCmd.DEV_DEL.name().toLowerCase();
}
}
@@ -1,48 +0,0 @@
package cn.wvp.protocol.handler.impl;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.universal.common.constant.IoTConstant.DownCmd;
import cn.universal.common.domain.R;
import cn.universal.persistence.entity.IoTDevice;
import cn.universal.persistence.mapper.IoTDeviceMapper;
import cn.wvp.protocol.entity.WvpDownRequest;
import cn.wvp.protocol.handler.WvpCommandHandler;
import jakarta.annotation.Resource;
import java.util.Map;
import org.springframework.stereotype.Component;
import tk.mybatis.mapper.entity.Example;
@Component
public class WvpDeviceUpdateHandler implements WvpCommandHandler {
@Resource private IoTDeviceMapper ioTDeviceMapper;
public R<?> handle(WvpDownRequest request) {
Map<String, Object> data = request.getWvpRequestData();
if (data == null || MapUtil.isEmpty(data)) return R.error("缺少参数: data");
String newName = (String) data.get("deviceName");
if (newName == null || newName.isEmpty()) return R.error("缺少参数: deviceName");
String iotId = request.getProductKey() + request.getDeviceId();
String latitude = (String) data.get("latitude");
String longitude = (String) data.get("longitude");
// 根据iotId更新设备名称
IoTDevice dev = new IoTDevice();
dev.setDeviceName(newName);
if (StrUtil.isNotBlank(latitude) && StrUtil.isNotBlank(longitude)) {
dev.setCoordinate(longitude + "," + latitude);
}
// 使用Example构建更新条件,根据iotId更新
Example example = new Example(IoTDevice.class);
example.createCriteria().andEqualTo("iotId", iotId);
int count = ioTDeviceMapper.updateByExampleSelective(dev, example);
if (count > 0) {
return R.ok("更新成功");
}
return R.ok("无变化");
}
public String getSupportedCommand() {
return DownCmd.DEV_UPDATE.name().toLowerCase();
}
}
@@ -1,20 +0,0 @@
package cn.wvp.protocol.handler.impl;
import cn.universal.common.constant.CameraCommand;
import cn.universal.common.domain.R;
import cn.wvp.protocol.entity.WvpDownRequest;
import cn.wvp.protocol.handler.WvpCommandHandler;
import cn.wvp.protocol.service.WvpDeviceService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Component;
@Component
public class WvpLiveStreamHandler implements WvpCommandHandler {
@Resource private WvpDeviceService wvpDeviceService;
public R<?> handle(WvpDownRequest request) {
String function = request.getData() == null ? null : request.getData().getStr("function");
return wvpDeviceService.startPreview(request);
}
public String getSupportedCommand() { return CameraCommand.CAMERA_LIVE_STREAM.getFunctionName(); }
}
@@ -1,28 +0,0 @@
package cn.wvp.protocol.handler.impl;
import cn.universal.common.constant.CameraCommand;
import cn.universal.common.domain.R;
import cn.wvp.protocol.entity.WvpDownRequest;
import cn.wvp.protocol.handler.WvpCommandHandler;
import cn.wvp.protocol.service.WvpDeviceService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Component;
/**
* WVP加载云端录像处理器
*
* @version 1.0
* @since 2025/11/09
*/
@Component
public class WvpLoadCloudRecordHandler implements WvpCommandHandler {
@Resource private WvpDeviceService wvpDeviceService;
public R<?> handle(WvpDownRequest request) {
return wvpDeviceService.loadCloudRecord(request);
}
public String getSupportedCommand() {
return CameraCommand.LOAD_CLOUD_RECORD.getFunctionName();
}
}
@@ -1,19 +0,0 @@
package cn.wvp.protocol.handler.impl;
import cn.universal.common.constant.CameraCommand;
import cn.universal.common.domain.R;
import cn.wvp.protocol.entity.WvpDownRequest;
import cn.wvp.protocol.handler.WvpCommandHandler;
import cn.wvp.protocol.service.WvpDeviceService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Component;
@Component
public class WvpPlaybackHandler implements WvpCommandHandler {
@Resource private WvpDeviceService wvpDeviceService;
public R<?> handle(WvpDownRequest request) {
return wvpDeviceService.startPlayback(request);
}
public String getSupportedCommand() { return CameraCommand.CAMERA_PLAYBACK.getFunctionName(); }
}
@@ -1,19 +0,0 @@
package cn.wvp.protocol.handler.impl;
import cn.universal.common.constant.CameraCommand;
import cn.universal.common.domain.R;
import cn.wvp.protocol.entity.WvpDownRequest;
import cn.wvp.protocol.handler.WvpCommandHandler;
import cn.wvp.protocol.service.WvpDeviceService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Component;
@Component
public class WvpPtzControlHandler implements WvpCommandHandler {
@Resource private WvpDeviceService wvpDeviceService;
public R<?> handle(WvpDownRequest request) {
return wvpDeviceService.ptzControl(request);
}
public String getSupportedCommand() { return CameraCommand.CAMERA_TURN.getFunctionName(); }
}
@@ -1,28 +0,0 @@
package cn.wvp.protocol.handler.impl;
import cn.universal.common.constant.CameraCommand;
import cn.universal.common.domain.R;
import cn.wvp.protocol.entity.WvpDownRequest;
import cn.wvp.protocol.handler.WvpCommandHandler;
import cn.wvp.protocol.service.WvpDeviceService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Component;
/**
* WVP查询云端录像处理器
*
* @version 1.0
* @since 2025/11/09
*/
@Component
public class WvpQueryCloudRecordsHandler implements WvpCommandHandler {
@Resource private WvpDeviceService wvpDeviceService;
public R<?> handle(WvpDownRequest request) {
return wvpDeviceService.queryCloudRecords(request);
}
public String getSupportedCommand() {
return CameraCommand.QUERY_CLOUD_RECORDS.getFunctionName();
}
}
@@ -1,28 +0,0 @@
package cn.wvp.protocol.handler.impl;
import cn.universal.common.constant.CameraCommand;
import cn.universal.common.domain.R;
import cn.wvp.protocol.entity.WvpDownRequest;
import cn.wvp.protocol.handler.WvpCommandHandler;
import cn.wvp.protocol.service.WvpDeviceService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Component;
/**
* WVP查询录像处理器
*
* @version 1.0
* @since 2025/11/09
*/
@Component
public class WvpQueryRecordsHandler implements WvpCommandHandler {
@Resource private WvpDeviceService wvpDeviceService;
public R<?> handle(WvpDownRequest request) {
return wvpDeviceService.queryGBRecords(request);
}
public String getSupportedCommand() {
return CameraCommand.QUERY_RECORDS.getFunctionName();
}
}
@@ -1,22 +0,0 @@
package cn.wvp.protocol.handler.impl;
import cn.universal.common.constant.CameraCommand;
import cn.universal.common.domain.R;
import cn.wvp.protocol.entity.WvpDownRequest;
import cn.wvp.protocol.handler.WvpCommandHandler;
import cn.wvp.protocol.service.WvpDeviceService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Component;
@Component
public class WvpSnapshotHandler implements WvpCommandHandler {
@Resource private WvpDeviceService wvpDeviceService;
public R<?> handle(WvpDownRequest request) {
return wvpDeviceService.snapshot(request);
}
public String getSupportedCommand() {
return CameraCommand.CAMERA_SNAPSHOT.getFunctionName();
}
}
@@ -1,64 +0,0 @@
package cn.wvp.protocol.service;
import cn.universal.common.domain.R;
import cn.wvp.protocol.entity.WvpDownRequest;
import java.util.Map;
/** WVP 设备服务接口 */
public interface WvpDeviceService {
// 设备/组织/通道拉取(供缓存同步使用)
R<?> listDevices(String instanceKey, Map<String, Object> filters);
R<?> listOrgs(String instanceKey, Map<String, Object> filters);
R<?> listChannels(String serial, Map<String, Object> filters);
// 能力调用(供命令处理器使用)
R<?> ptzControl(WvpDownRequest request);
R<?> startPreview(WvpDownRequest request);
R<?> stopPreview(WvpDownRequest request);
R<?> startPlayback(WvpDownRequest request);
R<?> stopPlayback(WvpDownRequest request);
R<?> snapshot(WvpDownRequest request);
R<?> queryPresets(WvpDownRequest request);
// ==================== 国标录像相关 ====================
/** 查询国标录像 */
R<?> queryGBRecords(WvpDownRequest request);
/** 开始下载国标录像 */
R<?> startGBRecordDownload(WvpDownRequest request);
/** 停止下载国标录像 */
R<?> stopGBRecordDownload(WvpDownRequest request);
/** 获取下载进度 */
R<?> getGBRecordDownloadProgress(WvpDownRequest request);
// ==================== 云端录像相关 ====================
/** 查询云端录像日期列表 */
R<?> queryCloudRecordDates(WvpDownRequest request);
/** 查询云端录像列表 */
R<?> queryCloudRecords(WvpDownRequest request);
/** 加载云端录像文件 */
R<?> loadCloudRecord(WvpDownRequest request);
/** 云端录像定位 */
R<?> seekCloudRecord(WvpDownRequest request);
/** 设置云端录像倍速 */
R<?> setCloudRecordSpeed(WvpDownRequest request);
/** 获取云端录像下载地址 */
R<?> getCloudRecordPlayPath(WvpDownRequest request);
}
@@ -1,175 +0,0 @@
/*
*
* Copyright (c) 2025, NexIoT. All Rights Reserved.
*
* @Description: 本文件由 gitee.com/NexIoT 开发并拥有版权,未经授权严禁擅自商用、复制或传播。
* @Author: gitee.com/NexIoT
* @Email: wo8335224@gmail.com
* @Wechat: outlookFil
*
*
*/
package cn.wvp.protocol.service;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.universal.common.domain.R;
import cn.universal.core.service.IDown;
import cn.universal.dm.device.service.AbstractDownService;
import cn.universal.persistence.dto.IoTDeviceDTO;
import cn.universal.persistence.entity.IoTProduct;
import cn.universal.persistence.query.IoTDeviceQuery;
import cn.wvp.protocol.config.WvpModuleInfo;
import cn.wvp.protocol.entity.WvpDownRequest;
import cn.wvp.protocol.handle.WvpDownHandle;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* WVP下行服务
*
* @version 2.0
* @since 2025/1/15
*/
@Service("wvpDownService")
@Slf4j
public class WvpDownService extends AbstractDownService<WvpDownRequest> implements IDown {
@Resource private WvpModuleInfo wvpModuleInfo;
@Resource private WvpDownHandle wvpDownHandle;
@Resource private cn.universal.core.downlink.DownlinkInterceptorChain downlinkInterceptorChain;
@Override
public cn.universal.core.downlink.DownlinkInterceptorChain getInterceptorChain() {
return downlinkInterceptorChain;
}
@Override
public String code() {
return wvpModuleInfo.getCode();
}
@Override
public String name() {
return wvpModuleInfo.getName();
}
@Override
@SuppressWarnings("unchecked")
public R<?> doProcess(cn.universal.core.downlink.DownlinkContext<?> context) {
try {
WvpDownRequest downRequest;
if (context.getDownRequest() != null) {
downRequest = (WvpDownRequest) context.getDownRequest();
} else if (context.getRawMessage() != null) {
downRequest = convert(context.getRawMessage());
} else if (context.getJsonMessage() != null) {
downRequest = convert(context.getJsonMessage());
} else {
return R.error("消息为空");
}
if (downRequest == null) {
return R.error("转换下行请求失败");
}
// 设置上下文信息
((cn.universal.core.downlink.DownlinkContext<WvpDownRequest>) context)
.setDownRequest(downRequest);
context.setProductKey(downRequest.getProductKey());
context.setDeviceId(downRequest.getDeviceId());
context.setIotId(downRequest.getIotId());
log.info(
"[WVP下行] deviceId={} productKey={}",
downRequest.getDeviceId(),
downRequest.getProductKey());
// 执行处理
return wvpDownHandle.down(downRequest);
} catch (Exception e) {
log.error("[WVP下行] 处理异常", e);
return R.error("处理异常: " + e.getMessage());
}
}
@Override
protected WvpDownRequest convert(String request) {
return doConvert(request);
}
private WvpDownRequest convert(JSONObject request) {
return doConvert(request);
}
/**
* 执行转换操作
*
* @param request 请求对象
* @return 转换后的下行请求
*/
private WvpDownRequest doConvert(Object request) {
try {
WvpDownRequest value = parseRequest(request);
if (value == null) {
return null;
}
// 获取设备信息
IoTDeviceDTO deviceDTO = getDeviceInfo(value);
value.setIoTDeviceDTO(deviceDTO);
// 获取产品信息
IoTProduct product = getProduct(value.getProductKey());
if (product == null) {
log.warn("产品不存在: productKey={}", value.getProductKey());
return null;
}
// 设置相关信息
value.setIoTProduct(product);
value.setIoTDeviceDTO(deviceDTO);
value.setWvpRequestData(value.getWvpRequestData());
return value;
} catch (Exception e) {
log.error("转换下行请求失败", e);
return null;
}
}
/**
* 解析请求对象
*
* @param request 请求对象
* @return 解析后的下行请求
*/
private WvpDownRequest parseRequest(Object request) {
if (request instanceof JSONObject) {
return JSONUtil.toBean((JSONObject) request, WvpDownRequest.class);
} else if (request instanceof String) {
return JSONUtil.toBean((String) request, WvpDownRequest.class);
} else {
return JSONUtil.toBean(JSONUtil.toJsonStr(request), WvpDownRequest.class);
}
}
/**
* 获取设备信息
*
* @param value 下行请求
* @return 设备信息
*/
private IoTDeviceDTO getDeviceInfo(WvpDownRequest value) {
IoTDeviceQuery query =
IoTDeviceQuery.builder()
.productKey(value.getProductKey())
.deviceId(value.getDeviceId())
.iotId(value.getIotId())
.build();
return getIoTDeviceDTO(query);
}
}
@@ -1,873 +0,0 @@
package cn.wvp.protocol.service.impl;
import cn.hutool.json.JSONObject;
import cn.universal.common.domain.R;
import cn.wvp.protocol.constant.WvpApiConstants;
import cn.wvp.protocol.entity.WvpDownRequest;
import cn.wvp.protocol.service.WvpDeviceService;
import cn.wvp.protocol.util.VideoPlatformHttpUtil;
import java.util.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/** WVP设备服务实现 - 使用wvp-GB28181-pro标准接口 基于com.genersoft.iot.vmp.gb28181.controller下的标准API */
@Slf4j
@Service
public class WvpDeviceServiceImpl implements WvpDeviceService {
/** 从filters中提取endpoint */
private String getEndpoint(Map<String, Object> filters) {
return String.valueOf(Optional.ofNullable(filters).map(m -> m.get("endpoint")).orElse(""));
}
/** 从WvpDownRequest中提取endpoint */
private String getEndpoint(WvpDownRequest request) {
return String.valueOf(
Optional.ofNullable(request.getWvpRequestData()).map(m -> m.get("endpoint")).orElse(""));
}
@Override
public R<?> listDevices(String instanceKey, Map<String, Object> filters) {
String endpoint = getEndpoint(filters);
if (endpoint.isEmpty()) {
return R.error("缺少参数: endpoint");
}
JSONObject resp =
VideoPlatformHttpUtil.httpGetJson(endpoint, WvpApiConstants.API_DEVICE_QUERY, filters);
if (VideoPlatformHttpUtil.hasError(resp)) {
return R.error("获取设备列表失败: " + VideoPlatformHttpUtil.getErrorMessage(resp));
}
// 直接返回data部分,避免双层包装
Object data = resp.get("data");
return R.ok(data != null ? data : resp);
}
@Override
public R<?> listOrgs(String instanceKey, Map<String, Object> filters) {
// WVP GB28181通常不提供组织树结构,返回空列表
return R.ok(Collections.emptyList());
}
@Override
public R<?> listChannels(String serial, Map<String, Object> filters) {
String endpoint = getEndpoint(filters);
if (endpoint.isEmpty()) {
return R.error("缺少参数: endpoint");
}
String path = String.format(WvpApiConstants.API_DEVICE_CHANNELS, serial);
JSONObject resp = VideoPlatformHttpUtil.httpGetJson(endpoint, path, filters);
if (VideoPlatformHttpUtil.hasError(resp)) {
return R.error("获取通道列表失败: " + VideoPlatformHttpUtil.getErrorMessage(resp));
}
// 直接返回data部分,避免双层包装
Object data = resp.get("data");
return R.ok(data != null ? data : resp);
}
@Override
public R<?> ptzControl(WvpDownRequest request) {
JSONObject data = request.getData();
if (data == null) {
return R.error("缺少参数: data");
}
String endpoint = getEndpoint(request);
if (endpoint.isEmpty()) {
return R.error("缺少参数: endpoint");
}
String command = data.getStr("command"); // left/right/up/down/stop/zoomin/zoomout/focusIn/focusOut/irisIn/irisOut等
String channelId = data.getStr("channelId");
Integer speed = data.getInt("speed", 50); // 速度默认50
if (command == null || command.isEmpty()) {
return R.error("缺少参数: command");
}
if (channelId == null || channelId.isEmpty()) {
return R.error("缺少参数: channelId");
}
// 根据命令类型选择不同的接口
String path;
Map<String, Object> filters = new HashMap<>();
if (request.getWvpRequestData() != null) {
filters.putAll(request.getWvpRequestData());
}
// 判断是否为聚焦命令
if ("focusIn".equalsIgnoreCase(command) || "focusOut".equalsIgnoreCase(command)) {
path = String.format(WvpApiConstants.API_FOCUS_CONTROL, request.getDeviceId(), channelId);
// 转换命令: focusIn -> near, focusOut -> far
String focusCmd = "focusIn".equalsIgnoreCase(command) ? "near" : "far";
filters.put("command", focusCmd);
filters.put("speed", speed);
}
// 判断是否为光圈命令
else if ("irisIn".equalsIgnoreCase(command) || "irisOut".equalsIgnoreCase(command)) {
path = String.format(WvpApiConstants.API_IRIS_CONTROL, request.getDeviceId(), channelId);
// 转换命令: irisIn -> in, irisOut -> out
String irisCmd = "irisIn".equalsIgnoreCase(command) ? "in" : "out";
filters.put("command", irisCmd);
filters.put("speed", speed);
}
// 云台方向和缩放命令
else {
path = String.format(WvpApiConstants.API_PTZ_CONTROL, request.getDeviceId(), channelId);
// 计算horizonSpeed和verticalSpeed
int horizonSpeed = 0;
int verticalSpeed = 0;
int zoomSpeed = 0;
// 根据命令设置速度
switch (command.toLowerCase()) {
case "left":
case "right":
horizonSpeed = speed;
break;
case "up":
case "down":
verticalSpeed = speed;
break;
case "leftup":
case "upleft":
case "rightup":
case "upright":
case "leftdown":
case "downleft":
case "rightdown":
case "downright":
horizonSpeed = speed;
verticalSpeed = speed;
break;
case "zoomin":
case "zoomout":
zoomSpeed = Math.min(speed / 16, 15); // zoomSpeed范围0-15
break;
case "stop":
// stop命令速度都为0
break;
default:
return R.error("不支持的命令: " + command);
}
filters.put("command", command.toLowerCase());
filters.put("horizonSpeed", horizonSpeed);
filters.put("verticalSpeed", verticalSpeed);
filters.put("zoomSpeed", zoomSpeed);
}
JSONObject resp = VideoPlatformHttpUtil.httpGetJson(endpoint, path, filters);
if (VideoPlatformHttpUtil.hasError(resp)) {
return R.error("云台控制失败: " + VideoPlatformHttpUtil.getErrorMessage(resp));
}
Map<String, Object> result = new HashMap<>();
result.put("executed", true);
result.put("command", command);
result.put("channelId", channelId);
return R.ok(result);
}
@Override
public R<?> startPreview(WvpDownRequest request) {
JSONObject data = request.getData();
String endpoint = getEndpoint(request);
if (endpoint.isEmpty()) {
return R.error("缺少参数: endpoint");
}
String channelId = data == null ? null : data.getStr("channelId");
if (channelId == null || channelId.isEmpty()) {
return R.error("缺少参数: channelId");
}
Map<String, Object> filters = new HashMap<>();
if (request.getWvpRequestData() != null) {
filters.putAll(request.getWvpRequestData());
}
filters.put("channelId", channelId);
String path = String.format(WvpApiConstants.API_PLAY_START, request.getDeviceId(), channelId);
JSONObject resp = VideoPlatformHttpUtil.httpGetJson(endpoint, path, filters);
if (VideoPlatformHttpUtil.hasError(resp)) {
return R.error("启动预览失败: " + VideoPlatformHttpUtil.getErrorMessage(resp));
}
return R.ok(resp.getJSONObject("data"));
}
@Override
public R<?> startPlayback(WvpDownRequest request) {
JSONObject data = request.getData();
String endpoint = getEndpoint(request);
if (endpoint.isEmpty()) {
return R.error("缺少参数: endpoint");
}
if (data == null) {
return R.error("缺少参数: data");
}
String channelId = data.getStr("channelId");
Object startTimeObj = data.get("startTime");
Object endTimeObj = data.get("endTime");
if (channelId == null || startTimeObj == null || endTimeObj == null) {
return R.error("缺少参数: channelId/startTime/endTime");
}
// 将时间戳转换为时间字符串格式(wvp API 需要 yyyy-MM-dd HH:mm:ss 格式)
String startTimeStr;
String endTimeStr;
try {
long startTime;
long endTime;
// 处理时间参数:可能是 Long 类型的时间戳,也可能是 String 类型
if (startTimeObj instanceof Long) {
startTime = (Long) startTimeObj;
} else if (startTimeObj instanceof String) {
startTime = Long.parseLong((String) startTimeObj);
} else {
startTime = ((Number) startTimeObj).longValue();
}
if (endTimeObj instanceof Long) {
endTime = (Long) endTimeObj;
} else if (endTimeObj instanceof String) {
endTime = Long.parseLong((String) endTimeObj);
} else {
endTime = ((Number) endTimeObj).longValue();
}
// 如果是10位秒级时间戳,转换为毫秒
if (startTime > 0 && startTime < 10000000000L) {
startTime = startTime * 1000;
}
if (endTime > 0 && endTime < 10000000000L) {
endTime = endTime * 1000;
}
// 转换为时间字符串格式
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
startTimeStr = sdf.format(new java.util.Date(startTime));
endTimeStr = sdf.format(new java.util.Date(endTime));
} catch (Exception e) {
return R.error("时间参数格式错误: " + e.getMessage());
}
Map<String, Object> filters = new HashMap<>();
if (request.getWvpRequestData() != null) {
filters.putAll(request.getWvpRequestData());
}
// wvp API 需要时间字符串格式
filters.put("startTime", startTimeStr);
filters.put("endTime", endTimeStr);
String path =
String.format(WvpApiConstants.API_PLAYBACK_START, request.getDeviceId(), channelId);
JSONObject resp = VideoPlatformHttpUtil.httpGetJson(endpoint, path, filters);
if (VideoPlatformHttpUtil.hasError(resp)) {
return R.error("启动回放失败: " + VideoPlatformHttpUtil.getErrorMessage(resp));
}
// 从响应中获取 data 对象
JSONObject dataObj = resp.getJSONObject("data");
if (dataObj == null) {
return R.error("启动回放失败: 响应数据为空");
}
// 提取所有流地址字段
Map<String, String> urls = new LinkedHashMap<>();
String flv = dataObj.getStr("flv");
String httpsFlv = dataObj.getStr("https_flv");
String wsFlv = dataObj.getStr("ws_flv");
String wssFlv = dataObj.getStr("wss_flv");
String hls = dataObj.getStr("hls");
String httpsHls = dataObj.getStr("https_hls");
String wsHls = dataObj.getStr("ws_hls");
String wssHls = dataObj.getStr("wss_hls");
String rtmp = dataObj.getStr("rtmp");
String rtmps = dataObj.getStr("rtmps");
String rtsp = dataObj.getStr("rtsp");
String rtsps = dataObj.getStr("rtsps");
if (flv != null) urls.put("flv", flv);
if (httpsFlv != null) urls.put("https_flv", httpsFlv);
if (wsFlv != null) urls.put("ws_flv", wsFlv);
if (wssFlv != null) urls.put("wss_flv", wssFlv);
if (hls != null) urls.put("hls", hls);
if (httpsHls != null) urls.put("https_hls", httpsHls);
if (wsHls != null) urls.put("ws_hls", wsHls);
if (wssHls != null) urls.put("wss_hls", wssHls);
if (rtmp != null) urls.put("rtmp", rtmp);
if (rtmps != null) urls.put("rtmps", rtmps);
if (rtsp != null) urls.put("rtsp", rtsp);
if (rtsps != null) urls.put("rtsps", rtsps);
// 提取其他重要字段
String app = dataObj.getStr("app");
String stream = dataObj.getStr("stream");
String mediaServerId = dataObj.getStr("mediaServerId");
String key = dataObj.getStr("key");
String respStartTimeStr = dataObj.getStr("startTime");
String respEndTimeStr = dataObj.getStr("endTime");
Map<String, Object> res = new HashMap<>();
res.put("channelId", channelId);
// 保存原始时间戳(毫秒)- 使用请求时的时间字符串
try {
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
res.put("startTime", sdf.parse(startTimeStr).getTime());
res.put("endTime", sdf.parse(endTimeStr).getTime());
} catch (Exception e) {
// 如果解析失败,使用当前时间
res.put("startTime", System.currentTimeMillis());
res.put("endTime", System.currentTimeMillis());
}
// 将所有流地址直接放在根级别,而不是嵌套在 urls 中(前端需要直接访问)
res.putAll(urls);
// 添加其他重要字段
if (app != null) res.put("app", app);
if (stream != null) res.put("stream", stream);
if (mediaServerId != null) res.put("mediaServerId", mediaServerId);
if (key != null) res.put("key", key);
// 使用响应中的时间字符串(如果存在)
if (respStartTimeStr != null) res.put("startTimeStr", respStartTimeStr);
else if (startTimeStr != null) res.put("startTimeStr", startTimeStr);
if (respEndTimeStr != null) res.put("endTimeStr", respEndTimeStr);
else if (endTimeStr != null) res.put("endTimeStr", endTimeStr);
return R.ok(res);
}
@Override
public R<?> snapshot(WvpDownRequest request) {
JSONObject data = request.getData();
String endpoint = getEndpoint(request);
if (endpoint.isEmpty()) {
return R.error("缺少参数: endpoint");
}
String channelId = data == null ? null : data.getStr("channelId");
if (channelId == null) {
return R.error("缺少参数: channelId");
}
Map<String, Object> filters = new HashMap<>();
if (request.getWvpRequestData() != null) {
filters.putAll(request.getWvpRequestData());
}
filters.put("channelId", channelId);
filters.put("deviceId", request.getDeviceId());
String path = String.format(WvpApiConstants.API_SNAPSHOT, request.getDeviceId(), channelId);
JSONObject resp = VideoPlatformHttpUtil.httpGetJson(endpoint, path, filters);
if (VideoPlatformHttpUtil.hasError(resp)) {
return R.error("抓拍失败: " + VideoPlatformHttpUtil.getErrorMessage(resp));
}
String snapshotUrl = resp.getStr("snap", resp.getStr("url", ""));
Map<String, Object> res = new HashMap<>();
res.put("channelId", channelId);
res.put("snapshotUrl", snapshotUrl);
return R.ok(res);
}
@Override
public R<?> stopPreview(WvpDownRequest request) {
JSONObject data = request.getData();
String endpoint = getEndpoint(request);
if (endpoint.isEmpty()) {
return R.error("缺少参数: endpoint");
}
String channelId = data == null ? null : data.getStr("channelId");
if (channelId == null || channelId.isEmpty()) {
return R.error("缺少参数: channelId");
}
Map<String, Object> filters = new HashMap<>();
if (request.getWvpRequestData() != null) {
filters.putAll(request.getWvpRequestData());
}
String path = String.format(WvpApiConstants.API_PLAY_STOP, request.getDeviceId(), channelId);
JSONObject resp = VideoPlatformHttpUtil.httpGetJson(endpoint, path, filters);
if (VideoPlatformHttpUtil.hasError(resp)) {
return R.error("停止预览失败: " + VideoPlatformHttpUtil.getErrorMessage(resp));
}
return R.ok();
}
@Override
public R<?> queryPresets(WvpDownRequest request) {
JSONObject data = request.getData();
String endpoint = getEndpoint(request);
if (endpoint.isEmpty()) {
return R.error("缺少参数: endpoint");
}
String channelId = data == null ? null : data.getStr("channelId");
if (channelId == null || channelId.isEmpty()) {
return R.error("缺少参数: channelId");
}
Map<String, Object> filters = new HashMap<>();
if (request.getWvpRequestData() != null) {
filters.putAll(request.getWvpRequestData());
}
String path = String.format(WvpApiConstants.API_PRESET_QUERY, request.getDeviceId(), channelId);
JSONObject resp = VideoPlatformHttpUtil.httpGetJson(endpoint, path, filters);
if (VideoPlatformHttpUtil.hasError(resp)) {
return R.error("查询预置位失败: " + VideoPlatformHttpUtil.getErrorMessage(resp));
}
return R.ok(resp);
}
// ==================== 以下为新增录像相关接口 ====================
@Override
public R<?> stopPlayback(WvpDownRequest request) {
JSONObject data = request.getData();
String endpoint = getEndpoint(request);
if (endpoint.isEmpty()) {
return R.error("缺少参数: endpoint");
}
String channelId = data == null ? null : data.getStr("channelId");
if (channelId == null || channelId.isEmpty()) {
return R.error("缺岑参数: channelId");
}
Map<String, Object> filters = new HashMap<>();
if (request.getWvpRequestData() != null) {
filters.putAll(request.getWvpRequestData());
}
String path =
String.format(WvpApiConstants.API_PLAYBACK_STOP, request.getDeviceId(), channelId);
JSONObject resp = VideoPlatformHttpUtil.httpGetJson(endpoint, path, filters);
if (VideoPlatformHttpUtil.hasError(resp)) {
return R.error("停止回放失败: " + VideoPlatformHttpUtil.getErrorMessage(resp));
}
return R.ok();
}
@Override
public R<?> queryGBRecords(WvpDownRequest request) {
JSONObject data = request.getData();
String endpoint = getEndpoint(request);
if (endpoint.isEmpty()) {
return R.error("缺岑参数: endpoint");
}
if (data == null) {
return R.error("缺岑参数: data");
}
String channelId = data.getStr("channelId");
String startTime = data.getStr("startTime");
String endTime = data.getStr("endTime");
if (channelId == null || startTime == null || endTime == null) {
return R.error("缺岑参数: channelId/startTime/endTime");
}
Map<String, Object> filters = new HashMap<>();
if (request.getWvpRequestData() != null) {
filters.putAll(request.getWvpRequestData());
}
filters.put("startTime", startTime);
filters.put("endTime", endTime);
String path =
String.format(WvpApiConstants.API_GB_RECORD_QUERY, request.getDeviceId(), channelId);
JSONObject resp = VideoPlatformHttpUtil.httpGetJson(endpoint, path, filters);
if (VideoPlatformHttpUtil.hasError(resp)) {
return R.error("国标录像查询失败: " + VideoPlatformHttpUtil.getErrorMessage(resp));
}
return R.ok(resp.getJSONObject("data"));
}
@Override
public R<?> startGBRecordDownload(WvpDownRequest request) {
JSONObject data = request.getData();
String endpoint = getEndpoint(request);
if (endpoint.isEmpty()) {
return R.error("缺岑参数: endpoint");
}
if (data == null) {
return R.error("缺岑参数: data");
}
String channelId = data.getStr("channelId");
String startTime = data.getStr("startTime");
String endTime = data.getStr("endTime");
Integer downloadSpeed = data.getInt("downloadSpeed", 1);
if (channelId == null || startTime == null || endTime == null) {
return R.error("缺岑参数: channelId/startTime/endTime");
}
Map<String, Object> filters = new HashMap<>();
if (request.getWvpRequestData() != null) {
filters.putAll(request.getWvpRequestData());
}
filters.put("startTime", startTime);
filters.put("endTime", endTime);
filters.put("downloadSpeed", downloadSpeed);
String path =
String.format(
WvpApiConstants.API_GB_RECORD_DOWNLOAD_START, request.getDeviceId(), channelId);
JSONObject resp = VideoPlatformHttpUtil.httpGetJson(endpoint, path, filters);
if (VideoPlatformHttpUtil.hasError(resp)) {
return R.error("开始下载失败: " + VideoPlatformHttpUtil.getErrorMessage(resp));
}
return R.ok(resp.getJSONObject("data"));
}
@Override
public R<?> stopGBRecordDownload(WvpDownRequest request) {
JSONObject data = request.getData();
String endpoint = getEndpoint(request);
if (endpoint.isEmpty()) {
return R.error("缺岑参数: endpoint");
}
if (data == null) {
return R.error("缺岑参数: data");
}
String channelId = data.getStr("channelId");
String stream = data.getStr("stream");
if (channelId == null || stream == null) {
return R.error("缺岑参数: channelId/stream");
}
Map<String, Object> filters = new HashMap<>();
if (request.getWvpRequestData() != null) {
filters.putAll(request.getWvpRequestData());
}
String path =
String.format(
WvpApiConstants.API_GB_RECORD_DOWNLOAD_STOP, request.getDeviceId(), channelId, stream);
JSONObject resp = VideoPlatformHttpUtil.httpGetJson(endpoint, path, filters);
if (VideoPlatformHttpUtil.hasError(resp)) {
return R.error("停止下载失败: " + VideoPlatformHttpUtil.getErrorMessage(resp));
}
return R.ok();
}
@Override
public R<?> getGBRecordDownloadProgress(WvpDownRequest request) {
JSONObject data = request.getData();
String endpoint = getEndpoint(request);
if (endpoint.isEmpty()) {
return R.error("缺岑参数: endpoint");
}
if (data == null) {
return R.error("缺岑参数: data");
}
String channelId = data.getStr("channelId");
String stream = data.getStr("stream");
if (channelId == null || stream == null) {
return R.error("缺岑参数: channelId/stream");
}
Map<String, Object> filters = new HashMap<>();
if (request.getWvpRequestData() != null) {
filters.putAll(request.getWvpRequestData());
}
String path =
String.format(
WvpApiConstants.API_GB_RECORD_DOWNLOAD_PROGRESS,
request.getDeviceId(),
channelId,
stream);
JSONObject resp = VideoPlatformHttpUtil.httpGetJson(endpoint, path, filters);
if (VideoPlatformHttpUtil.hasError(resp)) {
return R.error("获取下载进度失败: " + VideoPlatformHttpUtil.getErrorMessage(resp));
}
// 直接返回data部分,避免双层包装
Object respData = resp.get("data");
return R.ok(respData != null ? respData : resp);
}
@Override
public R<?> queryCloudRecordDates(WvpDownRequest request) {
JSONObject data = request.getData();
String endpoint = getEndpoint(request);
if (endpoint.isEmpty()) {
return R.error("缺岑参数: endpoint");
}
if (data == null) {
return R.error("缺岑参数: data");
}
String app = data.getStr("app");
String stream = data.getStr("stream");
if (app == null || stream == null) {
return R.error("缺岑参数: app/stream");
}
Map<String, Object> filters = new HashMap<>();
if (request.getWvpRequestData() != null) {
filters.putAll(request.getWvpRequestData());
}
filters.put("app", app);
filters.put("stream", stream);
// 可选参数
if (data.containsKey("year")) filters.put("year", data.getInt("year"));
if (data.containsKey("month")) filters.put("month", data.getInt("month"));
if (data.containsKey("mediaServerId"))
filters.put("mediaServerId", data.getStr("mediaServerId"));
JSONObject resp =
VideoPlatformHttpUtil.httpGetJson(
endpoint, WvpApiConstants.API_CLOUD_RECORD_DATE_LIST, filters);
if (VideoPlatformHttpUtil.hasError(resp)) {
return R.error("查询云端录像日期失败: " + VideoPlatformHttpUtil.getErrorMessage(resp));
}
// 直接返回data部分,避免双层包装
Object respData = resp.get("data");
return R.ok(respData != null ? respData : resp);
}
@Override
public R<?> queryCloudRecords(WvpDownRequest request) {
JSONObject data = request.getData();
String endpoint = getEndpoint(request);
if (endpoint.isEmpty()) {
return R.error("缺岑参数: endpoint");
}
if (data == null) {
return R.error("缺岑参数: data");
}
Map<String, Object> filters = new HashMap<>();
if (request.getWvpRequestData() != null) {
filters.putAll(request.getWvpRequestData());
}
// 必填分页参数
filters.put("page", data.getInt("page", 1));
filters.put("count", data.getInt("count", 10));
// 可选参数
if (data.containsKey("app")) filters.put("app", data.getStr("app"));
if (data.containsKey("stream")) filters.put("stream", data.getStr("stream"));
if (data.containsKey("startTime")) filters.put("startTime", data.getStr("startTime"));
if (data.containsKey("endTime")) filters.put("endTime", data.getStr("endTime"));
if (data.containsKey("mediaServerId"))
filters.put("mediaServerId", data.getStr("mediaServerId"));
if (data.containsKey("callId")) filters.put("callId", data.getStr("callId"));
if (data.containsKey("query")) filters.put("query", data.getStr("query"));
JSONObject resp =
VideoPlatformHttpUtil.httpGetJson(endpoint, WvpApiConstants.API_CLOUD_RECORD_LIST, filters);
if (VideoPlatformHttpUtil.hasError(resp)) {
return R.error("查询云端录像列表失败: " + VideoPlatformHttpUtil.getErrorMessage(resp));
}
// 直接返回data部分,避免双层包装
Object respData = resp.get("data");
return R.ok(respData != null ? respData : resp);
}
@Override
public R<?> loadCloudRecord(WvpDownRequest request) {
JSONObject data = request.getData();
String endpoint = getEndpoint(request);
if (endpoint.isEmpty()) {
return R.error("缺岑参数: endpoint");
}
if (data == null) {
return R.error("缺岑参数: data");
}
String app = data.getStr("app");
String stream = data.getStr("stream");
Integer cloudRecordId = data.getInt("cloudRecordId");
if (app == null || stream == null || cloudRecordId == null) {
return R.error("缺岑参数: app/stream/cloudRecordId");
}
Map<String, Object> filters = new HashMap<>();
if (request.getWvpRequestData() != null) {
filters.putAll(request.getWvpRequestData());
}
filters.put("app", app);
filters.put("stream", stream);
filters.put("cloudRecordId", cloudRecordId);
JSONObject resp =
VideoPlatformHttpUtil.httpGetJson(endpoint, WvpApiConstants.API_CLOUD_RECORD_LOAD, filters);
if (VideoPlatformHttpUtil.hasError(resp)) {
return R.error("加载云端录像失败: " + VideoPlatformHttpUtil.getErrorMessage(resp));
}
return R.ok(resp.getJSONObject("data"));
}
@Override
public R<?> seekCloudRecord(WvpDownRequest request) {
JSONObject data = request.getData();
String endpoint = getEndpoint(request);
if (endpoint.isEmpty()) {
return R.error("缺岑参数: endpoint");
}
if (data == null) {
return R.error("缺岑参数: data");
}
String mediaServerId = data.getStr("mediaServerId");
String app = data.getStr("app");
String stream = data.getStr("stream");
Double seek = data.getDouble("seek");
if (mediaServerId == null || app == null || stream == null || seek == null) {
return R.error("缺岑参数: mediaServerId/app/stream/seek");
}
Map<String, Object> filters = new HashMap<>();
if (request.getWvpRequestData() != null) {
filters.putAll(request.getWvpRequestData());
}
filters.put("mediaServerId", mediaServerId);
filters.put("app", app);
filters.put("stream", stream);
filters.put("seek", seek);
if (data.containsKey("schema")) filters.put("schema", data.getStr("schema"));
JSONObject resp =
VideoPlatformHttpUtil.httpGetJson(endpoint, WvpApiConstants.API_CLOUD_RECORD_SEEK, filters);
if (VideoPlatformHttpUtil.hasError(resp)) {
return R.error("云端录像定位失败: " + VideoPlatformHttpUtil.getErrorMessage(resp));
}
return R.ok();
}
@Override
public R<?> setCloudRecordSpeed(WvpDownRequest request) {
JSONObject data = request.getData();
String endpoint = getEndpoint(request);
if (endpoint.isEmpty()) {
return R.error("缺岑参数: endpoint");
}
if (data == null) {
return R.error("缺岑参数: data");
}
String mediaServerId = data.getStr("mediaServerId");
String app = data.getStr("app");
String stream = data.getStr("stream");
Integer speed = data.getInt("speed");
if (mediaServerId == null || app == null || stream == null || speed == null) {
return R.error("缺岑参数: mediaServerId/app/stream/speed");
}
Map<String, Object> filters = new HashMap<>();
if (request.getWvpRequestData() != null) {
filters.putAll(request.getWvpRequestData());
}
filters.put("mediaServerId", mediaServerId);
filters.put("app", app);
filters.put("stream", stream);
filters.put("speed", speed);
if (data.containsKey("schema")) filters.put("schema", data.getStr("schema"));
JSONObject resp =
VideoPlatformHttpUtil.httpGetJson(
endpoint, WvpApiConstants.API_CLOUD_RECORD_SPEED, filters);
if (VideoPlatformHttpUtil.hasError(resp)) {
return R.error("设置云端录像倍速失败: " + VideoPlatformHttpUtil.getErrorMessage(resp));
}
return R.ok();
}
@Override
public R<?> getCloudRecordPlayPath(WvpDownRequest request) {
JSONObject data = request.getData();
String endpoint = getEndpoint(request);
if (endpoint.isEmpty()) {
return R.error("缺岑参数: endpoint");
}
if (data == null || !data.containsKey("recordId")) {
return R.error("缺岑参数: recordId");
}
Map<String, Object> filters = new HashMap<>();
if (request.getWvpRequestData() != null) {
filters.putAll(request.getWvpRequestData());
}
filters.put("recordId", data.getInt("recordId"));
JSONObject resp =
VideoPlatformHttpUtil.httpGetJson(
endpoint, WvpApiConstants.API_CLOUD_RECORD_PLAY_PATH, filters);
if (VideoPlatformHttpUtil.hasError(resp)) {
return R.error("获取云端录像下载地址失败: " + VideoPlatformHttpUtil.getErrorMessage(resp));
}
// 解析WVP返回的data字段,避免双重包裹
JSONObject result = resp.getJSONObject("data");
if (result == null) {
result = new JSONObject();
}
return R.ok(result);
}
}
@@ -1,270 +0,0 @@
/*
* Copyright (c) 2025, NexIoT. All Rights Reserved.
*/
package cn.wvp.protocol.util;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
/**
* 视频平台HTTP请求工具类
*/
@Slf4j
public class VideoPlatformHttpUtil {
private static final int DEFAULT_TIMEOUT = 10000; // 10秒超时
/**
* 执行GET请求并返回JSON结果(自动从filters提取token)
*
* @param baseUrl 基础URL
* @param path 接口路径
* @param filters 过滤参数(包含token)
* @return JSON响应对象
*/
public static JSONObject httpGetJson(
String baseUrl, String path, Map<String, Object> filters) {
return httpGetJson(baseUrl, path, filters, DEFAULT_TIMEOUT);
}
/**
* 执行GET请求并返回JSON结果(支持自定义超时)
*
* @param baseUrl 基础URL
* @param path 接口路径
* @param filters 过滤参数(包含token和其他查询参数)
* @param timeout 超时时间(毫秒)
* @return JSON响应对象
*/
public static JSONObject httpGetJson(
String baseUrl,
String path,
Map<String, Object> filters,
int timeout) {
String url = buildUrl(baseUrl, path);
HttpRequest request = HttpRequest.get(url).timeout(timeout);
// 提取并设置token
String token = extractToken(filters);
if (token != null && !token.isEmpty()) {
request.header("Access-Token", token);
}
// 添加查询参数(排除token和endpoint/auth等元数据)
Map<String, Object> params = buildQueryParams(filters);
if (!params.isEmpty()) {
request.form(params);
}
try {
log.info("HTTP GET请求: URL={}, params={}", url, filters);
HttpResponse response = request.execute();
String body = response.body();
log.info("x`HTTP GET响应: status={}, body={}", response.getStatus(), body);
if (body == null || body.isEmpty()) {
return buildErrorResponse("响应体为空");
}
return JSONUtil.parseObj(body);
} catch (Exception e) {
log.error("HTTP GET请求失败: URL={}, error={}", url, e.getMessage(), e);
return buildErrorResponse(e.getMessage());
}
}
/**
* 执行POST请求并返回JSON结果(自动从filters提取token)
*
* @param baseUrl 基础URL
* @param path 接口路径
* @param body 请求体(JSON)
* @param filters 过滤参数(包含token)
* @return JSON响应对象
*/
public static JSONObject httpPostJson(
String baseUrl, String path, JSONObject body, Map<String, Object> filters) {
return httpPostJson(baseUrl, path, body, filters, DEFAULT_TIMEOUT);
}
/**
* 执行POST请求并返回JSON结果(支持自定义超时)
*
* @param baseUrl 基础URL
* @param path 接口路径
* @param body 请求体(JSON)
* @param filters 过滤参数(包含token)
* @param timeout 超时时间(毫秒)
* @return JSON响应对象
*/
public static JSONObject httpPostJson(
String baseUrl, String path, JSONObject body, Map<String, Object> filters, int timeout) {
String url = buildUrl(baseUrl, path);
HttpRequest request = HttpRequest.post(url).timeout(timeout);
// 提取并设置token
String token = extractToken(filters);
if (token != null && !token.isEmpty()) {
request.header("Access-Token", token);
}
// 设置请求体
if (body != null) {
request.body(body.toString()).contentType("application/json");
}
try {
log.debug("HTTP POST请求: URL={}, body={}", url, body);
HttpResponse response = request.execute();
String responseBody = response.body();
log.debug("HTTP POST响应: status={}, body={}", response.getStatus(), responseBody);
if (responseBody == null || responseBody.isEmpty()) {
return buildErrorResponse("响应体为空");
}
return JSONUtil.parseObj(responseBody);
} catch (Exception e) {
log.error("HTTP POST请求失败: URL={}, error={}", url, e.getMessage(), e);
return buildErrorResponse(e.getMessage());
}
}
/**
* 构建完整URL
*/
private static String buildUrl(String baseUrl, String path) {
String url = baseUrl;
if (!url.endsWith("/")) {
url += "/";
}
if (path.startsWith("/")) {
path = path.substring(1);
}
return url + path;
}
/**
* 构建错误响应
*/
private static JSONObject buildErrorResponse(String errorMsg) {
JSONObject error = new JSONObject();
error.set("error", errorMsg);
error.set("success", false);
return error;
}
/**
* 检查响应是否包含错误
*/
public static boolean hasError(JSONObject response) {
if (response == null) {
return true;
}
// 检查error字段
if (response.containsKey("error")) {
return true;
}
// 检查code字段(非0表示错误)
if (response.containsKey("code")) {
Integer code = response.getInt("code");
return code != null && code != 0;
}
// 检查success字段
if (response.containsKey("success")) {
Boolean success = response.getBool("success");
return success == null || !success;
}
return false;
}
/**
* 获取错误消息
*/
public static String getErrorMessage(JSONObject response) {
if (response == null) {
return "响应为空";
}
if (response.containsKey("error")) {
return response.getStr("error");
}
if (response.containsKey("msg")) {
return response.getStr("msg");
}
if (response.containsKey("message")) {
return response.getStr("message");
}
if (response.containsKey("errMsg")) {
return response.getStr("errMsg");
}
return "未知错误";
}
/**
* 从filters中提取token
* 优先从token字段获取,其次从auth字段解析
*/
private static String extractToken(Map<String, Object> filters) {
if (filters == null) {
return null;
}
// 优先使用token字段
Object tokenObj = filters.get("token");
if (tokenObj != null) {
return String.valueOf(tokenObj);
}
// 兼容旧的auth字段(JSON格式)
Object authObj = filters.get("auth");
if (authObj != null) {
try {
String authStr = String.valueOf(authObj);
if (authStr.startsWith("{")) {
JSONObject authJson = JSONUtil.parseObj(authStr);
if (authJson.containsKey("apiKey")) {
return authJson.getStr("apiKey");
}
if (authJson.containsKey("token")) {
return authJson.getStr("token");
}
} else {
// 直接是token字符串
return authStr;
}
} catch (Exception e) {
log.warn("解析auth字段失败: {}", e.getMessage());
}
}
return null;
}
/**
* 构建查询参数(排除元数据字段)
*/
private static Map<String, Object> buildQueryParams(Map<String, Object> filters) {
Map<String, Object> params = new java.util.HashMap<>();
if (filters == null) {
return params;
}
// 排除的元数据字段
java.util.Set<String> excludeKeys = new java.util.HashSet<>();
excludeKeys.add("endpoint");
excludeKeys.add("auth");
excludeKeys.add("token");
excludeKeys.add("baseUrl");
excludeKeys.add("apiKey");
for (Map.Entry<String, Object> entry : filters.entrySet()) {
if (!excludeKeys.contains(entry.getKey()) && entry.getValue() != null) {
params.put(entry.getKey(), entry.getValue());
}
}
return params;
}
}
@@ -1,318 +0,0 @@
/*
*
* Copyright (c) 2025, NexIoT. All Rights Reserved.
*
* @Description: WVP设备服务测试类
* @Author: gitee.com/NexIoT
*
*/
package cn.wvp.protocol.service;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.universal.common.domain.R;
import cn.wvp.protocol.entity.WvpDownRequest;
import cn.wvp.protocol.service.impl.WvpDeviceServiceImpl;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/**
* WVP设备服务单元测试
*
* <p>使用说明: 1. 修改 WVP_BASE_URL 为你的 WVP 平台地址 2. 修改 DEVICE_ID 和 CHANNEL_ID 为真实的设备与通道ID 3. 运行测试方法验证各项功能
*/
public class WvpDeviceServiceTest {
private WvpDeviceServiceImpl wvpDeviceService;
// ==================== 配置区域,请根据实际环境修改 ====================
/** WVP平台地址 */
private static final String WVP_BASE_URL = "http://192.168.31.100:18080";
/** 测试设备ID(国标编号) */
private static final String DEVICE_ID = "34020000001320000001";
/** 测试通道ID(国标编号) */
private static final String CHANNEL_ID = "34020000001310000001";
/** WVP实例标识 */
private static final String INSTANCE_KEY = "wvp20250115ABC";
/** WVP API Key(如启用鉴权则必填) */
private static final String API_KEY =
"eyJhbGciOiJSUzI1NiIsImtpZCI6IjNlNzk2NDZjNGRiYzQwODM4M2E5ZWVkMDlmMmI4NWFlIn0.eyJqdGkiOiJMLUI4cXBqUDdWdlU4VzFjbVU4bWpBIiwiaWF0IjoxNzYyNDI2MzYxLCJleHAiOjEwNTc0NzM0NjkxNDgwOSwibmJmIjoxNzYyNDI2MzYxLCJzdWIiOiJsb2dpbiIsImF1ZCI6IkF1ZGllbmNlIiwidXNlck5hbWUiOiJhZG1pbiIsImFwaUtleUlkIjoxfQ.YK1dbexi66p8dbztPsTWpG2r5pb8gGebH59A-tYZKPqrLstLC6KhiYtPBoB_KItc-OBdlEQDpL5mZAtYgYY-ogS4JwTpOzgvOUh8mbEa68tdiughRrSohO5O43kUF67iwmEk9XalA0OpuOLTZPFcElApAiwUULtWz21AYOU17kFtkQ4AXvugDpOgRDmhHVpAvxW7bMbq70BroieO2iYNVtfCNS-qEWmA7zG0nDiQY3kk4mX7ftVg5IrCnS8xD5vg0GBq3mib06UaagWUFJ10I8vIe5smAQOe2s96aKuT88y_Ok43xVdsAkCS9pO9qjFKRz-aXHnb6zZmMBwaL3KcSQ"; // 请从WVP后台获取
// ====================================================================
@BeforeEach
public void setUp() {
wvpDeviceService = new WvpDeviceServiceImpl();
System.out.println("=".repeat(80));
System.out.println("WVP平台地址: " + WVP_BASE_URL);
System.out.println("测试设备ID: " + DEVICE_ID);
System.out.println("测试通道ID: " + CHANNEL_ID);
System.out.println("=".repeat(80));
}
/** 创建WVP下行请求对象 */
private WvpDownRequest createWvpRequest(String deviceId, JSONObject data) {
WvpDownRequest request = new WvpDownRequest();
request.setDeviceId(deviceId);
request.setData(data);
Map<String, Object> wvpRequestData = new HashMap<>();
wvpRequestData.put("endpoint", WVP_BASE_URL);
// 添加API Key鉴权
if (API_KEY != null && !API_KEY.equals("your_api_key_here")) {
wvpRequestData.put("token", API_KEY);
}
request.setWvpRequestData(wvpRequestData);
return request;
}
/** 打印测试结果 */
private void printResult(String testName, R<?> result) {
System.out.println("\n【" + testName + "】");
System.out.println("成功: " + result.isSuccess());
System.out.println("消息: " + result.getMsg());
if (result.getData() != null) {
System.out.println("数据: " + JSONUtil.toJsonPrettyStr(result.getData()));
}
System.out.println("-".repeat(80));
}
@Test
public void testListDevices() {
System.out.println("\n>>> 测试设备列表查询");
Map<String, Object> filters = new HashMap<>();
filters.put("endpoint", WVP_BASE_URL);
filters.put("page", 1);
filters.put("count", 10);
JSONObject set = new JSONObject();
set.put("apiKey", API_KEY);
Map<String, Object> auth = new HashMap<>();
filters.put("auth", set);
R<?> result = wvpDeviceService.listDevices(INSTANCE_KEY, filters);
printResult("设备列表查询", result);
}
@Test
public void testListChannels() {
System.out.println("\n>>> 测试通道列表查询");
Map<String, Object> filters = new HashMap<>();
filters.put("endpoint", WVP_BASE_URL);
filters.put("page", 1); // 当前页
filters.put("count", 20); // 每页查询数量
// filters.put("query", null);
// filters.put("online", true);
filters.put("channelType", 0); // 0:国标设备,1:推流设备,2:拉流代理"
// filters.put("groupDeviceId", null);// 0:国标设备,1:推流设备,2:拉流代理"
JSONObject set = new JSONObject();
set.put("apiKey", API_KEY);
Map<String, Object> auth = new HashMap<>();
filters.put("auth", set);
R<?> result = wvpDeviceService.listChannels(DEVICE_ID, filters);
printResult("通道列表查询", result);
}
@Test
public void testStartPreview() {
System.out.println("\n>>> 测试直播开始");
JSONObject data = new JSONObject();
data.put("endpoint", WVP_BASE_URL);
data.set("channelId", CHANNEL_ID);
data.set("protocol", "flv"); // 优先协议
data.set("token", API_KEY);
WvpDownRequest request = createWvpRequest(DEVICE_ID, data);
R<?> result = wvpDeviceService.startPreview(request);
printResult("直播开始", result);
if (result.isSuccess()) {
System.out.println("\n播放地址已获取,可用于 jessibuca 播放器:");
JSONObject resultData = JSONUtil.parseObj(result.getData());
JSONObject urls = resultData.getJSONObject("urls");
if (urls != null) {
System.out.println("FLV: " + urls.getStr("flv"));
System.out.println("HLS: " + urls.getStr("hls"));
System.out.println("优先协议: " + resultData.getStr("prefer"));
}
}
}
@Test
public void testStopPreview() {
System.out.println("\n>>> 测试直播停止");
JSONObject data = new JSONObject();
data.set("channelId", CHANNEL_ID);
WvpDownRequest request = createWvpRequest(DEVICE_ID, data);
R<?> result = wvpDeviceService.stopPreview(request);
printResult("直播停止", result);
}
@Test
public void testPtzControl() {
System.out.println("\n>>> 测试云台控制");
// 测试向左转
JSONObject dataLeft = new JSONObject();
dataLeft.set("channelId", CHANNEL_ID);
dataLeft.set("command", "left");
dataLeft.set("speed", 50);
WvpDownRequest requestLeft = createWvpRequest(DEVICE_ID, dataLeft);
R<?> resultLeft = wvpDeviceService.ptzControl(requestLeft);
printResult("云台控制-左转", resultLeft);
// 延时
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
// 测试停止
JSONObject dataStop = new JSONObject();
dataStop.set("channelId", CHANNEL_ID);
dataStop.set("command", "stop");
dataStop.set("speed", 0);
WvpDownRequest requestStop = createWvpRequest(DEVICE_ID, dataStop);
R<?> resultStop = wvpDeviceService.ptzControl(requestStop);
printResult("云台控制-停止", resultStop);
}
@Test
public void testQueryPresets() {
System.out.println("\n>>> 测试预置位查询");
JSONObject data = new JSONObject();
data.set("channelId", CHANNEL_ID);
data.set("timeout", 15);
WvpDownRequest request = createWvpRequest(DEVICE_ID, data);
R<?> result = wvpDeviceService.queryPresets(request);
printResult("预置位查询", result);
}
@Test
public void testSnapshot() {
System.out.println("\n>>> 测试抓拍");
JSONObject data = new JSONObject();
data.set("channelId", CHANNEL_ID);
data.set("mark", "abc" + RandomUtil.randomString(3));
WvpDownRequest request = createWvpRequest(DEVICE_ID, data);
R<?> result = wvpDeviceService.snapshot(request);
printResult("抓拍", result);
if (result.isSuccess()) {
JSONObject resultData = JSONUtil.parseObj(result.getData());
System.out.println("\n抓拍图片URL: " + resultData.getStr("snapshotUrl"));
}
}
@Test
public void testStartPlayback() {
System.out.println("\n>>> 测试历史回放");
// 查询最近1小时的录像
long endTime = System.currentTimeMillis() / 1000;
long startTime = endTime - 3600; // 1小时前
JSONObject data = new JSONObject();
data.set("channelId", CHANNEL_ID);
data.set("startTime", startTime);
data.set("endTime", endTime);
WvpDownRequest request = createWvpRequest(DEVICE_ID, data);
R<?> result = wvpDeviceService.startPlayback(request);
printResult("历史回放", result);
if (result.isSuccess()) {
System.out.println("\n回放地址已获取:");
JSONObject resultData = JSONUtil.parseObj(result.getData());
JSONObject urls = resultData.getJSONObject("urls");
if (urls != null) {
System.out.println("FLV: " + urls.getStr("flv"));
System.out.println("HLS: " + urls.getStr("hls"));
}
}
}
/** 完整流程测试:设备列表 -> 通道列表 -> 直播 -> 云台 -> 停止 */
@Test
public void testCompleteFlow() {
System.out.println("\n========== 完整流程测试 ==========");
// 1. 查询设备列表
System.out.println("\n步骤1: 查询设备列表");
Map<String, Object> filters = new HashMap<>();
filters.put("endpoint", WVP_BASE_URL);
filters.put("limit", 5);
R<?> devicesResult = wvpDeviceService.listDevices(INSTANCE_KEY, filters);
printResult("设备列表", devicesResult);
// 2. 查询通道列表
System.out.println("\n步骤2: 查询通道列表");
Map<String, Object> channelFilters = new HashMap<>();
channelFilters.put("endpoint", WVP_BASE_URL);
channelFilters.put("limit", 5);
R<?> channelsResult = wvpDeviceService.listChannels(DEVICE_ID, channelFilters);
printResult("通道列表", channelsResult);
// 3. 开始直播
System.out.println("\n步骤3: 开始直播");
JSONObject previewData = new JSONObject();
previewData.set("channelId", CHANNEL_ID);
previewData.set("protocol", "flv");
WvpDownRequest previewRequest = createWvpRequest(DEVICE_ID, previewData);
R<?> previewResult = wvpDeviceService.startPreview(previewRequest);
printResult("开始直播", previewResult);
if (previewResult.isSuccess()) {
// 4. 云台控制
System.out.println("\n步骤4: 云台控制(向右转2秒)");
JSONObject ptzData = new JSONObject();
ptzData.set("channelId", CHANNEL_ID);
ptzData.set("command", "right");
ptzData.set("speed", 50);
WvpDownRequest ptzRequest = createWvpRequest(DEVICE_ID, ptzData);
R<?> ptzResult = wvpDeviceService.ptzControl(ptzRequest);
printResult("云台控制", ptzResult);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
// 5. 停止云台
System.out.println("\n步骤5: 停止云台");
JSONObject stopPtzData = new JSONObject();
stopPtzData.set("channelId", CHANNEL_ID);
stopPtzData.set("command", "stop");
stopPtzData.set("speed", 0);
WvpDownRequest stopPtzRequest = createWvpRequest(DEVICE_ID, stopPtzData);
wvpDeviceService.ptzControl(stopPtzRequest);
// 6. 停止直播
System.out.println("\n步骤6: 停止直播");
JSONObject stopData = new JSONObject();
stopData.set("channelId", CHANNEL_ID);
WvpDownRequest stopRequest = createWvpRequest(DEVICE_ID, stopData);
R<?> stopResult = wvpDeviceService.stopPreview(stopRequest);
printResult("停止直播", stopResult);
}
System.out.println("\n========== 流程测试完成 ==========");
}
}
@@ -1,605 +0,0 @@
/*
*
* Copyright (c) 2025, NexIoT. All Rights Reserved.
*
* @Description: WVP设备服务测试类
* @Author: gitee.com/NexIoT
*
*/
package cn.wvp.protocol.service;
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.universal.common.domain.R;
import cn.wvp.protocol.entity.WvpDownRequest;
import cn.wvp.protocol.service.impl.WvpDeviceServiceImpl;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/**
* WVP设备服务单元测试
*
* <p>使用说明: 1. 修改 WVP_BASE_URL 为你的 WVP 平台地址 2. 修改 DEVICE_ID 和 CHANNEL_ID 为真实的设备与通道ID 3. 运行测试方法验证各项功能
*/
public class WvpDeviceServiceTest2 {
private WvpDeviceServiceImpl wvpDeviceService;
// ==================== 配置区域,请根据实际环境修改 ====================
/** WVP平台地址 */
private static final String WVP_BASE_URL = "http://192.168.31.100:18080";
/** 测试设备ID(国标编号) */
private static final String DEVICE_ID = "34020000001320000001";
/** 测试通道ID(国标编号) */
private static final String CHANNEL_ID = "34020000001310000001";
/** WVP实例标识 */
private static final String INSTANCE_KEY = "wvp20250115ABC";
/** WVP API Key(如启用鉴权则必填) */
private static final String API_KEY =
"eyJhbGciOiJSUzI1NiIsImtpZCI6IjNlNzk2NDZjNGRiYzQwODM4M2E5ZWVkMDlmMmI4NWFlIn0.eyJqdGkiOiJMLUI4cXBqUDdWdlU4VzFjbVU4bWpBIiwiaWF0IjoxNzYyNDI2MzYxLCJleHAiOjEwNTc0NzM0NjkxNDgwOSwibmJmIjoxNzYyNDI2MzYxLCJzdWIiOiJsb2dpbiIsImF1ZCI6IkF1ZGllbmNlIiwidXNlck5hbWUiOiJhZG1pbiIsImFwaUtleUlkIjoxfQ.YK1dbexi66p8dbztPsTWpG2r5pb8gGebH59A-tYZKPqrLstLC6KhiYtPBoB_KItc-OBdlEQDpL5mZAtYgYY-ogS4JwTpOzgvOUh8mbEa68tdiughRrSohO5O43kUF67iwmEk9XalA0OpuOLTZPFcElApAiwUULtWz21AYOU17kFtkQ4AXvugDpOgRDmhHVpAvxW7bMbq70BroieO2iYNVtfCNS-qEWmA7zG0nDiQY3kk4mX7ftVg5IrCnS8xD5vg0GBq3mib06UaagWUFJ10I8vIe5smAQOe2s96aKuT88y_Ok43xVdsAkCS9pO9qjFKRz-aXHnb6zZmMBwaL3KcSQ"; // 请从WVP后台获取
// ====================================================================
@BeforeEach
public void setUp() {
wvpDeviceService = new WvpDeviceServiceImpl();
System.out.println("=".repeat(80));
System.out.println("WVP平台地址: " + WVP_BASE_URL);
System.out.println("测试设备ID: " + DEVICE_ID);
System.out.println("测试通道ID: " + CHANNEL_ID);
System.out.println("=".repeat(80));
}
/** 创建WVP下行请求对象 */
private WvpDownRequest createWvpRequest(String deviceId, JSONObject data) {
WvpDownRequest request = new WvpDownRequest();
request.setDeviceId(deviceId);
request.setData(data);
Map<String, Object> wvpRequestData = new HashMap<>();
wvpRequestData.put("endpoint", WVP_BASE_URL);
// 添加token鉴权
if (API_KEY != null && !API_KEY.isEmpty()) {
wvpRequestData.put("token", API_KEY);
}
request.setWvpRequestData(wvpRequestData);
return request;
}
/** 打印测试结果 */
private void printResult(String testName, R<?> result) {
System.out.println("\n【" + testName + "】");
System.out.println("成功: " + result.isSuccess());
System.out.println("消息: " + result.getMsg());
if (result.getData() != null) {
System.out.println("数据: " + JSONUtil.toJsonPrettyStr(result.getData()));
}
System.out.println("-".repeat(80));
}
/** 打印测试结果 */
private void printResult(String testName, String body) {
System.out.println("\n【" + testName + "】");
System.out.println("成功: " + body);
}
@Test
public void testListDevices() {
System.out.println("\n>>> 测试设备列表查询");
Map<String, Object> filters = new HashMap<>();
filters.put("endpoint", WVP_BASE_URL);
filters.put("token", API_KEY);
filters.put("page", 1);
filters.put("count", 10);
R<?> result = wvpDeviceService.listDevices(INSTANCE_KEY, filters);
printResult("设备列表查询", result);
}
@Test
public void testListOrgs() {
System.out.println("\n>>> 测试组织列表查询");
Map<String, Object> filters = new HashMap<>();
filters.put("endpoint", WVP_BASE_URL);
filters.put("token", API_KEY);
R<?> result = wvpDeviceService.listOrgs(INSTANCE_KEY, filters);
printResult("组织列表查询", result);
}
@Test
public void testListChannels() {
System.out.println("\n>>> 测试通道列表查询");
Map<String, Object> filters = new HashMap<>();
filters.put("endpoint", WVP_BASE_URL);
filters.put("token", API_KEY);
filters.put("page", 1);
filters.put("count", 20);
R<?> result = wvpDeviceService.listChannels(DEVICE_ID, filters);
printResult("通道列表查询", result);
}
@Test
public void testStartPreview() {
System.out.println("\n>>> 测试直播开始");
JSONObject data = new JSONObject();
data.set("channelId", CHANNEL_ID);
data.set("protocol", "flv"); // 优先协议
WvpDownRequest request = createWvpRequest(DEVICE_ID, data);
R<?> result = wvpDeviceService.startPreview(request);
printResult("直播开始", result);
if (result.isSuccess()) {
System.out.println("\n播放地址已获取,可用于 jessibuca 播放器:");
JSONObject resultData = JSONUtil.parseObj(result.getData());
JSONObject urls = resultData.getJSONObject("urls");
if (urls != null) {
System.out.println("FLV: " + urls.getStr("flv"));
System.out.println("HLS: " + urls.getStr("hls"));
System.out.println("优先协议: " + resultData.getStr("prefer"));
}
}
}
@Test
public void testStopPreview() {
System.out.println("\n>>> 测试直播停止");
JSONObject data = new JSONObject();
data.set("channelId", CHANNEL_ID);
WvpDownRequest request = createWvpRequest(DEVICE_ID, data);
R<?> result = wvpDeviceService.stopPreview(request);
printResult("直播停止", result);
}
@Test
public void testPtzControl() {
System.out.println("\n>>> 测试云台控制");
// 测试向左转
JSONObject dataLeft = new JSONObject();
dataLeft.set("channelId", CHANNEL_ID);
dataLeft.set("command", "left");
dataLeft.set("speed", 50);
WvpDownRequest requestLeft = createWvpRequest(DEVICE_ID, dataLeft);
R<?> resultLeft = wvpDeviceService.ptzControl(requestLeft);
printResult("云台控制-左转", resultLeft);
// 延时
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
// 测试停止
JSONObject dataStop = new JSONObject();
dataStop.set("channelId", CHANNEL_ID);
dataStop.set("command", "stop");
dataStop.set("speed", 0);
WvpDownRequest requestStop = createWvpRequest(DEVICE_ID, dataStop);
R<?> resultStop = wvpDeviceService.ptzControl(requestStop);
printResult("云台控制-停止", resultStop);
}
@Test
public void testQueryPresets() {
System.out.println("\n>>> 测试预置位查询");
JSONObject data = new JSONObject();
data.set("channelId", CHANNEL_ID);
data.set("timeout", 15);
WvpDownRequest request = createWvpRequest(DEVICE_ID, data);
R<?> result = wvpDeviceService.queryPresets(request);
printResult("预置位查询", result);
}
@Test
public void testSnapshot() {
System.out.println("\n>>> 测试抓拍");
JSONObject data = new JSONObject();
data.set("channelId", CHANNEL_ID);
WvpDownRequest request = createWvpRequest(DEVICE_ID, data);
R<?> result = wvpDeviceService.snapshot(request);
printResult("抓拍", result);
if (result.isSuccess()) {
JSONObject resultData = JSONUtil.parseObj(result.getData());
System.out.println("\n抓拍图片URL: " + resultData.getStr("snapshotUrl"));
}
}
@Test
public void testStartPlayback() {
System.out.println("\n>>> 测试历史回放");
// 查询最近1小时的录像
long endTime = System.currentTimeMillis() / 1000;
long startTime = endTime - 13600; // 1小时前
JSONObject data = new JSONObject();
data.set("channelId", CHANNEL_ID);
data.set("startTime", startTime);
data.set("endTime", endTime);
WvpDownRequest request = createWvpRequest(DEVICE_ID, data);
R<?> result = wvpDeviceService.startPlayback(request);
printResult("历史回放", result);
if (result.isSuccess()) {
System.out.println("\n回放地址已获取:");
JSONObject resultData = JSONUtil.parseObj(result.getData());
JSONObject urls = resultData.getJSONObject("urls");
if (urls != null) {
System.out.println("FLV: " + urls.getStr("flv"));
System.out.println("HLS: " + urls.getStr("hls"));
}
}
}
@Test
public void testStopPlayback() {
System.out.println("\n>>> 测试停止回放");
JSONObject data = new JSONObject();
data.set("channelId", CHANNEL_ID);
WvpDownRequest request = createWvpRequest(DEVICE_ID, data);
R<?> result = wvpDeviceService.stopPlayback(request);
printResult("停止回放", result);
}
@Test
public void testQueryGBRecords() {
System.out.println("\n>>> 测试国标录像查询");
// 查询最近24小时的国标录像
long endTime = System.currentTimeMillis() / 1000;
long startTime = endTime - 86400; // 24小时前
JSONObject data = new JSONObject();
data.set("channelId", CHANNEL_ID);
data.set("startTime",DateUtil.formatDateTime(DateUtil.offset(new Date(), DateField.HOUR,-78)));
data.set("endTime", DateUtil.now());
WvpDownRequest request = createWvpRequest(DEVICE_ID, data);
R<?> result = wvpDeviceService.queryGBRecords(request);
printResult("国标录像查询", result);
}
@Test
public void testStartGBRecordDownload() {
System.out.println("\n>>> 测试开始下载国标录像");
// 查询最近1小时的录像
long endTime = System.currentTimeMillis() / 1000;
long startTime = endTime - 3600; // 1小时前
JSONObject data = new JSONObject();
data.set("channelId", CHANNEL_ID);
data.set("startTime", startTime);
data.set("endTime", endTime);
WvpDownRequest request = createWvpRequest(DEVICE_ID, data);
R<?> result = wvpDeviceService.startGBRecordDownload(request);
printResult("开始下载国标录像", result);
if (result.isSuccess()) {
JSONObject resultData = JSONUtil.parseObj(result.getData());
String downloadId = resultData.getStr("downloadId");
System.out.println("\n下载ID: " + downloadId);
System.out.println("可使用此ID查询下载进度或停止下载");
}
}
@Test
public void testStopGBRecordDownload() {
System.out.println("\n>>> 测试停止下载国标录像");
JSONObject data = new JSONObject();
data.set("channelId", CHANNEL_ID);
data.set("stream", "your_stream_id"); // 需要从开始下载接口获取
WvpDownRequest request = createWvpRequest(DEVICE_ID, data);
R<?> result = wvpDeviceService.stopGBRecordDownload(request);
printResult("停止下载国标录像", result);
}
@Test
public void testGetGBRecordDownloadProgress() {
System.out.println("\n>>> 测试查询国标录像下载进度");
JSONObject data = new JSONObject();
data.set("channelId", CHANNEL_ID);
data.set("stream", "your_stream_id"); // 需要从开始下载接口获取
WvpDownRequest request = createWvpRequest(DEVICE_ID, data);
R<?> result = wvpDeviceService.getGBRecordDownloadProgress(request);
printResult("查询国标录像下载进度", result);
if (result.isSuccess()) {
JSONObject resultData = JSONUtil.parseObj(result.getData());
System.out.println("\n下载进度: " + resultData.getInt("progress") + "%");
}
}
@Test
public void testQueryCloudRecordDates() {
System.out.println("\n>>> 测试查询云端录像日期列表");
JSONObject data = new JSONObject();
data.set("app", "rtp");
data.set("stream", DEVICE_ID + "_" + CHANNEL_ID);
WvpDownRequest request = createWvpRequest(DEVICE_ID, data);
R<?> result = wvpDeviceService.queryCloudRecordDates(request);
printResult("查询云端录像日期列表", result);
}
@Test
public void testQueryCloudRecords() {
System.out.println("\n>>> 测试查询云端录像列表");
JSONObject data = new JSONObject();
data.set("app", "rtp");
data.set("stream", DEVICE_ID + "_" + CHANNEL_ID);
data.set("page", 1);
data.set("count", 20);
data.set("startTime", "2025-11-01 00:00:00");
data.set("endTime", "2025-11-31 23:59:59");
WvpDownRequest request = createWvpRequest(DEVICE_ID, data);
R<?> result = wvpDeviceService.queryCloudRecords(request);
printResult("查询云端录像列表", result);
}
@Test
public void testLoadCloudRecord() {
System.out.println("\n>>> 测试加载云端录像文件");
JSONObject data = new JSONObject();
data.set("app", "rtp");
data.set("stream", DEVICE_ID + "_" + CHANNEL_ID);
data.set("cloudRecordId", 64);
WvpDownRequest request = createWvpRequest(DEVICE_ID, data);
R<?> result = wvpDeviceService.loadCloudRecord(request);
printResult("加载云端录像文件", result);
}
@Test
public void testSeekCloudRecord() {
System.out.println("\n>>> 测试定位云端录像进度");
JSONObject data = new JSONObject();
data.set("app", "rtp");
data.set("stream", DEVICE_ID + "_" + CHANNEL_ID);
data.set("seek", 60000); // 定位到60秒处
WvpDownRequest request = createWvpRequest(DEVICE_ID, data);
R<?> result = wvpDeviceService.seekCloudRecord(request);
printResult("定位云端录像进度", result);
}
@Test
public void testSetCloudRecordSpeed() {
System.out.println("\n>>> 测试设置云端录像倍速");
JSONObject data = new JSONObject();
data.set("app", "rtp");
data.set("stream", DEVICE_ID + "_" + CHANNEL_ID);
data.set("speed", 2.0); // 2倍速播放
WvpDownRequest request = createWvpRequest(DEVICE_ID, data);
R<?> result = wvpDeviceService.setCloudRecordSpeed(request);
printResult("设置云端录像倍速", result);
}
/** 完整流程测试:设备列表 -> 通道列表 -> 直播 -> 云台 -> 停止 */
@Test
public void testCompleteFlow() {
System.out.println("\n========== 完整流程测试 ==========");
// 1. 查询设备列表
System.out.println("\n步骤1: 查询设备列表");
Map<String, Object> filters = new HashMap<>();
filters.put("endpoint", WVP_BASE_URL);
filters.put("token", API_KEY);
filters.put("page", 1);
filters.put("count", 5);
R<?> devicesResult = wvpDeviceService.listDevices(INSTANCE_KEY, filters);
printResult("设备列表", devicesResult);
// 2. 查询通道列表
System.out.println("\n步骤2: 查询通道列表");
Map<String, Object> channelFilters = new HashMap<>();
channelFilters.put("endpoint", WVP_BASE_URL);
channelFilters.put("token", API_KEY);
channelFilters.put("page", 1);
channelFilters.put("count", 5);
R<?> channelsResult = wvpDeviceService.listChannels(DEVICE_ID, channelFilters);
printResult("通道列表", channelsResult);
// 3. 开始直播
System.out.println("\n步骤3: 开始直播");
JSONObject previewData = new JSONObject();
previewData.set("channelId", CHANNEL_ID);
previewData.set("protocol", "flv");
WvpDownRequest previewRequest = createWvpRequest(DEVICE_ID, previewData);
R<?> previewResult = wvpDeviceService.startPreview(previewRequest);
printResult("开始直播", previewResult);
if (previewResult.isSuccess()) {
// 4. 云台控制
System.out.println("\n步骤4: 云台控制(向右转2秒)");
JSONObject ptzData = new JSONObject();
ptzData.set("channelId", CHANNEL_ID);
ptzData.set("command", "right");
ptzData.set("speed", 50);
WvpDownRequest ptzRequest = createWvpRequest(DEVICE_ID, ptzData);
R<?> ptzResult = wvpDeviceService.ptzControl(ptzRequest);
printResult("云台控制", ptzResult);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
// 5. 停止云台
System.out.println("\n步骤5: 停止云台");
JSONObject stopPtzData = new JSONObject();
stopPtzData.set("channelId", CHANNEL_ID);
stopPtzData.set("command", "stop");
stopPtzData.set("speed", 0);
WvpDownRequest stopPtzRequest = createWvpRequest(DEVICE_ID, stopPtzData);
wvpDeviceService.ptzControl(stopPtzRequest);
// 6. 停止直播
System.out.println("\n步骤6: 停止直播");
JSONObject stopData = new JSONObject();
stopData.set("channelId", CHANNEL_ID);
WvpDownRequest stopRequest = createWvpRequest(DEVICE_ID, stopData);
R<?> stopResult = wvpDeviceService.stopPreview(stopRequest);
printResult("停止直播", stopResult);
}
System.out.println("\n========== 流程测试完成 ==========");
}
/** 完整录像流程测试:国标录像查询 -> 录像回放 -> 停止回放 */
@Test
public void testRecordCompleteFlow() {
System.out.println("\n========== 完整录像流程测试 ==========");
long endTime = System.currentTimeMillis() / 1000;
long startTime = endTime - 86400; // 24小时前
// 1. 查询国标录像
System.out.println("\n步骤1: 查询国标录像");
JSONObject queryData = new JSONObject();
queryData.set("channelId", CHANNEL_ID);
queryData.set("startTime", startTime);
queryData.set("endTime", endTime);
WvpDownRequest queryRequest = createWvpRequest(DEVICE_ID, queryData);
R<?> queryResult = wvpDeviceService.queryGBRecords(queryRequest);
printResult("国标录像查询", queryResult);
// 2. 开始录像回放
System.out.println("\n步骤2: 开始录像回放");
JSONObject playbackData = new JSONObject();
playbackData.set("channelId", CHANNEL_ID);
playbackData.set("startTime", startTime);
playbackData.set("endTime", endTime);
WvpDownRequest playbackRequest = createWvpRequest(DEVICE_ID, playbackData);
R<?> playbackResult = wvpDeviceService.startPlayback(playbackRequest);
printResult("开始录像回放", playbackResult);
if (playbackResult.isSuccess()) {
// 3. 等待5秒
try {
System.out.println("\n等待5秒...");
Thread.sleep(5000);
} catch (InterruptedException e) {
}
// 4. 停止回放
System.out.println("\n步骤3: 停止录像回放");
JSONObject stopData = new JSONObject();
stopData.set("channelId", CHANNEL_ID);
WvpDownRequest stopRequest = createWvpRequest(DEVICE_ID, stopData);
R<?> stopResult = wvpDeviceService.stopPlayback(stopRequest);
printResult("停止录像回放", stopResult);
}
System.out.println("\n========== 录像流程测试完成 ==========");
}
/** 云端录像完整流程测试 */
@Test
public void testCloudRecordCompleteFlow() {
System.out.println("\n========== 云端录像完整流程测试 ==========");
String app = "rtp";
String stream = DEVICE_ID + "_" + CHANNEL_ID;
// 1. 查询云端录像日期列表
System.out.println("\n步骤1: 查询云端录像日期列表");
JSONObject dateData = new JSONObject();
dateData.set("app", app);
dateData.set("stream", stream);
WvpDownRequest dateRequest = createWvpRequest(DEVICE_ID, dateData);
R<?> dateResult = wvpDeviceService.queryCloudRecordDates(dateRequest);
printResult("云端录像日期列表", dateResult);
// 2. 查询云端录像列表
System.out.println("\n步骤2: 查询云端录像列表");
JSONObject listData = new JSONObject();
listData.set("app", app);
listData.set("stream", stream);
listData.set("page", 1);
listData.set("count", 20);
WvpDownRequest listRequest = createWvpRequest(DEVICE_ID, listData);
R<?> listResult = wvpDeviceService.queryCloudRecords(listRequest);
printResult("云端录像列表", listResult);
// 3. 加载云端录像文件
System.out.println("\n步骤3: 加载云端录像文件");
long endTime = System.currentTimeMillis();
long startTime = endTime - 3600000; // 1小时前
JSONObject loadData = new JSONObject();
loadData.set("app", app);
loadData.set("stream", stream);
loadData.set("startTime", startTime);
loadData.set("endTime", endTime);
WvpDownRequest loadRequest = createWvpRequest(DEVICE_ID, loadData);
R<?> loadResult = wvpDeviceService.loadCloudRecord(loadRequest);
printResult("加载云端录像文件", loadResult);
if (loadResult.isSuccess()) {
// 4. 设置倍速播放
System.out.println("\n步骤4: 设置2倍速播放");
JSONObject speedData = new JSONObject();
speedData.set("app", app);
speedData.set("stream", stream);
speedData.set("speed", 2.0);
WvpDownRequest speedRequest = createWvpRequest(DEVICE_ID, speedData);
R<?> speedResult = wvpDeviceService.setCloudRecordSpeed(speedRequest);
printResult("设置倍速播放", speedResult);
// 5. 定位录像进度
System.out.println("\n步骤5: 定位到60秒处");
JSONObject seekData = new JSONObject();
seekData.set("app", app);
seekData.set("stream", stream);
seekData.set("seek", 60000); // 60秒
WvpDownRequest seekRequest = createWvpRequest(DEVICE_ID, seekData);
R<?> seekResult = wvpDeviceService.seekCloudRecord(seekRequest);
printResult("定位录像进度", seekResult);
}
System.out.println("\n========== 云端录像流程测试完成 ==========");
}
}
@@ -1,123 +0,0 @@
{
"tags": [],
"events": [
{
"id": "online",
"name": "设备上线",
"valueType": {"type": "string"}
},
{
"id": "offline",
"name": "设备下线",
"valueType": {"type": "string"}
}
],
"functions": [
{
"id": "cameraLiveStream",
"name": "实时预览",
"config": false,
"inputs": [
{"id": "deviceId", "name": "设备ID", "valueType": {"type": "string"}},
{"id": "channelId", "name": "通道ID", "valueType": {"type": "string"}},
{"id": "streamType", "name": "码流类型", "valueType": {"type": "enum", "elements": [{"text": "主码流", "value": "main"}, {"text": "子码流", "value": "sub"}]}}
],
"output": {}
},
{
"id": "cameraTurn",
"name": "云台控制",
"config": false,
"inputs": [
{"id": "deviceId", "name": "设备ID", "valueType": {"type": "string"}},
{"id": "channelId", "name": "通道ID", "valueType": {"type": "string"}},
{"id": "command", "name": "控制命令", "valueType": {"type": "string"}},
{"id": "speed", "name": "速度", "valueType": {"type": "int"}}
],
"output": {}
},
{
"id": "cameraPlayback",
"name": "录像回放",
"config": false,
"inputs": [
{"id": "deviceId", "name": "设备ID", "valueType": {"type": "string"}},
{"id": "channelId", "name": "通道ID", "valueType": {"type": "string"}},
{"id": "startTime", "name": "开始时间", "valueType": {"type": "long"}},
{"id": "endTime", "name": "结束时间", "valueType": {"type": "long"}}
],
"output": {}
},
{
"id": "cameraSnapshot",
"name": "抓拍",
"config": false,
"inputs": [
{"id": "deviceId", "name": "设备ID", "valueType": {"type": "string"}},
{"id": "channelId", "name": "通道ID", "valueType": {"type": "string"}}
],
"output": {}
},
{
"id": "queryRecords",
"name": "查询录像",
"config": false,
"inputs": [
{"id": "deviceId", "name": "设备ID", "valueType": {"type": "string"}},
{"id": "channelId", "name": "通道ID", "valueType": {"type": "string"}},
{"id": "startTime", "name": "开始时间", "valueType": {"type": "string"}},
{"id": "endTime", "name": "结束时间", "valueType": {"type": "string"}}
],
"output": {}
},
{
"id": "queryCloudRecords",
"name": "查询云端录像",
"config": false,
"inputs": [
{"id": "app", "name": "应用名", "valueType": {"type": "string"}},
{"id": "stream", "name": "流ID", "valueType": {"type": "string"}},
{"id": "page", "name": "页码", "valueType": {"type": "int"}},
{"id": "count", "name": "每页数量", "valueType": {"type": "int"}}
],
"output": {}
},
{
"id": "loadCloudRecord",
"name": "加载云端录像",
"config": false,
"inputs": [
{"id": "app", "name": "应用名", "valueType": {"type": "string"}},
{"id": "stream", "name": "流ID", "valueType": {"type": "string"}},
{"id": "cloudRecordId", "name": "录像ID", "valueType": {"type": "int"}}
],
"output": {}
}
],
"properties": [
{
"id": "deviceStatus",
"mode": "r",
"name": "设备状态",
"config": false,
"source": "device",
"valueType": {"type": "enum", "elements": [{"text": "在线", "value": "online"}, {"text": "离线", "value": "offline"}]}
},
{
"id": "manufacturer",
"mode": "r",
"name": "厂商",
"config": false,
"source": "device",
"valueType": {"type": "string"}
},
{
"id": "model",
"mode": "r",
"name": "型号",
"config": false,
"source": "device",
"valueType": {"type": "string"}
}
]
}
-2
View File
@@ -15,8 +15,6 @@
<!-- 设备插件 -->
<module>cn-universal-mqtt-protocol</module>
<module>cn-universal-http-protocol</module>
<!-- 视频平台协议子模块 -->
<module>cn-wvp-protocol</module>
</modules>
<build>
-5
View File
@@ -163,11 +163,6 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-wvp-protocol</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.universal.iot</groupId>
<artifactId>cn-universal-mqtt-protocol</artifactId>