Преглед изворни кода

dfs-store 视频文件上传接口

reghao пре 3 година
родитељ
комит
22a1408195
24 измењених фајлова са 269 додато и 272 уклоњено
  1. 5 0
      pom.xml
  2. 6 8
      src/main/java/cn/reghao/dfs/store/controller/FileUploadController.java
  3. 34 15
      src/main/java/cn/reghao/dfs/store/controller/MediaUploadController.java
  4. 1 1
      src/main/java/cn/reghao/dfs/store/controller/SpiderFileController.java
  5. 9 31
      src/main/java/cn/reghao/dfs/store/db/repository/FileRepository.java
  6. 1 0
      src/main/java/cn/reghao/dfs/store/model/dto/FileContentType.java
  7. 0 21
      src/main/java/cn/reghao/dfs/store/model/dto/ImgFileRetDto.java
  8. 18 8
      src/main/java/cn/reghao/dfs/store/model/dto/UploadFilePart.java
  9. 12 3
      src/main/java/cn/reghao/dfs/store/model/dto/UploadPart.java
  10. 9 0
      src/main/java/cn/reghao/dfs/store/model/dto/UploadedFile.java
  11. 7 13
      src/main/java/cn/reghao/dfs/store/model/po/FileInfo.java
  12. 27 0
      src/main/java/cn/reghao/dfs/store/model/vo/FilePartRet.java
  13. 0 13
      src/main/java/cn/reghao/dfs/store/model/vo/UploadFilePartRet.java
  14. 24 0
      src/main/java/cn/reghao/dfs/store/model/vo/VideoFileRet.java
  15. 1 1
      src/main/java/cn/reghao/dfs/store/service/FileDownloadService.java
  16. 22 0
      src/main/java/cn/reghao/dfs/store/service/FileStoreService.java
  17. 28 1
      src/main/java/cn/reghao/dfs/store/service/FileTypeService.java
  18. 31 86
      src/main/java/cn/reghao/dfs/store/service/FileUploadService.java
  19. 1 1
      src/main/java/cn/reghao/dfs/store/service/FileUrlService.java
  20. 1 42
      src/main/java/cn/reghao/dfs/store/service/media/ImageFileService.java
  21. 28 24
      src/main/java/cn/reghao/dfs/store/service/media/VideoFileService.java
  22. 1 1
      src/main/resources/application-dev.yml
  23. 1 1
      src/main/resources/application-dev1.yml
  24. 2 2
      src/test/java/ConsistentCheckTest.java

+ 5 - 0
pom.xml

@@ -36,6 +36,11 @@
             <artifactId>tool</artifactId>
             <version>1.0.0-SNAPSHOT</version>
         </dependency>
+        <dependency>
+            <groupId>cn.reghao.jutil</groupId>
+            <artifactId>web</artifactId>
+            <version>1.0.0-SNAPSHOT</version>
+        </dependency>
         <dependency>
             <groupId>cn.reghao.dfs</groupId>
             <artifactId>dfs-client</artifactId>

+ 6 - 8
src/main/java/cn/reghao/dfs/store/controller/FileUploadController.java

@@ -1,18 +1,17 @@
 package cn.reghao.dfs.store.controller;
 
-import cn.reghao.dfs.store.model.dto.UploadFile;
 import cn.reghao.dfs.store.model.dto.UploadFilePart;
 import cn.reghao.dfs.store.model.dto.UploadPrepare;
 import cn.reghao.dfs.store.service.FileUploadService;
 import cn.reghao.jutil.jdk.result.WebBody;
-import cn.reghao.dfs.store.model.vo.UploadFilePartRet;
+import cn.reghao.dfs.store.model.vo.FilePartRet;
 import cn.reghao.dfs.store.model.vo.UploadPrepareRet;
