jiumi
This commit is contained in:
commit
3f58700b38
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
/target/
|
||||
/.idea/
|
||||
/logs/
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 panll
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
3
README.md
Normal file
3
README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# wvp-pro-assist
|
||||
|
||||
wvp-pro-assist是wvp-pro的辅助录像程序,也可单独跟zlm一起使用,提供录像控制,录像合并下载接口
|
||||
BIN
lib-arm/ffmpeg
Normal file
BIN
lib-arm/ffmpeg
Normal file
Binary file not shown.
BIN
lib-arm/ffprobe
Normal file
BIN
lib-arm/ffprobe
Normal file
Binary file not shown.
BIN
lib/ffmpeg
Normal file
BIN
lib/ffmpeg
Normal file
Binary file not shown.
BIN
lib/ffprobe
Normal file
BIN
lib/ffprobe
Normal file
Binary file not shown.
121
pom.xml
Normal file
121
pom.xml
Normal file
@ -0,0 +1,121 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.7.2</version>
|
||||
</parent>
|
||||
<groupId>top.panll.assist</groupId>
|
||||
<artifactId>wvp-pro-assist</artifactId>
|
||||
<version>2.6.9</version>
|
||||
<name>wvp-pro-assist</name>
|
||||
<description></description>
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
<maven.build.timestamp.format>MMddHHmm</maven.build.timestamp.format>
|
||||
<!-- <pagehelper.version>5.2.0</pagehelper.version>-->
|
||||
</properties>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>nexus-aliyun</id>
|
||||
<name>Nexus aliyun</name>
|
||||
<url>https://maven.aliyun.com/repository/public</url>
|
||||
<layout>default</layout>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
</releases>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>net.bramp.ffmpeg</groupId>
|
||||
<artifactId>ffmpeg</artifactId>
|
||||
<version>0.6.2</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
<version>1.2.73</version>
|
||||
</dependency>
|
||||
|
||||
<!--在线文档 -->
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-ui</artifactId>
|
||||
<version>1.6.10</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>knife4j-springdoc-ui</artifactId>
|
||||
<version>3.0.3</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mp4parser</groupId>
|
||||
<artifactId>muxer</artifactId>
|
||||
<version>1.9.56</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mp4parser</groupId>
|
||||
<artifactId>streaming</artifactId>
|
||||
<version>1.9.56</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mp4parser</groupId>
|
||||
<artifactId>isoparser</artifactId>
|
||||
<version>1.9.27</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>${project.artifactId}-${project.version}-${maven.build.timestamp}</finalName>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<skipTests>true</skipTests>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
15
src/main/java/top/panll/assist/WvpProAssistApplication.java
Normal file
15
src/main/java/top/panll/assist/WvpProAssistApplication.java
Normal file
@ -0,0 +1,15 @@
|
||||
package top.panll.assist;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableScheduling
|
||||
public class WvpProAssistApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(WvpProAssistApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
package top.panll.assist.config;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.parser.ParserConfig;
|
||||
import com.alibaba.fastjson.serializer.SerializerFeature;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
import org.springframework.data.redis.serializer.SerializationException;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
|
||||
private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
|
||||
private Class<T> clazz;
|
||||
|
||||
/**
|
||||
* 添加autotype白名单
|
||||
* 解决redis反序列化对象时报错 :com.alibaba.fastjson.JSONException: autoType is not support
|
||||
*/
|
||||
static {
|
||||
ParserConfig.getGlobalInstance().addAccept("top.panll.assist");
|
||||
}
|
||||
|
||||
public FastJsonRedisSerializer(Class<T> clazz) {
|
||||
super();
|
||||
this.clazz = clazz;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] serialize(T t) throws SerializationException {
|
||||
if (null == t) {
|
||||
return new byte[0];
|
||||
}
|
||||
return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T deserialize(byte[] bytes) throws SerializationException {
|
||||
if (null == bytes || bytes.length <= 0) {
|
||||
return null;
|
||||
}
|
||||
String str = new String(bytes, DEFAULT_CHARSET);
|
||||
return JSON.parseObject(str, clazz);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
package top.panll.assist.config;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import top.panll.assist.controller.bean.ControllerException;
|
||||
import top.panll.assist.controller.bean.ErrorCode;
|
||||
import top.panll.assist.controller.bean.WVPResult;
|
||||
|
||||
/**
|
||||
* 全局异常处理
|
||||
*/
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
|
||||
|
||||
/**
|
||||
* 默认异常处理
|
||||
* @param e 异常
|
||||
* @return 统一返回结果
|
||||
*/
|
||||
@ExceptionHandler(Exception.class)
|
||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
public WVPResult<String> exceptionHandler(Exception e) {
|
||||
logger.error("[全局异常]: ", e);
|
||||
return WVPResult.fail(ErrorCode.ERROR500.getCode(), e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义异常处理, 处理controller中返回的错误
|
||||
* @param e 异常
|
||||
* @return 统一返回结果
|
||||
*/
|
||||
@ExceptionHandler(ControllerException.class)
|
||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
public WVPResult<String> exceptionHandler(ControllerException e) {
|
||||
return WVPResult.fail(e.getCode(), e.getMsg());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,54 @@
|
||||
package top.panll.assist.config;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.http.server.ServerHttpResponse;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
|
||||
import top.panll.assist.controller.bean.ErrorCode;
|
||||
import top.panll.assist.controller.bean.WVPResult;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* 全局统一返回结果
|
||||
* @author lin
|
||||
*/
|
||||
@RestControllerAdvice
|
||||
public class GlobalResponseAdvice implements ResponseBodyAdvice<Object> {
|
||||
|
||||
|
||||
@Override
|
||||
public boolean supports(@NotNull MethodParameter returnType, @NotNull Class<? extends HttpMessageConverter<?>> converterType) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object beforeBodyWrite(Object body, @NotNull MethodParameter returnType, @NotNull MediaType selectedContentType, @NotNull Class<? extends HttpMessageConverter<?>> selectedConverterType, @NotNull ServerHttpRequest request, @NotNull ServerHttpResponse response) {
|
||||
// 排除api文档的接口,这个接口不需要统一
|
||||
String[] excludePath = {"/v3/api-docs","/api/v1","/index/hook"};
|
||||
for (String path : excludePath) {
|
||||
if (request.getURI().getPath().startsWith(path)) {
|
||||
return body;
|
||||
}
|
||||
}
|
||||
|
||||
if (body instanceof WVPResult) {
|
||||
return body;
|
||||
}
|
||||
|
||||
if (body instanceof ErrorCode) {
|
||||
ErrorCode errorCode = (ErrorCode) body;
|
||||
return new WVPResult<>(errorCode.getCode(), errorCode.getMsg(), null);
|
||||
}
|
||||
|
||||
if (body instanceof String) {
|
||||
return JSON.toJSONString(WVPResult.success(body));
|
||||
}
|
||||
|
||||
return WVPResult.success(body);
|
||||
}
|
||||
}
|
||||
66
src/main/java/top/panll/assist/config/RedisConfig.java
Normal file
66
src/main/java/top/panll/assist/config/RedisConfig.java
Normal file
@ -0,0 +1,66 @@
|
||||
package top.panll.assist.config;
|
||||
|
||||
import com.alibaba.fastjson.parser.ParserConfig;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisOperations;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
|
||||
/**
|
||||
* @Description:Redis中间件配置类,使用spring-data-redis集成,自动从application.yml中加载redis配置
|
||||
* @author: swwheihei
|
||||
* @date: 2019年5月30日 上午10:58:25
|
||||
*
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnClass(RedisOperations.class)
|
||||
@EnableConfigurationProperties(RedisProperties.class)
|
||||
public class RedisConfig {
|
||||
|
||||
static {
|
||||
ParserConfig.getGlobalInstance().addAccept("top.panll.assist");
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(name = "redisTemplate")
|
||||
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
|
||||
RedisTemplate<Object, Object> template = new RedisTemplate<>();
|
||||
template.setConnectionFactory(redisConnectionFactory);
|
||||
// 使用fastjson进行序列化处理,提高解析效率
|
||||
FastJsonRedisSerializer<Object> serializer = new FastJsonRedisSerializer<>(Object.class);
|
||||
// value值的序列化采用fastJsonRedisSerializer
|
||||
template.setValueSerializer(serializer);
|
||||
template.setHashValueSerializer(serializer);
|
||||
// key的序列化采用StringRedisSerializer
|
||||
template.setKeySerializer(new StringRedisSerializer());
|
||||
template.setHashKeySerializer(new StringRedisSerializer());
|
||||
template.setConnectionFactory(redisConnectionFactory);
|
||||
// 使用fastjson时需设置此项,否则会报异常not support type
|
||||
// ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
|
||||
return template;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* redis消息监听器容器 可以添加多个监听不同话题的redis监听器,只需要把消息监听器和相应的消息订阅处理器绑定,该消息监听器
|
||||
* 通过反射技术调用消息订阅处理器的相关方法进行一些业务处理
|
||||
*
|
||||
* @param connectionFactory
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
|
||||
|
||||
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
|
||||
container.setConnectionFactory(connectionFactory);
|
||||
return container;
|
||||
}
|
||||
|
||||
}
|
||||
45
src/main/java/top/panll/assist/config/SpringDocConfig.java
Normal file
45
src/main/java/top/panll/assist/config/SpringDocConfig.java
Normal file
@ -0,0 +1,45 @@
|
||||
package top.panll.assist.config;
|
||||
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.info.Contact;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
import io.swagger.v3.oas.models.info.License;
|
||||
import org.springdoc.core.GroupedOpenApi;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @author lin
|
||||
*/
|
||||
@Configuration
|
||||
public class SpringDocConfig {
|
||||
|
||||
@Value("${doc.enabled: true}")
|
||||
private boolean enable;
|
||||
|
||||
@Bean
|
||||
public OpenAPI springShopOpenApi() {
|
||||
Contact contact = new Contact();
|
||||
contact.setName("pan");
|
||||
contact.setEmail("648540858@qq.com");
|
||||
return new OpenAPI()
|
||||
.info(new Info().title("WVP-PRO-ASSIST 接口文档")
|
||||
.contact(contact)
|
||||
.description("WVP-PRO助手,补充ZLM功能")
|
||||
.version("v2.0")
|
||||
.license(new License().name("Apache 2.0").url("http://springdoc.org")));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加分组
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public GroupedOpenApi publicApi() {
|
||||
return GroupedOpenApi.builder()
|
||||
.group("1. 全部")
|
||||
.packagesToScan("top.panll.assist")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,59 @@
|
||||
package top.panll.assist.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
|
||||
@Configuration
|
||||
@EnableAsync(proxyTargetClass = true)
|
||||
public class ThreadPoolTaskConfig {
|
||||
|
||||
public static final int cpuNum = Runtime.getRuntime().availableProcessors();
|
||||
|
||||
/**
|
||||
* 默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,
|
||||
* 当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
|
||||
* 当队列满了,就继续创建线程,当线程数量大于等于maxPoolSize后,开始使用拒绝策略拒绝
|
||||
*/
|
||||
|
||||
/**
|
||||
* 核心线程数(默认线程数)
|
||||
*/
|
||||
private static final int corePoolSize = cpuNum;
|
||||
/**
|
||||
* 最大线程数
|
||||
*/
|
||||
private static final int maxPoolSize = cpuNum*2;
|
||||
/**
|
||||
* 允许线程空闲时间(单位:默认为秒)
|
||||
*/
|
||||
private static final int keepAliveTime = 30;
|
||||
/**
|
||||
* 缓冲队列大小
|
||||
*/
|
||||
private static final int queueCapacity = 500;
|
||||
/**
|
||||
* 线程池名前缀
|
||||
*/
|
||||
private static final String threadNamePrefix = "wvp-assist-";
|
||||
|
||||
@Bean("taskExecutor") // bean的名称,默认为首字母小写的方法名
|
||||
public ThreadPoolTaskExecutor taskExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setCorePoolSize(corePoolSize);
|
||||
executor.setMaxPoolSize(maxPoolSize);
|
||||
executor.setQueueCapacity(queueCapacity);
|
||||
executor.setKeepAliveSeconds(keepAliveTime);
|
||||
executor.setThreadNamePrefix(threadNamePrefix);
|
||||
|
||||
// 线程池对拒绝任务的处理策略
|
||||
// CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务
|
||||
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
||||
// 初始化
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
}
|
||||
24
src/main/java/top/panll/assist/config/WebMvcConfig.java
Normal file
24
src/main/java/top/panll/assist/config/WebMvcConfig.java
Normal file
@ -0,0 +1,24 @@
|
||||
package top.panll.assist.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
|
||||
import top.panll.assist.dto.UserSettings;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
|
||||
@Configuration
|
||||
public class WebMvcConfig extends WebMvcConfigurerAdapter {
|
||||
|
||||
@Autowired
|
||||
private UserSettings userSettings;
|
||||
|
||||
@Override
|
||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||
File file = new File(userSettings.getRecordTempPath());
|
||||
registry.addResourceHandler("/download/**").addResourceLocations("file://" + file.getAbsolutePath() + "/");
|
||||
super.addResourceHandlers(registry);
|
||||
}
|
||||
}
|
||||
130
src/main/java/top/panll/assist/controller/RecordController.java
Normal file
130
src/main/java/top/panll/assist/controller/RecordController.java
Normal file
@ -0,0 +1,130 @@
|
||||
package top.panll.assist.controller;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import top.panll.assist.controller.bean.*;
|
||||
import top.panll.assist.dto.*;
|
||||
import top.panll.assist.service.VideoFileService;
|
||||
import top.panll.assist.utils.RedisUtil;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
|
||||
@Tag(name = "录像管理", description = "录像管理")
|
||||
@CrossOrigin
|
||||
@RestController
|
||||
@RequestMapping("/api/record")
|
||||
public class RecordController {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(RecordController.class);
|
||||
|
||||
@Autowired
|
||||
private VideoFileService videoFileService;
|
||||
|
||||
@Autowired
|
||||
private RedisUtil redisUtil;
|
||||
|
||||
@Autowired
|
||||
private UserSettings userSettings;
|
||||
|
||||
private SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
|
||||
/**
|
||||
* 获取Assist服务配置信息
|
||||
*/
|
||||
@Operation(summary ="获取Assist服务配置信息")
|
||||
@GetMapping(value = "/info")
|
||||
@ResponseBody
|
||||
public UserSettings getInfo(){
|
||||
return userSettings;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 添加视频裁剪合并任务
|
||||
*/
|
||||
@Operation(summary ="添加视频裁剪合并任务")
|
||||
@Parameter(name = "videoTaskInfo", description = "视频合并任务的信息", required = true)
|
||||
@PostMapping(value = "/file/download/task/add")
|
||||
@ResponseBody
|
||||
public String addTaskForDownload(@RequestBody VideoTaskInfo videoTaskInfo ){
|
||||
if (videoTaskInfo.getFilePathList() == null || videoTaskInfo.getFilePathList().isEmpty()) {
|
||||
throw new ControllerException(ErrorCode.ERROR100.getCode(), "视频文件列表不可为空");
|
||||
}
|
||||
String id = videoFileService.mergeOrCut(videoTaskInfo);
|
||||
if (id== null) {
|
||||
throw new ControllerException(ErrorCode.ERROR100.getCode(), "可能未找到视频文件");
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询视频裁剪合并任务列表
|
||||
*/
|
||||
@Operation(summary ="查询视频裁剪合并任务列表")
|
||||
@Parameter(name = "taskId", description = "任务ID", required = true)
|
||||
@Parameter(name = "isEnd", description = "是否结束", required = true)
|
||||
@GetMapping(value = "/file/download/task/list")
|
||||
@ResponseBody
|
||||
public List<MergeOrCutTaskInfo> getTaskListForDownload(
|
||||
@RequestParam(required = false) String app,
|
||||
@RequestParam(required = false) String stream,
|
||||
@RequestParam(required = false) String callId,
|
||||
@RequestParam(required = false) String taskId,
|
||||
@RequestParam(required = false) Boolean isEnd){
|
||||
List<MergeOrCutTaskInfo> taskList = videoFileService.getTaskListForDownload(app, stream, callId, isEnd, taskId);
|
||||
if (taskList == null) {
|
||||
throw new ControllerException(ErrorCode.ERROR100);
|
||||
}
|
||||
return taskList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 中止视频裁剪合并任务列表
|
||||
*/
|
||||
@Operation(summary ="中止视频裁剪合并任务列表(暂不支持)")
|
||||
@GetMapping(value = "/file/download/task/stop")
|
||||
@ResponseBody
|
||||
public WVPResult<String> stopTaskForDownload(@RequestParam String taskId){
|
||||
// WVPResult<String> result = new WVPResult<>();
|
||||
// if (taskId == null) {
|
||||
// result.setCode(400);
|
||||
// result.setMsg("taskId 不能为空");
|
||||
// return result;
|
||||
// }
|
||||
// boolean stopResult = videoFileService.stopTask(taskId);
|
||||
// result.setCode(0);
|
||||
// result.setMsg(stopResult ? "success": "fail");
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 磁盘空间查询
|
||||
*/
|
||||
@Operation(summary ="磁盘空间查询")
|
||||
@ResponseBody
|
||||
@GetMapping(value = "/space", produces = "application/json;charset=UTF-8")
|
||||
public SpaceInfo getSpace() {
|
||||
return videoFileService.getSpaceInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
* 录像文件的时长
|
||||
*/
|
||||
@Operation(summary ="录像文件的时长")
|
||||
@Parameter(name = "app", description = "应用名", required = true)
|
||||
@Parameter(name = "stream", description = "流ID", required = true)
|
||||
@Parameter(name = "recordIng", description = "是否录制中", required = true)
|
||||
@ResponseBody
|
||||
@GetMapping(value = "/file/duration", produces = "application/json;charset=UTF-8")
|
||||
@PostMapping(value = "/file/duration", produces = "application/json;charset=UTF-8")
|
||||
public long fileDuration( @RequestParam String app, @RequestParam String stream) {
|
||||
return videoFileService.fileDuration(app, stream);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
package top.panll.assist.controller.bean;
|
||||
|
||||
/**
|
||||
* 自定义异常,controller出现错误时直接抛出异常由全局异常捕获并返回结果
|
||||
*/
|
||||
public class ControllerException extends RuntimeException{
|
||||
|
||||
private int code;
|
||||
private String msg;
|
||||
|
||||
public ControllerException(int code, String msg) {
|
||||
this.code = code;
|
||||
this.msg = msg;
|
||||
}
|
||||
public ControllerException(ErrorCode errorCode) {
|
||||
this.code = errorCode.getCode();
|
||||
this.msg = errorCode.getMsg();
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(int code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getMsg() {
|
||||
return msg;
|
||||
}
|
||||
|
||||
public void setMsg(String msg) {
|
||||
this.msg = msg;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
package top.panll.assist.controller.bean;
|
||||
|
||||
/**
|
||||
* 全局错误码
|
||||
*/
|
||||
public enum ErrorCode {
|
||||
SUCCESS(0, "成功"),
|
||||
ERROR100(100, "失败"),
|
||||
ERROR400(400, "参数不全或者错误"),
|
||||
ERROR403(403, "无权限操作"),
|
||||
ERROR401(401, "请登录后重新请求"),
|
||||
ERROR500(500, "系统异常");
|
||||
|
||||
private final int code;
|
||||
private final String msg;
|
||||
|
||||
ErrorCode(int code, String msg) {
|
||||
this.code = code;
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getMsg() {
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package top.panll.assist.controller.bean;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class FileLIstInfo {
|
||||
|
||||
private List<String> filePathList;
|
||||
|
||||
public List<String> getFilePathList() {
|
||||
return filePathList;
|
||||
}
|
||||
|
||||
public void setFilePathList(List<String> filePathList) {
|
||||
this.filePathList = filePathList;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
package top.panll.assist.controller.bean;
|
||||
|
||||
public class RecordFile {
|
||||
private String app;
|
||||
private String stream;
|
||||
|
||||
private String fileName;
|
||||
|
||||
private String date;
|
||||
|
||||
|
||||
public static RecordFile instance(String app, String stream, String fileName, String date) {
|
||||
RecordFile recordFile = new RecordFile();
|
||||
recordFile.setApp(app);
|
||||
recordFile.setStream(stream);
|
||||
recordFile.setFileName(fileName);
|
||||
recordFile.setDate(date);
|
||||
return recordFile;
|
||||
}
|
||||
|
||||
|
||||
public String getApp() {
|
||||
return app;
|
||||
}
|
||||
|
||||
public void setApp(String app) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
public String getStream() {
|
||||
return stream;
|
||||
}
|
||||
|
||||
public void setStream(String stream) {
|
||||
this.stream = stream;
|
||||
}
|
||||
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public void setFileName(String fileName) {
|
||||
this.fileName = fileName;
|
||||
}
|
||||
|
||||
public String getDate() {
|
||||
return date;
|
||||
}
|
||||
|
||||
public void setDate(String date) {
|
||||
this.date = date;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,66 @@
|
||||
package top.panll.assist.controller.bean;
|
||||
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@Schema(description = "统一返回结果")
|
||||
public class WVPResult<T> {
|
||||
|
||||
public WVPResult() {
|
||||
}
|
||||
|
||||
public WVPResult(int code, String msg, T data) {
|
||||
this.code = code;
|
||||
this.msg = msg;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
|
||||
@Schema(description = "错误码,0为成功")
|
||||
private int code;
|
||||
@Schema(description = "描述,错误时描述错误原因")
|
||||
private String msg;
|
||||
@Schema(description = "数据")
|
||||
private T data;
|
||||
|
||||
|
||||
public static <T> WVPResult<T> success(T t, String msg) {
|
||||
return new WVPResult<>(ErrorCode.SUCCESS.getCode(), msg, t);
|
||||
}
|
||||
|
||||
public static <T> WVPResult<T> success(T t) {
|
||||
return success(t, ErrorCode.SUCCESS.getMsg());
|
||||
}
|
||||
|
||||
public static <T> WVPResult<T> fail(int code, String msg) {
|
||||
return new WVPResult<>(code, msg, null);
|
||||
}
|
||||
|
||||
public static <T> WVPResult<T> fail(ErrorCode errorCode) {
|
||||
return fail(errorCode.getCode(), errorCode.getMsg());
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(int code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getMsg() {
|
||||
return msg;
|
||||
}
|
||||
|
||||
public void setMsg(String msg) {
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
public T getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(T data) {
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
8
src/main/java/top/panll/assist/dto/AssistConstants.java
Normal file
8
src/main/java/top/panll/assist/dto/AssistConstants.java
Normal file
@ -0,0 +1,8 @@
|
||||
package top.panll.assist.dto;
|
||||
|
||||
public class AssistConstants {
|
||||
|
||||
public final static String STREAM_CALL_INFO = "STREAM_CALL_INFO_";
|
||||
|
||||
public final static String MERGEORCUT = "MERGEORCUT_";
|
||||
}
|
||||
108
src/main/java/top/panll/assist/dto/MergeOrCutTaskInfo.java
Normal file
108
src/main/java/top/panll/assist/dto/MergeOrCutTaskInfo.java
Normal file
@ -0,0 +1,108 @@
|
||||
package top.panll.assist.dto;
|
||||
|
||||
|
||||
public class MergeOrCutTaskInfo {
|
||||
private String id;
|
||||
private String createTime;
|
||||
private String percentage;
|
||||
|
||||
private String recordFile;
|
||||
|
||||
private String downloadFile;
|
||||
|
||||
private String playFile;
|
||||
|
||||
private String app;
|
||||
private String stream;
|
||||
private String startTime;
|
||||
private String endTime;
|
||||
private String callId;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getPercentage() {
|
||||
return percentage;
|
||||
}
|
||||
|
||||
public void setPercentage(String percentage) {
|
||||
this.percentage = percentage;
|
||||
}
|
||||
|
||||
public String getRecordFile() {
|
||||
return recordFile;
|
||||
}
|
||||
|
||||
public void setRecordFile(String recordFile) {
|
||||
this.recordFile = recordFile;
|
||||
}
|
||||
|
||||
public String getDownloadFile() {
|
||||
return downloadFile;
|
||||
}
|
||||
|
||||
public void setDownloadFile(String downloadFile) {
|
||||
this.downloadFile = downloadFile;
|
||||
}
|
||||
|
||||
public String getPlayFile() {
|
||||
return playFile;
|
||||
}
|
||||
|
||||
public void setPlayFile(String playFile) {
|
||||
this.playFile = playFile;
|
||||
}
|
||||
|
||||
public String getCreateTime() {
|
||||
return createTime;
|
||||
}
|
||||
|
||||
public void setCreateTime(String createTime) {
|
||||
this.createTime = createTime;
|
||||
}
|
||||
|
||||
public String getApp() {
|
||||
return app;
|
||||
}
|
||||
|
||||
public void setApp(String app) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
public String getStream() {
|
||||
return stream;
|
||||
}
|
||||
|
||||
public void setStream(String stream) {
|
||||
this.stream = stream;
|
||||
}
|
||||
|
||||
public String getStartTime() {
|
||||
return startTime;
|
||||
}
|
||||
|
||||
public void setStartTime(String startTime) {
|
||||
this.startTime = startTime;
|
||||
}
|
||||
|
||||
public String getEndTime() {
|
||||
return endTime;
|
||||
}
|
||||
|
||||
public void setEndTime(String endTime) {
|
||||
this.endTime = endTime;
|
||||
}
|
||||
|
||||
public String getCallId() {
|
||||
return callId;
|
||||
}
|
||||
|
||||
public void setCallId(String callId) {
|
||||
this.callId = callId;
|
||||
}
|
||||
}
|
||||
31
src/main/java/top/panll/assist/dto/SignInfo.java
Normal file
31
src/main/java/top/panll/assist/dto/SignInfo.java
Normal file
@ -0,0 +1,31 @@
|
||||
package top.panll.assist.dto;
|
||||
|
||||
public class SignInfo {
|
||||
private String app;
|
||||
private String stream;
|
||||
private String type;
|
||||
|
||||
public String getApp() {
|
||||
return app;
|
||||
}
|
||||
|
||||
public void setApp(String app) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
public String getStream() {
|
||||
return stream;
|
||||
}
|
||||
|
||||
public void setStream(String stream) {
|
||||
this.stream = stream;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
23
src/main/java/top/panll/assist/dto/SpaceInfo.java
Normal file
23
src/main/java/top/panll/assist/dto/SpaceInfo.java
Normal file
@ -0,0 +1,23 @@
|
||||
package top.panll.assist.dto;
|
||||
|
||||
public class SpaceInfo {
|
||||
private long total;
|
||||
private long free;
|
||||
|
||||
public long getTotal() {
|
||||
return total;
|
||||
}
|
||||
|
||||
public void setTotal(long total) {
|
||||
this.total = total;
|
||||
}
|
||||
|
||||
public long getFree() {
|
||||
return free;
|
||||
}
|
||||
|
||||
public void setFree(long free) {
|
||||
this.free = free;
|
||||
}
|
||||
|
||||
}
|
||||
78
src/main/java/top/panll/assist/dto/UserSettings.java
Normal file
78
src/main/java/top/panll/assist/dto/UserSettings.java
Normal file
@ -0,0 +1,78 @@
|
||||
package top.panll.assist.dto;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author lin
|
||||
*/
|
||||
@Component
|
||||
public class UserSettings {
|
||||
|
||||
@Value("${userSettings.id}")
|
||||
private String id;
|
||||
|
||||
@Value("${userSettings.record-temp:./recordTemp}")
|
||||
private String recordTempPath;
|
||||
|
||||
@Value("${userSettings.record-temp-day:7}")
|
||||
private int recordTempDay;
|
||||
|
||||
@Value("${userSettings.ffmpeg}")
|
||||
private String ffmpeg;
|
||||
|
||||
@Value("${userSettings.ffprobe}")
|
||||
private String ffprobe;
|
||||
|
||||
@Value("${userSettings.threads:2}")
|
||||
private int threads;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getFfmpeg() {
|
||||
return ffmpeg;
|
||||
}
|
||||
|
||||
public void setFfmpeg(String ffmpeg) {
|
||||
this.ffmpeg = ffmpeg;
|
||||
}
|
||||
|
||||
public String getFfprobe() {
|
||||
return ffprobe;
|
||||
}
|
||||
|
||||
public void setFfprobe(String ffprobe) {
|
||||
this.ffprobe = ffprobe;
|
||||
}
|
||||
|
||||
|
||||
public int getRecordTempDay() {
|
||||
return recordTempDay;
|
||||
}
|
||||
|
||||
public void setRecordTempDay(int recordTempDay) {
|
||||
this.recordTempDay = recordTempDay;
|
||||
}
|
||||
|
||||
public int getThreads() {
|
||||
return threads;
|
||||
}
|
||||
|
||||
public void setThreads(int threads) {
|
||||
this.threads = threads;
|
||||
}
|
||||
|
||||
public String getRecordTempPath() {
|
||||
return recordTempPath;
|
||||
}
|
||||
|
||||
public void setRecordTempPath(String recordTempPath) {
|
||||
this.recordTempPath = recordTempPath;
|
||||
}
|
||||
}
|
||||
77
src/main/java/top/panll/assist/dto/VideoFile.java
Normal file
77
src/main/java/top/panll/assist/dto/VideoFile.java
Normal file
@ -0,0 +1,77 @@
|
||||
package top.panll.assist.dto;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 视频文件
|
||||
*/
|
||||
public class VideoFile {
|
||||
|
||||
/**
|
||||
* 文件对象
|
||||
*/
|
||||
private File file;
|
||||
|
||||
/**
|
||||
* 文件开始时间
|
||||
*/
|
||||
private Date startTime;
|
||||
|
||||
/**
|
||||
* 文件结束时间
|
||||
*/
|
||||
private Date endTime;
|
||||
|
||||
|
||||
/**
|
||||
* 时长, 单位:秒
|
||||
*/
|
||||
private long duration;
|
||||
|
||||
|
||||
/**
|
||||
* 是否是目标格式
|
||||
*/
|
||||
private boolean targetFormat;
|
||||
|
||||
public File getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
public void setFile(File file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
public Date getStartTime() {
|
||||
return startTime;
|
||||
}
|
||||
|
||||
public void setStartTime(Date startTime) {
|
||||
this.startTime = startTime;
|
||||
}
|
||||
|
||||
public Date getEndTime() {
|
||||
return endTime;
|
||||
}
|
||||
|
||||
public void setEndTime(Date endTime) {
|
||||
this.endTime = endTime;
|
||||
}
|
||||
|
||||
public long getDuration() {
|
||||
return duration;
|
||||
}
|
||||
|
||||
public void setDuration(long duration) {
|
||||
this.duration = duration;
|
||||
}
|
||||
|
||||
public boolean isTargetFormat() {
|
||||
return targetFormat;
|
||||
}
|
||||
|
||||
public void setTargetFormat(boolean targetFormat) {
|
||||
this.targetFormat = targetFormat;
|
||||
}
|
||||
}
|
||||
79
src/main/java/top/panll/assist/dto/VideoTaskInfo.java
Normal file
79
src/main/java/top/panll/assist/dto/VideoTaskInfo.java
Normal file
@ -0,0 +1,79 @@
|
||||
package top.panll.assist.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "视频合并任务的信息")
|
||||
public class VideoTaskInfo {
|
||||
|
||||
private String app;
|
||||
private String stream;
|
||||
private String startTime;
|
||||
private String endTime;
|
||||
private String callId;
|
||||
|
||||
|
||||
@Schema(description = "视频文件路径列表")
|
||||
private List<String> filePathList;
|
||||
|
||||
@Schema(description = "返回地址时的远程地址")
|
||||
private String remoteHost;
|
||||
|
||||
public List<String> getFilePathList() {
|
||||
return filePathList;
|
||||
}
|
||||
|
||||
public void setFilePathList(List<String> filePathList) {
|
||||
this.filePathList = filePathList;
|
||||
}
|
||||
|
||||
public String getRemoteHost() {
|
||||
return remoteHost;
|
||||
}
|
||||
|
||||
public void setRemoteHost(String remoteHost) {
|
||||
this.remoteHost = remoteHost;
|
||||
}
|
||||
|
||||
public String getApp() {
|
||||
return app;
|
||||
}
|
||||
|
||||
public void setApp(String app) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
public String getStream() {
|
||||
return stream;
|
||||
}
|
||||
|
||||
public void setStream(String stream) {
|
||||
this.stream = stream;
|
||||
}
|
||||
|
||||
public String getStartTime() {
|
||||
return startTime;
|
||||
}
|
||||
|
||||
public void setStartTime(String startTime) {
|
||||
this.startTime = startTime;
|
||||
}
|
||||
|
||||
public String getEndTime() {
|
||||
return endTime;
|
||||
}
|
||||
|
||||
public void setEndTime(String endTime) {
|
||||
this.endTime = endTime;
|
||||
}
|
||||
|
||||
public String getCallId() {
|
||||
return callId;
|
||||
}
|
||||
|
||||
public void setCallId(String callId) {
|
||||
this.callId = callId;
|
||||
}
|
||||
}
|
||||
159
src/main/java/top/panll/assist/service/FFmpegExecUtils.java
Normal file
159
src/main/java/top/panll/assist/service/FFmpegExecUtils.java
Normal file
@ -0,0 +1,159 @@
|
||||
package top.panll.assist.service;
|
||||
|
||||
import net.bramp.ffmpeg.FFmpeg;
|
||||
import net.bramp.ffmpeg.FFmpegExecutor;
|
||||
import net.bramp.ffmpeg.FFmpegUtils;
|
||||
import net.bramp.ffmpeg.FFprobe;
|
||||
import net.bramp.ffmpeg.builder.FFmpegBuilder;
|
||||
import net.bramp.ffmpeg.job.FFmpegJob;
|
||||
import net.bramp.ffmpeg.probe.FFmpegProbeResult;
|
||||
import net.bramp.ffmpeg.progress.Progress;
|
||||
import net.bramp.ffmpeg.progress.ProgressListener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.DigestUtils;
|
||||
import top.panll.assist.dto.UserSettings;
|
||||
import top.panll.assist.dto.VideoFile;
|
||||
import top.panll.assist.utils.RedisUtil;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Component
|
||||
public class FFmpegExecUtils implements InitializingBean{
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(FFmpegExecUtils.class);
|
||||
// private static FFmpegExecUtils instance;
|
||||
//
|
||||
// public FFmpegExecUtils() {
|
||||
// }
|
||||
//
|
||||
// public static FFmpegExecUtils getInstance(){
|
||||
// if(instance==null){
|
||||
// synchronized (FFmpegExecUtils.class){
|
||||
// if(instance==null){
|
||||
// instance=new FFmpegExecUtils();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return instance;
|
||||
// }
|
||||
@Autowired
|
||||
private UserSettings userSettings;
|
||||
|
||||
private FFprobe ffprobe;
|
||||
private FFmpeg ffmpeg;
|
||||
|
||||
public FFprobe getFfprobe() {
|
||||
return ffprobe;
|
||||
}
|
||||
|
||||
public FFmpeg getFfmpeg() {
|
||||
return ffmpeg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
String ffmpegPath = userSettings.getFfmpeg();
|
||||
String ffprobePath = userSettings.getFfprobe();
|
||||
this.ffmpeg = new FFmpeg(ffmpegPath);
|
||||
this.ffprobe = new FFprobe(ffprobePath);
|
||||
logger.info("wvp-pro辅助程序启动成功。 \n{}\n{} ", this.ffmpeg.version(), this.ffprobe.version());
|
||||
}
|
||||
|
||||
|
||||
|
||||
public interface VideoHandEndCallBack {
|
||||
void run(String status, double percentage, String result);
|
||||
}
|
||||
|
||||
@Async
|
||||
public void mergeOrCutFile(List<File> fils, File dest, String destFileName, VideoHandEndCallBack callBack){
|
||||
|
||||
if (fils == null || fils.size() == 0 || ffmpeg == null || ffprobe == null || dest== null || !dest.exists()){
|
||||
callBack.run("error", 0.0, null);
|
||||
return;
|
||||
}
|
||||
|
||||
File tempFile = new File(dest.getAbsolutePath());
|
||||
if (!tempFile.exists()) {
|
||||
tempFile.mkdirs();
|
||||
}
|
||||
FFmpegExecutor executor = new FFmpegExecutor(ffmpeg, ffprobe);
|
||||
String fileListName = tempFile.getAbsolutePath() + File.separator + "fileList";
|
||||
double durationAll = 0.0;
|
||||
try {
|
||||
BufferedWriter bw =new BufferedWriter(new FileWriter(fileListName));
|
||||
for (File file : fils) {
|
||||
VideoFile videoFile = VideoFileFactory.createFile(this, file);
|
||||
if (videoFile == null) {
|
||||
return;
|
||||
}
|
||||
bw.write("file " + file.getAbsolutePath());
|
||||
bw.newLine();
|
||||
durationAll += videoFile.getDuration();
|
||||
}
|
||||
bw.flush();
|
||||
bw.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
callBack.run("error", 0.0, null);
|
||||
}
|
||||
String recordFileResultPath = dest.getAbsolutePath() + File.separator + destFileName + ".mp4";
|
||||
long startTime = System.currentTimeMillis();
|
||||
FFmpegBuilder builder = new FFmpegBuilder()
|
||||
|
||||
.setFormat("concat")
|
||||
.overrideOutputFiles(true)
|
||||
.setInput(fileListName) // Or filename
|
||||
.addExtraArgs("-safe", "0")
|
||||
.addExtraArgs("-threads", userSettings.getThreads() + "")
|
||||
.addOutput(recordFileResultPath)
|
||||
.setVideoCodec("copy")
|
||||
.setAudioCodec("aac")
|
||||
.setFormat("mp4")
|
||||
.done();
|
||||
|
||||
double finalDurationAll = durationAll;
|
||||
FFmpegJob job = executor.createJob(builder, (Progress progress) -> {
|
||||
final double duration_ns = finalDurationAll * TimeUnit.SECONDS.toNanos(1);
|
||||
double percentage = progress.out_time_ns / duration_ns;
|
||||
|
||||
// Print out interesting information about the progress
|
||||
// System.out.println(String.format(
|
||||
// "[%.0f%%] status:%s frame:%d time:%s ms fps:%.0f speed:%.2fx",
|
||||
// percentage * 100,
|
||||
// progress.status,
|
||||
// progress.frame,
|
||||
// FFmpegUtils.toTimecode(progress.out_time_ns, TimeUnit.NANOSECONDS),
|
||||
// progress.fps.doubleValue(),
|
||||
// progress.speed
|
||||
// ));
|
||||
|
||||
if (progress.status.equals(Progress.Status.END)){
|
||||
callBack.run(progress.status.name(), percentage, recordFileResultPath);
|
||||
}else {
|
||||
callBack.run(progress.status.name(), percentage, null);
|
||||
}
|
||||
|
||||
});
|
||||
job.run();
|
||||
}
|
||||
|
||||
public long duration(File file) throws IOException {
|
||||
FFmpegProbeResult in = ffprobe.probe(file.getAbsolutePath());
|
||||
double duration = in.getFormat().duration * 1000;
|
||||
long durationLong = new Double(duration).longValue();
|
||||
return durationLong;
|
||||
}
|
||||
|
||||
}
|
||||
86
src/main/java/top/panll/assist/service/FileManagerTimer.java
Normal file
86
src/main/java/top/panll/assist/service/FileManagerTimer.java
Normal file
@ -0,0 +1,86 @@
|
||||
package top.panll.assist.service;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
import top.panll.assist.dto.AssistConstants;
|
||||
import top.panll.assist.dto.MergeOrCutTaskInfo;
|
||||
import top.panll.assist.dto.UserSettings;
|
||||
import top.panll.assist.utils.RedisUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
public class FileManagerTimer {
|
||||
|
||||
private SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
|
||||
private final SimpleDateFormat simpleDateFormatForTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(FileManagerTimer.class);
|
||||
|
||||
@Autowired
|
||||
private UserSettings userSettings;
|
||||
|
||||
@Autowired
|
||||
private VideoFileService videoFileService;
|
||||
|
||||
@Autowired
|
||||
private RedisUtil redisUtil;
|
||||
|
||||
// @Scheduled(fixedDelay = 2000) //测试 20秒执行一次
|
||||
@Scheduled(cron = "0 0 0 * * ?") //每天的0点执行
|
||||
public void execute(){
|
||||
if (userSettings.getRecordTempPath() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 清理任务临时文件
|
||||
int recordTempDay = userSettings.getRecordTempDay();
|
||||
Date lastTempDate = new Date();
|
||||
Calendar lastTempCalendar = Calendar.getInstance();
|
||||
lastTempCalendar.setTime(lastTempDate);
|
||||
lastTempCalendar.add(Calendar.DAY_OF_MONTH, -recordTempDay);
|
||||
lastTempDate = lastTempCalendar.getTime();
|
||||
logger.info("[录像巡查]移除合并任务临时文件 {} 之前的文件", formatter.format(lastTempDate));
|
||||
File recordTempFile = new File(userSettings.getRecordTempPath());
|
||||
if (recordTempFile.exists() && recordTempFile.isDirectory() && recordTempFile.canWrite()) {
|
||||
File[] tempFiles = recordTempFile.listFiles();
|
||||
if (tempFiles != null) {
|
||||
for (File tempFile : tempFiles) {
|
||||
if (tempFile.isFile() && tempFile.lastModified() < lastTempDate.getTime()) {
|
||||
boolean result = FileUtils.deleteQuietly(tempFile);
|
||||
if (result) {
|
||||
logger.info("[录像巡查]成功移除合并任务临时文件 {} ", tempFile.getAbsolutePath());
|
||||
}else {
|
||||
logger.info("[录像巡查]合并任务临时文件移除失败 {} ", tempFile.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 清理redis记录
|
||||
String key = String.format("%S_%S_*", AssistConstants.MERGEORCUT, userSettings.getId());
|
||||
List<Object> taskKeys = redisUtil.scan(key);
|
||||
for (Object taskKeyObj : taskKeys) {
|
||||
String taskKey = (String) taskKeyObj;
|
||||
MergeOrCutTaskInfo mergeOrCutTaskInfo = (MergeOrCutTaskInfo)redisUtil.get(taskKey);
|
||||
try {
|
||||
if (StringUtils.hasLength(mergeOrCutTaskInfo.getCreateTime())
|
||||
|| simpleDateFormatForTime.parse(mergeOrCutTaskInfo.getCreateTime()).before(lastTempDate)) {
|
||||
redisUtil.del(taskKey);
|
||||
}
|
||||
} catch (ParseException e) {
|
||||
logger.error("[清理过期的redis合并任务信息] 失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
152
src/main/java/top/panll/assist/service/VideoFileFactory.java
Normal file
152
src/main/java/top/panll/assist/service/VideoFileFactory.java
Normal file
@ -0,0 +1,152 @@
|
||||
package top.panll.assist.service;
|
||||
|
||||
import net.bramp.ffmpeg.probe.FFmpegProbeResult;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import top.panll.assist.dto.VideoFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
public class VideoFileFactory {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(VideoFileFactory.class);
|
||||
|
||||
public static VideoFile createFile(FFmpegExecUtils ffmpegExecUtils, File file){
|
||||
if (!file.exists()) {
|
||||
return null;
|
||||
}
|
||||
if (!file.isFile()){
|
||||
return null;
|
||||
}
|
||||
if (!file.getName().endsWith(".mp4")){
|
||||
return null;
|
||||
}
|
||||
if (file.isHidden()){
|
||||
return null;
|
||||
}
|
||||
String date = file.getParentFile().getName();
|
||||
if (file.getName().indexOf(":") > 0) {
|
||||
// 格式为 HH:mm:ss-HH:mm:ss-时长
|
||||
|
||||
String[] split = file.getName().split("-");
|
||||
if (split.length != 3) {
|
||||
return null;
|
||||
}
|
||||
String startTimeStr = date + " " + split[0];
|
||||
String endTimeStr = date + " " + split[1];
|
||||
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
VideoFile videoFile = new VideoFile();
|
||||
videoFile.setFile(file);
|
||||
videoFile.setTargetFormat(false);
|
||||
try {
|
||||
Date startTimeDate = simpleDateFormat.parse(startTimeStr);
|
||||
videoFile.setStartTime(startTimeDate);
|
||||
Date endTimeDate = simpleDateFormat.parse(endTimeStr);
|
||||
videoFile.setEndTime(endTimeDate);
|
||||
videoFile.setDuration((endTimeDate.getTime() - startTimeDate.getTime()));
|
||||
} catch (ParseException e) {
|
||||
logger.error("[构建视频文件对象] 格式化时间失败, file:{}", file.getAbsolutePath(), e);
|
||||
return null;
|
||||
}
|
||||
return videoFile;
|
||||
|
||||
}else if (getStrCountInStr(file.getName(), "-") == 3){
|
||||
|
||||
// 格式为zlm的录制格式 HH-mm-ss-序号
|
||||
String startStr = file.getName().substring(0, file.getName().lastIndexOf("-"));
|
||||
String startTimeStr = date + " " + startStr;
|
||||
VideoFile videoFile = null;
|
||||
try {
|
||||
FFmpegProbeResult fFmpegProbeResult = ffmpegExecUtils.getFfprobe().probe(file.getAbsolutePath());
|
||||
double duration = fFmpegProbeResult.getFormat().duration * 1000;
|
||||
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss");
|
||||
Date startTimeDate = simpleDateFormat.parse(startTimeStr);
|
||||
Date endTimeDate = new Date(startTimeDate.getTime() + new Double(duration).longValue());
|
||||
videoFile = new VideoFile();
|
||||
videoFile.setTargetFormat(false);
|
||||
videoFile.setFile(file);
|
||||
videoFile.setStartTime(startTimeDate);
|
||||
videoFile.setEndTime(endTimeDate);
|
||||
videoFile.setDuration((endTimeDate.getTime() - startTimeDate.getTime())/1000);
|
||||
} catch (IOException e) {
|
||||
logger.error("[构建视频文件对象] 获取视频时长失败, file:{}", file.getAbsolutePath(), e);
|
||||
return null;
|
||||
} catch (ParseException e) {
|
||||
logger.error("[构建视频文件对象] 格式化时间失败, file:{}", file.getAbsolutePath(), e);
|
||||
return null;
|
||||
}
|
||||
return videoFile;
|
||||
}else if (getStrCountInStr(file.getName(), "-") == 2 && file.getName().length() == 10 ){
|
||||
// 格式为zlm的录制格式 HH-mm-ss
|
||||
String startTimeStr = date + " " + file.getName();
|
||||
VideoFile videoFile = null;
|
||||
try {
|
||||
FFmpegProbeResult fFmpegProbeResult = ffmpegExecUtils.getFfprobe().probe(file.getAbsolutePath());
|
||||
double duration = fFmpegProbeResult.getFormat().duration * 1000;
|
||||
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss");
|
||||
Date startTimeDate = simpleDateFormat.parse(startTimeStr);
|
||||
Date endTimeDate = new Date(startTimeDate.getTime() + new Double(duration).longValue());
|
||||
videoFile = new VideoFile();
|
||||
videoFile.setTargetFormat(false);
|
||||
videoFile.setFile(file);
|
||||
videoFile.setStartTime(startTimeDate);
|
||||
videoFile.setEndTime(endTimeDate);
|
||||
videoFile.setDuration((endTimeDate.getTime() - startTimeDate.getTime())/1000);
|
||||
} catch (IOException e) {
|
||||
logger.error("[构建视频文件对象] 获取视频时长失败, file:{}", file.getAbsolutePath(), e);
|
||||
return null;
|
||||
} catch (ParseException e) {
|
||||
logger.warn("[构建视频文件对象] 格式化时间失败, file:{}", file.getAbsolutePath(), e);
|
||||
return null;
|
||||
}
|
||||
return videoFile;
|
||||
}else if (getStrCountInStr(file.getName(), "-") == 1 ){
|
||||
// 格式为zlm的录制格式 HH-mm-ss
|
||||
// 格式为 HH:mm:ss-HH:mm:ss-时长
|
||||
|
||||
String[] split = file.getName().split("-");
|
||||
if (split.length != 2) {
|
||||
return null;
|
||||
}
|
||||
String startTimeStr = date + " " + split[0];
|
||||
String endTimeStr = date + " " + split[1];
|
||||
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HHmmss");
|
||||
VideoFile videoFile = new VideoFile();
|
||||
videoFile.setTargetFormat(true);
|
||||
videoFile.setFile(file);
|
||||
try {
|
||||
Date startTimeDate = simpleDateFormat.parse(startTimeStr);
|
||||
videoFile.setStartTime(startTimeDate);
|
||||
Date endTimeDate = simpleDateFormat.parse(endTimeStr);
|
||||
videoFile.setEndTime(endTimeDate);
|
||||
videoFile.setDuration((endTimeDate.getTime() - startTimeDate.getTime()));
|
||||
} catch (ParseException e) {
|
||||
logger.error("[构建视频文件对象] 格式化时间失败, file:{}", file.getAbsolutePath(), e);
|
||||
return null;
|
||||
}
|
||||
return videoFile;
|
||||
}else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static int getStrCountInStr(String sourceStr, String content) {
|
||||
int index = sourceStr.indexOf(content);
|
||||
if (index < 0) {
|
||||
return 0;
|
||||
}
|
||||
int count = 1;
|
||||
int lastIndex = sourceStr.lastIndexOf(content);
|
||||
while (index != lastIndex) {
|
||||
index = sourceStr.indexOf(content, index + 1);
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
}
|
||||
505
src/main/java/top/panll/assist/service/VideoFileService.java
Normal file
505
src/main/java/top/panll/assist/service/VideoFileService.java
Normal file
@ -0,0 +1,505 @@
|
||||
package top.panll.assist.service;
|
||||
|
||||
import net.bramp.ffmpeg.FFprobe;
|
||||
import net.bramp.ffmpeg.probe.FFmpegProbeResult;
|
||||
import net.bramp.ffmpeg.progress.Progress;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.DigestUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import top.panll.assist.controller.bean.ControllerException;
|
||||
import top.panll.assist.controller.bean.ErrorCode;
|
||||
import top.panll.assist.dto.*;
|
||||
import top.panll.assist.utils.RedisUtil;
|
||||
import top.panll.assist.utils.DateUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
|
||||
@Service
|
||||
public class VideoFileService {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(VideoFileService.class);
|
||||
|
||||
@Autowired
|
||||
private UserSettings userSettings;
|
||||
|
||||
@Autowired
|
||||
private RedisUtil redisUtil;
|
||||
|
||||
@Autowired
|
||||
private FFmpegExecUtils ffmpegExecUtils;
|
||||
|
||||
|
||||
|
||||
private final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
|
||||
private final SimpleDateFormat simpleDateFormatForTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
public List<File> getAppList(Boolean sort) {
|
||||
File recordFile = new File(userSettings.getRecordTempPath());
|
||||
if (recordFile.isDirectory()) {
|
||||
File[] files = recordFile.listFiles((File dir, String name) -> {
|
||||
File currentFile = new File(dir.getAbsolutePath() + File.separator + name);
|
||||
return currentFile.isDirectory() && !name.equals("recordTemp");
|
||||
});
|
||||
List<File> result = Arrays.asList(files);
|
||||
if (sort != null && sort) {
|
||||
Collections.sort(result);
|
||||
}
|
||||
return result;
|
||||
}else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public SpaceInfo getSpaceInfo(){
|
||||
File recordFile = new File(userSettings.getRecordTempPath());
|
||||
SpaceInfo spaceInfo = new SpaceInfo();
|
||||
spaceInfo.setFree(recordFile.getFreeSpace());
|
||||
spaceInfo.setTotal(recordFile.getTotalSpace());
|
||||
return spaceInfo;
|
||||
}
|
||||
|
||||
|
||||
public List<File> getStreamList(File appFile, Boolean sort) {
|
||||
if (appFile != null && appFile.isDirectory()) {
|
||||
File[] files = appFile.listFiles((File dir, String name) -> {
|
||||
File currentFile = new File(dir.getAbsolutePath() + File.separator + name);
|
||||
return currentFile.isDirectory();
|
||||
});
|
||||
List<File> result = Arrays.asList(files);
|
||||
if (sort != null && sort) {
|
||||
Collections.sort(result);
|
||||
}
|
||||
return result;
|
||||
}else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取制定推流的指定时间段内的推流
|
||||
* @param app
|
||||
* @param stream
|
||||
* @param startTime
|
||||
* @param endTime
|
||||
* @return
|
||||
*/
|
||||
public List<File> getFilesInTime(String app, String stream, Date startTime, Date endTime){
|
||||
|
||||
List<File> result = new ArrayList<>();
|
||||
if (app == null || stream == null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HHmmss");
|
||||
SimpleDateFormat formatterForDate = new SimpleDateFormat("yyyy-MM-dd");
|
||||
String startTimeStr = null;
|
||||
String endTimeStr = null;
|
||||
if (startTime != null) {
|
||||
startTimeStr = formatter.format(startTime);
|
||||
}
|
||||
if (endTime != null) {
|
||||
endTimeStr = formatter.format(endTime);
|
||||
}
|
||||
|
||||
logger.debug("获取[app: {}, stream: {}, statime: {}, endTime: {}]的视频", app, stream,
|
||||
startTimeStr, endTimeStr);
|
||||
|
||||
File recordFile = new File(userSettings.getRecordTempPath());
|
||||
File streamFile = new File(recordFile.getAbsolutePath() + File.separator + app + File.separator + stream + File.separator);
|
||||
if (!streamFile.exists()) {
|
||||
logger.warn("获取[app: {}, stream: {}, statime: {}, endTime: {}]的视频时未找到目录: {}", app, stream,
|
||||
startTimeStr, endTimeStr, stream);
|
||||
return null;
|
||||
}
|
||||
|
||||
File[] dateFiles = streamFile.listFiles((File dir, String name) -> {
|
||||
Date fileDate = null;
|
||||
Date startDate = null;
|
||||
Date endDate = null;
|
||||
if (new File(dir + File.separator + name).isFile()) {
|
||||
return false;
|
||||
}
|
||||
if (startTime != null) {
|
||||
startDate = new Date(startTime.getTime() - ((startTime.getTime() + 28800000) % (86400000)));
|
||||
}
|
||||
if (endTime != null) {
|
||||
endDate = new Date(endTime.getTime() - ((endTime.getTime() + 28800000) % (86400000)));
|
||||
}
|
||||
try {
|
||||
fileDate = formatterForDate.parse(name);
|
||||
} catch (ParseException e) {
|
||||
logger.error("过滤日期文件时异常: {}-{}", name, e.getMessage());
|
||||
return false;
|
||||
}
|
||||
boolean filterResult = true;
|
||||
|
||||
if (startDate != null) {
|
||||
filterResult = filterResult && DateUtils.getStartOfDay(startDate).compareTo(fileDate) <= 0;
|
||||
}
|
||||
|
||||
if (endDate != null) {
|
||||
filterResult = filterResult && DateUtils.getEndOfDay(endDate).compareTo(fileDate) >= 0;
|
||||
}
|
||||
|
||||
return filterResult ;
|
||||
});
|
||||
|
||||
if (dateFiles != null && dateFiles.length > 0) {
|
||||
for (File dateFile : dateFiles) {
|
||||
File[] files = dateFile.listFiles((File dir, String name) ->{
|
||||
File currentFile = new File(dir + File.separator + name);
|
||||
VideoFile videoFile = VideoFileFactory.createFile(ffmpegExecUtils, currentFile);
|
||||
if (videoFile == null ) {
|
||||
return false;
|
||||
}else {
|
||||
if (!videoFile.isTargetFormat()) {
|
||||
return false;
|
||||
}
|
||||
if (startTime == null && endTime == null) {
|
||||
return true;
|
||||
}else if (startTime == null && endTime != null) {
|
||||
return videoFile.getEndTime().before(endTime)
|
||||
|| videoFile.getEndTime().equals(endTime)
|
||||
|| (videoFile.getEndTime().after(endTime) && videoFile.getStartTime().before(endTime));
|
||||
}else if (startTime != null && endTime == null) {
|
||||
return videoFile.getStartTime().after(startTime)
|
||||
|| videoFile.getStartTime().equals(startTime)
|
||||
|| (videoFile.getStartTime().before(startTime) && videoFile.getEndTime().after(startTime));
|
||||
}else {
|
||||
return videoFile.getStartTime().after(startTime)
|
||||
|| videoFile.getStartTime().equals(startTime)
|
||||
|| (videoFile.getStartTime().before(startTime) && videoFile.getEndTime().after(startTime))
|
||||
|| videoFile.getEndTime().before(endTime)
|
||||
|| videoFile.getEndTime().equals(endTime)
|
||||
|| (videoFile.getEndTime().after(endTime) && videoFile.getStartTime().before(endTime));
|
||||
}
|
||||
}
|
||||
});
|
||||
if (files != null && files.length > 0) {
|
||||
result.addAll(Arrays.asList(files));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!result.isEmpty()) {
|
||||
result.sort((File f1, File f2) -> {
|
||||
VideoFile videoFile1 = VideoFileFactory.createFile(ffmpegExecUtils, f1);
|
||||
VideoFile videoFile2 = VideoFileFactory.createFile(ffmpegExecUtils, f2);
|
||||
if (videoFile1 == null || !videoFile1.isTargetFormat() || videoFile2 == null || !videoFile2.isTargetFormat()) {
|
||||
logger.warn("[根据时间获取视频文件] 排序错误,文件错误: {}/{}", f1.getName(), f2.getName());
|
||||
return 0;
|
||||
}
|
||||
return videoFile1.getStartTime().compareTo(videoFile2.getStartTime());
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public String mergeOrCut(VideoTaskInfo videoTaskInfo) {
|
||||
assert videoTaskInfo.getFilePathList() != null;
|
||||
assert !videoTaskInfo.getFilePathList().isEmpty();
|
||||
String taskId = DigestUtils.md5DigestAsHex(String.valueOf(System.currentTimeMillis()).getBytes());
|
||||
String logInfo = String.format("app: %S, stream: %S, callId: %S, 任务ID:%S",
|
||||
videoTaskInfo.getApp(), videoTaskInfo.getStream(), videoTaskInfo.getCallId(), taskId);
|
||||
logger.info("[录像合并] 开始合并,{} ", logInfo);
|
||||
List<File> fileList = new ArrayList<>();
|
||||
for (String filePath : videoTaskInfo.getFilePathList()) {
|
||||
File file = new File(filePath);
|
||||
if (!file.exists()) {
|
||||
logger.info("[录像合并] 失败,{} ", logInfo);
|
||||
throw new ControllerException(ErrorCode.ERROR100.getCode(), filePath + "文件不存在");
|
||||
}
|
||||
logger.info("[录像合并] 添加文件,{}, 文件: {}", logInfo, filePath);
|
||||
fileList.add(file);
|
||||
}
|
||||
|
||||
File recordFile = new File(userSettings.getRecordTempPath() );
|
||||
if (!recordFile.exists()) {
|
||||
if (!recordFile.mkdirs()) {
|
||||
logger.info("[录像合并] 失败,{}, 创建临时目录失败", logInfo);
|
||||
throw new ControllerException(ErrorCode.ERROR100.getCode(), "创建临时目录失败");
|
||||
}
|
||||
}
|
||||
MergeOrCutTaskInfo mergeOrCutTaskInfo = new MergeOrCutTaskInfo();
|
||||
mergeOrCutTaskInfo.setId(taskId);
|
||||
mergeOrCutTaskInfo.setApp(videoTaskInfo.getApp());
|
||||
mergeOrCutTaskInfo.setStream(videoTaskInfo.getStream());
|
||||
mergeOrCutTaskInfo.setCallId(videoTaskInfo.getCallId());
|
||||
mergeOrCutTaskInfo.setStartTime(videoTaskInfo.getStartTime());
|
||||
mergeOrCutTaskInfo.setEndTime(videoTaskInfo.getEndTime());
|
||||
mergeOrCutTaskInfo.setCreateTime(simpleDateFormatForTime.format(System.currentTimeMillis()));
|
||||
String destFileName = videoTaskInfo.getStream() + "_" + videoTaskInfo.getCallId();
|
||||
if (fileList.size() == 1) {
|
||||
|
||||
// 文件只有一个则不合并,直接复制过去
|
||||
mergeOrCutTaskInfo.setPercentage("1");
|
||||
// 处理文件路径
|
||||
String recordFileResultPath = recordFile.getAbsolutePath() + File.separator + destFileName + ".mp4";
|
||||
File destFile = new File(recordFileResultPath);
|
||||
destFile.deleteOnExit();
|
||||
try {
|
||||
Files.copy(fileList.get(0).toPath(), Paths.get(recordFileResultPath));
|
||||
} catch (IOException e) {
|
||||
logger.info("[录像合并] 失败, {}", logInfo, e);
|
||||
throw new ControllerException(ErrorCode.ERROR100.getCode(), e.getMessage());
|
||||
}
|
||||
mergeOrCutTaskInfo.setRecordFile("/download/" + destFileName + ".mp4");
|
||||
if (videoTaskInfo.getRemoteHost() != null) {
|
||||
mergeOrCutTaskInfo.setDownloadFile(videoTaskInfo.getRemoteHost() + "/download.html?url=download/" + destFileName + ".mp4");
|
||||
mergeOrCutTaskInfo.setPlayFile(videoTaskInfo.getRemoteHost() + "/download/" + destFileName + ".mp4");
|
||||
}
|
||||
String key = String.format("%S_%S_%S", AssistConstants.MERGEORCUT , userSettings.getId(), mergeOrCutTaskInfo.getId());
|
||||
redisUtil.set(key, mergeOrCutTaskInfo);
|
||||
logger.info("[录像合并] 成功, 任务ID:{}", taskId);
|
||||
}else {
|
||||
ffmpegExecUtils.mergeOrCutFile(fileList, recordFile, destFileName, (status, percentage, result)->{
|
||||
// 发出redis通知
|
||||
if (status.equals(Progress.Status.END.name())) {
|
||||
mergeOrCutTaskInfo.setPercentage("1");
|
||||
|
||||
// 处理文件路径
|
||||
String relativize = new File(result).getName();
|
||||
mergeOrCutTaskInfo.setRecordFile(relativize.toString());
|
||||
if (videoTaskInfo.getRemoteHost() != null) {
|
||||
mergeOrCutTaskInfo.setDownloadFile(videoTaskInfo.getRemoteHost() + "/download.html?url=download/" + relativize);
|
||||
mergeOrCutTaskInfo.setPlayFile(videoTaskInfo.getRemoteHost() + "/download/" + relativize);
|
||||
}
|
||||
logger.info("[录像合并] 成功, {}", logInfo);
|
||||
}else {
|
||||
mergeOrCutTaskInfo.setPercentage(percentage + "");
|
||||
}
|
||||
String key = String.format("%S_%S_%S", AssistConstants.MERGEORCUT, userSettings.getId(), mergeOrCutTaskInfo.getId());
|
||||
redisUtil.set(key, mergeOrCutTaskInfo);
|
||||
});
|
||||
}
|
||||
|
||||
return taskId;
|
||||
}
|
||||
|
||||
public List<File> getDateList(File streamFile, Integer year, Integer month, Boolean sort) {
|
||||
if (!streamFile.exists() && streamFile.isDirectory()) {
|
||||
logger.warn("获取[]的视频时未找到目录: {}",streamFile.getName());
|
||||
return null;
|
||||
}
|
||||
File[] dateFiles = streamFile.listFiles((File dir, String name)->{
|
||||
File currentFile = new File(dir.getAbsolutePath() + File.separator + name);
|
||||
if (!currentFile.isDirectory()){
|
||||
return false;
|
||||
}
|
||||
Date date = null;
|
||||
try {
|
||||
date = simpleDateFormat.parse(name);
|
||||
} catch (ParseException e) {
|
||||
logger.error("格式化时间{}错误", name);
|
||||
return false;
|
||||
}
|
||||
Calendar c = Calendar.getInstance();
|
||||
c.setTime(date);
|
||||
int y = c.get(Calendar.YEAR);
|
||||
int m = c.get(Calendar.MONTH) + 1;
|
||||
if (year != null) {
|
||||
if (month != null) {
|
||||
return y == year && m == month;
|
||||
}else {
|
||||
return y == year;
|
||||
}
|
||||
}else {
|
||||
return true;
|
||||
}
|
||||
|
||||
});
|
||||
if (dateFiles == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
List<File> dateFileList = Arrays.asList(dateFiles);
|
||||
if (sort != null && sort) {
|
||||
dateFileList.sort((File f1, File f2)->{
|
||||
int sortResult = 0;
|
||||
|
||||
try {
|
||||
sortResult = simpleDateFormat.parse(f1.getName()).compareTo(simpleDateFormat.parse(f2.getName()));
|
||||
} catch (ParseException e) {
|
||||
logger.error("格式化时间{}/{}错误", f1.getName(), f2.getName());
|
||||
}
|
||||
return sortResult;
|
||||
});
|
||||
}
|
||||
|
||||
return dateFileList;
|
||||
}
|
||||
|
||||
public List<MergeOrCutTaskInfo> getTaskListForDownload(String app, String stream, String callId, Boolean isEnd, String taskId) {
|
||||
logger.info("[查询录像合成列表] app: {}, stream: {}, callId: {}, isEnd: {}, taskId: {}",
|
||||
app, stream, callId, isEnd, taskId);
|
||||
ArrayList<MergeOrCutTaskInfo> result = new ArrayList<>();
|
||||
if (taskId == null) {
|
||||
taskId = "*";
|
||||
}
|
||||
List<Object> taskCatch = redisUtil.scan(String.format("%S_%S_%S", AssistConstants.MERGEORCUT,
|
||||
userSettings.getId(), taskId));
|
||||
for (int i = 0; i < taskCatch.size(); i++) {
|
||||
String keyItem = taskCatch.get(i).toString();
|
||||
MergeOrCutTaskInfo mergeOrCutTaskInfo = (MergeOrCutTaskInfo)redisUtil.get(keyItem);
|
||||
if (mergeOrCutTaskInfo != null){
|
||||
if ((!ObjectUtils.isEmpty(app) && !mergeOrCutTaskInfo.getApp().equals(app))
|
||||
|| (!ObjectUtils.isEmpty(stream) && !mergeOrCutTaskInfo.getStream().equals(stream))
|
||||
|| (!ObjectUtils.isEmpty(callId) && !mergeOrCutTaskInfo.getCallId().equals(callId))
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
if (mergeOrCutTaskInfo.getPercentage() != null){
|
||||
if (isEnd != null ) {
|
||||
if (isEnd) {
|
||||
if (Double.parseDouble(mergeOrCutTaskInfo.getPercentage()) == 1){
|
||||
result.add(mergeOrCutTaskInfo);
|
||||
}
|
||||
}else {
|
||||
if (Double.parseDouble(mergeOrCutTaskInfo.getPercentage()) < 1){
|
||||
result.add((MergeOrCutTaskInfo)redisUtil.get(keyItem));
|
||||
}
|
||||
}
|
||||
}else {
|
||||
result.add((MergeOrCutTaskInfo)redisUtil.get(keyItem));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
result.sort((MergeOrCutTaskInfo m1, MergeOrCutTaskInfo m2)->{
|
||||
int sortResult = 0;
|
||||
try {
|
||||
sortResult = simpleDateFormatForTime.parse(m1.getCreateTime()).compareTo(simpleDateFormatForTime.parse(m2.getCreateTime()));
|
||||
if (sortResult == 0) {
|
||||
sortResult = simpleDateFormatForTime.parse(m1.getCreateTime()).compareTo(simpleDateFormatForTime.parse(m2.getCreateTime()));
|
||||
}
|
||||
if (sortResult == 0) {
|
||||
sortResult = simpleDateFormatForTime.parse(m1.getCreateTime()).compareTo(simpleDateFormatForTime.parse(m2.getCreateTime()));
|
||||
}
|
||||
} catch (ParseException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return sortResult * -1;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean collection(String app, String stream, String type) {
|
||||
File streamFile = new File(userSettings.getRecordTempPath() + File.separator + app + File.separator + stream);
|
||||
boolean result = false;
|
||||
if (streamFile.exists() && streamFile.isDirectory() && streamFile.canWrite()) {
|
||||
File signFile = new File(streamFile.getAbsolutePath() + File.separator + type + ".sign");
|
||||
try {
|
||||
result = signFile.createNewFile();
|
||||
} catch (IOException e) {
|
||||
logger.error("[收藏文件]失败,{}/{}", app, stream);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean removeCollection(String app, String stream, String type) {
|
||||
File signFile = new File(userSettings.getRecordTempPath() + File.separator + app + File.separator + stream + File.separator + type + ".sign");
|
||||
boolean result = false;
|
||||
if (signFile.exists() && signFile.isFile()) {
|
||||
result = signFile.delete();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<SignInfo> getCollectionList(String app, String stream, String type) {
|
||||
List<File> appList = this.getAppList(true);
|
||||
List<SignInfo> result = new ArrayList<>();
|
||||
if (appList.size() > 0) {
|
||||
for (File appFile : appList) {
|
||||
if (app != null) {
|
||||
if (!app.equals(appFile.getName())) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
List<File> streamList = getStreamList(appFile, true);
|
||||
if (streamList.size() > 0) {
|
||||
for (File streamFile : streamList) {
|
||||
if (stream != null) {
|
||||
if (!stream.equals(streamFile.getName())) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (type != null) {
|
||||
File signFile = new File(streamFile.getAbsolutePath() + File.separator + type + ".sign");
|
||||
if (signFile.exists()) {
|
||||
SignInfo signInfo = new SignInfo();
|
||||
signInfo.setApp(appFile.getName());
|
||||
signInfo.setStream(streamFile.getName());
|
||||
signInfo.setType(type);
|
||||
result.add(signInfo);
|
||||
}
|
||||
}else {
|
||||
streamFile.listFiles((File dir, String name) -> {
|
||||
File currentFile = new File(dir.getAbsolutePath() + File.separator + name);
|
||||
if (currentFile.isFile() && name.endsWith(".sign")){
|
||||
String currentType = name.substring(0, name.length() - ".sign".length());
|
||||
SignInfo signInfo = new SignInfo();
|
||||
signInfo.setApp(appFile.getName());
|
||||
signInfo.setStream(streamFile.getName());
|
||||
signInfo.setType(currentType);
|
||||
result.add(signInfo);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public long fileDuration(String app, String stream) {
|
||||
List<File> allFiles = getFilesInTime(app, stream, null, null);
|
||||
long durationResult = 0;
|
||||
if (allFiles != null && allFiles.size() > 0) {
|
||||
for (File file : allFiles) {
|
||||
try {
|
||||
durationResult += ffmpegExecUtils.duration(file);
|
||||
} catch (IOException e) {
|
||||
logger.error("获取{}视频时长错误:{}", file.getAbsolutePath(), e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
return durationResult;
|
||||
}
|
||||
|
||||
public int deleteFile(List<String> filePathList) {
|
||||
assert filePathList != null;
|
||||
assert filePathList.isEmpty();
|
||||
int deleteResult = 0;
|
||||
for (String filePath : filePathList) {
|
||||
File file = new File(filePath);
|
||||
if (file.exists()) {
|
||||
if (file.delete()) {
|
||||
deleteResult ++;
|
||||
}
|
||||
}else {
|
||||
logger.warn("[删除文件] 文件不存在,{}", filePath);
|
||||
}
|
||||
}
|
||||
if (deleteResult == 0) {
|
||||
throw new ControllerException(ErrorCode.ERROR100.getCode(), "未删除任何文件");
|
||||
}
|
||||
return deleteResult;
|
||||
}
|
||||
}
|
||||
46
src/main/java/top/panll/assist/utils/DateUtils.java
Normal file
46
src/main/java/top/panll/assist/utils/DateUtils.java
Normal file
@ -0,0 +1,46 @@
|
||||
package top.panll.assist.utils;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
public class DateUtils {
|
||||
|
||||
public static final String PATTERNForDateTime = "yyyy-MM-dd HH:mm:ss";
|
||||
|
||||
public static final String PATTERNForDate = "yyyy-MM-dd";
|
||||
|
||||
public static final String zoneStr = "Asia/Shanghai";
|
||||
|
||||
|
||||
|
||||
// 获得某天最大时间 2020-02-19 23:59:59
|
||||
public static Date getEndOfDay(Date date) {
|
||||
LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(date.getTime()), ZoneId.systemDefault());;
|
||||
LocalDateTime endOfDay = localDateTime.with(LocalTime.MAX);
|
||||
return Date.from(endOfDay.atZone(ZoneId.systemDefault()).toInstant());
|
||||
}
|
||||
|
||||
// 获得某天最小时间 2020-02-17 00:00:00
|
||||
public static Date getStartOfDay(Date date) {
|
||||
LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(date.getTime()), ZoneId.systemDefault());
|
||||
LocalDateTime startOfDay = localDateTime.with(LocalTime.MIN);
|
||||
return Date.from(startOfDay.atZone(ZoneId.systemDefault()).toInstant());
|
||||
}
|
||||
|
||||
public static String getDateStr(Date date) {
|
||||
SimpleDateFormat formatter = new SimpleDateFormat(PATTERNForDate);
|
||||
return formatter.format(date);
|
||||
}
|
||||
|
||||
public static String getDateTimeStr(Date date) {
|
||||
SimpleDateFormat formatter = new SimpleDateFormat(PATTERNForDateTime);
|
||||
return formatter.format(date);
|
||||
}
|
||||
|
||||
}
|
||||
723
src/main/java/top/panll/assist/utils/RedisUtil.java
Normal file
723
src/main/java/top/panll/assist/utils/RedisUtil.java
Normal file
@ -0,0 +1,723 @@
|
||||
package top.panll.assist.utils;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.*;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @Description:Redis工具类
|
||||
* @author: swwheihei
|
||||
* @date: 2020年5月6日 下午8:27:29
|
||||
*/
|
||||
@Component
|
||||
@SuppressWarnings(value = {"rawtypes", "unchecked"})
|
||||
public class RedisUtil {
|
||||
|
||||
@Autowired
|
||||
private RedisTemplate<Object, Object> redisTemplate;
|
||||
|
||||
/**
|
||||
* 指定缓存失效时间
|
||||
* @param key 键
|
||||
* @param time 时间(秒)
|
||||
* @return true / false
|
||||
*/
|
||||
public boolean expire(String key, long time) {
|
||||
try {
|
||||
if (time > 0) {
|
||||
redisTemplate.expire(key, time, TimeUnit.SECONDS);
|
||||
}
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 key 获取过期时间
|
||||
* @param key 键
|
||||
* @return
|
||||
*/
|
||||
public long getExpire(String key) {
|
||||
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断 key 是否存在
|
||||
* @param key 键
|
||||
* @return true / false
|
||||
*/
|
||||
public boolean hasKey(String key) {
|
||||
try {
|
||||
return redisTemplate.hasKey(key);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除缓存
|
||||
* @SuppressWarnings("unchecked") 忽略类型转换警告
|
||||
* @param key 键(一个或者多个)
|
||||
*/
|
||||
public boolean del(String... key) {
|
||||
try {
|
||||
if (key != null && key.length > 0) {
|
||||
if (key.length == 1) {
|
||||
redisTemplate.delete(key[0]);
|
||||
} else {
|
||||
// 传入一个 Collection<String> 集合
|
||||
redisTemplate.delete(CollectionUtils.arrayToList(key));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================== String ==============================
|
||||
|
||||
/**
|
||||
* 普通缓存获取
|
||||
* @param key 键
|
||||
* @return 值
|
||||
*/
|
||||
public Object get(String key) {
|
||||
return key == null ? null : redisTemplate.opsForValue().get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 普通缓存放入
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @return true / false
|
||||
*/
|
||||
public boolean set(String key, Object value) {
|
||||
try {
|
||||
redisTemplate.opsForValue().set(key, value);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
// e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 普通缓存放入并设置时间
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @param time 时间(秒),如果 time < 0 则设置无限时间
|
||||
* @return true / false
|
||||
*/
|
||||
public boolean set(String key, Object value, long time) {
|
||||
try {
|
||||
if (time > 0) {
|
||||
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
|
||||
} else {
|
||||
set(key, value);
|
||||
}
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 递增
|
||||
* @param key 键
|
||||
* @param delta 递增大小
|
||||
* @return
|
||||
*/
|
||||
public long incr(String key, long delta) {
|
||||
if (delta < 0) {
|
||||
throw new RuntimeException("递增因子必须大于 0");
|
||||
}
|
||||
return redisTemplate.opsForValue().increment(key, delta);
|
||||
}
|
||||
|
||||
/**
|
||||
* 递减
|
||||
* @param key 键
|
||||
* @param delta 递减大小
|
||||
* @return
|
||||
*/
|
||||
public long decr(String key, long delta) {
|
||||
if (delta < 0) {
|
||||
throw new RuntimeException("递减因子必须大于 0");
|
||||
}
|
||||
return redisTemplate.opsForValue().increment(key, delta);
|
||||
}
|
||||
|
||||
// ============================== Map ==============================
|
||||
|
||||
/**
|
||||
* HashGet
|
||||
* @param key 键(no null)
|
||||
* @param item 项(no null)
|
||||
* @return 值
|
||||
*/
|
||||
public Object hget(String key, String item) {
|
||||
return redisTemplate.opsForHash().get(key, item);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 key 对应的 map
|
||||
* @param key 键(no null)
|
||||
* @return 对应的多个键值
|
||||
*/
|
||||
public Map<Object, Object> hmget(String key) {
|
||||
return redisTemplate.opsForHash().entries(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* HashSet
|
||||
* @param key 键
|
||||
* @param map 值
|
||||
* @return true / false
|
||||
*/
|
||||
public boolean hmset(String key, Map<Object, Object> map) {
|
||||
try {
|
||||
redisTemplate.opsForHash().putAll(key, map);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* HashSet 并设置时间
|
||||
* @param key 键
|
||||
* @param map 值
|
||||
* @param time 时间
|
||||
* @return true / false
|
||||
*/
|
||||
public boolean hmset(String key, Map<Object, Object> map, long time) {
|
||||
try {
|
||||
redisTemplate.opsForHash().putAll(key, map);
|
||||
if (time > 0) {
|
||||
expire(key, time);
|
||||
}
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 向一张 Hash表 中放入数据,如不存在则创建
|
||||
* @param key 键
|
||||
* @param item 项
|
||||
* @param value 值
|
||||
* @return true / false
|
||||
*/
|
||||
public boolean hset(String key, String item, Object value) {
|
||||
try {
|
||||
redisTemplate.opsForHash().put(key, item, value);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 向一张 Hash表 中放入数据,并设置时间,如不存在则创建
|
||||
* @param key 键
|
||||
* @param item 项
|
||||
* @param value 值
|
||||
* @param time 时间(如果原来的 Hash表 设置了时间,这里会覆盖)
|
||||
* @return true / false
|
||||
*/
|
||||
public boolean hset(String key, String item, Object value, long time) {
|
||||
try {
|
||||
redisTemplate.opsForHash().put(key, item, value);
|
||||
if (time > 0) {
|
||||
expire(key, time);
|
||||
}
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除 Hash表 中的值
|
||||
* @param key 键
|
||||
* @param item 项(可以多个,no null)
|
||||
*/
|
||||
public void hdel(String key, Object... item) {
|
||||
redisTemplate.opsForHash().delete(key, item);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断 Hash表 中是否有该键的值
|
||||
* @param key 键(no null)
|
||||
* @param item 值(no null)
|
||||
* @return true / false
|
||||
*/
|
||||
public boolean hHasKey(String key, String item) {
|
||||
return redisTemplate.opsForHash().hasKey(key, item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash递增,如果不存在则创建一个,并把新增的值返回
|
||||
* @param key 键
|
||||
* @param item 项
|
||||
* @param by 递增大小 > 0
|
||||
* @return
|
||||
*/
|
||||
public Double hincr(String key, String item, Double by) {
|
||||
return redisTemplate.opsForHash().increment(key, item, by);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash递减
|
||||
* @param key 键
|
||||
* @param item 项
|
||||
* @param by 递减大小
|
||||
* @return
|
||||
*/
|
||||
public Double hdecr(String key, String item, Double by) {
|
||||
return redisTemplate.opsForHash().increment(key, item, -by);
|
||||
}
|
||||
|
||||
// ============================== Set ==============================
|
||||
|
||||
/**
|
||||
* 根据 key 获取 set 中的所有值
|
||||
* @param key 键
|
||||
* @return 值
|
||||
*/
|
||||
public Set<Object> sGet(String key) {
|
||||
try {
|
||||
return redisTemplate.opsForSet().members(key);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从键为 key 的 set 中,根据 value 查询是否存在
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @return true / false
|
||||
*/
|
||||
public boolean sHasKey(String key, Object value) {
|
||||
try {
|
||||
return redisTemplate.opsForSet().isMember(key, value);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数据放入 set缓存
|
||||
* @param key 键值
|
||||
* @param values 值(可以多个)
|
||||
* @return 成功个数
|
||||
*/
|
||||
public long sSet(String key, Object... values) {
|
||||
try {
|
||||
return redisTemplate.opsForSet().add(key, values);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数据放入 set缓存,并设置时间
|
||||
* @param key 键
|
||||
* @param time 时间
|
||||
* @param values 值(可以多个)
|
||||
* @return 成功放入个数
|
||||
*/
|
||||
public long sSet(String key, long time, Object... values) {
|
||||
try {
|
||||
long count = redisTemplate.opsForSet().add(key, values);
|
||||
if (time > 0) {
|
||||
expire(key, time);
|
||||
}
|
||||
return count;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 set缓存的长度
|
||||
* @param key 键
|
||||
* @return 长度
|
||||
*/
|
||||
public long sGetSetSize(String key) {
|
||||
try {
|
||||
return redisTemplate.opsForSet().size(key);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除 set缓存中,值为 value 的
|
||||
* @param key 键
|
||||
* @param values 值
|
||||
* @return 成功移除个数
|
||||
*/
|
||||
public long setRemove(String key, Object... values) {
|
||||
try {
|
||||
return redisTemplate.opsForSet().remove(key, values);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
// ============================== ZSet ==============================
|
||||
|
||||
/**
|
||||
* 添加一个元素, zset与set最大的区别就是每个元素都有一个score,因此有个排序的辅助功能; zadd
|
||||
*
|
||||
* @param key
|
||||
* @param value
|
||||
* @param score
|
||||
*/
|
||||
public void zAdd(Object key, Object value, double score) {
|
||||
redisTemplate.opsForZSet().add(key, value, score);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除元素 zrem
|
||||
*
|
||||
* @param key
|
||||
* @param value
|
||||
*/
|
||||
public void zRemove(Object key, Object value) {
|
||||
redisTemplate.opsForZSet().remove(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* score的增加or减少 zincrby
|
||||
*
|
||||
* @param key
|
||||
* @param value
|
||||
* @param score
|
||||
*/
|
||||
public Double zIncrScore(Object key, Object value, double score) {
|
||||
return redisTemplate.opsForZSet().incrementScore(key, value, score);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询value对应的score zscore
|
||||
*
|
||||
* @param key
|
||||
* @param value
|
||||
* @return
|
||||
*/
|
||||
public Double zScore(Object key, Object value) {
|
||||
return redisTemplate.opsForZSet().score(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断value在zset中的排名 zrank
|
||||
*
|
||||
* @param key
|
||||
* @param value
|
||||
* @return
|
||||
*/
|
||||
public Long zRank(Object key, Object value) {
|
||||
return redisTemplate.opsForZSet().rank(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回集合的长度
|
||||
*
|
||||
* @param key
|
||||
* @return
|
||||
*/
|
||||
public Long zSize(Object key) {
|
||||
return redisTemplate.opsForZSet().zCard(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询集合中指定顺序的值, 0 -1 表示获取全部的集合内容 zrange
|
||||
*
|
||||
* 返回有序的集合,score小的在前面
|
||||
*
|
||||
* @param key
|
||||
* @param start
|
||||
* @param end
|
||||
* @return
|
||||
*/
|
||||
public Set<Object> ZRange(Object key, int start, int end) {
|
||||
return redisTemplate.opsForZSet().range(key, start, end);
|
||||
}
|
||||
/**
|
||||
* 查询集合中指定顺序的值和score,0, -1 表示获取全部的集合内容
|
||||
*
|
||||
* @param key
|
||||
* @param start
|
||||
* @param end
|
||||
* @return
|
||||
*/
|
||||
public Set<ZSetOperations.TypedTuple<Object>> zRangeWithScore(Object key, int start, int end) {
|
||||
return redisTemplate.opsForZSet().rangeWithScores(key, start, end);
|
||||
}
|
||||
/**
|
||||
* 查询集合中指定顺序的值 zrevrange
|
||||
*
|
||||
* 返回有序的集合中,score大的在前面
|
||||
*
|
||||
* @param key
|
||||
* @param start
|
||||
* @param end
|
||||
* @return
|
||||
*/
|
||||
public Set<Object> zRevRange(Object key, int start, int end) {
|
||||
return redisTemplate.opsForZSet().reverseRange(key, start, end);
|
||||
}
|
||||
/**
|
||||
* 根据score的值,来获取满足条件的集合 zrangebyscore
|
||||
*
|
||||
* @param key
|
||||
* @param min
|
||||
* @param max
|
||||
* @return
|
||||
*/
|
||||
public Set<Object> zSortRange(Object key, int min, int max) {
|
||||
return redisTemplate.opsForZSet().rangeByScore(key, min, max);
|
||||
}
|
||||
|
||||
|
||||
// ============================== List ==============================
|
||||
|
||||
/**
|
||||
* 获取 list缓存的内容
|
||||
* @param key 键
|
||||
* @param start 开始
|
||||
* @param end 结束(0 到 -1 代表所有值)
|
||||
* @return
|
||||
*/
|
||||
public List<Object> lGet(String key, long start, long end) {
|
||||
try {
|
||||
return redisTemplate.opsForList().range(key, start, end);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 list缓存的长度
|
||||
* @param key 键
|
||||
* @return 长度
|
||||
*/
|
||||
public long lGetListSize(String key) {
|
||||
try {
|
||||
return redisTemplate.opsForList().size(key);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据索引 index 获取键为 key 的 list 中的元素
|
||||
* @param key 键
|
||||
* @param index 索引
|
||||
* 当 index >= 0 时 {0:表头, 1:第二个元素}
|
||||
* 当 index < 0 时 {-1:表尾, -2:倒数第二个元素}
|
||||
* @return 值
|
||||
*/
|
||||
public Object lGetIndex(String key, long index) {
|
||||
try {
|
||||
return redisTemplate.opsForList().index(key, index);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将值 value 插入键为 key 的 list 中,如果 list 不存在则创建空 list
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @return true / false
|
||||
*/
|
||||
public boolean lSet(String key, Object value) {
|
||||
try {
|
||||
redisTemplate.opsForList().rightPush(key, value);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将值 value 插入键为 key 的 list 中,并设置时间
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @param time 时间
|
||||
* @return true / false
|
||||
*/
|
||||
public boolean lSet(String key, Object value, long time) {
|
||||
try {
|
||||
redisTemplate.opsForList().rightPush(key, value);
|
||||
if (time > 0) {
|
||||
expire(key, time);
|
||||
}
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 values 插入键为 key 的 list 中
|
||||
* @param key 键
|
||||
* @param values 值
|
||||
* @return true / false
|
||||
*/
|
||||
public boolean lSetList(String key, List<Object> values) {
|
||||
try {
|
||||
redisTemplate.opsForList().rightPushAll(key, values);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 values 插入键为 key 的 list 中,并设置时间
|
||||
* @param key 键
|
||||
* @param values 值
|
||||
* @param time 时间
|
||||
* @return true / false
|
||||
*/
|
||||
public boolean lSetList(String key, List<Object> values, long time) {
|
||||
try {
|
||||
redisTemplate.opsForList().rightPushAll(key, values);
|
||||
if (time > 0) {
|
||||
expire(key, time);
|
||||
}
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据索引 index 修改键为 key 的值
|
||||
* @param key 键
|
||||
* @param index 索引
|
||||
* @param value 值
|
||||
* @return true / false
|
||||
*/
|
||||
public boolean lUpdateIndex(String key, long index, Object value) {
|
||||
try {
|
||||
redisTemplate.opsForList().set(key, index, value);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在键为 key 的 list 中删除值为 value 的元素
|
||||
* @param key 键
|
||||
* @param count 如果 count == 0 则删除 list 中所有值为 value 的元素
|
||||
* 如果 count > 0 则删除 list 中最左边那个值为 value 的元素
|
||||
* 如果 count < 0 则删除 list 中最右边那个值为 value 的元素
|
||||
* @param value
|
||||
* @return
|
||||
*/
|
||||
public long lRemove(String key, long count, Object value) {
|
||||
try {
|
||||
return redisTemplate.opsForList().remove(key, count, value);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 模糊查询
|
||||
* @param key 键
|
||||
* @return true / false
|
||||
*/
|
||||
public List<Object> keys(String key) {
|
||||
try {
|
||||
Set<Object> set = redisTemplate.keys(key);
|
||||
return new ArrayList<>(set);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 模糊查询
|
||||
* @param query 查询参数
|
||||
* @return
|
||||
*/
|
||||
// public List<Object> scan(String query) {
|
||||
// List<Object> result = new ArrayList<>();
|
||||
// try {
|
||||
// Cursor<Map.Entry<Object,Object>> cursor = redisTemplate.opsForHash().scan("field",
|
||||
// ScanOptions.scanOptions().match(query).count(1000).build());
|
||||
// while (cursor.hasNext()) {
|
||||
// Map.Entry<Object,Object> entry = cursor.next();
|
||||
// result.add(entry.getKey());
|
||||
// Object key = entry.getKey();
|
||||
// Object valueSet = entry.getValue();
|
||||
// }
|
||||
// //关闭cursor
|
||||
// cursor.close();
|
||||
// } catch (Exception e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// return result;
|
||||
// }
|
||||
|
||||
/**
|
||||
* 模糊查询
|
||||
* @param query 查询参数
|
||||
* @return
|
||||
*/
|
||||
public List<Object> scan(String query) {
|
||||
Set<String> resultKeys = redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
|
||||
ScanOptions scanOptions = ScanOptions.scanOptions().match("*" + query + "*").count(1000).build();
|
||||
Cursor<byte[]> scan = connection.scan(scanOptions);
|
||||
Set<String> keys = new HashSet<>();
|
||||
while (scan.hasNext()) {
|
||||
byte[] next = scan.next();
|
||||
keys.add(new String(next));
|
||||
}
|
||||
return keys;
|
||||
});
|
||||
|
||||
return new ArrayList<>(resultKeys);
|
||||
}
|
||||
|
||||
}
|
||||
57
src/main/resources/all-application.yml
Normal file
57
src/main/resources/all-application.yml
Normal file
@ -0,0 +1,57 @@
|
||||
spring:
|
||||
# REDIS数据库配置
|
||||
redis:
|
||||
# [必须修改] Redis服务器IP, REDIS安装在本机的,使用127.0.0.1
|
||||
host: 127.0.0.1
|
||||
# [必须修改] 端口号
|
||||
port: 6379
|
||||
# [可选] 数据库 DB
|
||||
database: 8
|
||||
# [可选] 访问密码,若你的redis服务器没有设置密码,就不需要用密码去连接
|
||||
password:
|
||||
# [可选] 超时时间
|
||||
timeout: 10000
|
||||
|
||||
# [必选] WVP监听的HTTP端口, 网页和接口调用都是这个端口
|
||||
server:
|
||||
port: 18081
|
||||
# [可选] HTTPS配置, 默认不开启
|
||||
ssl:
|
||||
# [可选] 是否开启HTTPS访问
|
||||
enabled: false
|
||||
# [可选] 证书文件路径,放置在resource/目录下即可,修改xxx为文件名
|
||||
key-store: classpath:xxx.jks
|
||||
# [可选] 证书密码
|
||||
key-password: password
|
||||
# [可选] 证书类型, 默认为jks,根据实际修改
|
||||
key-store-type: JKS
|
||||
|
||||
# [根据业务需求配置]
|
||||
user-settings:
|
||||
# [可选 ] 临时录像路径
|
||||
record-temp-path: ./recordTemp
|
||||
# [可选 ] 录像保存时长(单位: 天)每天晚12点自动对过期文件执行清理, 不配置则不删除
|
||||
record-temp-day: 7
|
||||
# [可选 ] 录像下载合成临时文件保存时长, 不配置默认取值recordDay(单位: 天)每天晚12点自动对过期文件执行清理
|
||||
# recordTempDay: 7
|
||||
# [必选 ] ffmpeg路径
|
||||
ffmpeg: /usr/bin/ffmpeg
|
||||
# [必选 ] ffprobe路径, 一般安装ffmpeg就会自带, 一般跟ffmpeg在同一目录,用于查询文件的信息
|
||||
ffprobe: /usr/bin/ffprobe
|
||||
# [可选 ] 限制 ffmpeg 合并文件使用的线程数,间接限制cpu使用率, 默认2 限制到50%
|
||||
threads: 2
|
||||
|
||||
swagger-ui:
|
||||
|
||||
# [可选] 日志配置, 一般不需要改
|
||||
logging:
|
||||
file:
|
||||
name: logs/wvp.log
|
||||
max-history: 30
|
||||
max-size: 10MB
|
||||
total-size-cap: 300MB
|
||||
level:
|
||||
root: WARN
|
||||
top:
|
||||
panll:
|
||||
assist: info
|
||||
57
src/main/resources/application-dev.yml
Normal file
57
src/main/resources/application-dev.yml
Normal file
@ -0,0 +1,57 @@
|
||||
spring:
|
||||
# REDIS数据库配置
|
||||
redis:
|
||||
# [必须修改] Redis服务器IP, REDIS安装在本机的,使用127.0.0.1
|
||||
host: 127.0.0.1
|
||||
# [必须修改] 端口号
|
||||
port: 6379
|
||||
# [可选] 数据库 DB
|
||||
database: 8
|
||||
# [可选] 访问密码,若你的redis服务器没有设置密码,就不需要用密码去连接
|
||||
password:
|
||||
# [可选] 超时时间
|
||||
timeout: 10000
|
||||
|
||||
# [可选] WVP监听的HTTP端口, 网页和接口调用都是这个端口
|
||||
server:
|
||||
port: 18081
|
||||
# [可选] HTTPS配置, 默认不开启
|
||||
ssl:
|
||||
# [可选] 是否开启HTTPS访问
|
||||
enabled: false
|
||||
# [可选] 证书文件路径,放置在resource/目录下即可,修改xxx为文件名
|
||||
key-store: classpath:xxx.jks
|
||||
# [可选] 证书密码
|
||||
key-password: password
|
||||
# [可选] 证书类型, 默认为jks,根据实际修改
|
||||
key-store-type: JKS
|
||||
|
||||
# [根据业务需求配置]
|
||||
userSettings:
|
||||
# [可选 ] zlm配置的录像路径,
|
||||
record: /media/lin/Server/ZLMediaKit/dev/ZLMediaKit/release/linux/Debug/www/record
|
||||
# [可选 ] 录像保存时长(单位: 天)每天晚12点自动对过期文件执行清理
|
||||
recordDay: 7
|
||||
# [可选 ] 录像下载合成临时文件保存时长, 不配置默认取值recordDay(单位: 天)每天晚12点自动对过期文件执行清理
|
||||
# recordTempDay: 7
|
||||
# [必选 ] ffmpeg路径
|
||||
ffmpeg: /usr/bin/ffmpeg
|
||||
# [必选 ] ffprobe路径, 一般安装ffmpeg就会自带, 一般跟ffmpeg在同一目录,用于查询文件的信息
|
||||
ffprobe: /usr/bin/ffprobe
|
||||
# [可选 ] 限制 ffmpeg 合并文件使用的线程数,间接限制cpu使用率, 默认2 限制到50%
|
||||
threads: 2
|
||||
|
||||
swagger-ui:
|
||||
|
||||
# [可选] 日志配置, 一般不需要改
|
||||
logging:
|
||||
file:
|
||||
name: logs/wvp.log
|
||||
max-history: 30
|
||||
max-size: 10MB
|
||||
total-size-cap: 300MB
|
||||
level:
|
||||
root: WARN
|
||||
top:
|
||||
panll:
|
||||
assist: info
|
||||
57
src/main/resources/application-local.yml
Normal file
57
src/main/resources/application-local.yml
Normal file
@ -0,0 +1,57 @@
|
||||
spring:
|
||||
# REDIS数据库配置
|
||||
redis:
|
||||
# [可选] 超时时间
|
||||
timeout: 10000
|
||||
# 以下为单机配置
|
||||
# [必须修改] Redis服务器IP, REDIS安装在本机的,使用127.0.0.1
|
||||
host: 127.0.0.1
|
||||
# [必须修改] 端口号
|
||||
port: 6379
|
||||
# [可选] 数据库 DB
|
||||
database: 1
|
||||
# [可选] 访问密码,若你的redis服务器没有设置密码,就不需要用密码去连接
|
||||
password:
|
||||
# 以下为集群配置
|
||||
# cluster:
|
||||
# nodes: 192.168.1.242:7001
|
||||
# password: 4767cb971b40a1300fa09b7f87b09d1c
|
||||
|
||||
# [可选] WVP监听的HTTP端口, 网页和接口调用都是这个端口
|
||||
server:
|
||||
port: 18089
|
||||
# [可选] HTTPS配置, 默认不开启
|
||||
ssl:
|
||||
# [可选] 是否开启HTTPS访问
|
||||
enabled: false
|
||||
# [可选] 证书文件路径,放置在resource/目录下即可,修改xxx为文件名
|
||||
key-store: classpath:xxx.jks
|
||||
# [可选] 证书密码
|
||||
key-password: password
|
||||
# [可选] 证书类型, 默认为jks,根据实际修改
|
||||
key-store-type: JKS
|
||||
|
||||
# [根据业务需求配置]
|
||||
userSettings:
|
||||
id: 111
|
||||
# [可选 ] 录像保存时长(单位: 天)每天晚12点自动对过期文件执行清理, 不配置则不删除
|
||||
record-temp-day: 7
|
||||
# [必选 ] ffmpeg路径
|
||||
ffmpeg: ./lib/ffmpeg
|
||||
# [必选 ] ffprobe路径, 一般安装ffmpeg就会自带, 一般跟ffmpeg在同一目录,用于查询文件的信息,
|
||||
ffprobe: ./lib/ffprobe
|
||||
# [可选 ] 限制 ffmpeg 合并文件使用的线程数,间接限制cpu使用率, 默认2 限制到50%
|
||||
threads: 2
|
||||
|
||||
# [可选] 日志配置, 一般不需要改
|
||||
logging:
|
||||
file:
|
||||
name: logs/wvp.log
|
||||
max-history: 30
|
||||
max-size: 10MB
|
||||
total-size-cap: 300MB
|
||||
level:
|
||||
root: WARN
|
||||
top:
|
||||
panll:
|
||||
assist: info
|
||||
3
src/main/resources/application.yml
Normal file
3
src/main/resources/application.yml
Normal file
@ -0,0 +1,3 @@
|
||||
spring:
|
||||
profiles:
|
||||
active: local
|
||||
25
src/main/resources/static/download.html
Normal file
25
src/main/resources/static/download.html
Normal file
@ -0,0 +1,25 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>下载</title>
|
||||
</head>
|
||||
<body>
|
||||
<a id="download" download></a>
|
||||
<script>
|
||||
(function () {
|
||||
let searchParams = new URLSearchParams(location.search);
|
||||
var download = document.getElementById("download");
|
||||
download.setAttribute("href", searchParams.get("url"))
|
||||
download.click()
|
||||
setTimeout(() => {
|
||||
window.location.href = "about:blank";
|
||||
window.close();
|
||||
}, 200)
|
||||
})();
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -0,0 +1,13 @@
|
||||
package top.panll.assist;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
@SpringBootTest
|
||||
class WvpProAssistApplicationTests {
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user