‘liusuyi’
2023-10-27 1335ac25bedf2ee2f79dca26707542b87242883f
增加井和相机批量导入
已修改13个文件
792 ■■■■ 文件已修改
ard-work/src/main/java/com/ruoyi/alarmpoints/tube/controller/ArdTubesDetailsController.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ard-work/src/main/java/com/ruoyi/alarmpoints/well/controller/ArdAlarmpointsWellController.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ard-work/src/main/java/com/ruoyi/alarmpoints/well/mapper/ArdAlarmpointsWellMapper.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ard-work/src/main/java/com/ruoyi/alarmpoints/well/service/IArdAlarmpointsWellService.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ard-work/src/main/java/com/ruoyi/alarmpoints/well/service/impl/ArdAlarmpointsWellServiceImpl.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ard-work/src/main/java/com/ruoyi/device/camera/controller/ArdCamerasController.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ard-work/src/main/java/com/ruoyi/device/camera/service/IArdCamerasService.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ard-work/src/main/java/com/ruoyi/device/camera/service/impl/ArdCamerasServiceImpl.java 88 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ard-work/src/main/java/com/ruoyi/utils/sdk/dhsdk/service/impl/DhClientServiceImpl.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ard-work/src/main/java/com/ruoyi/utils/sdk/hiksdk/lib/ExceptionCallBack.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ard-work/src/main/java/com/ruoyi/utils/sdk/hiksdk/service/impl/HikClientServiceImpl.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ard-work/src/main/resources/mapper/alarmpoints/ArdAlarmpointsWellMapper.xml 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ard-work/src/main/resources/templates/test.html 555 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ard-work/src/main/java/com/ruoyi/alarmpoints/tube/controller/ArdTubesDetailsController.java
@@ -120,6 +120,7 @@
        String message = ardTubesDetailsService.importArdTubesDetails(tubesDetailsList, updateSupport, operName,tubeId);
        return success(message);
    }
    @PostMapping("/importTemplate")
    @ApiOperation(value = "管线详情模板下载接口")
    public void importTemplate(HttpServletResponse response)
ard-work/src/main/java/com/ruoyi/alarmpoints/well/controller/ArdAlarmpointsWellController.java
@@ -152,9 +152,9 @@
    @ApiOperation("导入井")
    public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception {
        ExcelUtil<ArdAlarmpointsWell> util = new ExcelUtil<ArdAlarmpointsWell>(ArdAlarmpointsWell.class);
        List<ArdAlarmpointsWell> userList = util.importExcel(file.getInputStream());
        List<ArdAlarmpointsWell> wellList = util.importExcel(file.getInputStream());
        String operName = getUsername();
        String message = ardAlarmpointsWellService.importUser(userList, updateSupport, operName);
        String message = ardAlarmpointsWellService.importWell(wellList, updateSupport, operName);
        return success(message);
    }