-import cn.reghao.dfs.store.model.vo.UploadFileRet;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import org.springframework.http.MediaType;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
 
 /**
  * @author reghao
@@ -37,15 +36,14 @@ public class FileUploadController {
 
     @ApiOperation(value = "单个文件上传")
     @PostMapping(value = "/single", produces = MediaType.APPLICATION_JSON_VALUE)
-    public String uploadFile(@Validated UploadFile uploadFile) throws Exception {
-        fileUploadService.putFile(uploadFile);
-        return WebBody.success(new UploadFileRet(uploadFile.getUploadId(), null));
+    public String uploadFile(MultipartFile multipartFile) throws Exception {
+        return WebBody.success();
     }
 
     @ApiOperation("分片文件上传")
     @PostMapping("/part")
     public String uploadFilePart(@Validated UploadFilePart uploadFilePart) throws Exception {
-        UploadFilePartRet uploadFilePartRet = fileUploadService.putFilePart(uploadFilePart);
-        return WebBody.success(uploadFilePartRet);
+        FilePartRet filePartRet = fileUploadService.putFilePart(uploadFilePart);
+        return WebBody.success(filePartRet);
     }
 }

+ 34 - 15
src/main/java/cn/reghao/dfs/store/controller/MediaUploadController.java

@@ -1,9 +1,11 @@
 package cn.reghao.dfs.store.controller;
 
-import cn.reghao.dfs.store.model.dto.UploadFile;
-import cn.reghao.dfs.store.model.dto.UploadedFile;
+import cn.reghao.dfs.store.model.dto.*;
+import cn.reghao.dfs.store.model.po.VideoFile;
+import cn.reghao.dfs.store.model.vo.FilePartRet;
+import cn.reghao.dfs.store.model.vo.UploadPrepareRet;
+import cn.reghao.dfs.store.model.vo.VideoFileRet;
 import cn.reghao.dfs.store.service.FileUploadService;
-import cn.reghao.dfs.store.service.media.ImageFileService;
 import cn.reghao.jutil.jdk.result.WebBody;
 import cn.reghao.dfs.store.model.vo.UploadFileRet;
 import cn.reghao.dfs.store.service.media.VideoFileService;
@@ -12,13 +14,13 @@ import io.swagger.annotations.ApiOperation;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.http.MediaType;
 import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 import org.springframework.web.multipart.MultipartFile;
 
 import javax.validation.constraints.NotNull;
-import java.io.InputStream;
 
 /**
  * @author reghao
@@ -30,36 +32,53 @@ import java.io.InputStream;
 @Slf4j
 public class MediaUploadController {
     private final FileUploadService fileUploadService;
-    private ImageFileService imageService;
-    private VideoFileService videoService;
+    private final VideoFileService videoService;
 
-    public MediaUploadController(FileUploadService fileUploadService, ImageFileService imageService,
-                                 VideoFileService videoService) {
+    public MediaUploadController(FileUploadService fileUploadService, VideoFileService videoService) {
         this.fileUploadService = fileUploadService;
-        this.imageService = imageService;
         this.videoService = videoService;
     }
 
     @ApiOperation(value = "上传图片文件")
     @PostMapping(value = "/image", produces = MediaType.APPLICATION_JSON_VALUE)
     public String uploadImageFile(@NotNull MultipartFile file) throws Exception {
-        String filename = file.getOriginalFilename();
-        long size = file.getSize();
         String contentType = file.getContentType();
-        InputStream inputStream = file.getInputStream();
         if (contentType == null || !contentType.startsWith("image")) {
             return WebBody.failWithMsg("content-type 错误");
         }
 
-        UploadedFile uploadedFile = new UploadedFile(filename, size, contentType, inputStream);
+        UploadedFile uploadedFile = new UploadedFile(file);
         UploadFileRet uploadFileRet = fileUploadService.put(uploadedFile);
         return WebBody.success(uploadFileRet);
     }
 
+    @ApiOperation("文件上传前的准备, 客户端传递文件的 sha256sum, 文件若存在则直接返回文件关联的 id")
+    @PostMapping(value = "/video/prepare", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String videoFilePrepare(@Validated UploadPrepare uploadPrepare) {
+        UploadPrepareRet uploadPrepareRet = fileUploadService.prepareUpload(uploadPrepare);
+        return WebBody.success(uploadPrepareRet);
+    }
+
     @ApiOperation(value = "上传视频文件")
-    @PostMapping(value = "/video", produces = MediaType.APPLICATION_JSON_VALUE)
-    public String uploadVideoFile(@Validated UploadFile uploadFile) throws Exception {
+    @GetMapping(value = "/video", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String uploadVideoFile1() throws Exception {
         log.info("视频文件");
         return WebBody.success();
     }
+
+    @ApiOperation(value = "上传视频分片文件")
+    @PostMapping(value = "/video", produces = MediaType.APPLICATION_JSON_VALUE)
+    public String uploadVideoFile(@Validated UploadFilePart uploadFilePart) throws Exception {
+        FilePartRet filePartRet = fileUploadService.putFilePart(uploadFilePart);
+        String uploadId = filePartRet.getUploadId();
+        if (!filePartRet.isMerged()) {
+            return WebBody.success(new VideoFileRet(uploadId));
+        }
+
+        PathUrl pathUrl = filePartRet.getPathUrl();
+        VideoFile videoFile = videoService.process(uploadId, pathUrl);
+        String videoUrl = videoFile.getBaseUrl();
+        String coverUrl = videoFile.getBaseCoverUrl();
+        return WebBody.success(new VideoFileRet(uploadId, videoUrl, coverUrl));
+    }
 }

+ 1 - 1
src/main/java/cn/reghao/dfs/store/controller/SpiderFileController.java

@@ -47,7 +47,7 @@ public class SpiderFileController {
             }
 
             UploadedFile uploadedFile = new UploadedFile(filename, size, contentType, inputStream);
-            UploadFileRet uploadFileRet = fileUploadService.put(uploadedFile, userId);
+            UploadFileRet uploadFileRet = fileUploadService.put(uploadedFile/*, userId*/);
             return WebBody.success(uploadFileRet);
         }
         return WebBody.failWithMsg("content-type 错误");

+ 9 - 31
src/main/java/cn/reghao/dfs/store/db/repository/FileRepository.java

@@ -6,15 +6,13 @@ import cn.reghao.dfs.store.db.mapper.FileUserMapper;
 import cn.reghao.dfs.store.model.dto.FileContentType;
 import cn.reghao.dfs.store.model.dto.PathUrl;
 import cn.reghao.dfs.store.model.dto.UploadPrepare;
-import cn.reghao.dfs.store.model.dto.UploadedFile;
 import cn.reghao.dfs.store.util.store.LocalStores;
 import cn.reghao.jutil.tool.id.IdGenerator;
 import cn.reghao.dfs.store.model.po.FileInfo;
 import cn.reghao.dfs.store.model.po.FileUrl;
 import cn.reghao.dfs.store.model.po.FileUser;
 import cn.reghao.dfs.store.model.vo.FileMap;
-import cn.reghao.dfs.store.util.StringUtil;
-import cn.reghao.dfs.store.util.UserContext;
+import cn.reghao.jutil.web.ServletUtil;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Repository;
 import org.springframework.transaction.annotation.Transactional;
@@ -41,16 +39,15 @@ public class FileRepository {
     }
 
     @Transactional(rollbackFor = Exception.class)
