feat(chaos): 实现视频分片上传和后台处理功能- 新增视频上传相关控制器、服务接口和实现类
- 实现了视频分片上传、合并和后台处理的逻辑 - 添加了 RabbitMQ 消息队列配置和消息转换器 -优化了 JWT 认证过滤器和日志记录 - 新增了跨域配置
This commit is contained in:
@@ -63,6 +63,11 @@
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -71,7 +76,7 @@
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<version>2.7.0</version>
|
||||
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package cn.nopj.chaos_api.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@Configuration
|
||||
public class CorsConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
registry.addMapping("/**") // 匹配所有路径
|
||||
.allowedOrigins(
|
||||
"http://localhost:63342", // 你的前端运行的地址
|
||||
"http://localhost:8080", // 其他可能的前端地址
|
||||
"http://127.0.0.1:5500", // 另一个常见的本地开发地址
|
||||
"null"
|
||||
) // 允许的来源
|
||||
// 请根据你的前端实际部署地址修改或增加这里的源
|
||||
// 在开发环境可以加 "null" 来处理从文件系统打开的页面
|
||||
// 生产环境请务必替换为明确的域名或IP,避免使用"*"或"null"
|
||||
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 允许的方法
|
||||
.allowedHeaders("*") // 允许所有请求头
|
||||
.allowCredentials(true) // 允许发送认证信息(如 Cookies, HTTP 认证及客户端 SSL 证书)
|
||||
.maxAge(3600); // 预检请求的缓存时间(秒)
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,13 @@
|
||||
package cn.nopj.chaos_api.config.sec;
|
||||
package cn.nopj.chaos_api.config;
|
||||
|
||||
import cn.nopj.chaos_api.config.jwt.JwtAuthenticationTokenFilter;
|
||||
import cn.nopj.chaos_api.config.sec.RestAuthenticationEntryPoint;
|
||||
import cn.nopj.chaos_api.config.sec.RestfulAccessDeniedHandler;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||
@@ -13,9 +16,16 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableMethodSecurity
|
||||
public class SecurityConfig {
|
||||
|
||||
private final RestAuthenticationEntryPoint restAuthenticationEntryPoint;
|
||||
@@ -51,9 +61,30 @@ public class SecurityConfig {
|
||||
.exceptionHandling(e -> e
|
||||
.authenticationEntryPoint(restAuthenticationEntryPoint)
|
||||
.accessDeniedHandler(restfulAccessDeniedHandler))
|
||||
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
|
||||
;
|
||||
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public CorsConfigurationSource corsConfigurationSource() {
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
|
||||
CorsConfiguration configuration = new CorsConfiguration();
|
||||
configuration.setAllowedOrigins(Arrays.asList(
|
||||
"http://localhost:63342", // 你的前端运行的地址
|
||||
"http://localhost:8080", // 其他可能的前端地址
|
||||
"http://127.0.0.1:5500", // 另一个常见的本地开发地址
|
||||
"null" // 如果是从文件系统直接打开HTML文件,Origin 会是 "null"。仅用于开发!
|
||||
));
|
||||
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
|
||||
configuration.setAllowedHeaders(List.of("*"));
|
||||
configuration.setAllowCredentials(true);
|
||||
configuration.setMaxAge(3600L); // 注意这里是 Long 类型
|
||||
source.registerCorsConfiguration("/**", configuration); // 对所有路径生效
|
||||
return source;
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
@@ -21,6 +22,7 @@ import java.io.IOException;
|
||||
* JWT 登录授权过滤器
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
|
||||
|
||||
@Autowired
|
||||
@@ -36,10 +38,15 @@ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
|
||||
protected void doFilterInternal(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
FilterChain chain) throws ServletException, IOException {
|
||||
log.info("JWT 登录授权过滤器");
|
||||
|
||||
String authHeader = request.getHeader(this.tokenHeader);
|
||||
log.info("authHeader: {}", authHeader);
|
||||
if (authHeader != null && authHeader.startsWith(this.tokenHead)) {
|
||||
String authToken = authHeader.substring(this.tokenHead.length());
|
||||
String authToken = authHeader.substring(this.tokenHead.length()+1);
|
||||
log.info("authToken={}", authToken);
|
||||
String username = jwtTokenUtil.getUsernameFromToken(authToken);
|
||||
log.info("username={}", username);
|
||||
// 如果 Token 中有用户名但上下文中没有,说明是首次登录
|
||||
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
|
||||
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
|
||||
|
||||
@@ -23,6 +23,12 @@ public class AuthController {
|
||||
@Value("${jwt.tokenHead}")
|
||||
private String tokenHead;
|
||||
|
||||
/**
|
||||
* 注册
|
||||
*
|
||||
* @param registerRequest 注册信息
|
||||
* @return 注册结果
|
||||
*/
|
||||
@PostMapping("/register")
|
||||
public ApiResult<?> register(@RequestBody RegisterRequest registerRequest) {
|
||||
if (authService.register(registerRequest) != null) {
|
||||
@@ -30,7 +36,12 @@ public class AuthController {
|
||||
}
|
||||
return ApiResult.failed("用户名已存在");
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录
|
||||
*
|
||||
* @param loginRequest 登录信息
|
||||
* @return 登录结果
|
||||
*/
|
||||
@PostMapping("/login")
|
||||
public ApiResult<?> login(@RequestBody LoginRequest loginRequest) {
|
||||
String token = authService.login(loginRequest.getUsername(), loginRequest.getPassword());
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package cn.nopj.chaos_api.controller;
|
||||
|
||||
|
||||
import cn.nopj.chaos_api.dto.ProcessVideoPath;
|
||||
import cn.nopj.chaos_api.model.ApiResult;
|
||||
import cn.nopj.chaos_api.service.VideoFileUploadService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/video")
|
||||
@Slf4j
|
||||
public class VideoController {
|
||||
|
||||
@Autowired
|
||||
private VideoFileUploadService videoFileUploadService;
|
||||
|
||||
/**
|
||||
* 初始化分片上传任务
|
||||
* @param fileInfo 包含文件名(fileName)和总大小(totalSize)
|
||||
* @return 返回 uploadId 和每个分片的大小
|
||||
*/
|
||||
@PostMapping("/init")
|
||||
public ApiResult<?> initUpload(@RequestBody Map<String, Object> fileInfo) {
|
||||
log.info("初始化上传任务: {}", fileInfo);
|
||||
String fileName = fileInfo.get("fileName").toString();
|
||||
long totalSize = Long.parseLong(fileInfo.get("totalSize").toString());
|
||||
return ApiResult.success(videoFileUploadService.initUpload(fileName, totalSize));
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传分片
|
||||
* @param chunk 分片文件
|
||||
* @param uploadId 上传任务ID
|
||||
* @param chunkIndex 分片序号 (从0开始)
|
||||
*/
|
||||
@PostMapping("/chunk")
|
||||
|
||||
public ApiResult<?> uploadChunk(@RequestParam("chunk") MultipartFile chunk,
|
||||
@RequestParam("uploadId") String uploadId,
|
||||
@RequestParam("chunkIndex") int chunkIndex) throws IOException {
|
||||
videoFileUploadService.uploadChunk(uploadId, chunkIndex, chunk.getBytes());
|
||||
return ApiResult.success("分片 " + chunkIndex + " 上传成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并文件并触发异步处理
|
||||
* @param params 包含 uploadId 和 fileName
|
||||
*/
|
||||
@PostMapping("/merge")
|
||||
public ApiResult<?> mergeAndProcess(@RequestBody Map<String, String> params) throws IOException {
|
||||
String uploadId = params.get("uploadId");
|
||||
String fileName = params.get("fileName");
|
||||
videoFileUploadService.mergeAndProcess(uploadId, fileName);
|
||||
return ApiResult.success("文件上传成功,已加入后台处理队列。");
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理视频
|
||||
* @param processVideoPath 待处理的视频路径
|
||||
*/
|
||||
@PostMapping("/process")
|
||||
public ApiResult<?> processVideo(@RequestBody ProcessVideoPath processVideoPath) throws IOException{
|
||||
videoFileUploadService.pushResultToMQ(processVideoPath.getPath());
|
||||
return ApiResult.success("视频处理任务已加入队列");
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,7 @@ public class AuthServiceImpl implements AuthService {
|
||||
user.setPassword(passwordEncoder.encode(registerRequest.getPassword()));
|
||||
userMapper.insert(user);
|
||||
// 你可以在这里为新用户分配默认角色
|
||||
userMapper.insertUserRole(user.getId(), 2);
|
||||
return user;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.auth0.jwt.JWTVerifier;
|
||||
import com.auth0.jwt.algorithms.Algorithm;
|
||||
import com.auth0.jwt.interfaces.DecodedJWT;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
@@ -13,7 +14,9 @@ import org.springframework.security.core.userdetails.UserDetails;
|
||||
import java.util.Date;
|
||||
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
|
||||
public class JwtTokenUtil {
|
||||
|
||||
|
||||
@@ -38,7 +41,9 @@ public class JwtTokenUtil {
|
||||
*/
|
||||
public String getUsernameFromToken(String token) {
|
||||
try {
|
||||
log.info("token:{};验证状态:{}",token,verifier.verify( token));
|
||||
DecodedJWT jwt = verifier.verify(token);
|
||||
log.info("从 Token 中获取用户名:{}", jwt.getSubject());
|
||||
return jwt.getSubject();
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
|
||||
@@ -15,6 +15,16 @@ spring:
|
||||
min-idle: 5
|
||||
max-active: 20
|
||||
max-wait: 60000
|
||||
rabbitmq:
|
||||
host: 10.91.3.24
|
||||
port: 5672
|
||||
username: chaos
|
||||
password: zx123456..
|
||||
servlet:
|
||||
multipart:
|
||||
max-file-size: 100GB
|
||||
max-request-size: 100GB
|
||||
|
||||
|
||||
mybatis-plus:
|
||||
mapper-locations: classpath*:/mapper/**/*.xml
|
||||
@@ -26,8 +36,17 @@ mybatis-plus:
|
||||
map-underscore-to-camel-case: true
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
|
||||
|
||||
|
||||
|
||||
jwt:
|
||||
tokenHeader: Authorization
|
||||
tokenHead: Chaos
|
||||
secret: zHANgcHao@1995!20250506
|
||||
expiration: 604800
|
||||
expiration: 604800
|
||||
|
||||
|
||||
file:
|
||||
upload:
|
||||
temp-dir: ./chaos/upload
|
||||
ffmpeg-path: C:\Users\Chaos\AppData\Local\Microsoft\WinGet\Packages\Gyan.FFmpeg_Microsoft.Winget.Source_8wekyb3d8bbwe\ffmpeg-7.1.1-full_build\bin\ffmpeg.exe
|
||||
Reference in New Issue
Block a user