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; } 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; } } ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/controller/VideoMonitorController.java
New file @@ -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; } } 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; } ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/query/VideoMonitorControlRequest.java
New file @@ -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; } ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/query/VideoMonitorPageQuery.java
New file @@ -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; } 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); } 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; } ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/util/EzvizOpenApiUtils.java
New file @@ -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); } } } ruoyi-service/ruoyi-other/src/main/java/com/ruoyi/other/vo/VideoMonitorDeviceVO.java
New file @@ -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; } 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>