ard-work/src/main/java/com/ruoyi/alarmpoints/well/mapper/ArdAlarmpointsWellMapper.java
@@ -62,7 +62,13 @@
     * @return 结果
     */
    public int updateArdAlarmpointsWell(ArdAlarmpointsWell ardAlarmpointsWell);
    /**
     * 修改井管理按井号
     *
     * @param ardAlarmpointsWell 井管理
     * @return 结果
     */
    public int updateArdAlarmpointsWellByWellId(ArdAlarmpointsWell ardAlarmpointsWell);
    /**
     * 删除井管理
     *
ard-work/src/main/java/com/ruoyi/alarmpoints/well/service/IArdAlarmpointsWellService.java
@@ -60,7 +60,13 @@
     * @return 结果
     */
    public int updateArdAlarmpointsWell(ArdAlarmpointsWell ardAlarmpointsWell);
    /**
     * 修改井管理按井号
     *
     * @param ardAlarmpointsWell 井管理
     * @return 结果
     */
    public int updateArdAlarmpointsWellByWellId(ArdAlarmpointsWell ardAlarmpointsWell);
    /**
     * 批量删除井管理
     * 
@@ -84,7 +90,7 @@
     * @param operName 操作用户
     * @return 结果
     */
    public String importUser(List<ArdAlarmpointsWell> ardAlarmpointsWellList, Boolean isUpdateSupport, String operName);
    public String importWell(List<ArdAlarmpointsWell> ardAlarmpointsWellList, Boolean isUpdateSupport, String operName);
    /**
     * 校验用户是否有数据权限
     *
ard-work/src/main/java/com/ruoyi/alarmpoints/well/service/impl/ArdAlarmpointsWellServiceImpl.java
@@ -126,7 +126,13 @@
        ardAlarmpointsWell.setUpdateTime(DateUtils.getNowDate());
        return ardAlarmpointsWellMapper.updateArdAlarmpointsWell(ardAlarmpointsWell);
    }
    @Override
    @Transactional
    public int updateArdAlarmpointsWellByWellId(ArdAlarmpointsWell ardAlarmpointsWell) {
        ardAlarmpointsWell.setUpdateBy(SecurityUtils.getUsername());
        ardAlarmpointsWell.setUpdateTime(DateUtils.getNowDate());
        return ardAlarmpointsWellMapper.updateArdAlarmpointsWellByWellId(ardAlarmpointsWell);
    }
    /**
     * 批量删除井管理
     *
@@ -150,7 +156,7 @@
    }
    @Override
    public String importUser(List<ArdAlarmpointsWell> ardAlarmpointsWellList, Boolean isUpdateSupport, String operName) {
    public String importWell(List<ArdAlarmpointsWell> ardAlarmpointsWellList, Boolean isUpdateSupport, String operName) {
        if (StringUtils.isNull(ardAlarmpointsWellList) || ardAlarmpointsWellList.size() == 0) {
            throw new ServiceException("导入井数据不能为空!");
        }
@@ -163,9 +169,9 @@
                //获取当前登录用户id
                String userId = SecurityUtils.getUserId();
                well.setUserId(userId);
                // 验证是否存在这个用户
                ArdAlarmpointsWell u = ardAlarmpointsWellMapper.selectArdAlarmpointsWellByWellId(well.getWellId());
                if (StringUtils.isNull(u)) {
                // 验证是否存在这个井
                ArdAlarmpointsWell w = ardAlarmpointsWellMapper.selectArdAlarmpointsWellByWellId(well.getWellId());
                if (StringUtils.isNull(w)) {
                    BeanValidators.validateWithException(validator, well);
                    well.setCreateBy(operName);
                    this.insertArdAlarmpointsWell(well);
@@ -173,10 +179,9 @@
                    successMsg.append("<br/>" + successNum + "、井号 " + well.getWellId() + " 导入成功");
                } else if (isUpdateSupport) {
                    BeanValidators.validateWithException(validator, well);
                    checkWellAllowed(well);
                    checkWellDataScope(well.getUserId());
                    well.setUpdateBy(operName);
                    this.updateArdAlarmpointsWell(well);
                    this.updateArdAlarmpointsWellByWellId(well);
                    successNum++;
                    successMsg.append("<br/>" + successNum + "、井号 " + well.getWellId() + " 更新成功");
                } else {
ard-work/src/main/java/com/ruoyi/device/camera/controller/ArdCamerasController.java
@@ -3,6 +3,7 @@
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.ruoyi.alarmpoints.well.domain.ArdAlarmpointsWell;
import com.ruoyi.common.constant.CameraConstants;
import com.ruoyi.device.camera.domain.ArdCameras;
import com.ruoyi.device.camera.domain.CameraCmd;
@@ -27,6 +28,7 @@
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.core.page.TableDataInfo;
import org.springframework.web.multipart.MultipartFile;
import java.util.*;
@@ -137,6 +139,27 @@
        return toAjax(ardCamerasService.deleteArdCamerasByIds(ids));
    }
    @Log(title = "导入相机设备", businessType = BusinessType.IMPORT)
    @PreAuthorize("@ss.hasPermi('device:cameras:import')")
    @PostMapping("/importData")
    @ApiOperation("导入相机设备")
    public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception {
        ExcelUtil<ArdCameras> util = new ExcelUtil<ArdCameras>(ArdCameras.class);
        List<ArdCameras> camerasList = util.importExcel(file.getInputStream());
        String operName = getUsername();
        String message = ardCamerasService.importCameras(camerasList, updateSupport, operName);
        return success(message);
    }
    @PostMapping("/importTemplate")
    @ApiOperation("相机设备导入模板")
    public void importTemplate(HttpServletResponse response) {
        ExcelUtil<ArdCameras> util = new ExcelUtil<ArdCameras>(ArdCameras.class);
        util.importTemplateExcel(response, "相机设备数据");
    }
    @GetMapping("/options")
    @ApiOperation("选择相机数据")
    public List options(ArdCameras ardCameras) {
ard-work/src/main/java/com/ruoyi/device/camera/service/IArdCamerasService.java
@@ -5,6 +5,7 @@
import java.util.Map;
import java.util.TreeMap;
import com.ruoyi.alarmpoints.well.domain.ArdAlarmpointsWell;
import com.ruoyi.device.camera.domain.ArdCameras;
import com.ruoyi.device.camera.domain.CameraCmd;
import com.ruoyi.scheduling.domian.SchedulingParam;
@@ -71,6 +72,30 @@
     */
    public int deleteArdCamerasById(String id);
    /**
     * 导入相机设备信息
     *
     * @param ardCamerasList 相机设备数据列表
     * @param isUpdateSupport 是否更新支持,如果已存在,则进行更新数据
     * @param operName 操作用户
     * @return 结果
     */
    public String importCameras(List<ArdCameras> ardCamerasList, Boolean isUpdateSupport, String operName);
    /**
     * 校验相机是否允许操作
     *
     * @param ardCameras 相机信息
     */
    public void checkCameraAllowed(ArdCameras ardCameras);
    /**
     * 校验用户是否有数据权限
     *
     * @param userId 用户id
     */
    public void checkCameraDataScope(String userId);
    public List findOptions(ArdCameras ardCameras);
    /**
     * @描述 获取本部门以下的所有相机和部门
ard-work/src/main/java/com/ruoyi/device/camera/service/impl/ArdCamerasServiceImpl.java
@@ -3,13 +3,18 @@
import java.util.*;
import java.util.stream.Collectors;
import com.ruoyi.alarmpoints.well.domain.ArdAlarmpointsWell;
import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.constant.CameraConstants;
import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.bean.BeanValidators;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.common.utils.uuid.IdUtils;
import com.ruoyi.device.camera.domain.CameraCmd;
import com.ruoyi.device.channel.domain.ArdChannel;
@@ -25,10 +30,12 @@
import com.ruoyi.system.mapper.SysDeptMapper;
import com.ruoyi.utils.gis.Point;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.validation.Validator;
/**
 * 相机设备Service业务层处理
@@ -50,7 +57,8 @@
    private ArdChannelMapper ardChannelMapper;
    @Resource
    private IVtduService vtduService;
    @Autowired
    protected Validator validator;
    @PostConstruct
    public void loadCameras() {
@@ -200,6 +208,84 @@
        return i;
    }
    @Override
    public String importCameras(List<ArdCameras> ardCamerasList, Boolean isUpdateSupport, String operName) {
        if (StringUtils.isNull(ardCamerasList) || ardCamerasList.size() == 0) {
            throw new ServiceException("导入井数据不能为空!");
        }
        int successNum = 0;
        int failureNum = 0;
        StringBuilder successMsg = new StringBuilder();
        StringBuilder failureMsg = new StringBuilder();
        for (ArdCameras camera : ardCamerasList) {
            try {
                //获取当前登录用户id
                String userId = SecurityUtils.getUserId();
                camera.setUserId(userId);
                // 验证是否存在这个用户
                ArdCameras u = ardCamerasMapper.selectArdCamerasById(camera.getId());
                if (StringUtils.isNull(u)) {
                    BeanValidators.validateWithException(validator, camera);
                    camera.setCreateBy(operName);
                    this.insertArdCameras(camera);
                    successNum++;
                    successMsg.append("<br/>" + successNum + "、相机ID " + camera.getId() + " 导入成功");
                } else if (isUpdateSupport) {
                    BeanValidators.validateWithException(validator, camera);
                    checkCameraDataScope(camera.getUserId());
                    camera.setUpdateBy(operName);
                    this.updateArdCameras(camera);
                    successNum++;
                    successMsg.append("<br/>" + successNum + "、相机ID " + camera.getId() + " 更新成功");
                } else {
                    failureNum++;
                    failureMsg.append("<br/>" + failureNum + "、相机ID " + camera.getId() + " 已存在");
                }
            } catch (Exception e) {
                failureNum++;
                String msg = "<br/>" + failureNum + "、相机ID " + camera.getId() + " 导入失败:";
                failureMsg.append(msg + e.getMessage());
                log.error(msg, e);
            }
        }
        if (failureNum > 0) {
            failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:");
            throw new ServiceException(failureMsg.toString());
        } else {
            successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:");
        }
        return successMsg.toString();
    }
    /**
     * 校验相机是否允许操作
     *
     * @param ardCameras 相机信息
     */
    @Override
    public void checkCameraAllowed(ArdCameras ardCameras) {
        if (StringUtils.isNotNull(ardCameras.getId())) {
            throw new ServiceException("不允许操作井");
        }
    }
    /**
     * 校验用户是否有数据权限
     *
     * @param userId 用户id
     */
    @Override
    public void checkCameraDataScope(String userId) {
        if (!SysUser.isAdmin(SecurityUtils.getUserId())) {
            ArdCameras camera = new ArdCameras();
            camera.setUserId(userId);
            List<ArdCameras> cameras = SpringUtils.getAopProxy(this).selectArdCamerasList(camera);
            if (StringUtils.isEmpty(cameras)) {
                throw new ServiceException("没有权限访问井数据!");
            }
        }
    }
    public List findOptions(ArdCameras ardCameras) {
        List<ArdCameras> options = ardCamerasMapper.findOptions(ardCameras);
        for (ArdCameras camera :
ard-work/src/main/java/com/ruoyi/utils/sdk/dhsdk/service/impl/DhClientServiceImpl.java
@@ -896,7 +896,7 @@
    // 设备断线回调: 当设备出现断线时,SDK会调用该函数
    private static class DisConnect implements NetSDKLib.fDisConnect {
        public void invoke(LLong m_hLoginHandle, String pchDVRIP, int nDVRPort, Pointer dwUser) {
            log.warn("Device[" + pchDVRIP + "] Port[" + nDVRPort + "] DisConnect!");
            log.warn("Device[" + pchDVRIP + ":" + nDVRPort + "] DisConnect!");
        }
    }
@@ -904,7 +904,7 @@
    private static class HaveReConnect implements NetSDKLib.fHaveReConnect {
        @Override
        public void invoke(LLong lLoginID, String pchDVRIP, int nDVRPort, Pointer dwUser) {
            log.warn("ReConnect Device[" + pchDVRIP + "] Port[" + nDVRPort + "]");
            log.warn("ReConnect Device[" + pchDVRIP + ":" + nDVRPort + "]");
        }
    }
ard-work/src/main/java/com/ruoyi/utils/sdk/hiksdk/lib/ExceptionCallBack.java
@@ -22,19 +22,19 @@
            Integer port = camera.getPort();
            switch (dwType) {
                case EXCEPTION_EXCHANGE:
                    log.warn("Device[" + ip + "] Port[" + port + "]用户交互时异常");
                    log.warn("Device[" + ip + ":" + port + "]用户交互时异常");
                    break;
                case EXCEPTION_PREVIEW:
                    log.warn("Device[" + ip + "] Port[" + port + "]网络预览异常");
                    log.warn("Device[" + ip + ":" + port + "]网络预览异常");
                    break;
                case EXCEPTION_RECONNECT:
                    log.warn("Device[" + ip + "] Port[" + port + "]预览时重连");
                    log.warn("Device[" + ip + ":" + port + "]预览时重连");
                    break;
                case RELOGIN_SUCCESS:
                    log.warn("Device[" + ip + "] Port[" + port + "]用户重登陆成功");
                    log.warn("Device[" + ip + ":" + port + "]用户重登陆成功");
                    break;
                case EXCEPTION_RELOGIN:
                    log.warn("Device[" + ip + "] Port[" + port + "]用户重登陆");
                    log.warn("Device[" + ip + ":" + port + "]用户重登陆");
                    break;
            }
        }
ard-work/src/main/java/com/ruoyi/utils/sdk/hiksdk/service/impl/HikClientServiceImpl.java
@@ -1094,7 +1094,7 @@
    public boolean controlInfrarecfg(CameraCmd cmd) {
        String cameraId = cmd.getCameraId();
        boolean enable = cmd.isEnable();
        Integer channelNum = cmd.getChanNo();
        Integer chanNo = cmd.getChanNo();
        if (!GlobalVariable.loginMap.containsKey(cameraId)) {
            return false;
        }
@@ -1102,17 +1102,17 @@
        NET_DVR_CAMERAPARAMCFG_EX struDayNigh = new NET_DVR_CAMERAPARAMCFG_EX();
        Pointer point = struDayNigh.getPointer();
        IntByReference ibrBytesReturned = new IntByReference(0);
        boolean b_GetCameraParam = hCNetSDK.NET_DVR_GetDVRConfig(userId, NET_DVR_GET_CCDPARAMCFG_EX, channelNum, point, struDayNigh.size(), ibrBytesReturned);
        boolean b_GetCameraParam = hCNetSDK.NET_DVR_GetDVRConfig(userId, NET_DVR_GET_CCDPARAMCFG_EX, chanNo, point, struDayNigh.size(), ibrBytesReturned);
        if (!b_GetCameraParam) {
            log.error("获取前端参数失败,错误码:" + hCNetSDK.NET_DVR_GetLastError());
        }
        struDayNigh.read();
        log.debug("是否开启夜视:" + struDayNigh.struDayNight.byDayNightFilterType);
        String current = struDayNigh.struDayNight.byDayNightFilterType == 1 ? "开启" : "关闭";
        log.debug("当前状态:" + current);
        NET_DVR_DAYNIGHT daynight = new NET_DVR_DAYNIGHT();
        if (enable) {
            daynight.byDayNightFilterType = 1;//夜晚
        } else {
            daynight.byDayNightFilterType = 0;//白天
        }
@@ -1120,12 +1120,15 @@
        daynight.byDayNightFilterTime = 60;
        struDayNigh.struDayNight = daynight;
        struDayNigh.write();
        boolean bool = hCNetSDK.NET_DVR_SetDVRConfig(userId, NET_DVR_SET_CCDPARAMCFG_EX, channelNum, point, struDayNigh.size());
        boolean bool = hCNetSDK.NET_DVR_SetDVRConfig(userId, NET_DVR_SET_CCDPARAMCFG_EX, chanNo, point, struDayNigh.size());
        if (!bool) {
            int code = hCNetSDK.NET_DVR_GetLastError();
            log.error("设置夜视失败,请稍后重试" + code);
        }
        log.debug("设置夜视成功");
        else {
            log.debug("设置夜视成功");
        }
        return bool;
    }
ard-work/src/main/resources/mapper/alarmpoints/ArdAlarmpointsWellMapper.xml
@@ -185,7 +185,35 @@
        </trim>
        where id = #{id}
    </update>
    <update id="updateArdAlarmpointsWellByWellId" parameterType="ArdAlarmpointsWell">
        update ard_alarmpoints_well
        <trim prefix="SET" suffixOverrides=",">
            <if test="wellId != null">well_id = #{wellId},</if>
            <if test="wellNumber != null">well_number = #{wellNumber},</if>
            <if test="oilProduction != null">oil_production = #{oilProduction},</if>
            <if test="wellBlock != null">well_block = #{wellBlock},</if>
            <if test="productionDate != null">production_date = #{productionDate},</if>
            <if test="displacementMode != null">displacement_mode = #{displacementMode},</if>
            <if test="surroundingEnvironment != null">surrounding_environment = #{surroundingEnvironment},</if>
            <if test="wellType != null">well_type = #{wellType},</if>
            <if test="installedLoad != null">installed_load = #{installedLoad},</if>
            <if test="meteringStation != null">metering_station = #{meteringStation},</if>
            <if test="transferStation != null">transfer_station = #{transferStation},</if>
            <if test="dehydrationStation != null">dehydration_station = #{dehydrationStation},</if>
            <if test="runStatus != null">run_status = #{runStatus},</if>
            <if test="longitude != null">longitude = #{longitude},</if>
            <if test="latitude != null">latitude = #{latitude},</if>
            <if test="altitude != null">altitude = #{altitude},</if>
            <if test="deptId != null">dept_id = #{deptId},</if>
            <if test="userId != null">user_id = #{userId},</if>
            <if test="createBy != null">create_by = #{createBy},</if>
            <if test="createTime != null">create_time = #{createTime},</if>
            <if test="updateBy != null">update_by = #{updateBy},</if>
            <if test="updateTime != null">update_time = #{updateTime},</if>
            <if test="cameraId != null">camera_id = #{cameraId},</if>
        </trim>
        where well_id = #{wellId}
    </update>
    <delete id="deleteArdAlarmpointsWellById" parameterType="String">
        delete
        from ard_alarmpoints_well
ard-work/src/main/resources/templates/test.html
@@ -4,133 +4,139 @@
    <meta charset="UTF-8">
    <title>测试页</title>
    <script th:src="@{/js/jquery-3.6.4.min.js}"></script>
    <script th:src="@{/js/adapter.min.js}"></script>
    <script th:src="@{/js/webrtcstreamer.js}"></script>
    <link rel="stylesheet" th:href="@{/css/bootstrap.css}"/>
    <script th:src="@{/js/bootstrap.js}"></script>
    <style>
        .top-buffer {
            margin-top: 10px;
        }
        .container {
            border: 2px solid #1b6d85;
            padding: 20px;
        }
    </style>
<body>
<div class="container">
    <div class="row ">
        <div class="dropdown">
            相机id:<select id="select">
        <div class="col-md-12">
            相机id:<select id="select" style="width: 330px;">
        </select>
        </div>
    </div>
    <div class="row top-buffer">
        <div class="col-md-1 col-md-offset-1">
            <button id="up" type="button" class="btn btn-primary">上</button>
        </div>
        <div class="col-md-4 col-md-offset-3">
            <div class="btn-group" role="group">
                <button id="controlZoomIn" type="button" class="btn btn-primary">调焦-</button>
                <button id="controlZoomOut" type="button" class="btn btn-primary">调焦+</button>
            </div>
        </div>
    </div>
    <div class="row ">
        <div class="col-md-1">
            <button id="left" type="button" class="btn btn-primary">左</button>
        </div>
        <div class="col-md-1 col-md-offset-1">
            <button id="right" type="button" class="btn btn-primary">右</button>
        </div>
        <div class="col-md-4 col-md-offset-2">
            <div class="btn-group" role="group">
                <button id="controlFocusNear" type="button" class="btn btn-primary">聚焦-</button>
                <button id="controlFocusFar" type="button" class="btn btn-primary">聚焦+</button>
            </div>
        </div>
    </div>
    <div class="row ">
        <div class="col-md-1 col-md-offset-1">
            <button id="down" type="button" class="btn btn-primary">下</button>
        </div>
        <div class="col-md-4 col-md-offset-3">
            <div class="btn-group" role="group">
                <button id="controlIrisOpen" type="button" class="btn btn-primary">光圈-</button>
                <button id="controlIrisClose" type="button" class="btn btn-primary">光圈+</button>
            </div>
        </div>
    </div>
    <div class="row">
        <div class="col-md-6">
            <div class="row top-buffer">
                <div class="input-group">
                    <span class="input-group-addon">目的坐标值:</span>
                    <input id="targetPostion" class="form-control" placeholder="目的坐标"/>
                    <button id="setTargetPostion" type="button" class="btn btn-default">指向坐标</button>
                </div>
                <div class="input-group">
                    <span class="input-group-addon">P值:</span>
                    <input id="p" class="form-control" placeholder="请输入P值"/>
                </div>
                <div class="input-group">
                    <span class="input-group-addon">T值:</span>
                    <input id="t" class="form-control" placeholder="请输入T值"/>
                </div>
                <div class="input-group">
                    <span class="input-group-addon">Z值:</span>
                    <input id="z" class="form-control" placeholder="请输入Z值"/>
                </div>
            </div>
            <div class="row top-buffer">
                <div class="btn-group" role="group">
                    <button id="getPTZ" type="button" class="btn btn-default">获取ptz</button>
                    <button id="setPTZ" type="button" class="btn btn-default">设置ptz</button>
                    <button id="setPreset" type="button" class="btn btn-default">设预置点</button>
                    <button id="gotoPreset" type="button" class="btn btn-default">调预置点</button>
                    <button id="getZeroPTZ" type="button" class="btn btn-default">调用零方位角</button>
                    <button id="setZeroPTZ" type="button" class="btn btn-default">设置零方位角</button>
                </div>
            </div>
            <div class="row top-buffer">
                <div class="btn-group" role="group">
                    <button id="FocusMode" type="button" class="btn btn-default">手动聚焦</button>
                    <div id="focusDiv" class="input-group">
                        <span class="input-group-addon">聚焦值:</span>
                        <input id="focus" class="form-control" placeholder="聚焦值"/>
                    </div>
                    <button id="getFocusPos" type="button" class="btn btn-default">获取聚焦值</button>
                    <button id="setFocusPos" type="button" class="btn btn-default">设置聚焦值</button>
                </div>
            </div>
            <div class="row top-buffer">
                <div class="btn-group" role="group">
                    <button id="WiperPwron" type="button" class="btn btn-default">开启雨刷</button>
                    <button id="Defogcfg" type="button" class="btn btn-default">开启透雾</button>
                    <button id="Infrarecfg" type="button" class="btn btn-default">开启红外</button>
                    <button id="HeateRpwron" type="button" class="btn btn-default">开启云台加热</button>
                    <button id="CameraDeicing" type="button" class="btn btn-default">开启镜头加热</button>
                </div>
            </div>
            <div class="row top-buffer">
                <div class="btn-group" role="group">
                    <button id="voice" type="button" class="btn btn-default">开始语音对讲</button>
                    <button id="record" type="button" class="btn btn-default">开始录像</button>
                    <button id="realCutPic" type="button" class="btn btn-default">实时抓图</button>
                    <button id="saveCutPic" type="button" class="btn btn-default">存储抓图</button>
                </div>
            </div>
            <div class="row top-buffer">
                <div class="col-md-6">
                    <img class="thumbnail" id="imgContainer" style="width: 500px; height: 300px;"/>
                </div>
            </div>
        </div>
        <div class="col-md-1"/>
        <div class="col-md-5">
            <div class="row top-buffer">
                <video id="video" muted autoplay loop controls style="width: 800px; height: 100%; object-fit: fill;"/>
                <div class="col-md-1 col-md-offset-1">
                    <button id="up" type="button" class="btn btn-primary">上</button>
                </div>
                <div class="col-md-6 col-md-offset-2">
                    <div class="btn-group" role="group">
                        <button id="controlZoomIn" type="button" class="btn btn-primary">调焦-</button>
                        <button id="controlZoomOut" type="button" class="btn btn-primary">调焦+</button>
                    </div>
                </div>
            </div>
            <div class="row ">
                <div class="col-md-1">
                    <button id="left" type="button" class="btn btn-primary">左</button>
                </div>
                <div class="col-md-1 col-md-offset-1">
                    <button id="right" type="button" class="btn btn-primary">右</button>
                </div>
                <div class="col-md-6 col-md-offset-1">
                    <div class="btn-group" role="group">
                        <button id="controlFocusNear" type="button" class="btn btn-primary">聚焦-</button>
                        <button id="controlFocusFar" type="button" class="btn btn-primary">聚焦+</button>
                    </div>
                </div>
            </div>
            <div class="row ">
                <div class="col-md-1 col-md-offset-1">
                    <button id="down" type="button" class="btn btn-primary">下</button>
                </div>
                <div class="col-md-6 col-md-offset-2">
                    <div class="btn-group" role="group">
                        <button id="controlIrisOpen" type="button" class="btn btn-primary">光圈-</button>
                        <button id="controlIrisClose" type="button" class="btn btn-primary">光圈+</button>
                    </div>
                </div>
            </div>
            <div class="row ">
                <div class="col-md-10">
                    <div class="row top-buffer">
                        <div class="input-group">
                            <span class="input-group-addon">目的坐标值:</span>
                            <input id="targetPostion" class="form-control" placeholder="目的坐标"/>
                            <button id="setTargetPostion" type="button" class="btn btn-default">指向坐标</button>
                        </div>
                        <div class="input-group">
                            <span class="input-group-addon">P值:</span>
                            <input id="p" class="form-control" placeholder="请输入P值"/>
                        </div>
                        <div class="input-group">
                            <span class="input-group-addon">T值:</span>
                            <input id="t" class="form-control" placeholder="请输入T值"/>
                        </div>
                        <div class="input-group">
                            <span class="input-group-addon">Z值:</span>
                            <input id="z" class="form-control" placeholder="请输入Z值"/>
                        </div>
                    </div>
                    <div class="row top-buffer">
                        <div class="btn-group" role="group">
                            <button id="getPTZ" type="button" class="btn btn-default">获取ptz</button>
                            <button id="setPTZ" type="button" class="btn btn-default">设置ptz</button>
                            <button id="setPreset" type="button" class="btn btn-default">设预置点</button>
                            <button id="gotoPreset" type="button" class="btn btn-default">调预置点</button>
                            <button id="setZeroPTZ" type="button" class="btn btn-default">设置零方位角</button>
                        </div>
                    </div>
                    <div class="row top-buffer">
                        <div class="btn-group" role="group">
                            <button id="FocusMode" type="button" class="btn btn-default">手动聚焦</button>
                            <div id="focusDiv" class="input-group">
                                <span class="input-group-addon">聚焦值:</span>
                                <input id="focus" class="form-control" placeholder="聚焦值"/>
                            </div>
                            <button id="getFocusPos" type="button" class="btn btn-default">获取聚焦值</button>
                            <button id="setFocusPos" type="button" class="btn btn-default">设置聚焦值</button>
                        </div>
                    </div>
                    <div class="row top-buffer">
                        <div class="btn-group" role="group">
                            <button id="WiperPwron" type="button" class="btn btn-default">开启雨刷</button>
                            <button id="Defogcfg" type="button" class="btn btn-default">开启透雾</button>
                            <button id="Infrarecfg" type="button" class="btn btn-default">开启红外</button>
                            <button id="HeateRpwron" type="button" class="btn btn-default">开启云台加热</button>
                            <button id="CameraDeicing" type="button" class="btn btn-default">开启镜头加热</button>
                        </div>
                    </div>
                    <div class="row top-buffer">
                        <div class="btn-group" role="group">
                            <button id="voice" type="button" class="btn btn-default">开始语音对讲</button>
                            <button id="record" type="button" class="btn btn-default">开始录像</button>
                            <button id="saveCutPic" type="button" class="btn btn-default">存储抓图</button>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        <div class="col-md-6">
            <div class="row">
                <div class="row top-buffer">
                    <video id="video" muted autoplay loop controls
                           style="width: 100%; height: 360px; object-fit: fill; border: 2px solid #3498db;"/>
                </div>
                <div class="row">
                    <img class="thumbnail" id="imgContainer"
                         style="width: 100%; height: 360px; border: 2px solid #3498db;"/>
                </div>
            </div>
        </div>
    </div>
</div>
<script th:inline="javascript" th:type="module">
<script th:inline="javascript">
    var cameraId, opt, optOpen, optClose, token;
    window.onload = function () {
@@ -175,7 +181,6 @@
            dataType: "json",
            data: JSON.stringify(opt),
            success: function (data) {
                console.log(data);
                token = data.token;
            }
        })
