648540858
2023-10-13 e4d37864d1c9fa525807ceecf131a556549672b4
开始重构云端录像继续
10个文件已修改
2个文件已添加
20120 ■■■■ 已修改文件
sql/2.6.9更新.sql 27 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/初始化.sql 27 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/OnRecordMp4HookParam.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/ICloudRecordService.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/bean/CloudRecordItem.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/impl/CloudRecordServiceImpl.java 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/dao/CloudRecordServiceMapper.java 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/utils/DateUtil.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/cloudRecord/CloudRecordController.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/package-lock.json 19813 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/CloudRecordDetail.vue 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/2.6.9¸üÐÂ.sql
@@ -8,17 +8,18 @@
    add stream_key varying(255)
create table wvp_cloud_record (
                                 id serial primary key,
                                 app character varying(255),
                                 stream character varying(255),
                                 call_id character varying(255),
                                 start_time integer,
                                 end_time integer,
                                 media_server_id character varying(50),
                                 file_name character varying(50),
                                 folder character varying(50),
                                 file_path character varying(255),
                                 file_size integer,
                                 time_len integer,
                                 constraint uk_stream_push_app_stream_path unique (app, stream, file_path)
      id serial primary key,
      app character varying(255),
      stream character varying(255),
      call_id character varying(255),
      start_time integer,
      end_time integer,
      media_server_id character varying(50),
      file_name character varying(255),
      folder character varying(255),
      file_path character varying(255),
      collect_type character varying(255),
      file_size integer,
      time_len integer,
      constraint uk_stream_push_app_stream_path unique (app, stream, file_path)
);
sql/³õʼ»¯.sql
@@ -267,19 +267,20 @@
                                 constraint uk_stream_push_app_stream unique (app, stream)
);
create table wvp_cloud_record (
                                        id serial primary key,
                                        app character varying(255),
                                        stream character varying(255),
                                        call_id character varying(255),
                                        start_time integer,
                                        end_time integer,
                                        mediaServerId character varying(50),
                                        file_name character varying(50),
                                        folder character varying(50),
                                        file_path character varying(255),
                                        file_size integer,
                                        time_len integer,
                                        constraint uk_stream_push_app_stream_path unique (app, stream, file_path)
                                id serial primary key,
                                app character varying(255),
                                stream character varying(255),
                                call_id character varying(255),
                                start_time integer,
                                end_time integer,
                                media_server_id character varying(50),
                                file_name character varying(255),
                                folder character varying(255),
                                file_path character varying(255),
                                collect_type character varying(255),
                                file_size integer,
                                time_len integer,
                                constraint uk_stream_push_app_stream_path unique (app, stream, file_path)
);
create table wvp_user (
src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java
@@ -111,6 +111,9 @@
    private IUserService userService;
    @Autowired
    private ICloudRecordService cloudRecordService;
    @Autowired
    private VideoStreamSessionManager sessionManager;
    @Autowired
@@ -775,7 +778,7 @@
        logger.info("[ZLM HOOK] å½•像完成事件:{}->{}", param.getMediaServerId(), param.getFile_path());
        taskExecutor.execute(() -> {
            cloudRecordService.addRecord(param);
        });
        return HookResult.SUCCESS();
src/main/java/com/genersoft/iot/vmp/media/zlm/dto/hook/OnRecordMp4HookParam.java
@@ -9,7 +9,7 @@
    private String stream;
    private String file_name;
    private String file_path;
    private String file_size;
    private long file_size;
    private String folder;
    private String url;
    private String vhost;
@@ -48,11 +48,11 @@
        this.file_path = file_path;
    }
    public String getFile_size() {
    public long getFile_size() {
        return file_size;
    }
    public void setFile_size(String file_size) {
    public void setFile_size(long file_size) {
        this.file_size = file_size;
    }
src/main/java/com/genersoft/iot/vmp/service/ICloudRecordService.java
@@ -1,5 +1,7 @@
package com.genersoft.iot.vmp.service;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.hook.OnRecordMp4HookParam;
import com.genersoft.iot.vmp.service.bean.CloudRecordItem;
import com.github.pagehelper.PageInfo;
@@ -14,14 +16,16 @@
    /**
     * åˆ†é¡µå›žåŽ»äº‘ç«¯å½•åƒåˆ—è¡¨
     */
    PageInfo<CloudRecordItem> getList(int page, int count, String startTime, String endTime);
    PageInfo<CloudRecordItem> getList(int page, int count, String app, String stream, String startTime, String endTime, List<MediaServerItem> mediaServerItems);
    /**
     * æ ¹æ®hook消息增加一条记录
     */
    void addRecord(OnRecordMp4HookParam param);
    /**
     * èŽ·å–æ‰€æœ‰çš„æ—¥æœŸ
     */
    List<String> getDateList(Integer year, Integer month, String app, String stream);
    List<String> getDateList(String app, String stream, int year, int month, List<MediaServerItem> mediaServerItems);
}
src/main/java/com/genersoft/iot/vmp/service/bean/CloudRecordItem.java
@@ -1,5 +1,7 @@
package com.genersoft.iot.vmp.service.bean;
import com.genersoft.iot.vmp.media.zlm.dto.hook.OnRecordMp4HookParam;
/**
 * äº‘端录像数据
 */
@@ -69,6 +71,21 @@
     */
    private long timeLen;
    public static CloudRecordItem getInstance(OnRecordMp4HookParam param) {
        CloudRecordItem cloudRecordItem = new CloudRecordItem();
        cloudRecordItem.setApp(param.getApp());
        cloudRecordItem.setStream(param.getStream());
        cloudRecordItem.setStartTime(param.getStart_time());
        cloudRecordItem.setFileName(param.getFile_name());
        cloudRecordItem.setFolder(param.getFolder());
        cloudRecordItem.setFileSize(param.getFile_size());
        cloudRecordItem.setFilePath(param.getFile_path());
        cloudRecordItem.setMediaServerId(param.getMediaServerId());
        cloudRecordItem.setTimeLen(param.getTime_len());
        cloudRecordItem.setEndTime(param.getStart_time() + param.getTime_len());
        return cloudRecordItem;
    }
    public int getId() {
        return id;
    }
src/main/java/com/genersoft/iot/vmp/service/impl/CloudRecordServiceImpl.java
New file
@@ -0,0 +1,94 @@
package com.genersoft.iot.vmp.service.impl;
import com.genersoft.iot.vmp.conf.exception.ControllerException;
import com.genersoft.iot.vmp.gb28181.bean.GbStream;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo;
import com.genersoft.iot.vmp.media.zlm.dto.hook.OnRecordMp4HookParam;
import com.genersoft.iot.vmp.service.ICloudRecordService;
import com.genersoft.iot.vmp.service.bean.CloudRecordItem;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.dao.CloudRecordServiceMapper;
import com.genersoft.iot.vmp.utils.DateUtil;
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.unit.DataUnit;
import java.time.*;
import java.time.temporal.TemporalAccessor;
import java.util.*;
@Service
public class CloudRecordServiceImpl implements ICloudRecordService {
    private final static Logger logger = LoggerFactory.getLogger(CloudRecordServiceImpl.class);
    @Autowired
    private CloudRecordServiceMapper cloudRecordServiceMapper;
    @Autowired
    private IRedisCatchStorage redisCatchStorage;
    @Override
    public PageInfo<CloudRecordItem> getList(int page, int count, String app, String stream, String startTime, String endTime, List<MediaServerItem> mediaServerItems) {
        // å¼€å§‹æ—¶é—´å’Œç»“束时间在数据库中都是以秒为单位的
        Long startTimeStamp = null;
        Long endTimeStamp = null;
        if (startTime != null ) {
            if (!DateUtil.verification(startTime, DateUtil.formatter)) {
                throw new ControllerException(ErrorCode.ERROR100.getCode(), "开始时间格式错误,正确格式为: " + DateUtil.formatter);
            }
            startTimeStamp = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime);
        }
        if (endTime != null ) {
            if (!DateUtil.verification(endTime, DateUtil.formatter)) {
                throw new ControllerException(ErrorCode.ERROR100.getCode(), "结束时间格式错误,正确格式为: " + DateUtil.formatter);
            }
            endTimeStamp = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime);
        }
        PageHelper.startPage(page, count);
        List<CloudRecordItem> all = cloudRecordServiceMapper.getList(app, stream, startTimeStamp, endTimeStamp, mediaServerItems);
        return new PageInfo<>(all);
    }
    @Override
    public List<String> getDateList(String app, String stream, int year, int month, List<MediaServerItem> mediaServerItems) {
        LocalDate startDate = LocalDate.of(year, month, 1);
        LocalDate endDate;
        if (month == 12) {
            endDate = LocalDate.of(year + 1, 1, 1);
        }else {
            endDate = LocalDate.of(year, month + 1, 1);
        }
        long startTimeStamp = startDate.atStartOfDay().toInstant(ZoneOffset.ofHours(8)).getEpochSecond();
        long endTimeStamp = endDate.atStartOfDay().toInstant(ZoneOffset.ofHours(8)).getEpochSecond();
        List<CloudRecordItem> cloudRecordItemList = cloudRecordServiceMapper.getList(app, stream, startTimeStamp, endTimeStamp, mediaServerItems);
        if (cloudRecordItemList.isEmpty()) {
            return new ArrayList<>();
        }
        Set<String> resultSet = new HashSet<>();
        cloudRecordItemList.stream().forEach(cloudRecordItem -> {
            String date = DateUtil.timestampTo_yyyy_MM_dd(cloudRecordItem.getStartTime());
            resultSet.add(date);
        });
        return new ArrayList<>(resultSet);
    }
    @Override
    public void addRecord(OnRecordMp4HookParam param) {
        CloudRecordItem  cloudRecordItem = CloudRecordItem.getInstance(param);
        StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(param.getApp(), param.getStream());
        if (streamAuthorityInfo != null) {
            cloudRecordItem.setCallId(streamAuthorityInfo.getCallId());
        }
        logger.info("[添加录像记录] {}/{} æ–‡ä»¶å¤§å°ï¼š{}", param.getApp(), param.getStream(), param.getFile_size());
        cloudRecordServiceMapper.add(cloudRecordItem);
    }
}
src/main/java/com/genersoft/iot/vmp/storager/dao/CloudRecordServiceMapper.java
New file
@@ -0,0 +1,58 @@
package com.genersoft.iot.vmp.storager.dao;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.service.bean.CloudRecordItem;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface CloudRecordServiceMapper {
    @Insert(" <script>" +
            "INSERT INTO wvp_cloud_record (" +
            " app," +
            " stream," +
            "<if test=\"callId != null\"> call_id,</if>" +
            " start_time," +
            " end_time," +
            " media_server_id," +
            " file_name," +
            " folder," +
            " file_path," +
            " file_size," +
            " time_len ) " +
            "VALUES (" +
            " #{app}," +
            " #{stream}," +
            " <if test=\"callId != null\"> #{callId},</if>" +
            " #{startTime}," +
            " #{endTime}," +
            " #{mediaServerId}," +
            " #{fileName}," +
            " #{folder}," +
            " #{filePath}," +
            " #{fileSize}," +
            " #{timeLen})" +
            " </script>")
    int add(CloudRecordItem cloudRecordItem);
    @Select(" <script>" +
            "select * " +
            "from wvp_cloud_record " +
            "where 0 = 0" +
            " <if test= 'app != null '> and app=#{app}</if>" +
            " <if test= 'stream != null '> and stream=#{stream}</if>" +
            " <if test= 'startTimeStamp != null '> and start_time &gt;= #{startTimeStamp}</if>" +
            " <if test= 'endTimeStamp != null '> and end_time &lt;= #{endTimeStamp}</if>" +
            " <if test= 'mediaServerItemList != null  ' > and media_server_id in " +
            " <foreach collection='mediaServerItemList'  item='item'  open='(' separator=',' close=')' > #{item.id}</foreach>" +
            " </if>" +
            " </script>")
    List<CloudRecordItem> getList(@Param("app") String app, @Param("stream") String stream,
                                  @Param("startTimeStamp")Long startTimeStamp, @Param("endTimeStamp")Long endTimeStamp,
                                  List<MediaServerItem> mediaServerItemList);
}
src/main/java/com/genersoft/iot/vmp/utils/DateUtil.java
@@ -40,11 +40,17 @@
     */
    public static final String URL_PATTERN = "yyyyMMddHHmmss";
    /**
     * æ—¥æœŸæ ¼å¼
     */
    public static final String date_PATTERN = "yyyy-MM-dd";
    public static final String zoneStr = "Asia/Shanghai";
    public static final DateTimeFormatter formatterCompatibleISO8601 = DateTimeFormatter.ofPattern(ISO8601_COMPATIBLE_PATTERN, Locale.getDefault()).withZone(ZoneId.of(zoneStr));
    public static final DateTimeFormatter formatterISO8601 = DateTimeFormatter.ofPattern(ISO8601_PATTERN, Locale.getDefault()).withZone(ZoneId.of(zoneStr));
    public static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(PATTERN, Locale.getDefault()).withZone(ZoneId.of(zoneStr));
    public static final DateTimeFormatter DateFormatter = DateTimeFormatter.ofPattern(date_PATTERN, Locale.getDefault()).withZone(ZoneId.of(zoneStr));
    public static final DateTimeFormatter urlFormatter = DateTimeFormatter.ofPattern(URL_PATTERN, Locale.getDefault()).withZone(ZoneId.of(zoneStr));
    public static String yyyy_MM_dd_HH_mm_ssToISO8601(String formatTime) {
@@ -72,6 +78,22 @@
    }
    /**
     * æ—¶é—´æˆ³ è½¬ yyyy_MM_dd_HH_mm_ss
     */
    public static String timestampTo_yyyy_MM_dd_HH_mm_ss(long timestamp) {
        Instant instant = Instant.ofEpochSecond(timestamp);
        return formatter.format(LocalDateTime.ofInstant(instant, ZoneId.of(zoneStr)));
    }
    /**
     * æ—¶é—´æˆ³ è½¬ yyyy_MM_dd
     */
    public static String timestampTo_yyyy_MM_dd(long timestamp) {
        Instant instant = Instant.ofEpochSecond(timestamp);
        return DateFormatter.format(LocalDateTime.ofInstant(instant, ZoneId.of(zoneStr)));
    }
    /**
     * èŽ·å–å½“å‰æ—¶é—´
     * @return
     */
src/main/java/com/genersoft/iot/vmp/vmanager/cloudRecord/CloudRecordController.java
@@ -7,10 +7,12 @@
import com.genersoft.iot.vmp.media.zlm.ZLMServerFactory;
import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.service.ICloudRecordService;
import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.service.bean.CloudRecordItem;
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
import com.genersoft.iot.vmp.vmanager.bean.PageInfo;
import com.genersoft.iot.vmp.vmanager.bean.RecordFile;
import com.github.pagehelper.PageInfo;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -41,7 +43,7 @@
    private final static Logger logger = LoggerFactory.getLogger(CloudRecordController.class);
    @Autowired
    private ZlmHttpHookSubscribe hookSubscribe;
    private ICloudRecordService cloudRecordService;
    @Autowired
    private IMediaServerService mediaServerService;
@@ -95,7 +97,7 @@
            return new ArrayList<>();
        }
        return mediaServerService.getRecordDates(app, stream, year, month, mediaServerItems);
        return cloudRecordService.getDateList(app, stream, year, month, mediaServerItems);
    }
    @ResponseBody
@@ -108,7 +110,7 @@
    @Parameter(name = "startTime", description = "开始时间(yyyy-MM-dd HH:mm:ss)", required = true)
    @Parameter(name = "endTime", description = "结束时间(yyyy-MM-dd HH:mm:ss)", required = true)
    @Parameter(name = "mediaServerId", description = "流媒体ID,置空则查询全部流媒体", required = false)
    public PageInfo<RecordFile> openRtpServer(
    public PageInfo<CloudRecordItem> openRtpServer(
            @RequestParam String app,
            @RequestParam String stream,
            @RequestParam int page,
@@ -133,12 +135,9 @@
            mediaServerItems = mediaServerService.getAll();
        }
        if (mediaServerItems.isEmpty()) {
            return new PageInfo<>();
            throw new ControllerException(ErrorCode.ERROR100.getCode(), "当前无流媒体");
        }
        List<RecordFile> records = mediaServerService.getRecords(app, stream, startTime, endTime, mediaServerItems);
        PageInfo<RecordFile> pageInfo = new PageInfo<>(records);
        pageInfo.startPage(page, count);
        return pageInfo;
        return cloudRecordService.getList(page, count, app, stream, startTime, endTime, mediaServerItems);
    }
