Merge branch 'master' into dev/1078-newUI

# Conflicts:
#	src/main/resources/application.yml
This commit is contained in:
648540858
2025-05-10 08:59:45 +08:00
378 changed files with 48292 additions and 475 deletions

View File

@@ -0,0 +1,97 @@
package com.genersoft.iot.vmp.common;
import lombok.Data;
/**
* 统计信息
*/
@Data
public class StatisticsInfo {
private long id;
/**
* ID
*/
private String deviceId;
/**
* 分支
*/
private String branch;
/**
* git提交版本ID
*/
private String gitCommitId;
/**
* git地址
*/
private String gitUrl;
/**
* 构建版本
*/
private String version;
/**
* 操作系统名称
*/
private String osName;
/**
* 是否是docker环境
*/
private Boolean docker;
/**
* 架构
*/
private String arch;
/**
* jdk版本
*/
private String jdkVersion;
/**
* redis版本
*/
private String redisVersion;
/**
* sql数据库版本
*/
private String sqlVersion;
/**
* sql数据库类型 mysql/postgresql/金仓等
*/
private String sqlType;
/**
* 创建时间
*/
private String time;
@Override
public String toString() {
return "StatisticsInfo{" +
"id=" + id +
", deviceId='" + deviceId + '\'' +
", branch='" + branch + '\'' +
", gitCommitId='" + gitCommitId + '\'' +
", gitUrl='" + gitUrl + '\'' +
", version='" + version + '\'' +
", osName='" + osName + '\'' +
", docker=" + docker +
", arch='" + arch + '\'' +
", jdkVersion='" + jdkVersion + '\'' +
", redisVersion='" + redisVersion + '\'' +
", sqlVersion='" + sqlVersion + '\'' +
", sqlType='" + sqlType + '\'' +
", time='" + time + '\'' +
'}';
}
}

View File

@@ -23,7 +23,7 @@ import java.nio.file.Files;
*/
@Slf4j
@Configuration
@Order(value=14)
@Order(value=15)
public class CivilCodeFileConf implements CommandLineRunner {
@Autowired

View File

@@ -1,6 +1,7 @@
package com.genersoft.iot.vmp.conf;
import com.genersoft.iot.vmp.conf.exception.ControllerException;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.media.service.IMediaServerService;
import com.genersoft.iot.vmp.service.bean.CloudRecordItem;
@@ -59,11 +60,14 @@ public class CloudRecordTimer {
// TODO 后续可以删除空了的过期日期文件夹
for (CloudRecordItem cloudRecordItem : cloudRecordItemList) {
String date = new File(cloudRecordItem.getFilePath()).getParentFile().getName();
boolean deleteResult = mediaServerService.deleteRecordDirectory(mediaServerItem, cloudRecordItem.getApp(),
cloudRecordItem.getStream(), date, cloudRecordItem.getFileName());
if (deleteResult) {
log.warn("[录像文件定时清理] 删除磁盘文件成功: {}", cloudRecordItem.getFilePath());
}
try {
boolean deleteResult = mediaServerService.deleteRecordDirectory(mediaServerItem, cloudRecordItem.getApp(),
cloudRecordItem.getStream(), date, cloudRecordItem.getFileName());
if (deleteResult) {
log.warn("[录像文件定时清理] 删除磁盘文件成功: {}", cloudRecordItem.getFilePath());
}
}catch (ControllerException ignored) {}
}
result += cloudRecordServiceMapper.deleteList(cloudRecordItemList);
}

View File

@@ -27,12 +27,12 @@ public class SipConfig {
private String id;
private String password;
Integer ptzSpeed = 50;
Integer registerTimeInterval = 120;
private boolean alarm = false;
private long timeout = 500;
private long timeout = 1000;
}

View File

@@ -30,6 +30,7 @@ public class SpringDocConfig {
Contact contact = new Contact();
contact.setName("pan");
contact.setEmail("648540858@qq.com");
return new OpenAPI()
.components(new Components()
.addSecuritySchemes(JwtUtils.HEADER, new SecurityScheme()
@@ -37,7 +38,11 @@ public class SpringDocConfig {
.bearerFormat("JWT")))
.info(new Info().title("WVP-PRO 接口文档")
.contact(contact)
.description("开箱即用的28181协议视频平台")
.description("开箱即用的28181协议视频平台。 <br/>" +
"1. 打开http://127.0.0.1:18080/doc.html#/1.%20全部/用户管理/login_1" +
" 登录成功后返回AccessToken。 <br/>" +
"2. 填写到AccessToken到参数值 http://127.0.0.1:18080/doc.html#/Authorize/1.%20全部 <br/>" +
"后续接口就可以直接测试了")
.version("v3.1.0")
.license(new License().name("Apache 2.0").url("http://springdoc.org")));
}

View File

@@ -0,0 +1,98 @@
package com.genersoft.iot.vmp.conf;
import com.alibaba.fastjson2.JSON;
import com.genersoft.iot.vmp.common.StatisticsInfo;
import com.genersoft.iot.vmp.utils.DateUtil;
import com.genersoft.iot.vmp.utils.GitUtil;
import com.genersoft.iot.vmp.utils.SystemInfoUtils;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.io.File;
import java.sql.DatabaseMetaData;
import java.util.Objects;
@Component
@Order(value=100)
@Slf4j
public class StatisticsInfoTask implements CommandLineRunner {
@Autowired
private GitUtil gitUtil;
@Autowired
private RedisTemplate<Object, Object> redisTemplate;
@Autowired
private DataSource dataSource;
@Override
public void run(String... args) throws Exception {
try {
StatisticsInfo statisticsInfo = new StatisticsInfo();
statisticsInfo.setDeviceId(SystemInfoUtils.getHardwareId());
statisticsInfo.setBranch(gitUtil.getBranch());
statisticsInfo.setGitCommitId(gitUtil.getGitCommitId());
statisticsInfo.setGitUrl(gitUtil.getGitUrl());
statisticsInfo.setVersion(gitUtil.getBuildVersion());
statisticsInfo.setOsName(System.getProperty("os.name"));
statisticsInfo.setArch(System.getProperty("os.arch"));
statisticsInfo.setJdkVersion(System.getProperty("java.version"));
statisticsInfo.setDocker(new File("/.dockerenv").exists());
try {
statisticsInfo.setRedisVersion(getRedisVersion());
}catch (Exception ignored) {}
try {
DatabaseMetaData metaData = dataSource.getConnection().getMetaData();
statisticsInfo.setSqlVersion(metaData.getDatabaseProductVersion());
statisticsInfo.setSqlType(metaData.getDriverName());
}catch (Exception ignored) {}
statisticsInfo.setTime(DateUtil.getNow());
sendPost(statisticsInfo);
}catch (Exception e) {
log.error("[获取信息失败] ", e);
}
}
public String getRedisVersion() {
if (redisTemplate.getConnectionFactory() == null) {
return null;
}
RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
if (connection.info() == null) {
return null;
}
return connection.info().getProperty("redis_version");
}
public void sendPost(StatisticsInfo statisticsInfo) {
OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
OkHttpClient client = httpClientBuilder.build();
RequestBody requestBodyJson = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), JSON.toJSONString(statisticsInfo));
Request request = new Request.Builder()
.post(requestBodyJson)
.url("http://api.wvp-pro.cn:136/api/statistics/ping")
// .url("http://127.0.0.1:11236/api/statistics/ping")
.addHeader("Content-Type", "application/json")
.build();
try {
Response response = client.newCall(request).execute();
response.close();
Objects.requireNonNull(response.body()).close();
}catch (Exception ignored){}
}
}

View File