@@ -589,20 +594,20 @@
        cameraId = $('#select option:selected').val();
        opt = {"cameraId": cameraId, "chanNo": 1};
        $.ajax({
                headers: {
                    'Accept': 'application/json',
                    'Content-Type': 'application/json',
                    'Authorization': token
                },
                url: "../cameraSdk/getFocusPos",
                type: "post",
                dataType: "json",
                data: JSON.stringify(opt),
                success: function (datas) {
                    console.log(datas);
                    $("#focus").val(datas.data);
                }
            })
            headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json',
                'Authorization': token
            },
            url: "../cameraSdk/getFocusPos",
            type: "post",
            dataType: "json",
            data: JSON.stringify(opt),
            success: function (datas) {
                console.log(datas);
                $("#focus").val(datas.data);
            }
        })
    })
    var heateRpwronflag = true;
    $("#HeateRpwron").click(function () {
@@ -656,7 +661,8 @@
            $.ajax({
                headers: {
                    'Accept': 'application/json',
                    'Content-Type': 'application/json'
                    'Content-Type': 'application/json',
                    'Authorization': token
                },
                url: "../cameraSdk/cameraDeicing",
                type: "post",
@@ -721,7 +727,7 @@
                console.log(data.data);
                setTimeout(() => {
                    $('#imgContainer').attr('src', data.data);
                }, 1000 )
                }, 1000)
            }
        })
