package com.ruoyi.other.controller; import com.alibaba.fastjson2.JSONObject; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ruoyi.common.core.domain.R; import com.ruoyi.common.core.web.domain.AjaxResult; import com.ruoyi.common.core.web.page.PageInfo; import com.ruoyi.common.security.utils.SecurityUtils; import com.ruoyi.other.api.domain.TDevice; import com.ruoyi.other.api.feignClient.RoleSiteClient; import com.ruoyi.other.api.feignClient.UserSiteClient; 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 com.ruoyi.system.api.domain.SysUser; import com.ruoyi.system.api.feignClient.SysUserClient; import com.ruoyi.system.api.feignClient.SysUserRoleClient; import com.ruoyi.system.api.model.SysUserRoleVo; 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.Collections; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; 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; @Resource private SysUserClient sysUserClient; @Resource private SysUserRoleClient sysUserRoleClient; @Resource private UserSiteClient userSiteClient; @Resource private RoleSiteClient roleSiteClient; @ResponseBody @GetMapping("/pageList") @ApiOperation(value = "获取视频监控列表", tags = {"管理后台-视频监控"}) public AjaxResult> pageList(VideoMonitorPageQuery query) { DeviceAuthScope scope = resolveAuthScope(); if (scope.siteIds != null && scope.siteIds.isEmpty()) { // 非管理员且无任何授权站点:直接返回空列表,避免全量查询 return AjaxResult.success(new PageInfo<>(query.getPageCurr(), query.getPageSize())); } Page page = deviceService.lambdaQuery() .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()) .eq(scope.partnerId != null, TDevice::getPartnerId, scope.partnerId) .in(scope.siteIds != null, TDevice::getSiteId, scope.siteIds) .orderByDesc(TDevice::getCreateTime) .page(Page.of(query.getPageCurr(), query.getPageSize())); List records = page.getRecords().stream() .map(device -> { VideoMonitorDeviceVO vo = toMonitorVO(device); fillOnlineStatus(vo); fillLiveAddress(vo, device); return vo; }) .filter(item -> query.getOnlineStatus() == null || query.getOnlineStatus().equals(item.getOnlineStatus())) .collect(Collectors.toList()); PageInfo 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 detail(@PathVariable Integer id) { TDevice device = getDevice(id); if (device == null) { return AjaxResult.error("设备不存在"); } if (!canAccessDevice(device)) { 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 = getDevice(request.getId()); if (device == null) { return AjaxResult.error("设备不存在"); } if (!canAccessDevice(device)) { return AjaxResult.error("无权访问该设备"); } JSONObject result = ezvizOpenApiUtils.ptzControl( device.getDeviceSerial(), defaultChannelNo(request.getChannelNo(), device), 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 = getDevice(request.getId()); if (device == null) { return AjaxResult.error("设备不存在"); } if (!canAccessDevice(device)) { return AjaxResult.error("无权访问该设备"); } JSONObject result = ezvizOpenApiUtils.setSceneSwitch( device.getDeviceSerial(), defaultChannelNo(request.getChannelNo(), device), request.getEnable() ); return buildEzvizResult(result, "摄像头开关控制失败"); } @ResponseBody @PostMapping("/focus") @ApiOperation(value = "视频监控焦距控制", tags = {"管理后台-视频监控"}) public AjaxResult focus(@RequestBody VideoMonitorControlRequest request) { TDevice device = getDevice(request.getId()); if (device == null) { return AjaxResult.error("设备不存在"); } if (!canAccessDevice(device)) { return AjaxResult.error("无权访问该设备"); } JSONObject result = ezvizOpenApiUtils.ptzControl( device.getDeviceSerial(), defaultChannelNo(request.getChannelNo(), device), "start", request.getCommand(), request.getSpeed() ); if (isEzvizSuccess(result)) { ezvizOpenApiUtils.ptzControl( device.getDeviceSerial(), defaultChannelNo(request.getChannelNo(), device), "stop", request.getCommand(), null ); } return buildEzvizResult(result, "焦距控制失败"); } @ResponseBody @PostMapping("/playback") @ApiOperation(value = "获取视频监控回放地址", tags = {"管理后台-视频监控"}) public AjaxResult playback(@RequestBody VideoMonitorControlRequest request) { TDevice device = getDevice(request.getId()); if (device == null) { return AjaxResult.error("设备不存在"); } if (!canAccessDevice(device)) { return AjaxResult.error("无权访问该设备"); } JSONObject result; if (Integer.valueOf(2).equals(request.getType())) { result = ezvizOpenApiUtils.getLocalPlaybackAddress( device.getDeviceSerial(), device.getValidateCode(), defaultChannelNo(request.getChannelNo(), device), request.getStartTime(), request.getStopTime() ); } else { result = ezvizOpenApiUtils.getCloudPlaybackAddress( device.getDeviceSerial(), device.getValidateCode(), defaultChannelNo(request.getChannelNo(), device), request.getStartTime(), request.getStopTime() ); } if (!isEzvizSuccess(result)) { return AjaxResult.error(ezvizErrorMessage("获取回放地址失败", result)); } VideoMonitorDeviceVO vo = toMonitorVO(device); vo.setPlaybackAddress(resolveVideoUrl(result)); return AjaxResult.success(vo); } /** * 设备数据访问范围 * siteIds 为 null 表示不限站点;partnerId 非空表示仅限该合作商名下设备 */ private static class DeviceAuthScope { private final Set siteIds; private final Integer partnerId; DeviceAuthScope(Set siteIds, Integer partnerId) { this.siteIds = siteIds; this.partnerId = partnerId; } } /** * 解析当前登录用户可访问的设备范围(与站点管理 getSiteList 口径一致): * 合作商(roleType=2)仅可见自己名下设备;roleId=1 管理员不限;其他用户按 用户站点 ∪ 角色站点 过滤 */ private DeviceAuthScope resolveAuthScope() { Long userId = SecurityUtils.getUserId(); SysUser user = sysUserClient.getSysUser(userId).getData(); if (user == null) { return new DeviceAuthScope(Collections.emptySet(), null); } Integer roleType = user.getRoleType(); Integer objectId = user.getObjectId(); // 合作商:仅可见自己名下的设备 if (Objects.equals(roleType, 2)) { return new DeviceAuthScope(null, objectId); } // 与站点管理一致:roleId != 1 视为非管理员 R admin = sysUserClient.isAdmin(userId); if (admin != null && admin.getData() != null && admin.getData() == 1) { return new DeviceAuthScope(null, null); } return new DeviceAuthScope(getAssignedSiteIds(user.getUserId()), null); } private Set getAssignedSiteIds(Long userId) { Set siteIds = new HashSet<>(); if (userId == null) { return siteIds; } R> userSiteResult = userSiteClient.getSiteIds(userId); if (userSiteResult != null && userSiteResult.getData() != null) { siteIds.addAll(userSiteResult.getData()); } R> roleResult = sysUserRoleClient.getRoleByUserId(userId); if (roleResult != null && roleResult.getData() != null) { for (SysUserRoleVo role : roleResult.getData()) { if (role == null || role.getRoleId() == null) { continue; } R> roleSiteResult = roleSiteClient.getSiteIds(role.getRoleId()); if (roleSiteResult != null && roleSiteResult.getData() != null) { siteIds.addAll(roleSiteResult.getData()); } } } return siteIds; } private boolean canAccessDevice(TDevice device) { if (device == null) { return false; } DeviceAuthScope scope = resolveAuthScope(); if (scope.partnerId != null) { return Objects.equals(device.getPartnerId(), scope.partnerId); } if (scope.siteIds == null) { return true; // 管理员 } return device.getSiteId() != null && scope.siteIds.contains(device.getSiteId()); } private TDevice getDevice(Integer id) { if (id == null) { return null; } TDevice device = deviceService.getById(id); if (device == null) { 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) { int channelNo = defaultChannelNo(null, device); try { vo.setAccessToken(ezvizOpenApiUtils.getAccessToken()); vo.setChannelNo(channelNo); vo.setLiveAddress(buildEzopenLiveAddress(device.getDeviceSerial(), channelNo)); if (Integer.valueOf(1).equals(vo.getOnlineStatus())) { vo.setStatusMsg("在线"); } } catch (Exception e) { vo.setLiveAddress(null); vo.setStatusMsg("获取直播凭证失败:" + e.getMessage()); } } private String buildEzopenLiveAddress(String deviceSerial, Integer channelNo) { return String.format("ezopen://open.ys7.com/%s/%d.live", deviceSerial, channelNo); } private AjaxResult buildEzvizResult(JSONObject result, String defaultMessage) { if (isEzvizSuccess(result)) { return AjaxResult.success(result); } return AjaxResult.error(ezvizErrorMessage(defaultMessage, result), 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"); if (StringUtils.hasText(message)) { return message; } String code = result.getString("code"); return StringUtils.hasText(code) ? defaultMessage + ",错误码:" + code : 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, TDevice device) { if (channelNo != null && channelNo > 0) { return channelNo; } if (device != null && device.getChannelNo() != null && device.getChannelNo() > 0) { return device.getChannelNo(); } return 1; } }