Merge branch 'master' into dev/springBoot3

# Conflicts:
#	src/main/java/com/genersoft/iot/vmp/gb28181/controller/MediaController.java
#	src/main/java/com/genersoft/iot/vmp/jt1078/config/JT1078Controller.java
This commit is contained in:
lin
2025-09-24 08:38:29 +08:00
476 changed files with 33561 additions and 3579 deletions

View File

@@ -2,6 +2,7 @@ package com.genersoft.iot.vmp.conf;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.utils.DateUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
@@ -15,6 +16,7 @@ import java.util.regex.Pattern;
@Slf4j
@Configuration("mediaConfig")
@Order(0)
@Data
public class MediaConfig{
// 修改必须配置,不再支持自动获取
@@ -45,6 +47,9 @@ public class MediaConfig{
@Value("${media.flv-port:0}")
private Integer flvPort = 0;
@Value("${media.mp4-port:0}")
private Integer mp4Port = 0;
@Value("${media.ws-flv-port:0}")
private Integer wsFlvPort = 0;
@@ -66,6 +71,9 @@ public class MediaConfig{
@Value("${media.rtp-proxy-port:0}")
private Integer rtpProxyPort = 0;
@Value("${media.jtt-proxy-port:0}")
private Integer jttProxyPort = 0;
@Value("${media.rtsp-port:0}")
private Integer rtspPort = 0;
@@ -99,33 +107,7 @@ public class MediaConfig{
@Value("${media.type:zlm}")
private String type;
public String getId() {
return id;
}
public String getIp() {
return ip;
}
public String getHookIp() {
return hookIp;
}
public int getHttpPort() {
return httpPort;
}
public int getHttpSSlPort() {
return httpSSlPort;
}
public int getRtmpPort() {
return rtmpPort;
}
public int getRtmpSSlPort() {
return rtmpSSlPort;
}
public int getRtpProxyPort() {
if (rtpProxyPort == null) {
@@ -136,32 +118,12 @@ public class MediaConfig{
}
public int getRtspPort() {
return rtspPort;
}
public int getRtspSSLPort() {
return rtspSSLPort;
}
public boolean isAutoConfig() {
return autoConfig;
}
public String getSecret() {
return secret;
}
public boolean isRtpEnable() {
return rtpEnable;
}
public String getRtpPortRange() {
return rtpPortRange;
}
public int getRecordAssistPort() {
return recordAssistPort;
public Integer getJttProxyPort() {
if (jttProxyPort == null) {
return 0;
}else {
return jttProxyPort;
}
}
public String getSdpIp() {
@@ -191,10 +153,6 @@ public class MediaConfig{
}
}
public String getSipDomain() {
return sipDomain;
}
public MediaServer getMediaSerItem(){
MediaServer mediaServer = new MediaServer();
mediaServer.setId(id);
@@ -204,31 +162,17 @@ public class MediaConfig{
mediaServer.setSdpIp(getSdpIp());
mediaServer.setStreamIp(getStreamIp());
mediaServer.setHttpPort(httpPort);
if (flvPort == 0) {
mediaServer.setFlvPort(httpPort);
}else {
mediaServer.setFlvPort(flvPort);
}
if (wsFlvPort == 0) {
mediaServer.setWsFlvPort(httpPort);
}else {
mediaServer.setWsFlvPort(wsFlvPort);
}
if (flvSSlPort == 0) {
mediaServer.setFlvSSLPort(httpSSlPort);
}else {
mediaServer.setFlvSSLPort(flvSSlPort);
}
if (wsFlvSSlPort == 0) {
mediaServer.setWsFlvSSLPort(httpSSlPort);
}else {
mediaServer.setWsFlvSSLPort(wsFlvSSlPort);
}
mediaServer.setFlvPort(flvPort);
mediaServer.setMp4Port(mp4Port);
mediaServer.setWsFlvPort(wsFlvPort);
mediaServer.setFlvSSLPort(flvSSlPort);
mediaServer.setWsFlvSSLPort(wsFlvSSlPort);
mediaServer.setHttpSSlPort(httpSSlPort);
mediaServer.setRtmpPort(rtmpPort);
mediaServer.setRtmpSSlPort(rtmpSSlPort);
mediaServer.setRtpProxyPort(getRtpProxyPort());
mediaServer.setJttProxyPort(getJttProxyPort());
mediaServer.setRtspPort(rtspPort);
mediaServer.setRtspSSLPort(rtspSSLPort);
mediaServer.setAutoConfig(autoConfig);
@@ -250,42 +194,10 @@ public class MediaConfig{
return mediaServer;
}
public Integer getRecordDay() {
return recordDay;
}
public void setRecordDay(Integer recordDay) {
this.recordDay = recordDay;
}
public String getRecordPath() {
return recordPath;
}
public void setRecordPath(String recordPath) {
this.recordPath = recordPath;
}
public String getRtpSendPortRange() {
return rtpSendPortRange;
}
public void setRtpSendPortRange(String rtpSendPortRange) {
this.rtpSendPortRange = rtpSendPortRange;
}
private boolean isValidIPAddress(String ipAddress) {
if ((ipAddress != null) && (!ipAddress.isEmpty())) {
return Pattern.matches("^([1-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])){3}$", ipAddress);
}
return false;
}
public String getWanIp() {
return wanIp;
}
public void setWanIp(String wanIp) {
this.wanIp = wanIp;
}
}

View File

@@ -1,5 +1,6 @@
package com.genersoft.iot.vmp.conf;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.context.WebServerInitializedEvent;
import org.springframework.context.ApplicationListener;
@@ -9,17 +10,14 @@ import org.springframework.stereotype.Component;
@Component
public class ServiceInfo implements ApplicationListener<WebServerInitializedEvent> {
@Getter
private static int serverPort;
public static int getServerPort() {
return serverPort;
}
@Override
public void onApplicationEvent(WebServerInitializedEvent event) {
// 项目启动获取启动的端口号
ServiceInfo.serverPort = event.getWebServer().getPort();
log.info("项目启动获取启动的端口号: " + ServiceInfo.serverPort);
log.info("项目启动获取启动的端口号: {}", ServiceInfo.serverPort);
}
public void setServerPort(int serverPort) {

View File

@@ -98,4 +98,12 @@ public class SpringDocConfig {
.packagesToScan("com.genersoft.iot.vmp.user")
.build();
}
@Bean
public GroupedOpenApi publicApi7() {
return GroupedOpenApi.builder()
.group("6. 部标设备")
.packagesToScan("com.genersoft.iot.vmp.jt1078.controller")
.build();
}
}

View File

@@ -204,6 +204,9 @@ public class UserSetting {
*/
private boolean sipCacheServerConnections = true;
/**
* 禁用date头变相禁用了校时
*/
private boolean disableDateHeader = false;
}

View File

@@ -0,0 +1,8 @@
package com.genersoft.iot.vmp.conf.ftpServer;
import java.io.OutputStream;
public interface FileCallback {
OutputStream run(String path);
}

View File

@@ -0,0 +1,17 @@
package com.genersoft.iot.vmp.conf.ftpServer;
import org.apache.ftpserver.ftplet.Authority;
import org.apache.ftpserver.ftplet.AuthorizationRequest;
public class FtpAuthority implements Authority {
@Override
public boolean canAuthorize(AuthorizationRequest authorizationRequest) {
return true;
}
@Override
public AuthorizationRequest authorize(AuthorizationRequest authorizationRequest) {
return authorizationRequest;
}
}

View File

@@ -0,0 +1,33 @@
package com.genersoft.iot.vmp.conf.ftpServer;
import org.apache.ftpserver.ftplet.FileSystemFactory;
import org.apache.ftpserver.ftplet.FileSystemView;
import org.apache.ftpserver.ftplet.FtpException;
import org.apache.ftpserver.ftplet.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.OutputStream;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class FtpFileSystemFactory implements FileSystemFactory {
private final Map<String, OutputStream> outputStreamMap = new ConcurrentHashMap<>();
@Override
public FileSystemView createFileSystemView(User user) throws FtpException {
return new FtpFileSystemView(user, path -> {
return outputStreamMap.get(path);
});
}
public void addOutputStream(String filePath, OutputStream outputStream) {
outputStreamMap.put(filePath, outputStream);
}
public void removeOutputStream(String filePath) {
outputStreamMap.remove(filePath);
}
}

View File

@@ -0,0 +1,63 @@
package com.genersoft.iot.vmp.conf.ftpServer;
import org.apache.ftpserver.ftplet.FileSystemView;
import org.apache.ftpserver.ftplet.FtpException;
import org.apache.ftpserver.ftplet.FtpFile;
import org.apache.ftpserver.ftplet.User;
import java.io.OutputStream;
public class FtpFileSystemView implements FileSystemView {
private User user;
private FileCallback fileCallback;
public FtpFileSystemView(User user, FileCallback fileCallback) {
this.user = user;
this.fileCallback = fileCallback;
}
public static String HOME_PATH = "root";
public FtpFile workDir = VirtualFtpFile.getDir(HOME_PATH);
@Override
public FtpFile getHomeDirectory() throws FtpException {
return VirtualFtpFile.getDir(HOME_PATH);
}
@Override
public FtpFile getWorkingDirectory() throws FtpException {
return workDir;
}
@Override
public boolean changeWorkingDirectory(String dir) throws FtpException {
workDir = VirtualFtpFile.getDir(dir);
return true;
}
@Override
public FtpFile getFile(String file) throws FtpException {
VirtualFtpFile ftpFile = VirtualFtpFile.getFile(file);
if (fileCallback != null) {
OutputStream outputStream = fileCallback.run(workDir.getName());
if (outputStream != null) {
ftpFile.setOutputStream(outputStream);
}
}
return ftpFile;
}
@Override
public boolean isRandomAccessible() throws FtpException {
return true;
}
@Override
public void dispose() {
}
}

View File

@@ -0,0 +1,69 @@
package com.genersoft.iot.vmp.conf.ftpServer;
import lombok.extern.slf4j.Slf4j;
import org.apache.ftpserver.*;
import org.apache.ftpserver.ftplet.FtpException;
import org.apache.ftpserver.listener.Listener;
import org.apache.ftpserver.listener.ListenerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
@ConditionalOnProperty(value = "ftp.enable", havingValue = "true")
@Slf4j
public class FtpServerConfig {
@Autowired
private UserManager userManager;
@Autowired
private FtpFileSystemFactory fileSystemFactory;
@Autowired
private Ftplet ftplet;
@Autowired
private FtpSetting ftpSetting;
/**
* ftp server init
*/
@Bean
public FtpServer ftpServer() {
FtpServerFactory serverFactory = new FtpServerFactory();
ListenerFactory listenerFactory = new ListenerFactory();
// 1、设置服务端口
listenerFactory.setPort(ftpSetting.getPort());
// 2、设置被动模式数据上传的接口范围,云服务器需要开放对应区间的端口给客户端
DataConnectionConfigurationFactory dataConnectionConfFactory = new DataConnectionConfigurationFactory();
dataConnectionConfFactory.setPassivePorts(ftpSetting.getPassivePorts());
listenerFactory.setDataConnectionConfiguration(dataConnectionConfFactory.createDataConnectionConfiguration());
// 4、替换默认的监听器
Listener listener = listenerFactory.createListener();
serverFactory.addListener("default", listener);
// 5、配置自定义用户事件
Map<String, org.apache.ftpserver.ftplet.Ftplet> ftpLets = new HashMap<>();
ftpLets.put("ftpService", ftplet);
serverFactory.setFtplets(ftpLets);
// 6、读取用户的配置信息
// 6.2、设置用信息
serverFactory.setUserManager(userManager);
serverFactory.setFileSystem(fileSystemFactory);
// 7、实例化FTP Server
FtpServer server = serverFactory.createServer();
try {
server.start();
if (!server.isStopped()) {
log.info("[FTP服务] 已启动, 端口: {}", ftpSetting.getPort());
}
} catch (FtpException e) {
log.info("[FTP服务] 启动失败 ", e);
}
return server;
}
}

View File

@@ -0,0 +1,21 @@
package com.genersoft.iot.vmp.conf.ftpServer;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* 配置文件 user-settings 映射的配置信息
*/
@Component
@ConfigurationProperties(prefix = "ftp", ignoreInvalidFields = true)
@Order(0)
@Data
public class FtpSetting {
private Boolean enable = Boolean.FALSE;
private int port = 21;
private String passivePorts = "10000-10500";
}

View File

@@ -0,0 +1,59 @@
package com.genersoft.iot.vmp.conf.ftpServer;
import com.genersoft.iot.vmp.jt1078.event.FtpUploadEvent;
import org.apache.ftpserver.ftplet.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
public class Ftplet extends DefaultFtplet {
private final Logger logger = LoggerFactory.getLogger(Ftplet.class);
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
@Override
public FtpletResult onUploadEnd(FtpSession session, FtpRequest request) throws FtpException, IOException {
FtpFile file = session.getFileSystemView().getFile(request.getArgument());
if (file == null) {
return super.onUploadEnd(session, request);
}
sendEvent(file.getAbsolutePath());
return super.onUploadUniqueEnd(session, request);
}
@Override
public FtpletResult onAppendEnd(FtpSession session, FtpRequest request) throws FtpException, IOException {
FtpFile file = session.getFileSystemView().getFile(request.getArgument());
if (file == null) {
return super.onUploadEnd(session, request);
}
sendEvent(file.getAbsolutePath());
return super.onUploadUniqueEnd(session, request);
}
@Override
public FtpletResult onUploadUniqueEnd(FtpSession session, FtpRequest request) throws FtpException, IOException {
FtpFile file = session.getFileSystemView().getFile(request.getArgument());
if (file == null) {
return super.onUploadEnd(session, request);
}
sendEvent(file.getAbsolutePath());
return super.onUploadUniqueEnd(session, request);
}
private void sendEvent(String filePath){
FtpUploadEvent event = new FtpUploadEvent(this);
logger.info("[文件已上传]: {}", filePath);
event.setFileName(filePath);
applicationEventPublisher.publishEvent(event);
}
}

View File

@@ -0,0 +1,86 @@
package com.genersoft.iot.vmp.conf.ftpServer;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.ftpserver.ftplet.*;
import org.apache.ftpserver.usermanager.UsernamePasswordAuthentication;
import org.apache.ftpserver.usermanager.impl.BaseUser;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.io.File;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
@Component
public class UserManager implements org.apache.ftpserver.ftplet.UserManager {
private static final String PREFIX = "VMP_FTP_USER_";
@Autowired
private RedisTemplate<Object, Object> redisTemplate;
@Override
public User getUserByName(String username) throws FtpException {
return (BaseUser)redisTemplate.opsForValue().get(PREFIX + username);
}
@Override
public String[] getAllUserNames() throws FtpException {
return new String[0];
}
@Override
public void delete(String username) throws FtpException {
}
@Override
public void save(User user) throws FtpException {}
@Override
public boolean doesExist(String username) throws FtpException {
return redisTemplate.opsForValue().get(PREFIX + username) != null;
}
@Override
public User authenticate(Authentication authentication) throws AuthenticationFailedException {
UsernamePasswordAuthentication usernamePasswordAuthentication = (UsernamePasswordAuthentication) authentication;
BaseUser user = (BaseUser)redisTemplate.opsForValue().get(PREFIX + usernamePasswordAuthentication.getUsername());
if (user != null && usernamePasswordAuthentication.getPassword().equals(user.getPassword())) {
return user;
}
return null;
}
@Override
public String getAdminName() throws FtpException {
return null;
}
@Override
public boolean isAdmin(String username) throws FtpException {
return false;
}
public BaseUser getRandomUser(){
BaseUser use = new BaseUser();
use.setName(RandomStringUtils.randomAlphabetic(6).toLowerCase());
use.setPassword(RandomStringUtils.randomAlphabetic(6).toLowerCase());
use.setEnabled(true);
use.setHomeDirectory("/");
List<Authority> authorities = new ArrayList<>();
authorities.add(new FtpAuthority());
use.setAuthorities(authorities);
String key = PREFIX + use.getName();
// 随机用户信息十分钟自动失效
Duration duration = Duration.ofMinutes(10);
redisTemplate.opsForValue().set(key, use, duration);
return use;
}
}

View File

@@ -0,0 +1,166 @@
package com.genersoft.iot.vmp.conf.ftpServer;
import lombok.Setter;
import org.apache.ftpserver.ftplet.FtpFile;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collections;
import java.util.List;
public class VirtualFtpFile implements FtpFile {
@Setter
private String name;
@Setter
private boolean hidden = false;
@Setter
private boolean directory = false;
@Setter
private String ownerName;
private Long lastModified = null;
@Setter
private long size = 0;
@Setter
private OutputStream outputStream;
public static VirtualFtpFile getFile(String name) {
VirtualFtpFile virtualFtpFile = new VirtualFtpFile();
virtualFtpFile.setName(name);
return virtualFtpFile;
}
public static VirtualFtpFile getDir(String name) {
if (name.endsWith("/")) {
name = name.replaceAll("/", "");
}
VirtualFtpFile virtualFtpFile = new VirtualFtpFile();
virtualFtpFile.setName(name);
virtualFtpFile.setDirectory(true);
return virtualFtpFile;
}
@Override
public String getAbsolutePath() {
return FtpFileSystemView.HOME_PATH + "/" + name;
}
@Override
public String getName() {
return name;
}
@Override
public boolean isHidden() {
return hidden;
}
@Override
public boolean isDirectory() {
return directory;
}
@Override
public boolean isFile() {
return !directory;
}
@Override
public boolean doesExist() {
return false;
}
@Override
public boolean isReadable() {
return true;
}
@Override
public boolean isWritable() {
return true;
}
@Override
public boolean isRemovable() {
return true;
}
@Override
public String getOwnerName() {
return ownerName;
}
@Override
public String getGroupName() {
return "root";
}
@Override
public int getLinkCount() {
return 0;
}
@Override
public long getLastModified() {
if (lastModified == null) {
lastModified = System.currentTimeMillis();
}
return lastModified;
}
@Override
public boolean setLastModified(long time) {
lastModified = time;
return true;
}
@Override
public long getSize() {
return size;
}
@Override
public Object getPhysicalFile() {
System.err.println("getPhysicalFile");
return null;
}
@Override
public boolean mkdir() {
return true;
}
@Override
public boolean delete() {
return true;
}
@Override
public boolean move(FtpFile destination) {
this.name = destination.getName();
return true;
}
@Override
public List<? extends FtpFile> listFiles() {
return Collections.emptyList();
}
@Override
public OutputStream createOutputStream(long offset) throws IOException {
return outputStream;
}
@Override
public InputStream createInputStream(long offset) throws IOException {
System.out.println("createInputStream----");
return null;
}
}

View File

@@ -58,7 +58,7 @@ public class JwtUtils implements InitializingBean {
private static IUserService userService;
private static IUserApiKeyService userApiKeyService;
private static UserSetting userSetting;
public static String getApiKeyHeader() {
@@ -236,7 +236,7 @@ public class JwtUtils implements InitializingBean {
jwtUser.setStatus(JwtUser.TokenStatus.EXPIRING_SOON);
} else {
jwtUser.setStatus(JwtUser.TokenStatus.NORMAL);
}
}
} else {
jwtUser.setStatus(JwtUser.TokenStatus.NORMAL);
}

View File

@@ -99,6 +99,8 @@ public class WebSecurityConfig {
defaultExcludes.add("/index/hook/**");
defaultExcludes.add("/api/device/query/snap/**");
defaultExcludes.add("/index/hook/abl/**");
defaultExcludes.add("/api/jt1078/playback/download");
defaultExcludes.add("/api/jt1078/snap");
if (userSetting.getInterfaceAuthentication() && !userSetting.getInterfaceAuthenticationExcludes().isEmpty()) {
defaultExcludes.addAll(userSetting.getInterfaceAuthenticationExcludes());