diff --git a/pqs-common/common-oss/src/main/java/com/njcn/oss/utils/FileStorageUtil.java b/pqs-common/common-oss/src/main/java/com/njcn/oss/utils/FileStorageUtil.java index 2d7525222..a2b02a079 100644 --- a/pqs-common/common-oss/src/main/java/com/njcn/oss/utils/FileStorageUtil.java +++ b/pqs-common/common-oss/src/main/java/com/njcn/oss/utils/FileStorageUtil.java @@ -143,7 +143,7 @@ public class FileStorageUtil { * @date 2023/3/7 23:04 * @param filePath 文件在服务器的路径 */ - public void downloadStream(HttpServletResponse response, String filePath) throws IOException { + public void downloadStream(HttpServletResponse response, String filePath) { InputStream inputStream; OutputStream toClient = null; try { diff --git a/pqs-device/pq-device/pq-device-boot/src/main/java/com/njcn/device/pq/controller/ResourceController.java b/pqs-device/pq-device/pq-device-boot/src/main/java/com/njcn/device/pq/controller/ResourceController.java index a378f3a9e..fb3e237f2 100644 --- a/pqs-device/pq-device/pq-device-boot/src/main/java/com/njcn/device/pq/controller/ResourceController.java +++ b/pqs-device/pq-device/pq-device-boot/src/main/java/com/njcn/device/pq/controller/ResourceController.java @@ -1,30 +1,36 @@ package com.njcn.device.pq.controller; +import cn.hutool.core.io.FileUtil; +import com.baomidou.mybatisplus.core.toolkit.IdWorker; +import com.njcn.common.config.GeneralInfo; import com.njcn.common.pojo.annotation.OperateInfo; import com.njcn.common.pojo.enums.common.LogEnum; import com.njcn.common.pojo.enums.response.CommonResponseEnum; import com.njcn.common.pojo.response.HttpResult; import com.njcn.common.utils.HttpResultUtil; -import com.njcn.device.pq.pojo.param.LargeScreenParam; +import com.njcn.device.pq.mapper.ResourceMapper; import com.njcn.device.pq.pojo.po.ResourceData; -import com.njcn.device.pq.pojo.vo.MonitoringPointScaleVO; -import com.njcn.device.pq.service.LargeScreenService; import com.njcn.device.pq.service.ResourceService; -import com.njcn.system.pojo.po.Resinformation; +import com.njcn.oss.utils.FileStorageUtil; import com.njcn.web.controller.BaseController; +import io.minio.GetObjectArgs; import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.validation.annotation.Validated; +import org.apache.commons.io.FileUtils; +import org.apache.tomcat.util.http.fileupload.IOUtils; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.io.IOException; +import java.io.*; import java.util.List; /** @@ -41,6 +47,12 @@ public class ResourceController extends BaseController { private final ResourceService iResourceAdministrationService; + private final ResourceMapper resourceMapper; + + private final FileStorageUtil fileStorageUtil; + + private final GeneralInfo generalInfo; + /** * 上传资源 */ @@ -70,18 +82,140 @@ public class ResourceController extends BaseController { return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); } - /** - * 预览资源 - */ - //todo 未完成 + @OperateInfo(info = LogEnum.BUSINESS_COMMON) - @GetMapping("/previewFile") - @ApiOperation("预览资源") + @GetMapping("/play1") + @ApiOperation("播放视频资源") @ApiImplicitParam(name = "id", value = "id", required = true) - public HttpResult previewFile(@RequestParam("id") String id, HttpServletRequest request, HttpServletResponse response) throws IOException { - String methodDescribe = getMethodDescribe("downloadFile"); - iResourceAdministrationService.previewFile(id, request, response); - return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, "成功", methodDescribe); + public void play1(@RequestParam("id") String id, HttpServletRequest request, HttpServletResponse response) { + ResourceData resourceData = resourceMapper.selectById(id); + fileStorageUtil.downloadStream(response,resourceData.getResUrl()); + } + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @GetMapping("/play2") + @ApiOperation("播放视频资源") + @ApiImplicitParam(name = "id", value = "id", required = true) + public void play2(@RequestParam("id") String id, HttpServletRequest request, HttpServletResponse response) { + RandomAccessFile targetFile = null; + OutputStream outputStream = null; + try { + outputStream = response.getOutputStream(); + response.reset(); + //获取请求头中Range的值 + String rangeString = request.getHeader(HttpHeaders.RANGE); + //打开文件 + File file = File.createTempFile(IdWorker.getIdStr(),".mp4"); + ResourceData resourceData = resourceMapper.selectById(id); + InputStream ins = fileStorageUtil.getFileStream(resourceData.getResUrl()); + long l = System.currentTimeMillis(); + FileUtil.writeFromStream(ins,file); + long ll = System.currentTimeMillis(); + System.out.println("流转文件耗时:"+(ll-l)+"============================="); + if (file.exists()) { + //使用RandomAccessFile读取文件 + targetFile = new RandomAccessFile(file, "r"); + long fileLength = targetFile.length(); + long requestSize = (int) fileLength; + //分段下载视频 + if (StringUtils.hasText(rangeString)) { + //从Range中提取需要获取数据的开始和结束位置 + long requestStart = 0, requestEnd = 0; + String[] ranges = rangeString.split("="); + if (ranges.length > 1) { + String[] rangeDatas = ranges[1].split("-"); + requestStart = Integer.parseInt(rangeDatas[0]); + if (rangeDatas.length > 1) { + requestEnd = Integer.parseInt(rangeDatas[1]); + } + } + if (requestEnd != 0 && requestEnd > requestStart) { + requestSize = requestEnd - requestStart + 1; + } + //根据协议设置请求头 + response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes"); + response.setHeader(HttpHeaders.CONTENT_TYPE, "video/mp4"); + if (!StringUtils.hasText(rangeString)) { + response.setHeader(HttpHeaders.CONTENT_LENGTH, fileLength + ""); + } else { + long length; + if (requestEnd > 0) { + length = requestEnd - requestStart + 1; + response.setHeader(HttpHeaders.CONTENT_LENGTH, "" + length); + response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes " + requestStart + "-" + requestEnd + "/" + fileLength); + } else { + length = fileLength - requestStart; + response.setHeader(HttpHeaders.CONTENT_LENGTH, "" + length); + response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes " + requestStart + "-" + (fileLength - 1) + "/" + + fileLength); + } + } + //断点传输下载视频返回206 + response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); + //设置targetFile,从自定义位置开始读取数据 + targetFile.seek(requestStart); + } else { + //如果Range为空则下载整个视频 + response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=demo.mp4"); + //设置文件长度 + response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(fileLength)); + } + + //从磁盘读取数据流返回 + byte[] cache = new byte[4096]; + try { + while (requestSize > 0) { + int len = targetFile.read(cache); + if (requestSize < cache.length) { + outputStream.write(cache, 0, (int) requestSize); + } else { + outputStream.write(cache, 0, len); + if (len < cache.length) { + break; + } + } + requestSize -= cache.length; + } + } catch (IOException e) { + // tomcat原话。写操作IO异常几乎总是由于客户端主动关闭连接导致,所以直接吃掉异常打日志 + //比如使用video播放视频时经常会发送Range为0- 的范围只是为了获取视频大小,之后就中断连接了 + log.info(e.getMessage()); + } + } else { + throw new RuntimeException("文件路劲有误"); + } + outputStream.flush(); + } catch (Exception e) { + log.error("文件传输错误", e); + throw new RuntimeException("文件传输错误"); + }finally { + if(outputStream != null){ + try { + outputStream.close(); + } catch (IOException e) { + log.error("流释放错误", e); + } + } + if(targetFile != null){ + try { + targetFile.close(); + } catch (IOException e) { + log.error("文件流释放错误", e); + } + } + } + } + + + public static long getFileSize(InputStream inputStream) throws IOException { + byte[] buffer = new byte[1024]; + int len; + long size = 0; + while ((len = inputStream.read(buffer)) != -1) { + size += len; + } + inputStream.close(); + return size; } /**