@@ -790,67 +796,264 @@
        })
    }
    let webRtcServer = null;
    let videoMap = new Map();
    $('video').click(function (e) {
        let ID = e.target.id;//获取当前点击事件的元素
        console.log(ID);
        if (videoMap.get(ID) != null) {
            closeVideo(ID, videoMap.get(ID));
        } else {
            var cameraId = $('#select option:selected').val();
            let camera = cameraMap.get(cameraId);
            console.log(camera);
            if (camera.factory == "3") {
                realViewYs("127.0.0.1", ID, camera.username, camera.password, camera.ipaddr, camera.port);
            } else if (camera.factory == "2") {
                realViewDh("127.0.0.1", ID, camera.username, camera.password, camera.ipaddr, camera.port);
            } else {
                realViewHik("127.0.0.1", ID, camera.username, camera.password, camera.ipaddr, camera.port);
        var cameraId = $('#select option:selected').val();
        $.ajax({
            headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json',
                'Authorization': token
            },
            url: "../vtdu/media/" + cameraId + "_" + 1,
            type: "get",
            dataType: "json",
            success: function (data) {
                realView(data.data.webrtcUrl + "/", e.target.id);
            }
        }
        })
    });
    //预览海康相机
    function realViewHik(serverip, elem, username, password, ipaddr, port) {
       // webRtcServer = new WebRtcStreamer(elem, "http://" + serverip + ":8000");
        webRtcServer = new WebRtcStreamer(elem, "http://192.168.1.227:8000");
        let rtspUrl = "rtsp://" + username + ":" + password + "@" + ipaddr + ":" + port + "/ch1/main/av_stream";
        let option = "rtptransport=tcp";
        console.log("rtsp地址:" + rtspUrl);
        webRtcServer.connect(rtspUrl, null, option, null);
        videoMap.set(elem, webRtcServer);
    let webrtcClient;
    //whep操作方法
    const restartPause = 2000;
    const unquoteCredential = (v) => (
        JSON.parse(`"${v}"`)
    );
    const linkToIceServers = (links) => (
        (links !== null) ? links.split(', ').map((link) => {
            const m = link.match(/^<(.+?)>; rel="ice-server"(; username="(.*?)"; credential="(.*?)"; credential-type="password")?/i);
            const ret = {
                urls: [m[1]],
            };
            if (m[3] !== undefined) {
                ret.username = unquoteCredential(m[3]);
                ret.credential = unquoteCredential(m[4]);
                ret.credentialType = "password";
            }
            return ret;
        }) : []
    );
    const parseOffer = (offer) => {
        const ret = {
            iceUfrag: '',
            icePwd: '',
            medias: [],
        };
        for (const line of offer.split('\r\n')) {
            if (line.startsWith('m=')) {
                ret.medias.push(line.slice('m='.length));
            } else if (ret.iceUfrag === '' && line.startsWith('a=ice-ufrag:')) {
                ret.iceUfrag = line.slice('a=ice-ufrag:'.length);
            } else if (ret.icePwd === '' && line.startsWith('a=ice-pwd:')) {
                ret.icePwd = line.slice('a=ice-pwd:'.length);
            }
        }
        return ret;
    };
    const generateSdpFragment = (offerData, candidates) => {
        const candidatesByMedia = {};
        for (const candidate of candidates) {
            const mid = candidate.sdpMLineIndex;
            if (candidatesByMedia[mid] === undefined) {
                candidatesByMedia[mid] = [];
            }
            candidatesByMedia[mid].push(candidate);
        }
        let frag = 'a=ice-ufrag:' + offerData.iceUfrag + '\r\n'
            + 'a=ice-pwd:' + offerData.icePwd + '\r\n';
        let mid = 0;
        for (const media of offerData.medias) {
            if (candidatesByMedia[mid] !== undefined) {
                frag += 'm=' + media + '\r\n'
                    + 'a=mid:' + mid + '\r\n';
                for (const candidate of candidatesByMedia[mid]) {
                    frag += 'a=' + candidate.candidate + '\r\n';
                }
            }
            mid++;
        }
        return frag;
    }
    //预览大华相机
    function realViewDh(serverip, elem, username, password, ipaddr, port) {
        webRtcServer = new WebRtcStreamer(elem, "http://" + serverip + ":8000");
        let rtspUrl = "rtsp://" + username + ":" + password + "@" + ipaddr + ":" + port + "/cam/realmonitor?channel=1&subtype=0";
        let option = "rtptransport=tcp";
        console.log("rtsp地址:" + rtspUrl);
    class WHEPClient {
        constructor(whepUrl, videoId) {
            this.video = videoId;
            this.wurl = new URL('whep', whepUrl);
            this.pc = null;
            this.restartTimeout = null;
            this.eTag = '';
            this.queuedCandidates = [];
            this.start();
        }
        webRtcServer.connect(rtspUrl, null, option, null);
        videoMap.set(elem, webRtcServer);
        start() {
            console.log("requesting ICE servers");
            fetch(this.wurl, {
                method: 'OPTIONS',
            })
                .then((res) => this.onIceServers(res))
                .catch((err) => {
                    console.log('error: ' + err);
                    this.scheduleRestart();
                });
        }
        onIceServers(res) {
            this.pc = new RTCPeerConnection({
                iceServers: linkToIceServers(res.headers.get('Link')),
            });
            const direction = "sendrecv";
            this.pc.addTransceiver("video", {direction});
            this.pc.addTransceiver("audio", {direction});
            this.pc.onicecandidate = (evt) => this.onLocalCandidate(evt);
            this.pc.oniceconnectionstatechange = () => this.onConnectionState();
            this.pc.ontrack = (evt) => {
                console.log("new track:", evt.track.kind);
                document.getElementById(this.video).srcObject = evt.streams[0];
            };
            this.pc.createOffer()
                .then((offer) => this.onLocalOffer(offer));
        }
        onLocalOffer(offer) {
            this.offerData = parseOffer(offer.sdp);
            this.pc.setLocalDescription(offer);
            console.log("sending offer");
            console.log(this.wurl);
            fetch(this.wurl, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/sdp',
                },
                body: offer.sdp,
            })
                .then((res) => {
                    if (res.status !== 201) {
                        throw new Error('bad status code');
                    }
                    // this.eTag = res.headers.get('ETag');
                    this.eTag = res.headers.get("ETag") || res.headers.get('E-Tag');
                    return res.text();
                })
                .then((sdp) => this.onRemoteAnswer(new RTCSessionDescription({
                    type: 'answer',
                    sdp,
                })))
                .catch((err) => {
                    console.log('error: ' + err);
                    this.scheduleRestart();
                });
        }
        onConnectionState() {
            if (this.restartTimeout !== null) {
                return;
            }
            console.log("peer connection state:", this.pc.iceConnectionState);
            switch (this.pc.iceConnectionState) {
                case "disconnected":
                    this.scheduleRestart();
            }
        }
        onRemoteAnswer(answer) {
            if (this.restartTimeout !== null) {
                return;
            }
            this.pc.setRemoteDescription(new RTCSessionDescription(answer));
            if (this.queuedCandidates.length !== 0) {
                this.sendLocalCandidates(this.queuedCandidates);
                this.queuedCandidates = [];
            }
        }
        onLocalCandidate(evt) {
            if (this.restartTimeout !== null) {
                return;
            }
            if (evt.candidate !== null) {
                if (this.eTag === '') {
                    this.queuedCandidates.push(evt.candidate);
                } else {
                    this.sendLocalCandidates([evt.candidate])
                }
            }
        }
        sendLocalCandidates(candidates) {
            fetch(this.wurl, {
                method: 'PATCH',
                headers: {
                    'Content-Type': 'application/trickle-ice-sdpfrag',
                    'If-Match': this.eTag,
                },
                body: generateSdpFragment(this.offerData, candidates),
            })
                .then((res) => {
                    if (res.status !== 204) {
                        throw new Error('bad status code');
                    }
                })
                .catch((err) => {
                    console.log('error: ' + err);
                    this.scheduleRestart();
                });
        }
        scheduleRestart() {
            if (this.restartTimeout !== null) {
                return;
            }
            if (this.pc !== null) {
                this.pc.close();
                this.pc = null;
            }
            this.restartTimeout = window.setTimeout(() => {
                this.restartTimeout = null;
                this.start();
            }, restartPause);
            this.eTag = '';
            this.queuedCandidates = [];
        }
        stop() {
            if (this.pc) {
                try {
                    this.pc.close();
                } catch (e) {
                    console.log("Failure close peer connection:" + e);
                }
                this.pc = null;
            }
        }
    }
    //预览宇视相机
    function realViewYs(serverip, elem, username, password, ipaddr, port) {
        webRtcServer = new WebRtcStreamer(elem, "http://" + serverip + ":8000");
        let rtspUrl = "rtsp://" + username + ":" + password + "@" + ipaddr + ":" + port + "/media/video1/multicast";
        console.log("rtsp地址:" + rtspUrl);
        let option = "rtptransport=tcp";
        webRtcServer.connect(rtspUrl, null, option, null);
        videoMap.set(elem, webRtcServer);
    }
    function closeVideo(id, webrtc) {
        webrtc.disconnect();
        videoMap.delete(id);
    }
    //页面退出时销毁
    window.onbeforeunload = function () {
        webRtcServer.disconnect();
    function realView(whepUrl, videoId) {
        console.log(whepUrl)
        webrtcClient = new WHEPClient(whepUrl, videoId);
    }
</script>
</body>