@@ -178,7 +178,7 @@ public class UserSetting {
/**
* 登录超时时间(分钟)
*/
private long loginTimeout = 30;
private long loginTimeout = 60;
/**
* jwk文件路径若不指定则使用resources目录下的jwk.json

View File

@@ -18,7 +18,6 @@ import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import javax.sip.message.Response;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;

View File

@@ -97,7 +97,6 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
// return;
default:
}
// 构建UsernamePasswordAuthenticationToken,这里密码为null是因为提供了正确的JWT,实现自动登录
User user = new User();
user.setId(jwtUser.getUserId());

View File

@@ -11,7 +11,6 @@ import org.springframework.security.authentication.dao.DaoAuthenticationProvider
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
@@ -24,7 +23,6 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
@@ -58,33 +56,6 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private JwtAuthenticationFilter jwtAuthenticationFilter;
/**
* 描述: 静态资源放行,这里的放行,是不走 Spring Security 过滤器链
**/
@Override
public void configure(WebSecurity web) {
if (userSetting.getInterfaceAuthentication()) {
ArrayList<String> matchers = new ArrayList<>();
matchers.add("/");
matchers.add("/#/**");
matchers.add("/static/**");
matchers.add("/swagger-ui.html");
matchers.add("/swagger-ui/");
matchers.add("/index.html");
matchers.add("/doc.html");
matchers.add("/webjars/**");
matchers.add("/swagger-resources/**");
matchers.add("/v3/api-docs/**");
matchers.add("/js/**");
matchers.add("/api/device/query/snap/**");
matchers.add("/record_proxy/*/**");
matchers.add("/api/emit");
matchers.add("/favicon.ico");
// 可以直接访问的静态数据
web.ignoring().antMatchers(matchers.toArray(new String[0]));
}
}
/**
* 配置认证方式
*
@@ -105,15 +76,36 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
List<String> defaultExcludes = new ArrayList<>();
defaultExcludes.add("/");
defaultExcludes.add("/#/**");
defaultExcludes.add("/static/**");
List<String> defaultExcludes = userSetting.getInterfaceAuthenticationExcludes();
defaultExcludes.add("/swagger-ui.html");
defaultExcludes.add("/swagger-ui/**");
defaultExcludes.add("/swagger-resources/**");
defaultExcludes.add("/doc.html");
defaultExcludes.add("/doc.html#/**");
defaultExcludes.add("/v3/api-docs/**");
defaultExcludes.add("/index.html");
defaultExcludes.add("/webjars/**");
defaultExcludes.add("/js/**");
defaultExcludes.add("/api/device/query/snap/**");
defaultExcludes.add("/record_proxy/*/**");
defaultExcludes.add("/api/emit");
defaultExcludes.add("/favicon.ico");
defaultExcludes.add("/api/user/login");
defaultExcludes.add("/index/hook/**");
defaultExcludes.add("/api/device/query/snap/**");
defaultExcludes.add("/index/hook/abl/**");
defaultExcludes.add("/swagger-ui/**");
defaultExcludes.add("/doc.html#/**");
// defaultExcludes.add("/channel/log");
if (userSetting.getInterfaceAuthentication() && !userSetting.getInterfaceAuthenticationExcludes().isEmpty()) {
defaultExcludes.addAll(userSetting.getInterfaceAuthenticationExcludes());
}
http.headers().contentTypeOptions().disable()
.and().cors().configurationSource(configurationSource())

View File

@@ -2,6 +2,8 @@ package com.genersoft.iot.vmp.conf.security.dto;
import com.genersoft.iot.vmp.storager.dao.dto.Role;
import com.genersoft.iot.vmp.storager.dao.dto.User;
import lombok.Getter;
import lombok.Setter;
import org.springframework.security.core.CredentialsContainer;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;
@@ -19,8 +21,13 @@ public class LoginUser implements UserDetails, CredentialsContainer {
*/
private User user;
@Getter
@Setter
private String accessToken;
@Setter
@Getter
private String serverId;
/**
* 登录时间
@@ -104,11 +111,4 @@ public class LoginUser implements UserDetails, CredentialsContainer {
return user.getPushKey();
}
public String getAccessToken() {
return accessToken;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
}

View File

@@ -2,10 +2,12 @@ package com.genersoft.iot.vmp.gb28181.bean;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 业务分组
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Schema(description = "业务分组树")
public class GroupTree extends Group{

View File

@@ -2,10 +2,12 @@ package com.genersoft.iot.vmp.gb28181.bean;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 区域
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Schema(description = "区域树")
public class RegionTree extends Region {

View File

@@ -79,7 +79,7 @@ public class DeviceQuery {
@Parameter(name = "deviceId", description = "设备国标编号", required = true)
@GetMapping("/devices/{deviceId}")
public Device devices(@PathVariable String deviceId){
return deviceService.getDeviceByDeviceId(deviceId);
}
@@ -224,16 +224,19 @@ public class DeviceQuery {
"UDPudp传输TCP-ACTIVEtcp主动模式TCP-PASSIVEtcp被动模式", required = true)
@PostMapping("/transport/{deviceId}/{streamMode}")
public void updateTransport(@PathVariable String deviceId, @PathVariable String streamMode){
Assert.isTrue(streamMode.equalsIgnoreCase("UDP")
|| streamMode.equalsIgnoreCase("TCP-ACTIVE")
|| streamMode.equalsIgnoreCase("TCP-PASSIVE"), "数据流传输模式, 取值UDP/TCP-ACTIVE/TCP-PASSIVE");
Device device = deviceService.getDeviceByDeviceId(deviceId);
device.setStreamMode(streamMode);
device.setStreamMode(streamMode.toUpperCase());
deviceService.updateCustomDevice(device);
}
@Operation(summary = "添加设备信息", security = @SecurityRequirement(name = JwtUtils.HEADER))
@Parameter(name = "device", description = "设备", required = true)
@PostMapping("/device/add/")
public void addDevice(Device device){
@PostMapping("/device/add")
public void addDevice(@RequestBody Device device){
if (device == null || device.getDeviceId() == null) {
throw new ControllerException(ErrorCode.ERROR400);
@@ -250,8 +253,8 @@ public class DeviceQuery {
@Operation(summary = "更新设备信息", security = @SecurityRequirement(name = JwtUtils.HEADER))
@Parameter(name = "device", description = "设备", required = true)
@PostMapping("/device/update/")
public void updateDevice(Device device){
@PostMapping("/device/update")
public void updateDevice(@RequestBody Device device){
if (device == null || device.getDeviceId() == null || device.getId() <= 0) {
throw new ControllerException(ErrorCode.ERROR400);
}

View File

@@ -17,7 +17,6 @@ import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
@@ -89,7 +88,7 @@ public class MediaController {
}
if (streamInfo != null){
return new StreamContent(streamInfo);
return new StreamContent(streamInfo);
}else {
//获取流失败,重启拉流后重试一次
streamProxyService.stopByAppAndStream(app,stream);

View File

@@ -215,7 +215,7 @@ public class PlaybackController {
@Operation(summary = "回放倍速播放", security = @SecurityRequirement(name = JwtUtils.HEADER))
@Parameter(name = "streamId", description = "回放流ID", required = true)
@Parameter(name = "speed", description = "倍速0.25 0.5 1、2、4", required = true)
@Parameter(name = "speed", description = "倍速0.25 0.5 1、2、4、8", required = true)
@GetMapping("/speed/{streamId}/{speed}")
public void playSpeed(@PathVariable String streamId, @PathVariable Double speed) {
log.info("playSpeed: "+streamId+", "+speed);
@@ -225,10 +225,6 @@ public class PlaybackController {
log.warn("streamId不存在!");
throw new ControllerException(ErrorCode.ERROR400.getCode(), "streamId不存在");
}
if(speed != 0.25 && speed != 0.5 && speed != 1 && speed != 2.0 && speed != 4.0) {
log.warn("不支持的speed " + speed);
throw new ControllerException(ErrorCode.ERROR100.getCode(), "不支持的speed0.25 0.5 1、2、4");
}
Device device = deviceService.getDeviceByDeviceId(inviteInfo.getDeviceId());
DeviceChannel channel = channelService.getOneById(inviteInfo.getChannelId());
try {

View File

@@ -381,7 +381,7 @@ public interface DeviceMapper {
" OR device_id LIKE concat('%',#{query},'%') escape '/' " +
" OR ip LIKE concat('%',#{query},'%') escape '/')" +
"</if> " +
" order by create_time desc "+
" order by create_time desc, device_id " +
" </script>")
List<Device> getDeviceList(@Param("dataType") Integer dataType, @Param("query") String query, @Param("status") Boolean status);

View File

@@ -101,6 +101,7 @@ public class DeviceChannelProvider {
}
sqlBuild.append(" )");
}
sqlBuild.append("ORDER BY device_id");
return sqlBuild.toString();
}

View File

@@ -23,7 +23,6 @@ import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcPlayService;
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.utils.DateUtil;
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
@@ -41,9 +40,6 @@ import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import javax.sip.InvalidArgumentException;
import javax.sip.SipException;
import java.text.ParseException;
import javax.sip.InvalidArgumentException;
import javax.sip.SipException;
import javax.sip.message.Response;
@@ -117,6 +113,9 @@ public class DeviceChannelServiceImpl implements IDeviceChannelService {
@Override
public int updateChannels(Device device, List<DeviceChannel> channels) {
if (CollectionUtils.isEmpty(channels)) {
return 0;
}
List<DeviceChannel> addChannels = new ArrayList<>();
List<DeviceChannel> updateChannels = new ArrayList<>();
HashMap<String, DeviceChannel> channelsInStore = new HashMap<>();
@@ -442,7 +441,7 @@ public class DeviceChannelServiceImpl implements IDeviceChannelService {
deviceMobilePositionMapper.insertNewPosition(mobilePosition);
}
if (deviceChannel.getDeviceId().equals(deviceChannel.getDeviceId())) {
if (deviceChannel.getDeviceId().equals(device.getDeviceId())) {
deviceChannel.setDeviceId(null);
}
if (deviceChannel.getGpsTime() == null) {

View File

@@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.gb28181.service.impl;
import com.genersoft.iot.vmp.common.InviteSessionType;
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.conf.exception.ControllerException;
import com.genersoft.iot.vmp.conf.exception.ServiceException;
import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.common.enums.ChannelDataType;
@@ -145,6 +146,9 @@ public class GbChannelPlayServiceImpl implements IGbChannelPlayService {
deviceChannelPlayService.play(channel, record, callback);
} catch (PlayException e) {
callback.run(e.getCode(), e.getMsg(), null);
} catch (ControllerException e) {
log.error("[点播失败] {}({}), {}", channel.getGbName(), channel.getGbDeviceId(), e.getMsg());
callback.run(Response.BUSY_HERE, "busy here", null);
} catch (Exception e) {
log.error("[点播失败] {}({})", channel.getGbName(), channel.getGbDeviceId(), e);
callback.run(Response.BUSY_HERE, "busy here", null);

View File

@@ -669,7 +669,6 @@ public class PlatformServiceImpl implements IPlatformService {
mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
sessionManager.removeByStream(ssrcInfo.getApp(), ssrcInfo.getStream());
mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
}
}
}, userSetting.getPlayTimeout());

View File

@@ -28,6 +28,7 @@ import com.genersoft.iot.vmp.media.event.media.MediaDepartureEvent;
import com.genersoft.iot.vmp.media.event.media.MediaNotFoundEvent;
import com.genersoft.iot.vmp.media.service.IMediaServerService;
import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo;
import com.genersoft.iot.vmp.service.ICloudRecordService;
import com.genersoft.iot.vmp.service.IReceiveRtpServerService;
import com.genersoft.iot.vmp.service.ISendRtpServerService;
import com.genersoft.iot.vmp.service.bean.*;
@@ -1047,8 +1048,8 @@ public class PlayServiceImpl implements IPlayService {
null);
return;
}
log.info("[录像下载] deviceId: {}, channelId: {}, 下载速度:{}, 收流端口:{}, 收流模式:{}, SSRC: {}({}), SSRC校验{}",
device.getDeviceId(), channel.getDeviceId(), downloadSpeed, ssrcInfo.getPort(), device.getStreamMode(),
log.info("[录像下载] deviceId: {}, channelId: {}, 开始时间: {}, 结束时间: {} 下载速度:{}, 收流端口:{}, 收流模式:{}, SSRC: {}({}), SSRC校验{}",
device.getDeviceId(), channel.getDeviceId(), startTime, endTime, downloadSpeed, ssrcInfo.getPort(), device.getStreamMode(),
ssrcInfo.getSsrc(), String.format("%08x", Long.parseLong(ssrcInfo.getSsrc())).toUpperCase(),
device.isSsrcCheck());
@@ -1723,7 +1724,14 @@ public class PlayServiceImpl implements IPlayService {
throw new PlayException(Response.SERVER_INTERNAL_ERROR, "server internal error");
}
DeviceChannel deviceChannel = deviceChannelService.getOneForSourceById(channel.getGbId());
play(device, deviceChannel, callback);
MediaServer mediaServerItem = getNewMediaServerItem(device);
if (mediaServerItem == null) {
log.warn("[点播] 未找到可用的zlm deviceId: {},channelId:{}", device.getDeviceId(), deviceChannel.getDeviceId());
throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到可用的zlm");
}
play(mediaServerItem, device, deviceChannel, null, record, callback);
}
@Override

View File

@@ -44,7 +44,7 @@ public class MediaInfo {
private Integer audioChannels;
@Schema(description = "音频采样率")
private Integer audioSampleRate;
@Schema(description = "音频采样率")
@Schema(description = "时长")
private Long duration;
@Schema(description = "在线")
private Boolean online;

View File

@@ -1,7 +1,9 @@
package com.genersoft.iot.vmp.media.bean;
import com.genersoft.iot.vmp.media.zlm.dto.hook.OnRecordMp4HookParam;
import lombok.Data;
@Data
public class RecordInfo {
private String fileName;
private String filePath;
@@ -24,70 +26,6 @@ public class RecordInfo {
return recordInfo;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public String getFilePath() {
return filePath;
}
public void setFilePath(String filePath) {
this.filePath = filePath;
}
public long getFileSize() {
return fileSize;
}
public void setFileSize(long fileSize) {
this.fileSize = fileSize;
}
public String getFolder() {
return folder;
}
public void setFolder(String folder) {
this.folder = folder;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public long getStartTime() {
return startTime;
}
public void setStartTime(long startTime) {
this.startTime = startTime;
}
public double getTimeLen() {
return timeLen;
}
public void setTimeLen(double timeLen) {
this.timeLen = timeLen;
}
public String getParams() {
return params;
}
public void setParams(String params) {
this.params = params;
}
@Override
public String toString() {
return "RecordInfo{" +

View File

@@ -70,4 +70,9 @@ public interface IMediaNodeServerService {
List<String> listRtpServer(MediaServer mediaServer);
void loadMP4File(MediaServer mediaServer, String app, String stream, String datePath);
void seekRecordStamp(MediaServer mediaServer, String app, String stream, Double stamp, String schema);
void setRecordSpeed(MediaServer mediaServer, String app, String stream, Integer speed, String schema);
}

View File

@@ -159,4 +159,10 @@ public interface IMediaServerService {
int createRTPServer(MediaServer mediaServerItem, String streamId, long ssrc, Integer port, boolean onlyAuto, boolean disableAudio, boolean reUsePort, Integer tcpMode);
List<String> listRtpServer(MediaServer mediaServer);
StreamInfo loadMP4File(MediaServer mediaServer, String app, String stream, String datePath);
void seekRecordStamp(MediaServer mediaServer, String app, String stream, Double stamp, String schema);
void setRecordSpeed(MediaServer mediaServer, String app, String stream, Integer speed, String schema);
}

View File

@@ -311,6 +311,10 @@ public class MediaServerServiceImpl implements IMediaServerService {
if (mediaServerInRedis == null || !ssrcFactory.hasMediaServerSSRC(mediaServerInDataBase.getId())) {
ssrcFactory.initMediaServerSSRC(mediaServerInDataBase.getId(),null);
}
if (mediaSerItem.getSecret() != null && !mediaServerInDataBase.getSecret().equals(mediaSerItem.getSecret())) {
mediaServerInDataBase.setSecret(mediaSerItem.getSecret());
}
mediaServerInDataBase.setSecret(mediaSerItem.getSecret());
String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + userSetting.getServerId();
redisTemplate.opsForHash().put(key, mediaServerInDataBase.getId(), mediaServerInDataBase);
if (mediaServerInDataBase.isStatus()) {
@@ -966,4 +970,35 @@ public class MediaServerServiceImpl implements IMediaServerService {
}
mediaNodeServerService.stopProxy(mediaServer, streamKey);
}
@Override
public StreamInfo loadMP4File(MediaServer mediaServer, String app, String stream, String datePath) {
IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType());
if (mediaNodeServerService == null) {
log.info("[loadMP4File] 失败, mediaServer的类型 {},未找到对应的实现类", mediaServer.getType());
throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到mediaServer对应的实现类");
}
mediaNodeServerService.loadMP4File(mediaServer, app, stream, datePath);
return getStreamInfoByAppAndStream(mediaServer, app, stream, null, null);
}
@Override
public void seekRecordStamp(MediaServer mediaServer, String app, String stream, Double stamp, String schema) {
IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType());
if (mediaNodeServerService == null) {
log.info("[seekRecordStamp] 失败, mediaServer的类型 {},未找到对应的实现类", mediaServer.getType());
throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到mediaServer对应的实现类");
}
mediaNodeServerService.seekRecordStamp(mediaServer, app, stream, stamp, schema);
}
@Override
public void setRecordSpeed(MediaServer mediaServer, String app, String stream, Integer speed, String schema) {
IMediaNodeServerService mediaNodeServerService = nodeServerServiceMap.get(mediaServer.getType());
if (mediaNodeServerService == null) {
log.info("[setRecordSpeed] 失败, mediaServer的类型 {},未找到对应的实现类", mediaServer.getType());
throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到mediaServer对应的实现类");
}
mediaNodeServerService.setRecordSpeed(mediaServer, app, stream, speed, schema);
}
}

View File

@@ -126,7 +126,6 @@ public class ZLMHttpHookListener {
@ResponseBody
@PostMapping(value = "/on_stream_changed", produces = "application/json;charset=UTF-8")
public HookResult onStreamChanged(@RequestBody OnStreamChangedHookParam param) {
MediaServer mediaServer = mediaServerService.getOne(param.getMediaServerId());
if (mediaServer == null) {
return HookResult.SUCCESS();
@@ -190,6 +189,8 @@ public class ZLMHttpHookListener {
JSONObject ret = new JSONObject();
boolean close = mediaService.closeStreamOnNoneReader(param.getMediaServerId(), param.getApp(), param.getStream(), param.getSchema());
log.info("[ZLM HOOK]流无人观看是否触发关闭:{}, {}->{}->{}/{}", close, param.getMediaServerId(), param.getSchema(),
param.getApp(), param.getStream());
ret.put("code", 0);
ret.put("close", close);
return ret;

View File

@@ -169,7 +169,7 @@ public class ZLMMediaNodeServerService implements IMediaNodeServerService {
return true;
}else {
log.info("[zlm-deleteRecordDirectory] 删除磁盘文件错误, server: {} {}:{}->{}/{}, 结果: {}", mediaServer.getId(), app, stream, date, fileName, jsonObject);
return false;
throw new ControllerException(ErrorCode.ERROR100.getCode(), "删除磁盘文件失败");
}
}
@@ -549,4 +549,37 @@ public class ZLMMediaNodeServerService implements IMediaNodeServerService {
}
return result;
}
@Override
public void loadMP4File(MediaServer mediaServer, String app, String stream, String datePath) {
JSONObject jsonObject = zlmresTfulUtils.loadMP4File(mediaServer, app, stream, datePath);
if (jsonObject == null) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "请求失败");
}
if (jsonObject.getInteger("code") != 0) {
throw new ControllerException(jsonObject.getInteger("code"), jsonObject.getString("msg"));
}
}
@Override
public void seekRecordStamp(MediaServer mediaServer, String app, String stream, Double stamp, String schema) {
JSONObject jsonObject = zlmresTfulUtils.seekRecordStamp(mediaServer, app, stream, stamp, schema);
if (jsonObject == null) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "请求失败");
}
if (jsonObject.getInteger("code") != 0) {
throw new ControllerException(jsonObject.getInteger("code"), jsonObject.getString("msg"));
}
}
@Override
public void setRecordSpeed(MediaServer mediaServer, String app, String stream, Integer speed, String schema) {
JSONObject jsonObject = zlmresTfulUtils.setRecordSpeed(mediaServer, app, stream, speed, schema);
if (jsonObject == null) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "请求失败");
}
if (jsonObject.getInteger("code") != 0) {
throw new ControllerException(jsonObject.getInteger("code"), jsonObject.getString("msg"));
}
}
}

View File

@@ -25,6 +25,7 @@ public class ZLMRESTfulUtils {
private OkHttpClient client;
public interface RequestCallback{
void run(JSONObject response);
}
@@ -415,4 +416,34 @@ public class ZLMRESTfulUtils {
param.put("name", fileName);
return sendPost(mediaServerItem, "deleteRecordDirectory",param, null);
}
public JSONObject loadMP4File(MediaServer mediaServer, String app, String stream, String datePath) {
Map<String, Object> param = new HashMap<>(1);
param.put("vhost", "__defaultVhost__");
param.put("app", app);
param.put("stream", stream);
param.put("file_path", datePath);
param.put("file_repeat", "0");
return sendPost(mediaServer, "loadMP4File",param, null);
}
public JSONObject setRecordSpeed(MediaServer mediaServer, String app, String stream, int speed, String schema) {
Map<String, Object> param = new HashMap<>(1);
param.put("vhost", "__defaultVhost__");
param.put("app", app);
param.put("stream", stream);
param.put("speed", speed);
param.put("schema", schema);
return sendPost(mediaServer, "setRecordSpeed",param, null);
}
public JSONObject seekRecordStamp(MediaServer mediaServer, String app, String stream, Double stamp, String schema) {
Map<String, Object> param = new HashMap<>(1);
param.put("vhost", "__defaultVhost__");
param.put("app", app);
param.put("stream", stream);
param.put("stamp", stamp);
param.put("schema", schema);
return sendPost(mediaServer, "seekRecordStamp",param, null);
}
}

View File

@@ -1,12 +1,15 @@
package com.genersoft.iot.vmp.gb28181.service;
package com.genersoft.iot.vmp.service;
import com.alibaba.fastjson2.JSONArray;
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.service.bean.CloudRecordItem;
import com.genersoft.iot.vmp.service.bean.DownloadFileInfo;
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
import com.github.pagehelper.PageInfo;
import java.util.List;
import java.util.Set;
/**
* 云端录像管理
@@ -17,7 +20,7 @@ public interface ICloudRecordService {
/**
* 分页回去云端录像列表
*/
PageInfo<CloudRecordItem> getList(int page, int count, String query, String app, String stream, String startTime, String endTime, List<MediaServer> mediaServerItems, String callId);
PageInfo<CloudRecordItem> getList(int page, int count, String query, String app, String stream, String startTime, String endTime, List<MediaServer> mediaServerItems, String callId, Boolean ascOrder);
/**
* 获取所有的日期
@@ -52,4 +55,15 @@ public interface ICloudRecordService {
DownloadFileInfo getPlayUrlPath(Integer recordId);
List<CloudRecordItem> getAllList(String query, String app, String stream, String startTime, String endTime, List<MediaServer> mediaServerItems, String callId, List<Integer> ids);
/**
* 加载录像文件形成录像流
*/
void loadRecord(String app, String stream, String date, ErrorCallback<StreamInfo> callback);
void seekRecord(String mediaServerId,String app, String stream, Double seek, String schema);
void setRecordSpeed(String mediaServerId, String app, String stream, Integer speed, String schema);
void deleteFileByIds(Set<Integer> ids);
}

View File

@@ -15,52 +15,52 @@ public class CloudRecordItem {
* 主键
*/
private int id;
/**
* 应用名
*/
private String app;
/**
* 流
*/
private String stream;
/**
* 健全ID
*/
private String callId;
/**
* 开始时间
*/
private long startTime;
/**
* 结束时间
*/
private long endTime;
/**
* ZLM Id
*/
private String mediaServerId;
/**
* 文件名称
*/
private String fileName;
/**
* 文件路径
*/
private String filePath;
/**
* 文件夹
*/
private String folder;
/**
* 收藏,收藏的文件不移除
*/
@@ -70,16 +70,16 @@ public class CloudRecordItem {
* 保留,收藏的文件不移除
*/
private Boolean reserve;
/**
* 文件大小
*/
private long fileSize;
/**
* 文件时长
*/
private long timeLen;
private double timeLen;
/**
* 所属服务ID
@@ -96,7 +96,7 @@ public class CloudRecordItem {
cloudRecordItem.setFileSize(param.getRecordInfo().getFileSize());
cloudRecordItem.setFilePath(param.getRecordInfo().getFilePath());
cloudRecordItem.setMediaServerId(param.getMediaServer().getId());
cloudRecordItem.setTimeLen((long) param.getRecordInfo().getTimeLen() * 1000);
cloudRecordItem.setTimeLen(param.getRecordInfo().getTimeLen() * 1000);
cloudRecordItem.setEndTime((param.getRecordInfo().getStartTime() + (long)param.getRecordInfo().getTimeLen()) * 1000);
Map<String, String> paramsMap = MediaServerUtils.urlParamToMap(param.getRecordInfo().getParams());
if (paramsMap.get("callId") != null) {

View File

@@ -2,16 +2,22 @@ package com.genersoft.iot.vmp.service.impl;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.conf.exception.ControllerException;
import com.genersoft.iot.vmp.gb28181.service.ICloudRecordService;
import com.genersoft.iot.vmp.media.bean.MediaInfo;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.media.event.hook.Hook;
import com.genersoft.iot.vmp.media.event.hook.HookSubscribe;
import com.genersoft.iot.vmp.media.event.hook.HookType;
import com.genersoft.iot.vmp.media.event.media.MediaRecordMp4Event;
import com.genersoft.iot.vmp.media.service.IMediaServerService;
import com.genersoft.iot.vmp.media.zlm.AssistRESTfulUtils;
import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo;
import com.genersoft.iot.vmp.service.ICloudRecordService;
import com.genersoft.iot.vmp.service.bean.CloudRecordItem;
import com.genersoft.iot.vmp.service.bean.DownloadFileInfo;
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
import com.genersoft.iot.vmp.service.redisMsg.IRedisRpcPlayService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.dao.CloudRecordServiceMapper;
@@ -28,12 +34,10 @@ import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import java.io.File;
import java.time.LocalDate;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.*;
@Slf4j
@Service
@@ -57,9 +61,12 @@ public class CloudRecordServiceImpl implements ICloudRecordService {
@Autowired
private IRedisRpcPlayService redisRpcPlayService;
@Autowired
private HookSubscribe subscribe;
@Override
public PageInfo<CloudRecordItem> getList(int page, int count, String query, String app, String stream, String startTime,
String endTime, List<MediaServer> mediaServerItems, String callId) {
String endTime, List<MediaServer> mediaServerItems, String callId, Boolean ascOrder) {
// 开始时间和结束时间在数据库中都是以秒为单位的
Long startTimeStamp = null;
Long endTimeStamp = null;
@@ -84,7 +91,7 @@ public class CloudRecordServiceImpl implements ICloudRecordService {
.replaceAll("_", "/_");
}
List<CloudRecordItem> all = cloudRecordServiceMapper.getList(query, app, stream, startTimeStamp, endTimeStamp,
callId, mediaServerItems, null);
callId, mediaServerItems, null, ascOrder);
return new PageInfo<>(all);
}
@@ -100,7 +107,7 @@ public class CloudRecordServiceImpl implements ICloudRecordService {
long startTimeStamp = startDate.atStartOfDay().toInstant(ZoneOffset.ofHours(8)).toEpochMilli();
long endTimeStamp = endDate.atStartOfDay().toInstant(ZoneOffset.ofHours(8)).toEpochMilli();
List<CloudRecordItem> cloudRecordItemList = cloudRecordServiceMapper.getList(null, app, stream, startTimeStamp,
endTimeStamp, null, mediaServerItems, null);
endTimeStamp, null, mediaServerItems, null, null);
if (cloudRecordItemList.isEmpty()) {
return new ArrayList<>();
}
@@ -213,7 +220,7 @@ public class CloudRecordServiceImpl implements ICloudRecordService {
}
List<CloudRecordItem> all = cloudRecordServiceMapper.getList(null, app, stream, startTimeStamp, endTimeStamp,
callId, mediaServerItems, null);
callId, mediaServerItems, null, null);
if (all.isEmpty()) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到待收藏的视频");
}
@@ -273,6 +280,96 @@ public class CloudRecordServiceImpl implements ICloudRecordService {
}
return cloudRecordServiceMapper.getList(query, app, stream, startTimeStamp, endTimeStamp,
callId, mediaServerItems, ids);
callId, mediaServerItems, ids, null);
}
@Override
public void loadRecord(String app, String stream, String date, ErrorCallback<StreamInfo> callback) {
long startTimestamp = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestampMs(date + " 00:00:00");
long endTimestamp = startTimestamp + 24 * 60 * 60 * 1000;
List<CloudRecordItem> recordItemList = cloudRecordServiceMapper.getList(null, app, stream, startTimestamp, endTimestamp, null, null, null, false);
if (recordItemList.isEmpty()) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "此时间无录像");
}
String mediaServerId = recordItemList.get(0).getMediaServerId();
MediaServer mediaServer = mediaServerService.getOne(mediaServerId);
if (mediaServer == null) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "媒体节点不存在: " + mediaServerId);
}
String buildApp = "mp4_record";
String buildStream = app + "_" + stream + "_" + date;
MediaInfo mediaInfo = mediaServerService.getMediaInfo(mediaServer, buildApp, buildStream);
if (mediaInfo != null) {
if (callback != null) {
StreamInfo streamInfo = mediaServerService.getStreamInfoByAppAndStream(mediaServer, buildApp, buildStream, mediaInfo, null);
callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo);
}
return;
}
Hook hook = Hook.getInstance(HookType.on_media_arrival, buildApp, buildStream, mediaServerId);
subscribe.addSubscribe(hook, (hookData) -> {
StreamInfo streamInfo = mediaServerService.getStreamInfoByAppAndStream(mediaServer, buildApp, buildStream, hookData.getMediaInfo(), null);
if (callback != null) {
callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo);
}
});
String dateDir = recordItemList.get(0).getFilePath().substring(0, recordItemList.get(0).getFilePath().lastIndexOf("/"));
mediaServerService.loadMP4File(mediaServer, buildApp, buildStream, dateDir);
}
@Override
public void seekRecord(String mediaServerId,String app, String stream, Double seek, String schema) {
MediaServer mediaServer = mediaServerService.getOne(mediaServerId);
if (mediaServer == null) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "媒体节点不存在: " + mediaServerId);
}
mediaServerService.seekRecordStamp(mediaServer, app, stream, seek, schema);
}
@Override
public void setRecordSpeed(String mediaServerId, String app, String stream, Integer speed, String schema) {
MediaServer mediaServer = mediaServerService.getOne(mediaServerId);
if (mediaServer == null) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "媒体节点不存在: " + mediaServerId);
}
mediaServerService.setRecordSpeed(mediaServer, app, stream, speed, schema);
}
@Override
public void deleteFileByIds(Set<Integer> ids) {
log.info("[删除录像文件] ids: {}", ids.toArray());
List<CloudRecordItem> cloudRecordItemList = cloudRecordServiceMapper.queryRecordByIds(ids);
if (cloudRecordItemList.isEmpty()) {
return;
}
List<CloudRecordItem> cloudRecordItemIdListForDelete = new ArrayList<>();
StringBuilder stringBuilder = new StringBuilder();
for (CloudRecordItem cloudRecordItem : cloudRecordItemList) {
String date = new File(cloudRecordItem.getFilePath()).getParentFile().getName();
MediaServer mediaServer = mediaServerService.getOne(cloudRecordItem.getMediaServerId());
try {
boolean deleteResult = mediaServerService.deleteRecordDirectory(mediaServer, cloudRecordItem.getApp(),
cloudRecordItem.getStream(), date, cloudRecordItem.getFileName());
if (deleteResult) {
log.warn("[录像文件] 删除磁盘文件成功: {}", cloudRecordItem.getFilePath());
cloudRecordItemIdListForDelete.add(cloudRecordItem);
}
}catch (ControllerException e) {
if (stringBuilder.length() > 0) {
stringBuilder.append(", ");
}
stringBuilder.append(cloudRecordItem.getFileName());
}
}
if (!cloudRecordItemIdListForDelete.isEmpty()) {
cloudRecordServiceMapper.deleteList(cloudRecordItemIdListForDelete);
}
if (stringBuilder.length() > 0) {
stringBuilder.append(" 删除失败");
throw new ControllerException(ErrorCode.ERROR100.getCode(), stringBuilder.toString());
}
}
}

View File

@@ -79,9 +79,8 @@ public class RecordPlanServiceImpl implements IRecordPlanService {
Map<Integer, StreamInfo> recordStreamMap = new HashMap<>();
@Scheduled(fixedRate = 10, timeUnit = TimeUnit.MINUTES)
@Scheduled(fixedRate = 1, timeUnit = TimeUnit.MINUTES)
public void execution() {
log.info("[录制计划] 执行");
// 查询现在需要录像的通道Id
List<Integer> startChannelIdList = queryCurrentChannelRecord();
@@ -133,7 +132,7 @@ public class RecordPlanServiceImpl implements IRecordPlanService {
// 获取当前时间在一周内的序号, 数据库存储的从第几个30分钟开始, 0-47, 包括首尾
LocalDateTime now = LocalDateTime.now();
int week = now.getDayOfWeek().getValue();
int index = now.getHour() * 2 + (now.getMinute() > 30?1:0);
int index = now.getHour() * 60 + now.getMinute();
// 查询现在需要录像的通道Id
return recordPlanMapper.queryRecordIng(week, index);
@@ -221,7 +220,7 @@ public class RecordPlanServiceImpl implements IRecordPlanService {
}
}
// TODO 更新录像队列
}
@Override

View File

@@ -6,7 +6,7 @@ import com.genersoft.iot.vmp.conf.redis.RedisRpcConfig;
import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcMessage;
import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcRequest;
import com.genersoft.iot.vmp.conf.redis.bean.RedisRpcResponse;
import com.genersoft.iot.vmp.gb28181.service.ICloudRecordService;
import com.genersoft.iot.vmp.service.ICloudRecordService;
import com.genersoft.iot.vmp.service.bean.DownloadFileInfo;
import com.genersoft.iot.vmp.service.redisMsg.dto.RedisRpcController;
import com.genersoft.iot.vmp.service.redisMsg.dto.RedisRpcMapping;

View File

@@ -5,6 +5,7 @@ import com.genersoft.iot.vmp.service.bean.CloudRecordItem;
import org.apache.ibatis.annotations.*;
import java.util.List;
import java.util.Set;
@Mapper
public interface CloudRecordServiceMapper {
@@ -55,12 +56,13 @@ public interface CloudRecordServiceMapper {
" <if test= 'ids != null ' > and id in " +
" <foreach collection='ids' item='item' open='(' separator=',' close=')' > #{item}</foreach>" +
" </if>" +
" order by start_time desc" +
" <if test= 'ascOrder != null and ascOrder == true'> order by start_time asc</if>" +
" <if test= 'ascOrder == null or ascOrder == false'> order by start_time desc</if>" +
" </script>")
List<CloudRecordItem> getList(@Param("query") String query, @Param("app") String app, @Param("stream") String stream,
@Param("startTimeStamp")Long startTimeStamp, @Param("endTimeStamp")Long endTimeStamp,
@Param("callId")String callId, List<MediaServer> mediaServerItemList,
List<Integer> ids);
List<Integer> ids, @Param("ascOrder") Boolean ascOrder);
@Select(" <script>" +
@@ -124,4 +126,26 @@ public interface CloudRecordServiceMapper {
"where id = #{id}" +
" </script>")
CloudRecordItem queryOne(@Param("id") Integer id);
@Select(" <script>" +
"select media_server_id " +
" 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 end_time &gt;= #{startTimeStamp}</if>" +
" <if test= 'endTimeStamp != null '> and start_time &lt;= #{endTimeStamp}</if>" +
" group by media_server_id" +
" </script>")
List<String> queryMediaServerId(@Param("app") String app,
@Param("stream") String stream,
@Param("startTimeStamp")Long startTimeStamp,
@Param("endTimeStamp")Long endTimeStamp);
@Select(" <script>" +
"select * " +
" from wvp_cloud_record where id in " +
" <foreach collection='ids' item='item' open='(' separator=',' close=')' > #{item}</foreach>" +
" </script>")
List<CloudRecordItem> queryRecordByIds(Set<Integer> ids);
}

View File

@@ -51,6 +51,7 @@ public class StreamPushServiceImpl implements IStreamPushService {
private UserSetting userSetting;
@Autowired
private IMediaServerService mediaServerService;
@Autowired
@@ -123,25 +124,21 @@ public class StreamPushServiceImpl implements IStreamPushService {
public void onApplicationEvent(MediaDepartureEvent event) {
// 兼容流注销时类型从redis记录获取
MediaInfo mediaInfo = redisCatchStorage.getStreamInfo(
event.getApp(), event.getStream(), event.getMediaServer().getId());
MediaInfo mediaInfo = redisCatchStorage.getPushListItem(event.getApp(), event.getStream());
if (mediaInfo != null) {
log.info("[推流信息] 查询到redis存在推流缓存 开始清理,{}/{}", event.getApp(), event.getStream());
String type = OriginType.values()[mediaInfo.getOriginType()].getType();
redisCatchStorage.removeStream(event.getMediaServer().getId(), type, event.getApp(), event.getStream());
if ("PUSH".equalsIgnoreCase(type)) {
// 冗余数据,自己系统中自用
redisCatchStorage.removePushListItem(event.getApp(), event.getStream(), event.getMediaServer().getId());
}
if (type != null) {
// 发送流变化redis消息
JSONObject jsonObject = new JSONObject();
jsonObject.put("serverId", userSetting.getServerId());
jsonObject.put("app", event.getApp());
jsonObject.put("stream", event.getStream());
jsonObject.put("register", false);
jsonObject.put("mediaServerId", event.getMediaServer().getId());
redisCatchStorage.sendStreamChangeMsg(type, jsonObject);
}
// 冗余数据,自己系统中自用
redisCatchStorage.removePushListItem(event.getApp(), event.getStream(), event.getMediaServer().getId());
// 发送流变化redis消息
JSONObject jsonObject = new JSONObject();
jsonObject.put("serverId", userSetting.getServerId());
jsonObject.put("app", event.getApp());
jsonObject.put("stream", event.getStream());
jsonObject.put("register", false);
jsonObject.put("mediaServerId", event.getMediaServer().getId());
redisCatchStorage.sendStreamChangeMsg(type, jsonObject);
}
StreamPush streamPush = getPush(event.getApp(), event.getStream());
if (streamPush == null) {
@@ -576,13 +573,13 @@ public class StreamPushServiceImpl implements IStreamPushService {
if (streamPushList.isEmpty()) {
return;
}
List<CommonGBChannel> commonGBChannelList = new ArrayList<>();
Set<Integer> channelIds = new HashSet<>();
streamPushList.stream().forEach(streamPush -> {
if (streamPush.getGbDeviceId() != null) {
commonGBChannelList.add(streamPush.buildCommonGBChannel());
channelIds.add(streamPush.getGbId());
}
});
streamPushMapper.batchDel(streamPushList);
gbChannelService.delete(ids);
gbChannelService.delete(channelIds);
}
}

View File

@@ -1,6 +1,7 @@
package com.genersoft.iot.vmp.utils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.DigestUtils;
import oshi.SystemInfo;
import oshi.hardware.CentralProcessor;
import oshi.hardware.GlobalMemory;
@@ -9,6 +10,7 @@ import oshi.hardware.NetworkIF;
import oshi.software.os.OperatingSystem;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -142,4 +144,19 @@ public class SystemInfoUtils {
}
return result;
}
public static String getHardwareId(){
SystemInfo systemInfo = new SystemInfo();
HardwareAbstractionLayer hardware = systemInfo.getHardware();
// CPU ID
String cpuId = hardware.getProcessor().getProcessorIdentifier().getProcessorID();
// 主板序号
String serialNumber = hardware.getComputerSystem().getSerialNumber();
return DigestUtils.md5DigestAsHex(
(
DigestUtils.md5DigestAsHex(cpuId.getBytes(StandardCharsets.UTF_8)) +
DigestUtils.md5DigestAsHex(serialNumber.getBytes(StandardCharsets.UTF_8))
).getBytes(StandardCharsets.UTF_8));
}
}

View File

@@ -1,15 +1,22 @@
package com.genersoft.iot.vmp.vmanager.cloudRecord;
import com.alibaba.fastjson2.JSONArray;
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.conf.exception.ControllerException;
import com.genersoft.iot.vmp.conf.security.JwtUtils;
import com.genersoft.iot.vmp.gb28181.service.ICloudRecordService;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.media.service.IMediaServerService;
import com.genersoft.iot.vmp.service.ICloudRecordService;
import com.genersoft.iot.vmp.service.bean.CloudRecordItem;
import com.genersoft.iot.vmp.service.bean.DownloadFileInfo;
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
import com.genersoft.iot.vmp.service.bean.InviteErrorCode;
import com.genersoft.iot.vmp.streamPush.bean.BatchRemoveParam;
import com.genersoft.iot.vmp.utils.DateUtil;
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
import com.genersoft.iot.vmp.vmanager.bean.StreamContent;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
import com.genersoft.iot.vmp.vmanager.cloudRecord.bean.CloudRecordUrl;
import com.github.pagehelper.PageInfo;
import io.swagger.v3.oas.annotations.Operation;
@@ -20,12 +27,15 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.async.DeferredResult;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
@@ -46,6 +56,9 @@ public class CloudRecordController {
@Autowired
private IMediaServerService mediaServerService;
@Autowired
private UserSetting userSetting;
@ResponseBody
@GetMapping("/date/list")
@@ -55,7 +68,12 @@ public class CloudRecordController {
@Parameter(name = "year", description = "年,置空则查询当年", required = false)
@Parameter(name = "month", description = "月,置空则查询当月", required = false)
@Parameter(name = "mediaServerId", description = "流媒体ID置空则查询全部", required = false)
public List<String> openRtpServer(@RequestParam(required = true) String app, @RequestParam(required = true) String stream, @RequestParam(required = false) Integer year, @RequestParam(required = false) Integer month, @RequestParam(required = false) String mediaServerId
public List<String> openRtpServer(
@RequestParam(required = true) String app,
@RequestParam(required = true) String stream,
@RequestParam(required = false) Integer year,
@RequestParam(required = false) Integer month,
@RequestParam(required = false) String mediaServerId
) {
log.info("[云端录像] 查询存在云端录像的日期 app->{}, stream->{}, mediaServerId->{}, year->{}, month->{}", app, stream, mediaServerId, year, month);
@@ -96,7 +114,17 @@ public class CloudRecordController {
@Parameter(name = "endTime", description = "结束时间(yyyy-MM-dd HH:mm:ss)", required = false)
@Parameter(name = "mediaServerId", description = "流媒体ID置空则查询全部流媒体", required = false)
@Parameter(name = "callId", description = "每次录像的唯一标识,置空则查询全部流媒体", required = false)
public PageInfo<CloudRecordItem> openRtpServer(@RequestParam(required = false) String query, @RequestParam(required = false) String app, @RequestParam(required = false) String stream, @RequestParam int page, @RequestParam int count, @RequestParam(required = false) String startTime, @RequestParam(required = false) String endTime, @RequestParam(required = false) String mediaServerId, @RequestParam(required = false) String callId
@Parameter(name = "ascOrder", description = "是否升序排序, 升序: true 降序: false", required = false)
public PageInfo<CloudRecordItem> openRtpServer(@RequestParam(required = false) String query,
@RequestParam(required = false) String app,
@RequestParam(required = false) String stream,
@RequestParam int page,
@RequestParam int count,
@RequestParam(required = false) String startTime,
@RequestParam(required = false) String endTime,
@RequestParam(required = false) String mediaServerId,
@RequestParam(required = false) String callId,
@RequestParam(required = false) Boolean ascOrder
) {
log.info("[云端录像] 查询 app->{}, stream->{}, mediaServerId->{}, page->{}, count->{}, startTime->{}, endTime->{}, callId->{}", app, stream, mediaServerId, page, count, startTime, endTime, callId);
@@ -130,7 +158,7 @@ public class CloudRecordController {
if (callId != null && ObjectUtils.isEmpty(callId.trim())) {
callId = null;
}
return cloudRecordService.getList(page, count, query, app, stream, startTime, endTime, mediaServers, callId);
return cloudRecordService.getList(page, count, query, app, stream, startTime, endTime, mediaServers, callId, ascOrder);
}
@ResponseBody
@@ -220,6 +248,113 @@ public class CloudRecordController {
return cloudRecordService.getPlayUrlPath(recordId);
}
@ResponseBody
@GetMapping("/loadRecord")
@Operation(summary = "加载录像文件形成播放地址")
@Parameter(name = "app", description = "应用名", required = true)
@Parameter(name = "stream", description = "流ID", required = true)
@Parameter(name = "date", description = "日期, 例如 2025-04-10", required = true)
public DeferredResult<WVPResult<StreamContent>> loadRecord(
HttpServletRequest request,
@RequestParam(required = true) String app,
@RequestParam(required = true) String stream,
@RequestParam(required = true) String date
) {
DeferredResult<WVPResult<StreamContent>> result = new DeferredResult<>();
result.onTimeout(()->{
log.info("[加载录像文件超时] app={}, stream={}, date={}", app, stream, date);
WVPResult<StreamContent> wvpResult = new WVPResult<>();
wvpResult.setCode(ErrorCode.ERROR100.getCode());
wvpResult.setMsg("加载录像文件超时");
result.setResult(wvpResult);
});
ErrorCallback<StreamInfo> callback = (code, msg, streamInfo) -> {
WVPResult<StreamContent> wvpResult = new WVPResult<>();
if (code == InviteErrorCode.SUCCESS.getCode()) {
wvpResult.setCode(ErrorCode.SUCCESS.getCode());
wvpResult.setMsg(ErrorCode.SUCCESS.getMsg());
if (streamInfo != null) {
if (userSetting.getUseSourceIpAsStreamIp()) {
streamInfo=streamInfo.clone();//深拷贝
String host;
try {
URL url=new URL(request.getRequestURL().toString());
host=url.getHost();
} catch (MalformedURLException e) {
host=request.getLocalAddr();
}
streamInfo.changeStreamIp(host);
}
if (!org.springframework.util.ObjectUtils.isEmpty(streamInfo.getMediaServer().getTranscodeSuffix()) && !"null".equalsIgnoreCase(streamInfo.getMediaServer().getTranscodeSuffix())) {
streamInfo.setStream(streamInfo.getStream() + "_" + streamInfo.getMediaServer().getTranscodeSuffix());
}
wvpResult.setData(new StreamContent(streamInfo));
}else {
wvpResult.setCode(code);
wvpResult.setMsg(msg);
}
}else {
wvpResult.setCode(code);
wvpResult.setMsg(msg);
}
result.setResult(wvpResult);
};
cloudRecordService.loadRecord(app, stream, date, callback);
return result;
}
@ResponseBody
@GetMapping("/seek")
@Operation(summary = "定位录像播放到制定位置")
@Parameter(name = "mediaServerId", description = "使用的节点Id", required = true)
@Parameter(name = "stream", description = "流ID", required = true)
@Parameter(name = "seek", description = "要定位的时间位置,从录像开始的时间算起", required = true)
public void seekRecord(
@RequestParam(required = true) String mediaServerId,
@RequestParam(required = true) String app,
@RequestParam(required = true) String stream,
@RequestParam(required = true) Double seek,
@RequestParam(required = false) String schema
) {
if (schema == null) {
schema = "ts";
}
cloudRecordService.seekRecord(mediaServerId, app, stream, seek, schema);
}
@ResponseBody
@GetMapping("/speed")
@Operation(summary = "设置录像播放速度")
@Parameter(name = "mediaServerId", description = "使用的节点Id", required = true)
@Parameter(name = "stream", description = "流ID", required = true)
@Parameter(name = "speed", description = "要设置的录像倍速", required = true)
public void setRecordSpeed(
@RequestParam(required = true) String mediaServerId,
@RequestParam(required = true) String app,
@RequestParam(required = true) String stream,
@RequestParam(required = true) Integer speed,
@RequestParam(required = false) String schema
) {
if (schema == null) {
schema = "ts";
}
cloudRecordService.setRecordSpeed(mediaServerId, app, stream, speed, schema);
}
@ResponseBody
@DeleteMapping("/delete")
@Operation(summary = "删除录像文件")
@Parameter(name = "ids", description = "文件ID集合", required = true)
public void deleteFileByIds(@RequestBody BatchRemoveParam ids) {
cloudRecordService.deleteFileByIds(ids.getIds());
}
/************************* 以下这些接口只适合wvp和zlm部署在同一台服务器的情况且wvp只有一个zlm节点的情况 ***************************************/
/**
@@ -282,7 +417,7 @@ public class CloudRecordController {
try {
ZipOutputStream zos = new ZipOutputStream(response.getOutputStream());
for (CloudRecordItem cloudRecordItem : cloudRecordItemList) {
zos.putNextEntry(new ZipEntry(DateUtil.timestampMsToUrlToyyyy_MM_dd_HH_mm_ss(cloudRecordItem.getStartTime()) + ".mp4"));
zos.putNextEntry(new ZipEntry(DateUtil.timestampMsToUrlToyyyy_MM_dd_HH_mm_ss((long)cloudRecordItem.getStartTime()) + ".mp4"));
File file = new File(cloudRecordItem.getFilePath());
if (!file.exists() || file.isDirectory()) {
continue;
@@ -366,7 +501,7 @@ public class CloudRecordController {
if (remoteHost == null) {
remoteHost = request.getScheme() + "://" + request.getLocalAddr() + ":" + (request.getScheme().equals("https") ? mediaServer.getHttpSSlPort() : mediaServer.getHttpPort());
}
PageInfo<CloudRecordItem> cloudRecordItemPageInfo = cloudRecordService.getList(page, count, query, app, stream, startTime, endTime, mediaServers, callId);
PageInfo<CloudRecordItem> cloudRecordItemPageInfo = cloudRecordService.getList(page, count, query, app, stream, startTime, endTime, mediaServers, callId, null);
PageInfo<CloudRecordUrl> cloudRecordUrlPageInfo = new PageInfo<>();
if (!ObjectUtils.isEmpty(cloudRecordItemPageInfo)) {
cloudRecordUrlPageInfo.setPageNum(cloudRecordItemPageInfo.getPageNum());
@@ -391,7 +526,7 @@ public class CloudRecordController {
for (CloudRecordItem cloudRecordItem : cloudRecordItemList) {
CloudRecordUrl cloudRecordUrl = new CloudRecordUrl();
cloudRecordUrl.setId(cloudRecordItem.getId());
cloudRecordUrl.setDownloadUrl(remoteHost + "/index/api/downloadFile?file_path=" + cloudRecordItem.getFilePath() + "&save_name=" + cloudRecordItem.getStream() + "_" + cloudRecordItem.getCallId() + "_" + DateUtil.timestampMsToUrlToyyyy_MM_dd_HH_mm_ss(cloudRecordItem.getStartTime()));
cloudRecordUrl.setDownloadUrl(remoteHost + "/index/api/downloadFile?file_path=" + cloudRecordItem.getFilePath() + "&save_name=" + cloudRecordItem.getStream() + "_" + cloudRecordItem.getCallId() + "_" + DateUtil.timestampMsToUrlToyyyy_MM_dd_HH_mm_ss((long)cloudRecordItem.getStartTime()));
cloudRecordUrl.setPlayUrl(remoteHost + "/index/api/downloadFile?file_path=" + cloudRecordItem.getFilePath());
cloudRecordUrlList.add(cloudRecordUrl);
}

View File

@@ -1,21 +1,10 @@
package com.genersoft.iot.vmp.vmanager.log;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.core.rolling.RollingFileAppender;
import com.alibaba.fastjson2.JSONArray;
import com.genersoft.iot.vmp.conf.exception.ControllerException;
import com.genersoft.iot.vmp.conf.security.JwtUtils;
import com.genersoft.iot.vmp.gb28181.service.ICloudRecordService;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.media.service.IMediaServerService;
import com.genersoft.iot.vmp.service.ILogService;
import com.genersoft.iot.vmp.service.bean.CloudRecordItem;
import com.genersoft.iot.vmp.service.bean.DownloadFileInfo;
import com.genersoft.iot.vmp.service.bean.LogFileInfo;
import com.genersoft.iot.vmp.utils.DateUtil;
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
import com.genersoft.iot.vmp.vmanager.cloudRecord.bean.CloudRecordUrl;
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.security.SecurityRequirement;
@@ -23,24 +12,17 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@SuppressWarnings("rawtypes")
@Tag(name = "日志文件查询接口")

View File

@@ -28,6 +28,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
@@ -41,13 +42,14 @@ import oshi.hardware.HardwareAbstractionLayer;
import oshi.hardware.NetworkIF;
import oshi.software.os.OperatingSystem;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.text.DecimalFormat;
import java.util.*;
@SuppressWarnings("rawtypes")
@Tag(name = "服务控制")
@Slf4j
@RestController
@RequestMapping("/api/server")
public class ServerController {
@@ -80,6 +82,7 @@ public class ServerController {
@Value("${server.port}")
private int serverPort;
@Autowired
private IRedisCatchStorage redisCatchStorage;
@@ -176,33 +179,14 @@ public class ServerController {
}
@Operation(summary = "重启服务", security = @SecurityRequirement(name = JwtUtils.HEADER))
@GetMapping(value = "/restart")
@Operation(summary = "关闭服务", security = @SecurityRequirement(name = JwtUtils.HEADER))
@GetMapping(value = "/shutdown")
@ResponseBody
public void restart() {
// taskExecutor.execute(()-> {
// try {
// Thread.sleep(3000);
// SipProvider up = (SipProvider) SpringBeanFactory.getBean("udpSipProvider");
// SipStackImpl stack = (SipStackImpl) up.getSipStack();
// stack.stop();
// Iterator listener = stack.getListeningPoints();
// while (listener.hasNext()) {
// stack.deleteListeningPoint((ListeningPoint) listener.next());
// }
// Iterator providers = stack.getSipProviders();
// while (providers.hasNext()) {
// stack.deleteSipProvider((SipProvider) providers.next());
// }
// VManageBootstrap.restart();
// } catch (InterruptedException | ObjectInUseException e) {
// throw new ControllerException(ErrorCode.ERROR100.getCode(), e.getMessage());
// }
// });
public void shutdown() {
log.info("正在关闭服务。。。");
System.exit(1);
}
;
@Operation(summary = "获取系统配置信息", security = @SecurityRequirement(name = JwtUtils.HEADER))
@GetMapping(value = "/system/configInfo")
@ResponseBody
@@ -293,7 +277,7 @@ public class ServerController {
@GetMapping(value = "/info")
@ResponseBody
@Operation(summary = "获取系统信息", security = @SecurityRequirement(name = JwtUtils.HEADER))
public Map<String, Map<String, String>> getInfo() {
public Map<String, Map<String, String>> getInfo(HttpServletRequest request) {
Map<String, Map<String, String>> result = new LinkedHashMap<>();
Map<String, String> hardwareMap = new LinkedHashMap<>();
result.put("硬件信息", hardwareMap);
@@ -341,6 +325,12 @@ public class ServerController {
platformMap.put("GIT版本", version.getGIT_Revision_SHORT());
platformMap.put("DOCKER环境", new File("/.dockerenv").exists()?"":"");
Map<String, String> docmap = new LinkedHashMap<>();
result.put("文档地址", docmap);
docmap.put("部署文档", "https://doc.wvp-pro.cn");
docmap.put("接口文档", String.format("%s://%s:%s/doc.html", request.getScheme(), request.getServerName(), request.getServerPort()));
return result;
}

View File

@@ -63,9 +63,9 @@ public class UserApiKeyController {
Long expirationTime = null;
if (expiresAt != null) {
long timestamp = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestampMs(expiresAt);
expirationTime = (timestamp - System.currentTimeMillis()) / (60 * 1000);
if (expirationTime < 0) {
expirationTime = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestampMs(expiresAt);
long difference = (expirationTime - System.currentTimeMillis()) / (60 * 1000);
if (difference < 0) {
throw new ControllerException(ErrorCode.ERROR400.getCode(), "过期时间不能早于当前时间");
}
}

View File

@@ -1,5 +1,6 @@
package com.genersoft.iot.vmp.vmanager.user;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.conf.exception.ControllerException;
import com.genersoft.iot.vmp.conf.security.JwtUtils;
import com.genersoft.iot.vmp.conf.security.SecurityUtils;
@@ -42,6 +43,9 @@ public class UserController {
@Autowired
private IRoleService roleService;
@Autowired
private UserSetting userSetting;
@GetMapping("/login")
@PostMapping("/login")
@Operation(summary = "登录", description = "登录成功后返回AccessToken 可以从返回值获取到也可以从响应头中获取到," +
@@ -62,6 +66,7 @@ public class UserController {
String jwt = JwtUtils.createToken(username);
response.setHeader(JwtUtils.getHeader(), jwt);
user.setAccessToken(jwt);
user.setServerId(userSetting.getServerId());
}
return user;
}

View File

@@ -37,10 +37,9 @@ public class ApiControlController {
* @param channel 通道序号
* @param code 通道编号
* @param speed 速度(0~255) 默认值: 129
* @return
*/
@GetMapping(value = "/ptz")
private void list(String serial,String command,
private void ptz(String serial,String command,
@RequestParam(required = false)Integer channel,
@RequestParam(required = false)String code,
@RequestParam(required = false)Integer speed){
@@ -55,7 +54,7 @@ public class ApiControlController {
if (device == null) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "device[ " + serial + " ]未找到");
}
int cmdCode = 0;
int cmdCode = -1;
switch (command){
case "left":
cmdCode = 2;
@@ -93,6 +92,9 @@ public class ApiControlController {
default:
break;
}
if (cmdCode == -1) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "未识别的指令:" + command);
}
// 默认值 50
try {
cmder.frontEndCmd(device, code, cmdCode, speed, speed, speed);
@@ -110,7 +112,6 @@ public class ApiControlController {
* @param command 控制指令 允许值: set, goto, remove
* @param preset 预置位编号(1~255)
* @param name 预置位名称, command=set 时有效
* @return
*/
@GetMapping(value = "/preset")
private void list(String serial,String command,

View File

@@ -2,4 +2,4 @@ spring:
application:
name: wvp
profiles:
active: 1078
active: 1078

57
src/main/resources/install.sh Executable file
View File

@@ -0,0 +1,57 @@
#! /bin/sh
WORD_DIR=$(cd $(dirname $0); pwd)
SERVICE_NAME="wvp"
# 检查是否为 root 用户
if [ "$(id -u)" -ne 0 ]; then
echo "提示: 建议使用 root 用户执行此脚本,否则可能权限不足!"
read -p "继续?(y/n) " -n 1 -r
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
echo
fi
# 当前目录直接搜索(不含子目录)
jar_files=(*.jar)
if [ ${#jar_files[@]} -eq 0 ]; then
echo "当前目录无 JAR 文件!"
exit 1
fi
# 遍历结果
for jar in "${jar_files[@]}"; do
echo "找到 JAR 文件: $jar"
done
# 写文件
# 生成 Systemd 服务文件内容
SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service"
cat << EOF | sudo tee "$SERVICE_FILE" > /dev/null
[Unit]
Description=${SERVICE_NAME}
After=syslog.target
[Service]
User=$USER
WorkingDirectory=${WORD_DIR}
ExecStart=java -jar ${jar_files}
SuccessExitStatus=143
Restart=on-failure
RestartSec=10s
Environment=SPRING_PROFILES_ACTIVE=prod
[Install]
WantedBy=multi-user.target
EOF
# 重载 Systemd 并启动服务
sudo systemctl daemon-reload
sudo systemctl enable "$SERVICE_NAME"
sudo systemctl start "$SERVICE_NAME"
# 验证服务状态
echo "服务已安装!执行以下命令查看状态:"
echo "sudo systemctl status $SERVICE_NAME"

View File

@@ -5,6 +5,10 @@
spring:
cache:
type: redis
thymeleaf:
cache: false
# 设置接口超时时间
mvc:
async:
@@ -113,8 +117,8 @@ sip:
# keepalliveToOnline: false
# 是否存储alarm信息
alarm: false
# 命令发送等待回复的超时时间, 单位:秒
timeout: 15
# 命令发送等待回复的超时时间, 单位:
timeout: 1000
# 做为JT1078服务器的配置
jt1078: