From 228c1e07d117d1f7f11dec8381eb951d92bcf2e8 Mon Sep 17 00:00:00 2001
From: liujie <1793218484@qq.com>
Date: 星期三, 22 七月 2026 09:50:09 +0800
Subject: [PATCH] 监控模块
---
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/query/VideoMonitorControlRequest.java | 42 ++
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/EzvizOpenApiUtils.java | 338 ++++++++++++++++++++++
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/service/TDeviceService.java | 2
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/controller/TDeviceController.java | 78 ++++
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/query/VideoMonitorPageQuery.java | 31 ++
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/service/impl/TDeviceServiceImpl.java | 6
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/controller/VideoMonitorController.java | 263 +++++++++++++++++
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/query/DevicePageList.java | 12
ruoyi-api/ruoyi-api-other/src/main/java/com/ruoyi/other/api/domain/TDevice.java | 22 +
ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/vo/VideoMonitorDeviceVO.java | 54 +++
ruoyi-service/ruoyi-other/src/main/resources/mapper/other/TDeviceMapper.xml | 23 +
11 files changed, 848 insertions(+), 23 deletions(-)
diff --git a/ruoyi-api/ruoyi-api-other/src/main/java/com/ruoyi/other/api/domain/TDevice.java b/ruoyi-api/ruoyi-api-other/src/main/java/com/ruoyi/other/api/domain/TDevice.java
index d3e8aaf..5bc60b1 100644
--- a/ruoyi-api/ruoyi-api-other/src/main/java/com/ruoyi/other/api/domain/TDevice.java
+++ b/ruoyi-api/ruoyi-api-other/src/main/java/com/ruoyi/other/api/domain/TDevice.java
@@ -32,12 +32,16 @@
private String deviceName;
@ApiModelProperty(value = "设备序列号")
- @TableField("serial_no")
- private String serialNo;
+ @TableField("device_serial")
+ private String deviceSerial;
- @ApiModelProperty(value = "设备型号")
- @TableField("equipment_model")
- private String equipmentModel;
+ @ApiModelProperty(value = "设备品牌(1=大华,2=海康,3=萤石)")
+ @TableField("device_brand")
+ private Integer deviceBrand;
+
+ @ApiModelProperty(value = "设备验证码")
+ @TableField("validate_code")
+ private String validateCode;
@ApiModelProperty(value = "合作商类型")
@TableField("partner_type")
@@ -62,4 +66,12 @@
@ApiModelProperty(value = "在线状态(0=不在线,1=在线)")
@TableField("online_status")
private Integer onlineStatus;
+
+ @ApiModelProperty(value = "下发状态(0=未下发,1=已下发,2=下发失败)")
+ @TableField("issue_status")
+ private Integer issueStatus;
+
+ @ApiModelProperty(value = "下发结果")
+ @TableField("issue_msg")
+ private String issueMsg;
}
diff --git a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/controller/TDeviceController.java b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/controller/TDeviceController.java
index ef482e6..41402e4 100644
--- a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/controller/TDeviceController.java
+++ b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/controller/TDeviceController.java
@@ -1,5 +1,6 @@
package com.ruoyi.other.controller;
+import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.core.web.domain.AjaxResult;
import com.ruoyi.common.core.web.page.PageInfo;
import com.ruoyi.common.log.annotation.Log;
@@ -9,6 +10,7 @@
import com.ruoyi.other.api.domain.TDevice;
import com.ruoyi.other.query.DevicePageList;
import com.ruoyi.other.service.TDeviceService;
+import com.ruoyi.other.util.EzvizOpenApiUtils;
import io.swagger.annotations.ApiOperation;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
@@ -27,6 +29,9 @@
@Resource
private TDeviceService deviceService;
+
+ @Resource
+ private EzvizOpenApiUtils ezvizOpenApiUtils;
@RequiresPermissions(value = {"/deviceManagement/list"}, logical = Logical.OR)
@ResponseBody
@@ -49,6 +54,9 @@
}
if (device.getOnlineStatus() == null) {
device.setOnlineStatus(1);
+ }
+ if (device.getIssueStatus() == null) {
+ device.setIssueStatus(0);
}
deviceService.save(device);
return AjaxResult.success();
@@ -79,6 +87,40 @@
return AjaxResult.success();
}
+ @RequiresPermissions(value = {"/deviceManagement/issue"}, logical = Logical.OR)
+ @ResponseBody
+ @PostMapping("/issueDevice/{id}")
+ @ApiOperation(value = "下发设备到萤石平台", tags = {"管理后台-设备管理"})
+ @Log(title = "【设备管理】下发设备到萤石平台", businessType = BusinessType.UPDATE)
+ public AjaxResult issueDevice(@PathVariable Integer id) {
+ TDevice device = deviceService.getById(id);
+ if (device == null) {
+ return AjaxResult.error("设备不存在");
+ }
+ if (Integer.valueOf(1).equals(device.getIssueStatus())) {
+ return AjaxResult.success("设备已下发");
+ }
+ JSONObject ezvizResult;
+ try {
+ ezvizResult = ezvizOpenApiUtils.addDevice(device.getDeviceSerial(), device.getValidateCode());
+ } catch (Exception e) {
+ device.setIssueStatus(2);
+ device.setIssueMsg("萤石设备下发失败:" + e.getMessage());
+ deviceService.updateById(device);
+ return AjaxResult.error(device.getIssueMsg());
+ }
+ if (!isEzvizSuccess(ezvizResult)) {
+ device.setIssueStatus(2);
+ device.setIssueMsg(ezvizErrorMessage("萤石设备下发失败", ezvizResult));
+ deviceService.updateById(device);
+ return AjaxResult.error(device.getIssueMsg());
+ }
+ device.setIssueStatus(1);
+ device.setIssueMsg("下发成功");
+ deviceService.updateById(device);
+ return AjaxResult.success();
+ }
+
@RequiresPermissions(value = {"/deviceManagement/del"}, logical = Logical.OR)
@ResponseBody
@DeleteMapping("/delDevice")
@@ -87,6 +129,19 @@
public AjaxResult<TDevice> delDevice(Integer[] id) {
if (id == null || id.length == 0) {
return AjaxResult.error("请选择要删除的设备");
+ }
+ for (TDevice device : deviceService.listByIds(Arrays.asList(id))) {
+ if (Integer.valueOf(1).equals(device.getIssueStatus())) {
+ JSONObject ezvizResult;
+ try {
+ ezvizResult = ezvizOpenApiUtils.deleteDevice(device.getDeviceSerial());
+ } catch (Exception e) {
+ return AjaxResult.error("萤石设备删除失败:" + e.getMessage());
+ }
+ if (!isEzvizSuccess(ezvizResult)) {
+ return AjaxResult.error(ezvizErrorMessage("萤石设备删除失败", ezvizResult));
+ }
+ }
}
deviceService.removeByIds(Arrays.asList(id));
return AjaxResult.success();
@@ -99,18 +154,33 @@
if (!StringUtils.hasText(device.getDeviceName())) {
return AjaxResult.error("设备名称不能为空");
}
- if (!StringUtils.hasText(device.getSerialNo())) {
+ if (!StringUtils.hasText(device.getDeviceSerial())) {
return AjaxResult.error("设备序列号不能为空");
}
- if (!StringUtils.hasText(device.getEquipmentModel())) {
- return AjaxResult.error("设备型号不能为空");
+ if (device.getDeviceBrand() == null) {
+ return AjaxResult.error("设备品牌不能为空");
+ }
+ if (!StringUtils.hasText(device.getValidateCode())) {
+ return AjaxResult.error("设备验证码不能为空");
}
if (device.getPartnerId() == null) {
return AjaxResult.error("合作商不能为空");
}
- if (deviceService.existsBySerialNo(device.getSerialNo(), excludeId)) {
+ if (deviceService.existsByDeviceSerial(device.getDeviceSerial(), excludeId)) {
return AjaxResult.error("设备序列号已存在");
}
return null;
}
+
+ private boolean isEzvizSuccess(JSONObject result) {
+ return result != null && "200".equals(result.getString("code"));
+ }
+
+ private String ezvizErrorMessage(String defaultMessage, JSONObject result) {
+ if (result == null) {
+ return defaultMessage;
+ }
+ String message = result.getString("msg");
+ return StringUtils.hasText(message) ? message : defaultMessage;
+ }
}
diff --git a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/controller/VideoMonitorController.java b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/controller/VideoMonitorController.java
new file mode 100644
index 0000000..5689735
--- /dev/null
+++ b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/controller/VideoMonitorController.java
@@ -0,0 +1,263 @@
+package com.ruoyi.other.controller;
+
+import com.alibaba.fastjson2.JSONObject;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.common.core.web.domain.AjaxResult;
+import com.ruoyi.common.core.web.page.PageInfo;
+import com.ruoyi.other.api.domain.TDevice;
+import com.ruoyi.other.query.VideoMonitorControlRequest;
+import com.ruoyi.other.query.VideoMonitorPageQuery;
+import com.ruoyi.other.service.TDeviceService;
+import com.ruoyi.other.util.EzvizOpenApiUtils;
+import com.ruoyi.other.vo.VideoMonitorDeviceVO;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.BeanUtils;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 视频监控控制器
+ *
+ * @author ruoyi
+ */
+@RestController
+@RequestMapping("/video-monitor")
+public class VideoMonitorController {
+
+ private static final String SUCCESS_CODE = "200";
+
+ @Resource
+ private TDeviceService deviceService;
+
+ @Resource
+ private EzvizOpenApiUtils ezvizOpenApiUtils;
+
+ @ResponseBody
+ @GetMapping("/pageList")
+ @ApiOperation(value = "获取视频监控列表", tags = {"管理后台-视频监控"})
+ public AjaxResult<PageInfo<VideoMonitorDeviceVO>> pageList(VideoMonitorPageQuery query) {
+ Page<TDevice> page = deviceService.lambdaQuery()
+ .eq(TDevice::getIssueStatus, 1)
+ .like(StringUtils.hasText(query.getDeviceName()), TDevice::getDeviceName, query.getDeviceName())
+ .eq(query.getPartnerType() != null, TDevice::getPartnerType, query.getPartnerType())
+ .eq(query.getPartnerId() != null, TDevice::getPartnerId, query.getPartnerId())
+ .eq(query.getSiteId() != null && query.getSiteId() != 0, TDevice::getSiteId, query.getSiteId())
+ .orderByDesc(TDevice::getCreateTime)
+ .page(Page.of(query.getPageCurr(), query.getPageSize()));
+
+ List<VideoMonitorDeviceVO> records = page.getRecords().stream()
+ .map(this::toMonitorVO)
+ .peek(this::fillOnlineStatus)
+ .filter(item -> query.getOnlineStatus() == null || query.getOnlineStatus().equals(item.getOnlineStatus()))
+ .collect(Collectors.toList());
+
+ PageInfo<VideoMonitorDeviceVO> pageInfo = new PageInfo<>(query.getPageCurr(), query.getPageSize());
+ pageInfo.setTotal(page.getTotal());
+ pageInfo.setRecords(records);
+ return AjaxResult.success(pageInfo);
+ }
+
+ @ResponseBody
+ @GetMapping("/detail/{id}")
+ @ApiOperation(value = "获取视频监控详情", tags = {"管理后台-视频监控"})
+ public AjaxResult<VideoMonitorDeviceVO> detail(@PathVariable Integer id) {
+ TDevice device = getIssuedDevice(id);
+ if (device == null) {
+ return AjaxResult.error("设备不存在或未下发");
+ }
+
+ VideoMonitorDeviceVO vo = toMonitorVO(device);
+ fillOnlineStatus(vo);
+ fillLiveAddress(vo, device);
+ return AjaxResult.success(vo);
+ }
+
+ @ResponseBody
+ @PostMapping("/ptz")
+ @ApiOperation(value = "视频监控云台控制", tags = {"管理后台-视频监控"})
+ public AjaxResult ptz(@RequestBody VideoMonitorControlRequest request) {
+ TDevice device = getIssuedDevice(request.getId());
+ if (device == null) {
+ return AjaxResult.error("设备不存在或未下发");
+ }
+
+ JSONObject result = ezvizOpenApiUtils.ptzControl(
+ device.getDeviceSerial(),
+ StringUtils.hasText(request.getAction()) ? request.getAction() : "start",
+ request.getCommand(),
+ request.getSpeed()
+ );
+ return buildEzvizResult(result, "云台控制失败");
+ }
+
+ @ResponseBody
+ @PostMapping("/sceneSwitch")
+ @ApiOperation(value = "视频监控镜头开关", tags = {"管理后台-视频监控"})
+ public AjaxResult sceneSwitch(@RequestBody VideoMonitorControlRequest request) {
+ TDevice device = getIssuedDevice(request.getId());
+ if (device == null) {
+ return AjaxResult.error("设备不存在或未下发");
+ }
+
+ JSONObject result = ezvizOpenApiUtils.setSceneSwitch(
+ device.getDeviceSerial(),
+ defaultChannelNo(request.getChannelNo()),
+ request.getEnable()
+ );
+ return buildEzvizResult(result, "摄像头开关控制失败");
+ }
+
+ @ResponseBody
+ @PostMapping("/focus")
+ @ApiOperation(value = "视频监控焦距控制", tags = {"管理后台-视频监控"})
+ public AjaxResult focus(@RequestBody VideoMonitorControlRequest request) {
+ TDevice device = getIssuedDevice(request.getId());
+ if (device == null) {
+ return AjaxResult.error("设备不存在或未下发");
+ }
+
+ JSONObject result = ezvizOpenApiUtils.ptzControl(
+ device.getDeviceSerial(),
+ "start",
+ request.getCommand(),
+ request.getSpeed()
+ );
+ if (isEzvizSuccess(result)) {
+ ezvizOpenApiUtils.ptzControl(device.getDeviceSerial(), "stop", request.getCommand(), null);
+ }
+ return buildEzvizResult(result, "焦距控制失败");
+ }
+
+ @ResponseBody
+ @PostMapping("/playback")
+ @ApiOperation(value = "获取视频监控回放地址", tags = {"管理后台-视频监控"})
+ public AjaxResult<VideoMonitorDeviceVO> playback(@RequestBody VideoMonitorControlRequest request) {
+ TDevice device = getIssuedDevice(request.getId());
+ if (device == null) {
+ return AjaxResult.error("设备不存在或未下发");
+ }
+
+ JSONObject result;
+ if (Integer.valueOf(2).equals(request.getType())) {
+ result = ezvizOpenApiUtils.getLocalPlaybackAddress(
+ device.getDeviceSerial(),
+ device.getValidateCode(),
+ defaultChannelNo(request.getChannelNo()),
+ request.getStartTime(),
+ request.getStopTime()
+ );
+ } else {
+ result = ezvizOpenApiUtils.getCloudPlaybackAddress(
+ device.getDeviceSerial(),
+ device.getValidateCode(),
+ defaultChannelNo(request.getChannelNo()),
+ request.getStartTime(),
+ request.getStopTime()
+ );
+ }
+
+ if (!isEzvizSuccess(result)) {
+ return AjaxResult.error(ezvizErrorMessage("获取回放地址失败", result));
+ }
+ VideoMonitorDeviceVO vo = toMonitorVO(device);
+ vo.setPlaybackAddress(resolveVideoUrl(result));
+ return AjaxResult.success(vo);
+ }
+
+ private TDevice getIssuedDevice(Integer id) {
+ if (id == null) {
+ return null;
+ }
+ TDevice device = deviceService.getById(id);
+ if (device == null || !Integer.valueOf(1).equals(device.getIssueStatus())) {
+ return null;
+ }
+ return device;
+ }
+
+ private VideoMonitorDeviceVO toMonitorVO(TDevice device) {
+ VideoMonitorDeviceVO vo = new VideoMonitorDeviceVO();
+ BeanUtils.copyProperties(device, vo);
+ return vo;
+ }
+
+ private void fillOnlineStatus(VideoMonitorDeviceVO vo) {
+ try {
+ JSONObject result = ezvizOpenApiUtils.getDeviceInfo(vo.getDeviceSerial());
+ if (!isEzvizSuccess(result)) {
+ result = ezvizOpenApiUtils.getDeviceStatus(vo.getDeviceSerial());
+ }
+ if (!isEzvizSuccess(result)) {
+ vo.setOnlineStatus(0);
+ vo.setStatusMsg(ezvizErrorMessage("获取在线状态失败", result));
+ return;
+ }
+ JSONObject data = result.getJSONObject("data");
+ Integer status = data == null ? null : data.getInteger("status");
+ if (status == null && data != null) {
+ status = data.getInteger("onlineStatus");
+ }
+ vo.setOnlineStatus(Integer.valueOf(1).equals(status) ? 1 : 0);
+ vo.setStatusMsg(Integer.valueOf(1).equals(vo.getOnlineStatus()) ? "在线" : "不在线");
+ } catch (Exception e) {
+ vo.setOnlineStatus(0);
+ vo.setStatusMsg("获取在线状态失败:" + e.getMessage());
+ }
+ }
+
+ private void fillLiveAddress(VideoMonitorDeviceVO vo, TDevice device) {
+ try {
+ JSONObject result = ezvizOpenApiUtils.getLiveAddress(
+ device.getDeviceSerial(),
+ device.getValidateCode(),
+ 1
+ );
+ if (isEzvizSuccess(result)) {
+ vo.setLiveAddress(resolveVideoUrl(result));
+ } else {
+ vo.setStatusMsg(ezvizErrorMessage("获取直播地址失败", result));
+ }
+ } catch (Exception e) {
+ vo.setStatusMsg("获取直播地址失败:" + e.getMessage());
+ }
+ }
+
+ private AjaxResult buildEzvizResult(JSONObject result, String defaultMessage) {
+ if (isEzvizSuccess(result)) {
+ return AjaxResult.success(result);
+ }
+ return AjaxResult.error(ezvizErrorMessage(defaultMessage, result));
+ }
+
+ private boolean isEzvizSuccess(JSONObject result) {
+ return result != null && SUCCESS_CODE.equals(result.getString("code"));
+ }
+
+ private String ezvizErrorMessage(String defaultMessage, JSONObject result) {
+ if (result == null) {
+ return defaultMessage;
+ }
+ String message = result.getString("msg");
+ return StringUtils.hasText(message) ? message : defaultMessage;
+ }
+
+ private String resolveVideoUrl(JSONObject result) {
+ JSONObject data = result.getJSONObject("data");
+ if (data == null) {
+ return null;
+ }
+ String url = data.getString("url");
+ if (StringUtils.hasText(url)) {
+ return url;
+ }
+ return data.getString("ezopen");
+ }
+
+ private Integer defaultChannelNo(Integer channelNo) {
+ return channelNo == null ? 1 : channelNo;
+ }
+}
diff --git a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/query/DevicePageList.java b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/query/DevicePageList.java
index 90ce33c..0c8d55b 100644
--- a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/query/DevicePageList.java
+++ b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/query/DevicePageList.java
@@ -18,10 +18,13 @@
private String deviceName;
@ApiModelProperty(value = "设备序列号")
- private String serialNo;
+ private String deviceSerial;
- @ApiModelProperty(value = "设备型号")
- private String equipmentModel;
+ @ApiModelProperty(value = "设备品牌(1=大华,2=海康,3=萤石)")
+ private Integer deviceBrand;
+
+ @ApiModelProperty(value = "设备验证码")
+ private String validateCode;
@ApiModelProperty(value = "合作商类型")
private Integer partnerType;
@@ -34,4 +37,7 @@
@ApiModelProperty(value = "在线状态(0=不在线,1=在线)")
private Integer onlineStatus;
+
+ @ApiModelProperty(value = "下发状态(0=未下发,1=已下发,2=下发失败)")
+ private Integer issueStatus;
}
diff --git a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/query/VideoMonitorControlRequest.java b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/query/VideoMonitorControlRequest.java
new file mode 100644
index 0000000..a70bd54
--- /dev/null
+++ b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/query/VideoMonitorControlRequest.java
@@ -0,0 +1,42 @@
+package com.ruoyi.other.query;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 视频监控控制请求
+ *
+ * @author ruoyi
+ */
+@Data
+@ApiModel
+public class VideoMonitorControlRequest {
+
+ @ApiModelProperty(value = "设备ID")
+ private Integer id;
+
+ @ApiModelProperty(value = "通道号")
+ private Integer channelNo;
+
+ @ApiModelProperty(value = "控制动作:start/stop")
+ private String action;
+
+ @ApiModelProperty(value = "控制命令")
+ private String command;
+
+ @ApiModelProperty(value = "速度")
+ private Integer speed;
+
+ @ApiModelProperty(value = "开关值:1=开启,0=关闭")
+ private Integer enable;
+
+ @ApiModelProperty(value = "回放类型:2=本地录像,3=云存储")
+ private Integer type;
+
+ @ApiModelProperty(value = "回放开始时间")
+ private String startTime;
+
+ @ApiModelProperty(value = "回放结束时间")
+ private String stopTime;
+}
diff --git a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/query/VideoMonitorPageQuery.java b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/query/VideoMonitorPageQuery.java
new file mode 100644
index 0000000..0660d40
--- /dev/null
+++ b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/query/VideoMonitorPageQuery.java
@@ -0,0 +1,31 @@
+package com.ruoyi.other.query;
+
+import com.ruoyi.common.core.web.page.BasePage;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 视频监控分页查询
+ *
+ * @author ruoyi
+ */
+@Data
+@ApiModel
+public class VideoMonitorPageQuery extends BasePage {
+
+ @ApiModelProperty(value = "合作商类型")
+ private Integer partnerType;
+
+ @ApiModelProperty(value = "合作商ID")
+ private Integer partnerId;
+
+ @ApiModelProperty(value = "归属电站ID")
+ private Integer siteId;
+
+ @ApiModelProperty(value = "设备名称")
+ private String deviceName;
+
+ @ApiModelProperty(value = "在线状态(0=不在线,1=在线)")
+ private Integer onlineStatus;
+}
diff --git a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/service/TDeviceService.java b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/service/TDeviceService.java
index 94ecb03..c9ae9b6 100644
--- a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/service/TDeviceService.java
+++ b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/service/TDeviceService.java
@@ -14,5 +14,5 @@
PageInfo<TDevice> pageList(DevicePageList pageList);
- boolean existsBySerialNo(String serialNo, Integer excludeId);
+ boolean existsByDeviceSerial(String deviceSerial, Integer excludeId);
}
diff --git a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/service/impl/TDeviceServiceImpl.java b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/service/impl/TDeviceServiceImpl.java
index 612d6c8..22c2446 100644
--- a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/service/impl/TDeviceServiceImpl.java
+++ b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/service/impl/TDeviceServiceImpl.java
@@ -28,12 +28,12 @@
}
@Override
- public boolean existsBySerialNo(String serialNo, Integer excludeId) {
- if (!StringUtils.hasText(serialNo)) {
+ public boolean existsByDeviceSerial(String deviceSerial, Integer excludeId) {
+ if (!StringUtils.hasText(deviceSerial)) {
return false;
}
return this.lambdaQuery()
- .eq(TDevice::getSerialNo, serialNo)
+ .eq(TDevice::getDeviceSerial, deviceSerial)
.ne(excludeId != null, TDevice::getId, excludeId)
.count() > 0;
}
diff --git a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/EzvizOpenApiUtils.java b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/EzvizOpenApiUtils.java
new file mode 100644
index 0000000..eff5ce9
--- /dev/null
+++ b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/EzvizOpenApiUtils.java
@@ -0,0 +1,338 @@
+package com.ruoyi.other.util;
+
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * 萤石开放平台对接工具类。
+ *
+ * 配置项:
+ * ezviz.open.base-url=https://open.ys7.com
+ * ezviz.open.app-key=应用AppKey
+ * ezviz.open.app-secret=应用Secret
+ */
+@Service
+public class EzvizOpenApiUtils {
+
+ private static final String SUCCESS_CODE = "200";
+ private static final String TOKEN_EXPIRED_CODE = "10002";
+ private static final long TOKEN_EXPIRE_ADVANCE_MS = 5 * 60 * 1000L;
+
+ @Value("${ezviz.open.base-url:https://open.ys7.com}")
+ private String baseUrl;
+
+ @Value("${ezviz.open.app-key:}")
+ private String appKey;
+
+ @Value("${ezviz.open.app-secret:}")
+ private String appSecret;
+
+ private volatile String accessToken;
+
+ private volatile long accessTokenExpireTime;
+
+ /**
+ * 获取并缓存 accessToken。
+ */
+ public String getAccessToken() {
+ if (StringUtils.hasText(accessToken) && System.currentTimeMillis() < accessTokenExpireTime) {
+ return accessToken;
+ }
+ return refreshAccessToken();
+ }
+
+ /**
+ * 添加萤石设备到当前账号下。
+ */
+ public JSONObject addDevice(String deviceSerial, String validateCode) {
+ requireText(deviceSerial, "deviceSerial");
+ requireText(validateCode, "validateCode");
+
+ Map<String, Object> form = new LinkedHashMap<>();
+ form.put("deviceSerial", deviceSerial);
+ form.put("validateCode", validateCode);
+ return postFormWithToken("/api/lapp/device/add", form);
+ }
+
+ /**
+ * 删除当前账号下的萤石设备。
+ */
+ public JSONObject deleteDevice(String deviceSerial) {
+ requireText(deviceSerial, "deviceSerial");
+
+ Map<String, Object> form = new LinkedHashMap<>();
+ form.put("deviceSerial", deviceSerial);
+ return postFormWithToken("/api/lapp/device/delete", form);
+ }
+
+ /**
+ * 获取设备基础信息,返回内容中包含设备在线状态。
+ */
+ public JSONObject getDeviceInfo(String deviceSerial) {
+ requireText(deviceSerial, "deviceSerial");
+
+ Map<String, Object> form = new LinkedHashMap<>();
+ form.put("deviceSerial", deviceSerial);
+ return postFormWithToken("/api/lapp/device/info", form);
+ }
+
+ /**
+ * 获取设备状态信息。
+ */
+ public JSONObject getDeviceStatus(String deviceSerial) {
+ requireText(deviceSerial, "deviceSerial");
+
+ Map<String, Object> form = new LinkedHashMap<>();
+ form.put("deviceSerial", deviceSerial);
+ return postFormWithToken("/api/lapp/device/status/get", form);
+ }
+
+ /**
+ * 云台角度控制。
+ *
+ * @param action start=开始,stop=结束
+ * @param command up/down/left/right/upleft/downleft/upright/downright
+ * @param speed 速度 1-7,为空时按萤石默认值
+ */
+ public JSONObject ptzControl(String deviceSerial, String action, String command, Integer speed) {
+ requireText(deviceSerial, "deviceSerial");
+ requireText(action, "action");
+
+ Map<String, Object> body = new LinkedHashMap<>();
+ body.put("timeStamp", String.valueOf(System.currentTimeMillis()));
+ body.put("action", action);
+ body.put("control", "ptz");
+ putIfNotNull(body, "command", command);
+ putIfNotNull(body, "speed", speed);
+
+ return otapAction(
+ deviceSerial,
+ "0",
+ "global",
+ "PTZManualCtrl",
+ "ModifyPTZCtrl",
+ body
+ );
+ }
+
+ /**
+ * 开始云台控制。
+ */
+ public JSONObject startPtz(String deviceSerial, String command, Integer speed) {
+ return ptzControl(deviceSerial, "start", command, speed);
+ }
+
+ /**
+ * 停止云台控制。
+ */
+ public JSONObject stopPtz(String deviceSerial, String command) {
+ return ptzControl(deviceSerial, "stop", command, null);
+ }
+
+ /**
+ * 通用设备开关/动作控制。用于后续按萤石具体功能文档传入 domainIdentifier/actionIdentifier。
+ */
+ public JSONObject otapAction(String deviceSerial,
+ String localIndex,
+ String resourceCategory,
+ String domainIdentifier,
+ String actionIdentifier,
+ Map<String, Object> body) {
+ requireText(deviceSerial, "deviceSerial");
+ requireText(localIndex, "localIndex");
+ requireText(resourceCategory, "resourceCategory");
+ requireText(domainIdentifier, "domainIdentifier");
+ requireText(actionIdentifier, "actionIdentifier");
+
+ Map<String, String> headers = new LinkedHashMap<>();
+ headers.put("accessToken", getAccessToken());
+ headers.put("deviceSerial", deviceSerial);
+ headers.put("localIndex", localIndex);
+ headers.put("resourceCategory", resourceCategory);
+ headers.put("domainIdentifier", domainIdentifier);
+ headers.put("actionIdentifier", actionIdentifier);
+
+ JSONObject result = putJson("/api/v3/device/otap/action", headers, body);
+ if (isTokenExpired(result)) {
+ headers.put("accessToken", refreshAccessToken());
+ result = putJson("/api/v3/device/otap/action", headers, body);
+ }
+ return result;
+ }
+
+ /**
+ * 布撤防开关。isDefence:1=布防,0=撤防。
+ */
+ public JSONObject setDefence(String deviceSerial, Integer channelNo, Integer isDefence) {
+ requireText(deviceSerial, "deviceSerial");
+ if (isDefence == null) {
+ throw new IllegalArgumentException("isDefence不能为空");
+ }
+
+ Map<String, Object> form = new LinkedHashMap<>();
+ form.put("deviceSerial", deviceSerial);
+ form.put("isDefence", isDefence);
+ putIfNotNull(form, "channelNo", channelNo);
+ return postFormWithToken("/api/lapp/device/defence/set", form);
+ }
+
+ /**
+ * 镜头遮蔽开关。enable:1=开启遮蔽,0=关闭遮蔽。
+ */
+ public JSONObject setSceneSwitch(String deviceSerial, Integer channelNo, Integer enable) {
+ requireText(deviceSerial, "deviceSerial");
+ if (enable == null) {
+ throw new IllegalArgumentException("enable不能为空");
+ }
+
+ Map<String, Object> form = new LinkedHashMap<>();
+ form.put("deviceSerial", deviceSerial);
+ form.put("enable", enable);
+ putIfNotNull(form, "channelNo", channelNo);
+ return postFormWithToken("/api/lapp/device/scene/switch/set", form);
+ }
+
+ /**
+ * 获取直播/回放取流地址。
+ *
+ * @param type 1=预览,2=本地录像回放,3=云存储录像回放
+ */
+ public JSONObject getVideoAddress(String deviceSerial,
+ String validateCode,
+ Integer channelNo,
+ Integer type,
+ String startTime,
+ String stopTime,
+ Integer protocol,
+ Integer quality,
+ Integer expireTime) {
+ requireText(deviceSerial, "deviceSerial");
+
+ Map<String, Object> form = new LinkedHashMap<>();
+ form.put("deviceSerial", deviceSerial);
+ putIfNotNull(form, "code", validateCode);
+ putIfNotNull(form, "channelNo", channelNo);
+ putIfNotNull(form, "type", type);
+ putIfNotNull(form, "startTime", startTime);
+ putIfNotNull(form, "stopTime", stopTime);
+ putIfNotNull(form, "protocol", protocol);
+ putIfNotNull(form, "quality", quality);
+ putIfNotNull(form, "expireTime", expireTime);
+ return postFormWithToken("/api/lapp/v2/live/address/get", form);
+ }
+
+ /**
+ * 获取本地录像回放地址。
+ */
+ public JSONObject getLocalPlaybackAddress(String deviceSerial,
+ String validateCode,
+ Integer channelNo,
+ String startTime,
+ String stopTime) {
+ return getVideoAddress(deviceSerial, validateCode, channelNo, 2, startTime, stopTime, 2, null, null);
+ }
+
+ /**
+ * 获取云存储录像回放地址。
+ */
+ public JSONObject getCloudPlaybackAddress(String deviceSerial,
+ String validateCode,
+ Integer channelNo,
+ String startTime,
+ String stopTime) {
+ return getVideoAddress(deviceSerial, validateCode, channelNo, 3, startTime, stopTime, 2, null, null);
+ }
+
+ /**
+ * 获取直播地址。
+ */
+ public JSONObject getLiveAddress(String deviceSerial, String validateCode, Integer channelNo) {
+ return getVideoAddress(deviceSerial, validateCode, channelNo, 1, null, null, 2, null, null);
+ }
+
+ private synchronized String refreshAccessToken() {
+ requireText(appKey, "ezviz.open.app-key");
+ requireText(appSecret, "ezviz.open.app-secret");
+
+ Map<String, Object> form = new LinkedHashMap<>();
+ form.put("appKey", appKey);
+ form.put("appSecret", appSecret);
+
+ JSONObject result = postForm("/api/lapp/token/get", form);
+ if (!SUCCESS_CODE.equals(result.getString("code"))) {
+ throw new IllegalStateException("获取萤石accessToken失败:" + result.toJSONString());
+ }
+
+ JSONObject data = result.getJSONObject("data");
+ if (data == null || !StringUtils.hasText(data.getString("accessToken"))) {
+ throw new IllegalStateException("萤石accessToken响应为空:" + result.toJSONString());
+ }
+
+ accessToken = data.getString("accessToken");
+ Long expireTime = data.getLong("expireTime");
+ accessTokenExpireTime = expireTime == null
+ ? System.currentTimeMillis() + 60 * 60 * 1000L
+ : expireTime - TOKEN_EXPIRE_ADVANCE_MS;
+ return accessToken;
+ }
+
+ private JSONObject postFormWithToken(String path, Map<String, Object> form) {
+ form.put("accessToken", getAccessToken());
+ JSONObject result = postForm(path, form);
+ if (isTokenExpired(result)) {
+ form.put("accessToken", refreshAccessToken());
+ result = postForm(path, form);
+ }
+ return result;
+ }
+
+ private JSONObject postForm(String path, Map<String, Object> form) {
+ HttpResponse response = HttpRequest.post(url(path))
+ .header("Content-Type", "application/x-www-form-urlencoded")
+ .form(form)
+ .timeout(10000)
+ .execute();
+ return JSON.parseObject(response.body());
+ }
+
+ private JSONObject putJson(String path, Map<String, String> headers, Map<String, Object> body) {
+ HttpRequest request = HttpRequest.put(url(path))
+ .header("Content-Type", "application/json;charset=UTF-8")
+ .timeout(10000);
+ for (Map.Entry<String, String> entry : headers.entrySet()) {
+ request.header(entry.getKey(), entry.getValue());
+ }
+ HttpResponse response = request.body(JSON.toJSONString(body == null ? new LinkedHashMap<>() : body)).execute();
+ return JSON.parseObject(response.body());
+ }
+
+ private boolean isTokenExpired(JSONObject result) {
+ return result != null && TOKEN_EXPIRED_CODE.equals(result.getString("code"));
+ }
+
+ private String url(String path) {
+ String normalizedBaseUrl = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl;
+ String normalizedPath = path.startsWith("/") ? path : "/" + path;
+ return normalizedBaseUrl + normalizedPath;
+ }
+
+ private void requireText(String value, String name) {
+ if (!StringUtils.hasText(value)) {
+ throw new IllegalArgumentException(name + "不能为空");
+ }
+ }
+
+ private void putIfNotNull(Map<String, Object> map, String key, Object value) {
+ if (value != null) {
+ map.put(key, value);
+ }
+ }
+}
diff --git a/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/vo/VideoMonitorDeviceVO.java b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/vo/VideoMonitorDeviceVO.java
new file mode 100644
index 0000000..cb3d59e
--- /dev/null
+++ b/ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/vo/VideoMonitorDeviceVO.java
@@ -0,0 +1,54 @@
+package com.ruoyi.other.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 视频监控设备展示对象
+ *
+ * @author ruoyi
+ */
+@Data
+@ApiModel
+public class VideoMonitorDeviceVO {
+
+ @ApiModelProperty(value = "设备ID")
+ private Integer id;
+
+ @ApiModelProperty(value = "设备名称")
+ private String deviceName;
+
+ @ApiModelProperty(value = "设备序列号")
+ private String deviceSerial;
+
+ @ApiModelProperty(value = "设备品牌(1=大华,2=海康,3=萤石)")
+ private Integer deviceBrand;
+
+ @ApiModelProperty(value = "合作商类型")
+ private Integer partnerType;
+
+ @ApiModelProperty(value = "合作商ID")
+ private Integer partnerId;
+
+ @ApiModelProperty(value = "合作商名称")
+ private String partnerName;
+
+ @ApiModelProperty(value = "归属电站ID")
+ private Integer siteId;
+
+ @ApiModelProperty(value = "归属电站名称")
+ private String siteName;
+
+ @ApiModelProperty(value = "在线状态(0=不在线,1=在线)")
+ private Integer onlineStatus;
+
+ @ApiModelProperty(value = "状态说明")
+ private String statusMsg;
+
+ @ApiModelProperty(value = "直播地址")
+ private String liveAddress;
+
+ @ApiModelProperty(value = "回放地址")
+ private String playbackAddress;
+}
diff --git a/ruoyi-service/ruoyi-other/src/main/resources/mapper/other/TDeviceMapper.xml b/ruoyi-service/ruoyi-other/src/main/resources/mapper/other/TDeviceMapper.xml
index dc36409..6f0f091 100644
--- a/ruoyi-service/ruoyi-other/src/main/resources/mapper/other/TDeviceMapper.xml
+++ b/ruoyi-service/ruoyi-other/src/main/resources/mapper/other/TDeviceMapper.xml
@@ -5,20 +5,23 @@
<resultMap id="BaseResultMap" type="com.ruoyi.other.api.domain.TDevice">
<id column="id" property="id" />
<result column="device_name" property="deviceName" />
- <result column="serial_no" property="serialNo" />
- <result column="equipment_model" property="equipmentModel" />
+ <result column="device_serial" property="deviceSerial" />
+ <result column="device_brand" property="deviceBrand" />
+ <result column="validate_code" property="validateCode" />
<result column="partner_type" property="partnerType" />
<result column="partner_id" property="partnerId" />
<result column="partner_name" property="partnerName" />
<result column="site_id" property="siteId" />
<result column="site_name" property="siteName" />
<result column="online_status" property="onlineStatus" />
+ <result column="issue_status" property="issueStatus" />
+ <result column="issue_msg" property="issueMsg" />
<result column="del_flag" property="delFlag" />
<result column="create_time" property="createTime" />
</resultMap>
<sql id="Base_Column_List">
- id, device_name, serial_no, equipment_model, partner_type, partner_id, partner_name, site_id, site_name, online_status, del_flag, create_time
+ id, device_name, device_serial, device_brand, validate_code, partner_type, partner_id, partner_name, site_id, site_name, online_status, issue_status, issue_msg, del_flag, create_time
</sql>
<select id="pageList" resultMap="BaseResultMap">
@@ -29,11 +32,14 @@
<if test="null != req.deviceName and '' != req.deviceName">
and device_name like CONCAT('%', #{req.deviceName}, '%')
</if>
- <if test="null != req.serialNo and '' != req.serialNo">
- and serial_no like CONCAT('%', #{req.serialNo}, '%')
+ <if test="null != req.deviceSerial and '' != req.deviceSerial">
+ and device_serial like CONCAT('%', #{req.deviceSerial}, '%')
</if>
- <if test="null != req.equipmentModel and '' != req.equipmentModel">
- and equipment_model like CONCAT('%', #{req.equipmentModel}, '%')
+ <if test="null != req.deviceBrand">
+ and device_brand = #{req.deviceBrand}
+ </if>
+ <if test="null != req.validateCode and '' != req.validateCode">
+ and validate_code like CONCAT('%', #{req.validateCode}, '%')
</if>
<if test="null != req.partnerType">
and partner_type = #{req.partnerType}
@@ -47,6 +53,9 @@
<if test="null != req.onlineStatus">
and online_status = #{req.onlineStatus}
</if>
+ <if test="null != req.issueStatus">
+ and issue_status = #{req.issueStatus}
+ </if>
order by create_time desc, id desc
</select>
</mapper>
--
Gitblit v1.7.1