1、新增回放请求接口

2、删除冗余代码
This commit is contained in:
1378012178@qq.com 2026-05-14 11:21:16 +08:00
parent 2d7b3d532b
commit bf058c7304
5 changed files with 271 additions and 367 deletions

View File

@ -0,0 +1,23 @@
package com.nu.entity;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
/**
* @Description: mp4视频处理信息
* @Author: jeecg-boot
* @Date: 2026-05-09
* @Version: V1.0
*/
@Data
public class Mp4HandleEntity implements Serializable {
private Date startTime;
private Date endTime;
private String orgCode;
private String orderId;
private List<TplinkInfoEntity> taskList;
}

View File

@ -0,0 +1,38 @@
package com.nu.entity;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* @Description: 视频切片信息
* @Author: jeecg-boot
* @Date: 2026-05-09
* @Version: V1.0
*/
@Data
public class TplinkInfoEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 设备标识
*/
private String sn;
/**
* 开始时间
*/
private Date startTime;
/**
* 结束时间
*/
private Date endTime;
/**
* tplink网络地址
*/
private String tplinkPath;
/**
* 机构编码
*/
private String orgCode;
}

View File

@ -1,57 +1,56 @@
package com.nu.modules.tplink.uploadlog.controller;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.system.query.QueryRuleEnum;
import org.jeecg.common.util.oConvertUtils;
import com.nu.modules.tplink.uploadlog.entity.TplinkUploadLog;
import com.nu.modules.tplink.uploadlog.service.ITplinkUploadLogService;
import cn.hutool.core.bean.BeanUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.jeecgframework.poi.excel.ExcelImportUtil;
import org.jeecgframework.poi.excel.def.NormalExcelConstants;
import org.jeecgframework.poi.excel.entity.ExportParams;
import org.jeecgframework.poi.excel.entity.ImportParams;
import org.jeecgframework.poi.excel.view.JeecgEntityExcelView;
import org.jeecg.common.system.base.controller.JeecgController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.servlet.ModelAndView;
import com.alibaba.fastjson.JSON;
import com.nu.entity.Mp4HandleEntity;
import com.nu.entity.TplinkInfoEntity;
import com.nu.modules.sysconfig.ISysConfigApi;
import com.nu.modules.tplink.uploadlog.entity.TplinkUploadLog;
import com.nu.modules.tplink.uploadlog.service.ITplinkUploadLogService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.jeecg.common.aspect.annotation.AutoLog;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.aspect.annotation.AutoLog;
import org.jeecg.common.system.base.controller.JeecgController;
import org.jeecg.common.system.query.QueryGenerator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.ModelAndView;
/**
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
/**
* @Description: tplink切片上传日志
* @Author: jeecg-boot
* @Date: 2026-05-09
* @Version: V1.0
*/
@Api(tags="tplink切片上传日志")
@Api(tags = "tplink切片上传日志")
@RestController
@RequestMapping("/tplink/uploadlog/tplinkUploadLog")
@Slf4j
public class TplinkUploadLogController extends JeecgController<TplinkUploadLog, ITplinkUploadLogService> {
@Autowired
private ITplinkUploadLogService tplinkUploadLogService;
@Autowired
private ISysConfigApi sysConfigApi;
/**
* 分页列表查询
@ -63,11 +62,11 @@ public class TplinkUploadLogController extends JeecgController<TplinkUploadLog,
* @return
*/
//@AutoLog(value = "tplink切片上传日志-分页列表查询")
@ApiOperation(value="tplink切片上传日志-分页列表查询", notes="tplink切片上传日志-分页列表查询")
@ApiOperation(value = "tplink切片上传日志-分页列表查询", notes = "tplink切片上传日志-分页列表查询")
@GetMapping(value = "/list")
public Result<IPage<TplinkUploadLog>> queryPageList(TplinkUploadLog tplinkUploadLog,
@RequestParam(name="pageNo", defaultValue="1") Integer pageNo,
@RequestParam(name="pageSize", defaultValue="10") Integer pageSize,
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
HttpServletRequest req) {
QueryWrapper<TplinkUploadLog> queryWrapper = QueryGenerator.initQueryWrapper(tplinkUploadLog, req.getParameterMap());
Page<TplinkUploadLog> page = new Page<TplinkUploadLog>(pageNo, pageSize);
@ -82,7 +81,7 @@ public class TplinkUploadLogController extends JeecgController<TplinkUploadLog,
* @return
*/
@AutoLog(value = "tplink切片上传日志-添加")
@ApiOperation(value="tplink切片上传日志-添加", notes="tplink切片上传日志-添加")
@ApiOperation(value = "tplink切片上传日志-添加", notes = "tplink切片上传日志-添加")
@RequiresPermissions("tplink.uploadlog:nu_iot_tplink_upload_log:add")
@PostMapping(value = "/add")
public Result<String> add(@RequestBody TplinkUploadLog tplinkUploadLog) {
@ -97,9 +96,9 @@ public class TplinkUploadLogController extends JeecgController<TplinkUploadLog,
* @return
*/
@AutoLog(value = "tplink切片上传日志-编辑")
@ApiOperation(value="tplink切片上传日志-编辑", notes="tplink切片上传日志-编辑")
@ApiOperation(value = "tplink切片上传日志-编辑", notes = "tplink切片上传日志-编辑")
@RequiresPermissions("tplink.uploadlog:nu_iot_tplink_upload_log:edit")
@RequestMapping(value = "/edit", method = {RequestMethod.PUT,RequestMethod.POST})
@RequestMapping(value = "/edit", method = {RequestMethod.PUT, RequestMethod.POST})
public Result<String> edit(@RequestBody TplinkUploadLog tplinkUploadLog) {
tplinkUploadLogService.updateById(tplinkUploadLog);
return Result.OK("编辑成功!");
@ -112,10 +111,10 @@ public class TplinkUploadLogController extends JeecgController<TplinkUploadLog,
* @return
*/
@AutoLog(value = "tplink切片上传日志-通过id删除")
@ApiOperation(value="tplink切片上传日志-通过id删除", notes="tplink切片上传日志-通过id删除")
@ApiOperation(value = "tplink切片上传日志-通过id删除", notes = "tplink切片上传日志-通过id删除")
@RequiresPermissions("tplink.uploadlog:nu_iot_tplink_upload_log:delete")
@DeleteMapping(value = "/delete")
public Result<String> delete(@RequestParam(name="id",required=true) String id) {
public Result<String> delete(@RequestParam(name = "id", required = true) String id) {
tplinkUploadLogService.removeById(id);
return Result.OK("删除成功!");
}
@ -127,10 +126,10 @@ public class TplinkUploadLogController extends JeecgController<TplinkUploadLog,
* @return
*/
@AutoLog(value = "tplink切片上传日志-批量删除")
@ApiOperation(value="tplink切片上传日志-批量删除", notes="tplink切片上传日志-批量删除")
@ApiOperation(value = "tplink切片上传日志-批量删除", notes = "tplink切片上传日志-批量删除")
@RequiresPermissions("tplink.uploadlog:nu_iot_tplink_upload_log:deleteBatch")
@DeleteMapping(value = "/deleteBatch")
public Result<String> deleteBatch(@RequestParam(name="ids",required=true) String ids) {
public Result<String> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
this.tplinkUploadLogService.removeByIds(Arrays.asList(ids.split(",")));
return Result.OK("批量删除成功!");
}
@ -142,11 +141,11 @@ public class TplinkUploadLogController extends JeecgController<TplinkUploadLog,
* @return
*/
//@AutoLog(value = "tplink切片上传日志-通过id查询")
@ApiOperation(value="tplink切片上传日志-通过id查询", notes="tplink切片上传日志-通过id查询")
@ApiOperation(value = "tplink切片上传日志-通过id查询", notes = "tplink切片上传日志-通过id查询")
@GetMapping(value = "/queryById")
public Result<TplinkUploadLog> queryById(@RequestParam(name="id",required=true) String id) {
public Result<TplinkUploadLog> queryById(@RequestParam(name = "id", required = true) String id) {
TplinkUploadLog tplinkUploadLog = tplinkUploadLogService.getById(id);
if(tplinkUploadLog==null) {
if (tplinkUploadLog == null) {
return Result.error("未找到对应数据");
}
return Result.OK(tplinkUploadLog);
@ -177,4 +176,68 @@ public class TplinkUploadLogController extends JeecgController<TplinkUploadLog,
return super.importExcel(request, response, TplinkUploadLog.class);
}
/**
* 获取tplink回放
*
* @param dto
* @return
*/
@AutoLog(value = "获取tplink回放")
@ApiOperation(value = "获取tplink回放", notes = "获取tplink回放")
@PostMapping(value = "/tplinkPlayBack")
public Result<String> tplinkPlayBack(@RequestBody Mp4HandleEntity dto) {
JSONObject mediaservOpenUrl = sysConfigApi.getByKeyByDS("master", "mediaserv_open_url");
String mediaservApiAddress = mediaservOpenUrl.getString("configValue");
//获取对应日期所有源视频这个多查出来一些不会影响处理效率
Date startTime = dto.getStartTime();
Date endTime = dto.getEndTime();
LocalDateTime startLdt = startTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
LocalDateTime endLdt = endTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
LocalDateTime normalizedStart = startLdt.toLocalDate().atStartOfDay();
LocalDateTime normalizedEnd = endLdt.toLocalDate().atTime(23, 59, 59);
QueryWrapper<TplinkUploadLog> qw = new QueryWrapper<>();
qw.ge("start_time", normalizedStart)
.le("start_time", normalizedEnd);
List<TplinkUploadLog> sourceMp4List = tplinkUploadLogService.list(qw);
if (CollectionUtils.isEmpty(sourceMp4List)) {
return Result.error("无回放文件");
}
dto.setOrgCode(sourceMp4List.get(0).getOrgCode());
dto.setTaskList(BeanUtil.copyToList(sourceMp4List, TplinkInfoEntity.class));
try {
//完整请求地址
String targetUrl = mediaservApiAddress + "/api/tplink/tplinkPlayBack";
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<Result<String>> response = restTemplate.exchange(
targetUrl,
HttpMethod.POST,
new HttpEntity<>(dto),
new ParameterizedTypeReference<Result<String>>() {
}
);
//处理返回结果
if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
Result<String> remoteResult = response.getBody();
if (remoteResult.getCode() == 200) {
String videoPath = remoteResult.getResult();
return Result.OK(videoPath);
} else {
return Result.error("无回放文件-api500");
}
} else {
return Result.error("无回放文件-api404");
}
} catch (Exception e) {
e.printStackTrace();
return Result.error("无回放文件-请求异常");
}
}
}

View File

@ -74,34 +74,6 @@
<scope>compile</scope>
</dependency>
<!-- 视频工具 -->
<!-- 1. JavaCV 核心 (排除 javacpp) -->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv</artifactId>
<version>1.5.9</version>
<exclusions>
<exclusion>
<groupId>org.bytedeco</groupId>
<artifactId>javacpp</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 2. FFmpeg API (对应版本) -->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>ffmpeg</artifactId>
<version>6.0-1.5.9</version>
</dependency>
<!-- 3. Linux 平台的原生库 -->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>ffmpeg</artifactId>
<version>6.0-1.5.9</version>
<classifier>linux-x86_64</classifier>
</dependency>
</dependencies>
</project>

View File

@ -1,192 +0,0 @@
package com.nu.modules.biz.order.job;
import lombok.extern.slf4j.Slf4j;
import org.bytedeco.ffmpeg.ffmpeg;
import org.bytedeco.javacpp.Loader;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.time.Duration;
import java.time.LocalDateTime;
/**
* 测试mp4视频切割与拼接
* 策略完全基于时间轴计算不依赖视频元数据
*/
@Slf4j
public class Mp4TestJob implements Job {
// ================= 常量配置 =================
// 宿主机映射到容器内的路径
private static final String BASE_PATH = "/opt/biz";
private static final String INPUT_FILE_CONTAINER = BASE_PATH + "/upFiles101/2510101004-20260429130857-2039611288698490882.mp4";
// 视频元数据硬编码
private static final LocalDateTime VIDEO_START_TIME = LocalDateTime.of(2026, 4, 29, 10, 59, 58);
private static final long VIDEO_TOTAL_DURATION = 3601L; // 1小时01秒
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
try {
// 确保输出目录存在
File outputDir = new File(BASE_PATH);
if (!outputDir.exists()) {
outputDir.mkdirs();
}
// ================= 任务规划 =================
// 目标
// 1. Part1: 11:15:15 -> 11:30:15
// 2. Part2: 11:30:16 -> 11:45:15
// 3. 拼接: Part1的后半段(从11:20:15开始) + Part2的前半段(到11:40:15结束)
// 1. 处理 Part 1
LocalDateTime p1StartTarget = LocalDateTime.of(2026, 4, 29, 11, 15, 15);
LocalDateTime p1EndTarget = LocalDateTime.of(2026, 4, 29, 11, 30, 15);
String part1File = BASE_PATH + "/part1_111515.mp4";
// 计算在源文件中的偏移量和时长
long p1Offset = getSecondsFromStart(p1StartTarget);
long p1Duration = Duration.between(p1StartTarget, p1EndTarget).getSeconds();
cutVideo(INPUT_FILE_CONTAINER, part1File, p1Offset, p1Duration);
// 2. 处理 Part 2
LocalDateTime p2StartTarget = LocalDateTime.of(2026, 4, 29, 11, 30, 16);
LocalDateTime p2EndTarget = LocalDateTime.of(2026, 4, 29, 11, 45, 15);
String part2File = BASE_PATH + "/part2_113016.mp4";
long p2Offset = getSecondsFromStart(p2StartTarget);
long p2Duration = Duration.between(p2StartTarget, p2EndTarget).getSeconds();
cutVideo(INPUT_FILE_CONTAINER, part2File, p2Offset, p2Duration);
// 3. 拼接逻辑
// 需求 Part1 11:20:15 之后的内容 + Part2 11:40:15 之前的内容
LocalDateTime splicePointP1 = LocalDateTime.of(2026, 4, 29, 11, 20, 15);
LocalDateTime splicePointP2 = LocalDateTime.of(2026, 4, 29, 11, 40, 15);
String finalFile = BASE_PATH + "/final_112015_114015.mp4";
spliceVideo(part1File, part2File, finalFile, splicePointP1, p1StartTarget, splicePointP2, p2StartTarget);
} catch (Exception e) {
log.error("视频处理失败", e);
throw new JobExecutionException(e);
}
}
/**
* 计算目标时间相对于视频开始时间2026-04-29 10:59:58的秒数偏移
*/
private long getSecondsFromStart(LocalDateTime targetTime) {
return Duration.between(VIDEO_START_TIME, targetTime).getSeconds();
}
/**
* 切割视频
* @param input 输入文件
* @param output 输出文件
* @param startTime 在源文件中的开始偏移量
* @param duration 持续时间
*/
private void cutVideo(String input, String output, long startTime, long duration) throws IOException, InterruptedException {
log.info("开始切割: [偏移{}秒, 时长{}秒] -> {}", startTime, duration, output);
long start = System.currentTimeMillis();
String ffmpeg = Loader.load(ffmpeg.class);
ProcessBuilder pb = new ProcessBuilder(
ffmpeg,
"-ss", String.valueOf(startTime),
"-i", input,
"-t", String.valueOf(duration),
"-c", "copy",
"-avoid_negative_ts", "make_zero",
output
);
// 继承标准输出和错误流方便调试
pb.inheritIO().start().waitFor();
long cost = System.currentTimeMillis() - start;
log.info("切割完成: {}, 耗时: {}秒", output, cost / 1000);
}
/**
* 拼接视频
* 逻辑
* 1. 计算 Part1 需要截取的时长 ( splicePoint Part1 结束)
* 2. 计算 Part2 需要截取的时长 ( Part2 开始 splicePoint)
* 3. 生成临时列表并执行拼接
*/
private void spliceVideo(String part1, String part2, String output,
LocalDateTime p1SplicePoint, LocalDateTime p1Base,
LocalDateTime p2SplicePoint, LocalDateTime p2Base) throws IOException, InterruptedException {
log.info("开始拼接: {} + {} -> {}", part1, part2, output);
long start = System.currentTimeMillis();
// 1. 计算 Part1 截取时长目标拼接点 - Part1起始时间
long p1CutDuration = Duration.between(p1Base, p1SplicePoint).getSeconds();
// 2. 计算 Part2 截取时长目标拼接点 - Part2起始时间
long p2CutDuration = Duration.between(p2Base, p2SplicePoint).getSeconds();
// 3. 创建临时 concat 文件
// 注意这里使用 input seeking 方式 (-ss -i 之前) 性能最好
String concatFile = BASE_PATH + "/concat_list.txt";
try (FileWriter writer = new FileWriter(concatFile)) {
// 第一部分 part1 开头截取 p1CutDuration
writer.write("file '" + part1 + "'\n");
// 第二部分 part2 开头截取 p2CutDuration
writer.write("file '" + part2 + "'\n");
}
String ffmpeg = Loader.load(ffmpeg.class);
// 使用 -ss 配合 -t concat 流进行切片
// 这里有个技巧因为我们要的是 Part1 的后半段 concat 只能顺序读
// 所以更简单的做法是分别切出这两段临时文件再合并
// 但为了演示 concat 协议我们采用先合并再修剪或者分别修剪再合并
// 鉴于 -c copy 的限制最稳妥的拼接特定片段方案是
// 1. 先切出 Part1 的后半段临时文件
// 2. 再切出 Part2 的前半段临时文件
// 3. 合并这两个临时文件
// --- 修正方案三步走 ---
String temp1 = BASE_PATH + "/temp_p1_part.mp4";
String temp2 = BASE_PATH + "/temp_p2_part.mp4";
// 步骤 A: 提取 Part1 的后半段 ( p1CutDuration 开始直到文件结束)
// 注意这里不需要 -t直接截取到末尾
ProcessBuilder pb1 = new ProcessBuilder(ffmpeg, "-ss", String.valueOf(p1CutDuration), "-i", part1, "-c", "copy", "-avoid_negative_ts", "make_zero", temp1);
pb1.inheritIO().start().waitFor();
// 步骤 B: 提取 Part2 的前半段 ( 0 开始时长 p2CutDuration)
ProcessBuilder pb2 = new ProcessBuilder(ffmpeg, "-i", part2, "-t", String.valueOf(p2CutDuration), "-c", "copy", temp2);
pb2.inheritIO().start().waitFor();
// 步骤 C: 拼接 temp1 temp2
try (FileWriter writer = new FileWriter(concatFile)) {
writer.write("file '" + temp1 + "'\n");
writer.write("file '" + temp2 + "'\n");
}
ProcessBuilder pbConcat = new ProcessBuilder(
ffmpeg,
"-f", "concat",
"-safe", "0",
"-i", concatFile,
"-c", "copy",
output
);
pbConcat.inheritIO().start().waitFor();
// 清理临时文件
new File(concatFile).delete();
new File(temp1).delete();
new File(temp2).delete();
long cost = System.currentTimeMillis() - start;
log.info("拼接完成: {}, 耗时: {}秒", output, cost / 1000);
}
}