web_src/package-lock.json
Diff too large
web_src/src/components/CloudRecordDetail.vue
@@ -39,11 +39,11 @@
              <li v-for="(item,index) in detailFiles" :key="index" class="infinite-list-item record-list-item" >
                <el-tag v-if="choosedFile !== item.filename" @click="chooseFile(item)">
                  <i class="el-icon-video-camera"  ></i>
                  {{ getFileShowName(item.fileName) }}
                  {{ getFileShowName(item) }}
                </el-tag>
                <el-tag type="danger" v-if="choosedFile === item.filename">
                  <i class="el-icon-video-camera"  ></i>
                  {{ getFileShowName(item.fileName) }}
                  {{ getFileShowName(item) }}
                </el-tag>
                <a class="el-icon-download" style="color: #409EFF;font-weight: 600;margin-left: 10px;"
                   :href="`${getFileBasePath(item)}/download.html?url=download/${app}/${stream}/${chooseDate}/${item.fileName}`"
@@ -319,7 +319,7 @@
          this.choosedFile = "";
        }else {
          this.choosedFile = file.fileName;
          this.videoUrl = `${this.getFileBasePath(file)}/download/${this.app}/${this.stream}/${this.chooseDate}/${this.choosedFile}`
          this.videoUrl = `${this.getFileBasePath(file)}/download/${this.app}/${this.stream}/${this.chooseDate}/${file.fileName}`
          console.log(this.videoUrl)
        }
@@ -327,9 +327,8 @@
      backToList() {
        this.$router.back()
      },
      getFileShowName(name) {
        return name.substring(0, 2) + ":" + name.substring(2, 4) + ":" + name.substring(4, 6) + "-" +
            name.substring(7, 9) + ":" + name.substring(9, 11) + ":" + name.substring(11, 13)
      getFileShowName(item) {
          return  moment.unix(item.startTime).format('HH:mm:ss') + "-" + moment.unix(item.endTime).format('HH:mm:ss')
      },
      chooseMediaChange() {
@@ -376,13 +375,8 @@
      },
      getTimeForFile(file){
        console.log(file)
        let timeStr = file.fileName.substring(0, 17);
        if(timeStr.indexOf("~") > 0){
          timeStr = timeStr.replaceAll("-",":")
        }
        let timeArr = timeStr.split("-");
        let starTime = new Date(this.chooseDate + " " + timeArr[0]);
        let endTime = new Date(this.chooseDate + " " + timeArr[1]);
        let starTime = new Date(file.startTime * 1000);
        let endTime = new Date(file.endTime * 1000);
        if(this.checkIsOver24h(starTime,endTime)){
           endTime = new Date(this.chooseDate + " " + "23:59:59");
        }