-    public String createFileUploadId(UploadPrepare uploadPrepare) {
+    public String createFileUploadId(UploadPrepare uploadPrepare, FileContentType fileContentType) {
         String filename = uploadPrepare.getFilename();
         long size = uploadPrepare.getSize();
         String sha256sum = uploadPrepare.getSha256sum();
         String fileId = idGenerator.getUuid();
-        String suffix = StringUtil.getSuffix(filename);
-        FileInfo fileInfo = new FileInfo(fileId, sha256sum, filename, size, suffix);
+        FileInfo fileInfo = new FileInfo(fileId, sha256sum, filename, size, fileContentType, false);
 
         String uploadId = idGenerator.getUuid();
-        String currentUserId = UserContext.getUserId();
+        String currentUserId = String.valueOf(ServletUtil.getUserId());
         FileUser fileUser = new FileUser(uploadId, fileId, currentUserId);
 
         fileInfoMapper.save(fileInfo);
@@ -58,31 +55,12 @@ public class FileRepository {
         return uploadId;
     }
 
-    public FileMap createUploadId(UploadedFile uploadedFile, String sha256sum, int size) {
-        String filename = uploadedFile.getFilename();
-        String suffix = StringUtil.getSuffix(filename);
-        String contentType = uploadedFile.getContentType();
+    public FileMap createUploadId(String filename, String sha256sum, int size, FileContentType fileContentType) {
         String fileId = idGenerator.getUuid();
-        FileInfo fileInfo = new FileInfo(fileId, sha256sum, filename, size, suffix, contentType);
-
-        String uploadId = idGenerator.getUuid();
-        String currentUserId = UserContext.getUserId();
-        FileUser fileUser = new FileUser(uploadId, fileId, currentUserId);
-
-        fileInfoMapper.save(fileInfo);
-        fileUserMapper.save(fileUser);
-        return new FileMap(fileId, uploadId);
-    }
-
-    @Deprecated
-    public FileMap createUploadId(UploadedFile uploadedFile, String sha256sum, int size, String currentUserId) {
-        String filename = uploadedFile.getFilename();
-        String suffix = StringUtil.getSuffix(filename);
-        String contentType = uploadedFile.getContentType();
-        String fileId = idGenerator.getUuid();
-        FileInfo fileInfo = new FileInfo(fileId, sha256sum, filename, size, suffix, contentType);
+        FileInfo fileInfo = new FileInfo(fileId, sha256sum, filename, size, fileContentType, true);
 
         String uploadId = idGenerator.getUuid();
+        String currentUserId = String.valueOf(ServletUtil.getUserId());
         FileUser fileUser = new FileUser(uploadId, fileId, currentUserId);
 
         fileInfoMapper.save(fileInfo);
@@ -91,7 +69,7 @@ public class FileRepository {
     }
 
     public String getOrCreateUploadId(String fileId) {
-        String currentUserId = UserContext.getUserId();
+        String currentUserId = String.valueOf(ServletUtil.getUserId());
         FileUser fileUser = fileUserMapper.findByFileAndUserId(fileId, currentUserId);
         if (fileUser != null) {
             return fileUser.getUploadId();
@@ -135,7 +113,7 @@ public class FileRepository {
 
     @Transactional(rollbackFor = Exception.class)
     public void deleteFile(String uploadId) {
-        String currentUserId = UserContext.getUserId();
+        String currentUserId = String.valueOf(ServletUtil.getUserId());
         FileUser fileUser = fileUserMapper.findByUploadAndUserId(uploadId, currentUserId);
         if (fileUser == null) {
             log.error("{} 试图删除不是自己上传的文件", currentUserId);

+ 1 - 0
src/main/java/cn/reghao/dfs/store/model/dto/FileContentType.java

@@ -10,6 +10,7 @@ import lombok.Getter;
 @AllArgsConstructor
 @Getter
 public class FileContentType {
+    private String suffix;
     private int fileType;
     private String contentType;
 }

+ 0 - 21
src/main/java/cn/reghao/dfs/store/model/dto/ImgFileRetDto.java

@@ -1,21 +0,0 @@
-package cn.reghao.dfs.store.model.dto;
-
-import lombok.Getter;
-import lombok.Setter;
-
-/**
- * @author reghao
- * @date 2021-12-05 22:04:05
- */
-@Getter
-@Setter
-@Deprecated
-public class ImgFileRetDto {
-    private String fileId;
-    private String url;
-
-    public ImgFileRetDto(String fileId, String url) {
-        this.fileId = fileId;
-        this.url = url;
-    }
-}

+ 18 - 8
src/main/java/cn/reghao/dfs/store/model/dto/UploadFilePart.java

@@ -1,7 +1,5 @@
 package cn.reghao.dfs.store.model.dto;
 
-import com.fasterxml.jackson.databind.PropertyNamingStrategy;
-import com.fasterxml.jackson.databind.annotation.JsonNaming;
 import lombok.Getter;
 import lombok.Setter;
 import org.springframework.web.multipart.MultipartFile;
@@ -16,18 +14,30 @@ import java.io.Serializable;
  */
 @Getter
 @Setter
-@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
 public class UploadFilePart implements Serializable {
     private static final long serialVersionUID = 1L;
 
     @NotNull
     private MultipartFile file;
+    // 文件标识
     @NotBlank
-    private String sha256sum;
-    @NotBlank
-    private String uploadId;
+    private String identifier;
+    private String filename;
+    private String relativePath;
+    // 文件大小
+    @NotNull
+    private Long totalSize;
+    // 分片文件大小
+    @NotNull
+    private Integer chunkSize;
+    // 当前分片文件大小
+    @NotNull
+    private Integer currentChunkSize;
+    // 分片文件数量
     @NotNull
-    private Integer splitIndex;
+    private Integer totalChunks;
+    // 当前分片文件索引
     @NotNull
-    private Integer splitNum;
+    private Integer chunkNumber;
+    private String partSha256sum;
 }

+ 12 - 3
src/main/java/cn/reghao/dfs/store/model/dto/UploadFile.java → src/main/java/cn/reghao/dfs/store/model/dto/UploadPart.java

@@ -1,5 +1,7 @@
 package cn.reghao.dfs.store.model.dto;
 
+import com.fasterxml.jackson.databind.PropertyNamingStrategy;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
 import lombok.Getter;
 import lombok.Setter;
 import org.springframework.web.multipart.MultipartFile;
@@ -10,15 +12,22 @@ import java.io.Serializable;
 
 /**
  * @author reghao
- * @date 2022-04-26 15:22:49
+ * @date 2022-04-25 10:42:38
  */
 @Getter
 @Setter
-public class UploadFile implements Serializable {
+@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
+public class UploadPart implements Serializable {
     private static final long serialVersionUID = 1L;
 
+    @NotNull
+    private MultipartFile file;
+    @NotBlank
+    private String sha256sum;
     @NotBlank
     private String uploadId;
     @NotNull
-    private MultipartFile file;
+    private Integer splitIndex;
+    @NotNull
+    private Integer splitNum;
 }

+ 9 - 0
src/main/java/cn/reghao/dfs/store/model/dto/UploadedFile.java

@@ -2,7 +2,9 @@ package cn.reghao.dfs.store.model.dto;
 
 import lombok.AllArgsConstructor;
 import lombok.Getter;
+import org.springframework.web.multipart.MultipartFile;
 
+import java.io.IOException;
 import java.io.InputStream;
 
 /**
@@ -16,4 +18,11 @@ public class UploadedFile {
     private long size;
     private String contentType;
     private InputStream inputStream;
+
+    public UploadedFile(MultipartFile multipartFile) throws IOException {
+        this.filename = multipartFile.getOriginalFilename();
+        this.size = multipartFile.getSize();
+        this.contentType = multipartFile.getContentType();
+        this.inputStream = multipartFile.getInputStream();
+    }
 }

+ 7 - 13
src/main/java/cn/reghao/dfs/store/model/po/FileInfo.java

@@ -1,5 +1,6 @@
 package cn.reghao.dfs.store.model.po;
 
+import cn.reghao.dfs.store.model.dto.FileContentType;
 import cn.reghao.jutil.jdk.db.BaseObject;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
@@ -25,22 +26,15 @@ public class FileInfo extends BaseObject<Integer> {
     private String contentType;
     private Boolean uploaded;
 
-    public FileInfo(String fileId, String sha256sum, String filename, long size, String suffix) {
+    public FileInfo(String fileId, String sha256sum, String filename, long size,
+                    FileContentType fileContentType, boolean uploaded) {
         this.fileId = fileId;
         this.sha256sum = sha256sum;
         this.filename = filename;
-        this.suffix = suffix;
         this.size = size;
-        this.uploaded = false;
-    }
-
-    public FileInfo(String fileId, String sha256sum, String filename, long size, String contentType, String suffix) {
-        this.fileId = fileId;
-        this.sha256sum = sha256sum;
-        this.filename = filename;
-        this.suffix = suffix;
-        this.size = size;
-        this.contentType = contentType;
-        this.uploaded = true;
+        this.suffix = fileContentType.getSuffix();
+        this.fileType = fileContentType.getFileType();
+        this.contentType = fileContentType.getContentType();
+        this.uploaded = uploaded;
     }
 }

+ 27 - 0
src/main/java/cn/reghao/dfs/store/model/vo/FilePartRet.java

@@ -0,0 +1,27 @@
+package cn.reghao.dfs.store.model.vo;
+
+import cn.reghao.dfs.store.model.dto.PathUrl;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author reghao
+ * @date 2022-04-21 09:32:00
+ */
+@Getter
+public class FilePartRet {
+    private String uploadId;
+    private boolean merged;
+    private PathUrl pathUrl;
+
+    public FilePartRet(String uploadId) {
+        this.uploadId = uploadId;
+        this.merged = false;
+    }
+
+    public FilePartRet(String uploadId, PathUrl pathUrl) {
+        this.uploadId = uploadId;
+        this.merged = true;
+        this.pathUrl = pathUrl;
+    }
+}

+ 0 - 13
src/main/java/cn/reghao/dfs/store/model/vo/UploadFilePartRet.java

@@ -1,13 +0,0 @@
-package cn.reghao.dfs.store.model.vo;
-
-import lombok.AllArgsConstructor;
-
-/**
- * @author reghao
- * @date 2022-04-21 09:32:00
- */
-@AllArgsConstructor
-public class UploadFilePartRet {
-    private String uploadId;
-    private boolean isMerge;
-}

+ 24 - 0
src/main/java/cn/reghao/dfs/store/model/vo/VideoFileRet.java

@@ -0,0 +1,24 @@
+package cn.reghao.dfs.store.model.vo;
+
+/**
+ * @author reghao
+ * @date 2022-07-23 06:21:32
+ */
+public class VideoFileRet {
+    private String uploadId;
+    private boolean merged;
+    private String videoUrl;
+    private String coverUrl;
+
+    public VideoFileRet(String uploadId) {
+        this.uploadId = uploadId;
+        this.merged = false;
+    }
+
+    public VideoFileRet(String uploadId, String videoUrl, String coverUrl) {
+        this.uploadId = uploadId;
+        this.merged = true;
+        this.videoUrl = videoUrl;
+        this.coverUrl = coverUrl;
+    }
+}

+ 1 - 1
src/main/java/cn/reghao/dfs/store/service/FileDownloadService.java

@@ -2,7 +2,7 @@ package cn.reghao.dfs.store.service;
 
 import cn.reghao.dfs.store.model.dto.DownloadFile;
 import cn.reghao.dfs.store.model.dto.FileUrlDto;
-import cn.reghao.dfs.store.util.ServletUtil;
+import cn.reghao.jutil.web.ServletUtil;
 import org.springframework.stereotype.Service;
 
 import javax.servlet.http.HttpServletResponse;

+ 22 - 0
src/main/java/cn/reghao/dfs/store/service/FileStoreService.java

@@ -16,6 +16,16 @@ import static java.nio.file.StandardOpenOption.*;
  */
 @Service
 public class FileStoreService {
+    public void createSparseFile(String absolutePath, long len) throws IOException {
+        File file = new File(absolutePath);
+        FileUtils.forceMkdirParent(file);
+        // 创建一个稀疏文件
+        Files.newByteChannel(Paths.get(absolutePath), EnumSet.of(CREATE_NEW, WRITE, SPARSE));
+        RandomAccessFile raf = new RandomAccessFile(file, "rw");
+        raf.setLength(len);
+        raf.close();
+    }
+
     public File createFile(String absolutePath, long len) throws IOException {
         File file = new File(absolutePath);
         if (!file.exists()) {
@@ -29,6 +39,18 @@ public class FileStoreService {
         return file;
     }
 
+    public void writeToFile(InputStream in, String absolutePath, long pos) throws IOException {
+        RandomAccessFile raf = new RandomAccessFile(absolutePath, "rw");
+        raf.seek(pos);
+        byte[] buf = new byte[1024*1024];
+        int len;
+        while ((len = in.read(buf)) != -1) {
+            raf.write(buf, 0, len);
+        }
+        raf.close();
+        in.close();
+    }
+
     public void writeToFile(InputStream in, File file, long pos) throws IOException {
         RandomAccessFile raf = new RandomAccessFile(file, "rw");
         raf.seek(pos);

+ 28 - 1
src/main/java/cn/reghao/dfs/store/service/FileTypeService.java

@@ -1,8 +1,10 @@
 package cn.reghao.dfs.store.service;
 
 import cn.reghao.dfs.store.model.dto.FileContentType;
+import cn.reghao.dfs.store.util.StringUtil;
 import org.springframework.stereotype.Service;
 
+import java.io.File;
 import java.nio.file.Files;
 import java.nio.file.Paths;
 
@@ -12,6 +14,28 @@ import java.nio.file.Paths;
  */
 @Service
 public class FileTypeService {
+    public FileContentType getFileType(String filename) {
+        String suffix = StringUtil.getSuffix(filename);
+
+        int fileType = 4;
+        String contentType = "application/octet-stream";
+        if (suffix.isBlank()) {
+            return new FileContentType(suffix, fileType, contentType);
+        }
+
+        if (suffix.startsWith("jpg")) {
+            fileType = 1;
+            contentType = "image/jpg";
+        } else if (suffix.startsWith("mp3")) {
+            fileType = 2;
+            contentType = "audio/mp3";
+        } else if (suffix.startsWith("mp4")) {
+            fileType = 3;
+            contentType = "video/mp4";
+        }
+        return new FileContentType(suffix, fileType, contentType);
+    }
+
     public FileContentType getFileType(String contentType, String filePath) throws Exception {
         if (contentType == null) {
             contentType = Files.probeContentType(Paths.get(filePath));
@@ -27,6 +51,9 @@ public class FileTypeService {
         } else if (contentType.startsWith("video")) {
             fileType = 3;
         }
-        return new FileContentType(fileType, contentType);
+
+        String filename = filePath.substring(filePath.lastIndexOf(File.separator)+1);
+        String suffix = StringUtil.getSuffix(filename);
+        return new FileContentType(suffix, fileType, contentType);
     }
 }

+ 31 - 86
src/main/java/cn/reghao/dfs/store/service/FileUploadService.java

@@ -3,7 +3,7 @@ package cn.reghao.dfs.store.service;
 import cn.reghao.dfs.store.model.dto.*;
 import cn.reghao.jutil.jdk.security.DigestUtil;
 import cn.reghao.dfs.store.model.vo.FileMap;
-import cn.reghao.dfs.store.model.vo.UploadFilePartRet;
+import cn.reghao.dfs.store.model.vo.FilePartRet;
 import cn.reghao.dfs.store.model.vo.UploadFileRet;
 import cn.reghao.dfs.store.model.vo.UploadPrepareRet;
 import cn.reghao.dfs.store.db.repository.FileRepository;
@@ -28,8 +28,8 @@ import java.util.Set;
 @Slf4j
 @Service
 public class FileUploadService {
-    // 10MiB
-    private final static long PART_SIZE = 1024*1024*10;
+    // 20MiB
+    private final static long PART_SIZE = 1024*1024*20;
 
     private final FileUrlService fileUrlService;
     private final FileStoreService fileStoreService;
@@ -51,31 +51,16 @@ public class FileUploadService {
         String sha256sum = uploadPrepare.getSha256sum();
         FileInfo fileInfo = fileRepository.getFileInfo(sha256sum);
         if (fileInfo == null) {
-            String uploadId = fileRepository.createFileUploadId(uploadPrepare);
+            String filename = uploadPrepare.getFilename();
+            FileContentType fileContentType = fileTypeService.getFileType(filename);
+            String uploadId = fileRepository.createFileUploadId(uploadPrepare, fileContentType);
             return new UploadPrepareRet(uploadId, PART_SIZE, false);
         }
 
         String uploadId = fileRepository.getOrCreateUploadId(fileInfo.getFileId());
         // uploaded 为 true 则 exist 也应该为 true, 但这里统一将 exist 设置为 false, 表示客户端总是要传输文件流
         boolean uploaded = fileInfo.getUploaded();
-        return new UploadPrepareRet(uploadId, PART_SIZE, false);
-    }
-
-    /**
-     * 将文件存放到 FileStore,并在数据库中记录文件相关信息
-     *
-     * @param
-     * @return
-     * @date 2021-12-08 下午3:30
-     */
-    public PathUrl putFile(UploadFile uploadFile) throws Exception {
-        String uploadId = uploadFile.getUploadId();
-        FileInfo fileInfo = fileRepository.getFileInfoByUploadId(uploadId);
-        if (!fileInfo.getUploaded()) {
-            return put(uploadFile.getFile(), fileInfo, uploadId);
-        } else {
-            return null;
-        }
+        return new UploadPrepareRet(uploadId, PART_SIZE, true);
     }
 
     @Transactional(rollbackFor = Exception.class)
@@ -91,13 +76,6 @@ public class FileUploadService {
         String fileId = fileInfo.getFileId();
         String suffix = fileInfo.getSuffix();
         PathUrl pathUrl = fileUrlService.genPathUrl(contentType, sha256sum, size, fileId, suffix, uploadId);
-
-        /*if (contentType != null && contentType.startsWith("image")) {
-            ImageOps.Size size1 = ImageOps.info(new ByteArrayInputStream(bytes));
-            fileUrlService.getImagePathAndUrl(sha256sum, uploadId, size, fileId, suffix,
-                    size1.getWidth(), size1.getHeight());
-        }*/
-
         FileContentType fileType = fileTypeService.getFileType(contentType, pathUrl.getAbsolutePath());
         try {
             fileStoreService.saveFile(pathUrl.getAbsolutePath(), bytes);
@@ -124,14 +102,14 @@ public class FileUploadService {
         } else {
             int size = bytes.length;
             String filename = uploadedFile.getFilename();
+            FileContentType fileContentType = fileTypeService.getFileType(filename);
             String suffix = StringUtil.getSuffix(filename);
             String contentType = uploadedFile.getContentType();
-            FileMap fileMap = fileRepository.createUploadId(uploadedFile, sha256sum, size);
+            FileMap fileMap = fileRepository.createUploadId(filename, sha256sum, size, fileContentType);
 
             String fileId = fileMap.getFileId();
             String uploadId = fileMap.getUploadId();
             PathUrl pathUrl = fileUrlService.genPathUrl(contentType, sha256sum, size, fileId, suffix, uploadId);
-            FileContentType fileType = fileTypeService.getFileType(contentType, pathUrl.getAbsolutePath());
             try {
                 fileStoreService.saveFile(pathUrl.getAbsolutePath(), bytes);
                 LocalStores.getStoreDir(pathUrl.getAbsolutePath()).incrCount();
@@ -140,44 +118,7 @@ public class FileUploadService {
                 throw e;
             }
 
-            fileRepository.saveFile(fileId, fileType, pathUrl);
-            String url = pathUrl.getUrl();
-            return new UploadFileRet(uploadId, url);
-        }
-    }
-
-    @Deprecated
-    @Transactional(rollbackFor = Exception.class)
-    public synchronized UploadFileRet put(UploadedFile uploadedFile, String currentUserId) throws Exception {
-        byte[] bytes = uploadedFile.getInputStream().readAllBytes();
-        String sha256sum = DigestUtil.sha256sum(bytes);
-        FileInfo fileInfo = fileRepository.getFileInfo(sha256sum);
-        if (fileInfo != null) {
-            String fileId = fileInfo.getFileId();
-            String uploadId = fileRepository.getOrCreateUploadId(fileInfo.getFileId(), currentUserId);
-            String url = fileRepository.getPathUrlByFileId(fileId);
-            return new UploadFileRet(uploadId, url);
-        } else {
-            int size = bytes.length;
-            String filename = uploadedFile.getFilename();
-            String suffix = StringUtil.getSuffix(filename);
-            String contentType = uploadedFile.getContentType();
-            FileMap fileMap = fileRepository.createUploadId(uploadedFile, sha256sum, size, currentUserId);
-
-            String fileId = fileMap.getFileId();
-            String uploadId = fileMap.getUploadId();
-            PathUrl pathUrl = fileUrlService.genPathUrl(contentType, sha256sum, size, fileId, suffix, uploadId);
-            String absolutePath = pathUrl.getAbsolutePath();
-            FileContentType fileType = fileTypeService.getFileType(contentType, absolutePath);
-            try {
-                fileStoreService.saveFile(absolutePath, bytes);
-                LocalStores.getStoreDir(absolutePath).incrCount();
-            } catch (Exception e) {
-                LocalStores.getStoreDir(absolutePath).decrCount();
-                throw e;
-            }
-
-            fileRepository.saveFile(fileId, fileType, pathUrl);
+            fileRepository.saveFile(fileId, fileContentType, pathUrl);
             String url = pathUrl.getUrl();
             return new UploadFileRet(uploadId, url);
         }
@@ -191,46 +132,50 @@ public class FileUploadService {
      * @date 2021-12-08 下午3:31
      */
     @Transactional(rollbackFor = Exception.class)
-    public synchronized UploadFilePartRet putFilePart(UploadFilePart uploadFilePart) throws Exception {
-        int partIndex = uploadFilePart.getSplitIndex();
-        int partNum = uploadFilePart.getSplitNum();
+    public synchronized FilePartRet putFilePart(UploadFilePart uploadFilePart) throws Exception {
+        int partIndex = uploadFilePart.getChunkNumber();
+        int partNum = uploadFilePart.getTotalChunks();
         MultipartFile multipartFile = uploadFilePart.getFile();
-        String uploadId = uploadFilePart.getUploadId();
+        String uploadId = uploadFilePart.getIdentifier();
         FileInfo fileInfo = fileRepository.getFileInfoByFileId(uploadId);
         // 没有检查文件的 sha256sum, 存在安全隐患
         if (fileInfo.getUploaded()) {
-            return new UploadFilePartRet(uploadId, true);
+            return new FilePartRet(uploadId, null);
         }
 
         String fileId = fileInfo.getFileId();
         String sha256sum = fileInfo.getSha256sum();
         String suffix = fileInfo.getSuffix();
-        @Deprecated
-        PathUrl pathUrl = fileUrlService.genPathAndUrl(sha256sum, 0, fileId, suffix, null);
+        String contentType = fileInfo.getContentType();
+        long size = fileInfo.getSize();
+        PathUrl pathUrl = fileUrlService.genPathUrl(contentType, sha256sum, size, fileId, suffix, uploadId);
         Set<Integer> set = map.computeIfAbsent(fileId, k -> new HashSet<>());
+        if (set.isEmpty()) {
+            fileStoreService.createSparseFile(pathUrl.getAbsolutePath(), size);
+        }
+
         if (!set.contains(partIndex)) {
             set.add(partIndex);
-            long size = fileInfo.getSize();
-            File file = fileStoreService.createFile(pathUrl.getAbsolutePath(), size);
-            long pos = partIndex * PART_SIZE;
-            fileStoreService.writeToFile(multipartFile.getInputStream(), file, pos);
+            long pos = (partIndex-1) * PART_SIZE;
+            String absolutePath = pathUrl.getAbsolutePath();
+            fileStoreService.writeToFile(multipartFile.getInputStream(), absolutePath, pos);
         }
 
         long len = set.size();
         if (len != partNum) {
-            return new UploadFilePartRet(fileId, false);
+            return new FilePartRet(fileId);
         } else {
-            String path = pathUrl.getAbsolutePath();
-            FileInputStream fis = new FileInputStream(path);
+            String absolutePath = pathUrl.getAbsolutePath();
+            FileInputStream fis = new FileInputStream(absolutePath);
             String sha256sumMerged = DigestUtil.sha256sum(fis);
             if (!sha256sum.equals(sha256sumMerged)) {
-                throw new Exception("uploadId 和 sha256sum 不匹配!");
+                throw new Exception("分片合并文件的 sha256sum 与原文件一致!");
             }
 
-            FileContentType fileType = fileTypeService.getFileType(fileInfo.getContentType(), pathUrl.getAbsolutePath());
+            FileContentType fileType = fileTypeService.getFileType(fileInfo.getContentType(), absolutePath);
             fileRepository.saveFile(fileId, fileType, pathUrl);
             map.remove(fileId);
-            return new UploadFilePartRet(uploadId, true);
+            return new FilePartRet(uploadId, pathUrl);
         }
     }
 }

+ 1 - 1
src/main/java/cn/reghao/dfs/store/service/FileUrlService.java

@@ -44,7 +44,7 @@ public class FileUrlService {
         return genPathAndUrl(sha256sum, fileSize, fileId, suffix, path);
     }
 
-    public PathUrl genPathAndUrl(String sha256sum, long fileSize, String fileId, String suffix, String path)
+    private PathUrl genPathAndUrl(String sha256sum, long fileSize, String fileId, String suffix, String path)
             throws IOException, NoSuchAlgorithmException {
         //StoreDir storeDir = loadBalancer.getStoreDir(fileSize, sha256sum);
         StoreDir storeDir = loadBalancer.getStoreDir(fileSize);

+ 1 - 42
src/main/java/cn/reghao/dfs/store/service/media/ImageFileService.java

@@ -1,53 +1,12 @@
 package cn.reghao.dfs.store.service.media;
 
-import cn.reghao.dfs.store.util.media.ImageOps;
-import cn.reghao.dfs.store.model.dto.PathUrl;
-import cn.reghao.dfs.store.db.mapper.ImageFileMapper;
-import cn.reghao.dfs.store.model.dto.FileDto;
-import cn.reghao.dfs.store.model.po.ImageFile;
-import cn.reghao.dfs.store.model.dto.ImgFileRetDto;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
-
-import java.io.File;
-import java.io.InputStream;
 
 /**
  * @author reghao
  * @date 2021-08-04 17:29:47
  */
 @Slf4j
-@Service
+//@Service
 public class ImageFileService {
-    private final ImageFileMapper imageFileMapper;
-
-    public ImageFileService(ImageFileMapper imageFileMapper) {
-        this.imageFileMapper = imageFileMapper;
-    }
-
-    public ImgFileRetDto process(String fileId, PathUrl pathUrl) throws Exception {
-        String path = pathUrl.getAbsolutePath();
-        String url = pathUrl.getUrl();
-        File imgLocalFile = new File(path);
-
-        ImageOps.Size size1 = ImageOps.info(imgLocalFile);
-        ImageFile imageFile = new ImageFile(fileId, url, size1.getWidth(), size1.getHeight());
-        imageFileMapper.save(imageFile);
-        return new ImgFileRetDto(imageFile.getFileId(), imageFile.getBaseUrl());
-    }
-
-    @Transactional(rollbackFor = Exception.class)
-    public ImgFileRetDto put(String sha256sum, InputStream in) throws Exception {
-        String filename = "default.jpg";
-        //FileDto fileDto = fileUploadService.put(sha256sum, in, filename, "image/jpeg");
-        FileDto fileDto = new FileDto();
-        String fileId = fileDto.getFileId();
-        String url = fileDto.getUrl();
-
-        ImageOps.Size size1 = ImageOps.info(new File(fileDto.getFilePath()));
-        ImageFile imageFile = new ImageFile(fileId, url, size1.getWidth(), size1.getHeight());
-        imageFileMapper.save(imageFile);
-        return new ImgFileRetDto(imageFile.getFileId(), imageFile.getBaseUrl());
-    }
 }

+ 28 - 24
src/main/java/cn/reghao/dfs/store/service/media/VideoFileService.java

@@ -1,16 +1,16 @@
 package cn.reghao.dfs.store.service.media;
 
 import cn.reghao.dfs.store.model.constant.VideoUrlType;
-import cn.reghao.dfs.store.model.dto.ImgFileRetDto;
+import cn.reghao.dfs.store.model.dto.UploadedFile;
+import cn.reghao.dfs.store.model.vo.UploadFileRet;
+import cn.reghao.dfs.store.service.FileUploadService;
 import cn.reghao.dfs.store.util.media.VideoOps;
-import cn.reghao.jutil.jdk.security.DigestUtil;
 import cn.reghao.dfs.store.model.dto.PathUrl;
 import cn.reghao.dfs.store.db.mapper.VideoFileMapper;
 import cn.reghao.dfs.store.model.po.VideoFile;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
-import org.springframework.web.multipart.MultipartFile;
 
 import java.io.*;
 import java.util.Map;
@@ -22,12 +22,12 @@ import java.util.Map;
 @Slf4j
 @Service
 public class VideoFileService {
-    private final ImageFileService imageFileService;
     private final VideoFileMapper videoFileMapper;
+    private final FileUploadService fileUploadService;
 
-    public VideoFileService(ImageFileService imageFileService, VideoFileMapper videoFileMapper) {
-        this.imageFileService = imageFileService;
+    public VideoFileService(VideoFileMapper videoFileMapper, FileUploadService fileUploadService) {
         this.videoFileMapper = videoFileMapper;
+        this.fileUploadService = fileUploadService;
     }
 
     /**
@@ -56,20 +56,6 @@ public class VideoFileService {
         return videoFile;
     }
 
-    @Transactional(rollbackFor = Exception.class)
-    public String setVideoCover(String fileId, MultipartFile multipartFile) throws Exception {
-        VideoFile videoFile = videoFileMapper.findByFileId(fileId);
-        if (videoFile == null) {
-            return null;
-        }
-
-        ImgFileRetDto imgFileRetDto = imageFileService.put(null, multipartFile.getInputStream());
-        String url = imgFileRetDto.getUrl();
-        videoFile.setBaseCoverUrl(url);
-        videoFileMapper.updateSetCover(fileId, url);
-        return url;
-    }
-
     /**
      * 设置视频属性
      *
@@ -96,7 +82,7 @@ public class VideoFileService {
     }
 
     /**
-     * 设置视频缩略图
+     * 将视频缩略图设置为视频封面
      *
      * @param
      * @return
@@ -107,8 +93,26 @@ public class VideoFileService {
         byte[] bytes = outputStream.toByteArray();
         outputStream.close();
 
-        String sha256sum = DigestUtil.sha256sum(bytes);
-        ImgFileRetDto imgFileRetDto = imageFileService.put(sha256sum, new ByteArrayInputStream(bytes));
-        videoFile.setBaseCoverUrl(imgFileRetDto.getUrl());
+        String filename = "default.jpg";
+        long size = bytes.length;
+        String contentType = "image/jpeg";
+        InputStream in = new ByteArrayInputStream(bytes);
+        UploadedFile uploadedFile = new UploadedFile(filename, size, contentType, in);
+        UploadFileRet uploadFileRet = fileUploadService.put(uploadedFile);
+        videoFile.setBaseCoverUrl(uploadFileRet.getUrl());
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    public String setVideoCover(String fileId, UploadedFile uploadedFile) throws Exception {
+        VideoFile videoFile = videoFileMapper.findByFileId(fileId);
+        if (videoFile == null) {
+            return null;
+        }
+
+        UploadFileRet uploadFileRet = fileUploadService.put(uploadedFile);
+        String url = uploadFileRet.getUrl();
+        videoFile.setBaseCoverUrl(url);
+        videoFileMapper.updateSetCover(fileId, url);
+        return url;
     }
 }

+ 1 - 1
src/main/resources/application-dev.yml

@@ -1,6 +1,6 @@
 spring:
   datasource:
-    url: jdbc:mysql://localhost:3306/reghao_tnb_rdb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2b8
+    url: jdbc:mysql://localhost:3306/reghao_vid_rdb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2b8
     username: dev
     password: Dev@123456
 dfs:

+ 1 - 1
src/main/resources/application-dev1.yml

@@ -1,6 +1,6 @@
 spring:
   datasource:
-    url: jdbc:mysql://localhost:3306/reghao_tnb_rdb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2b8
+    url: jdbc:mysql://localhost:3306/reghao_vid_rdb?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2b8
     username: dev
     password: Dev@123456
 dfs:

+ 2 - 2
src/test/java/ConsistentCheckTest.java

@@ -3,7 +3,7 @@ import cn.reghao.dfs.store.db.mapper.FileUrlMapper;
 import cn.reghao.dfs.store.db.mapper.FileUserMapper;
 import cn.reghao.dfs.store.db.mapper.VideoFileMapper;
 import cn.reghao.jutil.jdk.security.DigestUtil;
-import cn.reghao.dfs.store.StoreApplication;
+import cn.reghao.dfs.store.DfsStoreApplication;
 import cn.reghao.dfs.store.model.po.FileInfo;
 import cn.reghao.dfs.store.model.po.FileUrl;
 import cn.reghao.dfs.store.util.store.LocalStores;
@@ -29,7 +29,7 @@ import java.util.*;
  */
 @Slf4j
 @ActiveProfiles("dev")
-@SpringBootTest(classes = StoreApplication.class)
+@SpringBootTest(classes = DfsStoreApplication.class)
 @RunWith(SpringRunner.class)
 public class ConsistentCheckTest {
     @Autowired