diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index 211e95c..8d74a93 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -129,6 +129,8 @@ security: - /**/*.html - /**/*.css - /**/*.js + - # 配置白名单 + - /official/slideshow/** # 公共路径 - /favicon.ico - /error diff --git a/ruoyi-system/src/main/java/com/ruoyi/official/controller/FileChunkFilelistController.java b/ruoyi-system/src/main/java/com/ruoyi/official/controller/FileChunkFilelistController.java new file mode 100644 index 0000000..31ed0a1 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/official/controller/FileChunkFilelistController.java @@ -0,0 +1,73 @@ +package com.ruoyi.official.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.PageQuery; +import com.ruoyi.common.core.domain.R; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.official.domain.bo.FileChunkFilelistBo; +import com.ruoyi.official.domain.bo.FileChunkParamBo; +import com.ruoyi.official.domain.vo.CheckSysChunkVo; +import com.ruoyi.official.domain.vo.FileChunkFilelistVo; +import com.ruoyi.official.service.IFileChunkFilelistService; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletResponse; + +/** + * 已上传文件记录 + * + * @author ruoyi + * @date 2024-07-10 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/official/chunkFilelist") +public class FileChunkFilelistController extends BaseController { + + private final IFileChunkFilelistService iFileChunkFilelistService; + + /** + * 查询已上传文件记录列表 + */ + @SaCheckPermission("official:chunkFilelist:list") + @GetMapping("/list") + public TableDataInfo list(FileChunkFilelistBo bo, PageQuery pageQuery) { + return iFileChunkFilelistService.queryPageList(bo, pageQuery); + } + + /** + * 上传文件 + * @param fileChunkParam 文件分片实体对象 + * @param response 响应 + * @return 是否上传成功 + */ + @PostMapping("/upload") + public R postFileUpload(@ModelAttribute FileChunkParamBo fileChunkParam, HttpServletResponse response) { + return toAjax(iFileChunkFilelistService.postFileUpload(fileChunkParam, response)); + } + + + /** + * 检查文件上传状态 + */ + @GetMapping("/upload") + public R getFileUpload(@ModelAttribute FileChunkParamBo sysChunk, HttpServletResponse response) { + //查询根据md5查询文件是否存在 + return R.ok(iFileChunkFilelistService.getFileUpload(sysChunk, response)); + } + + /** + * 合并请求 + * @param filelistBo 已上传文件实体 + * @return 合并是否成功 + */ + @PostMapping("/merge") + public R merge(FileChunkFilelistBo filelistBo) { + return R.ok("操作成功", iFileChunkFilelistService.mergeFile(filelistBo)); + } + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/official/controller/FileChunkParamController.java b/ruoyi-system/src/main/java/com/ruoyi/official/controller/FileChunkParamController.java new file mode 100644 index 0000000..4092681 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/official/controller/FileChunkParamController.java @@ -0,0 +1,108 @@ +package com.ruoyi.official.controller; + +import java.util.List; +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import lombok.RequiredArgsConstructor; +import javax.servlet.http.HttpServletResponse; +import javax.validation.constraints.*; +import cn.dev33.satoken.annotation.SaCheckPermission; +import org.springframework.web.bind.annotation.*; +import org.springframework.validation.annotation.Validated; +import com.ruoyi.common.annotation.RepeatSubmit; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.PageQuery; +import com.ruoyi.common.core.domain.R; +import com.ruoyi.common.core.validate.AddGroup; +import com.ruoyi.common.core.validate.EditGroup; +import com.ruoyi.common.core.validate.QueryGroup; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.official.domain.vo.FileChunkParamVo; +import com.ruoyi.official.domain.bo.FileChunkParamBo; +import com.ruoyi.official.service.IFileChunkParamService; +import com.ruoyi.common.core.page.TableDataInfo; + +/** + * 文件分片记录 + * + * @author ruoyi + * @date 2024-07-10 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/official/chunkParam") +public class FileChunkParamController extends BaseController { + + private final IFileChunkParamService iFileChunkParamService; + + /** + * 查询文件分片记录列表 + */ + @SaCheckPermission("official:chunkParam:list") + @GetMapping("/list") + public TableDataInfo list(FileChunkParamBo bo, PageQuery pageQuery) { + return iFileChunkParamService.queryPageList(bo, pageQuery); + } + + /** + * 导出文件分片记录列表 + */ + @SaCheckPermission("official:chunkParam:export") + @Log(title = "文件分片记录", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(FileChunkParamBo bo, HttpServletResponse response) { + List list = iFileChunkParamService.queryList(bo); + ExcelUtil.exportExcel(list, "文件分片记录", FileChunkParamVo.class, response); + } + + /** + * 获取文件分片记录详细信息 + * + * @param id 主键 + */ + @SaCheckPermission("official:chunkParam:query") + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long id) { + return R.ok(iFileChunkParamService.queryById(id)); + } + + /** + * 新增文件分片记录 + */ + @SaCheckPermission("official:chunkParam:add") + @Log(title = "文件分片记录", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody FileChunkParamBo bo) { + return toAjax(iFileChunkParamService.insertByBo(bo)); + } + + /** + * 修改文件分片记录 + */ + @SaCheckPermission("official:chunkParam:edit") + @Log(title = "文件分片记录", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody FileChunkParamBo bo) { + return toAjax(iFileChunkParamService.updateByBo(bo)); + } + + /** + * 删除文件分片记录 + * + * @param ids 主键串 + */ + @SaCheckPermission("official:chunkParam:remove") + @Log(title = "文件分片记录", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] ids) { + return toAjax(iFileChunkParamService.deleteWithValidByIds(Arrays.asList(ids), true)); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/official/domain/FileChunkFilelist.java b/ruoyi-system/src/main/java/com/ruoyi/official/domain/FileChunkFilelist.java new file mode 100644 index 0000000..ae4e48e --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/official/domain/FileChunkFilelist.java @@ -0,0 +1,59 @@ +package com.ruoyi.official.domain; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; +import java.io.Serializable; +import java.util.Date; +import java.math.BigDecimal; + +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 已上传文件记录对象 file_chunk_filelist + * + * @author ruoyi + * @date 2024-07-10 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("file_chunk_filelist") +public class FileChunkFilelist extends BaseEntity { + + private static final long serialVersionUID=1L; + + /** + * 主键 + */ + @TableId(value = "id") + private Long id; + /** + * 链接 + */ + private String url; + /** + * 本地地址 + */ + private String locationAddress; + /** + * 文件标识,MD5 + */ + private String identifier; + /** + * 文件名 + */ + private String filename; + /** + * 相对路径 + */ + private String relativePath; + /** + * 创建者id + */ + private Long createUserId; + /** + * 更新者id + */ + private Long updateUserId; + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/official/domain/FileChunkParam.java b/ruoyi-system/src/main/java/com/ruoyi/official/domain/FileChunkParam.java new file mode 100644 index 0000000..03935b8 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/official/domain/FileChunkParam.java @@ -0,0 +1,78 @@ +package com.ruoyi.official.domain; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; +import java.io.Serializable; +import java.util.Date; +import java.math.BigDecimal; + +import com.ruoyi.common.core.domain.BaseEntity; +import org.springframework.web.multipart.MultipartFile; + +/** + * 文件分片记录对象 file_chunk_param + * + * @author ruoyi + * @date 2024-07-10 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("file_chunk_param") +public class FileChunkParam extends BaseEntity { + + private static final long serialVersionUID=1L; + + /** + * 主键 + */ + @TableId(value = "id") + private Long id; + /** + * 文件块编号 + */ + private Long chunkNumber; + /** + * 分块大小 + */ + private Long chunkSize; + /** + * 当前分块大小 + */ + private Long currentChunkSize; + /** + * 总块数 + */ + private Long totalChunks; + /** + * 总大小 + */ + private Long totalSize; + /** + * 文件标识,MD5 + */ + private String identifier; + /** + * 文件名 + */ + private String fileName; + /** + * 相对路径 + */ + private String relativePath; + /** + * 创建者id + */ + private Long createUserId; + /** + * 更新者id + */ + private Long updateUserId; + /** + * 二进制文件 + */ + @TableField(exist = false) + private MultipartFile file; + + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/official/domain/bo/FileChunkFilelistBo.java b/ruoyi-system/src/main/java/com/ruoyi/official/domain/bo/FileChunkFilelistBo.java new file mode 100644 index 0000000..8df0e06 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/official/domain/bo/FileChunkFilelistBo.java @@ -0,0 +1,73 @@ +package com.ruoyi.official.domain.bo; + +import com.ruoyi.common.core.validate.AddGroup; +import com.ruoyi.common.core.validate.EditGroup; +import lombok.Data; +import lombok.EqualsAndHashCode; +import javax.validation.constraints.*; + +import java.util.Date; + +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 已上传文件记录业务对象 file_chunk_filelist + * + * @author ruoyi + * @date 2024-07-10 + */ + +@Data +@EqualsAndHashCode(callSuper = true) +public class FileChunkFilelistBo extends BaseEntity { + + /** + * 主键 + */ + @NotNull(message = "主键不能为空", groups = { EditGroup.class }) + private Long id; + + /** + * 链接 + */ + @NotBlank(message = "链接不能为空", groups = { AddGroup.class, EditGroup.class }) + private String url; + + /** + * 本地地址 + */ + @NotBlank(message = "本地地址不能为空", groups = { AddGroup.class, EditGroup.class }) + private String locationAddress; + + /** + * 文件标识,MD5 + */ + @NotBlank(message = "文件标识,MD5不能为空", groups = { AddGroup.class, EditGroup.class }) + private String identifier; + + /** + * 文件名 + */ + @NotBlank(message = "文件名不能为空", groups = { AddGroup.class, EditGroup.class }) + private String filename; + + /** + * 相对路径 + */ + @NotBlank(message = "相对路径不能为空", groups = { AddGroup.class, EditGroup.class }) + private String relativePath; + + /** + * 创建者id + */ + @NotNull(message = "创建者id不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long createUserId; + + /** + * 更新者id + */ + @NotNull(message = "更新者id不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long updateUserId; + + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/official/domain/bo/FileChunkParamBo.java b/ruoyi-system/src/main/java/com/ruoyi/official/domain/bo/FileChunkParamBo.java new file mode 100644 index 0000000..ade1651 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/official/domain/bo/FileChunkParamBo.java @@ -0,0 +1,81 @@ +package com.ruoyi.official.domain.bo; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.ruoyi.common.core.validate.AddGroup; +import com.ruoyi.common.core.validate.EditGroup; +import lombok.Data; +import lombok.EqualsAndHashCode; +import javax.validation.constraints.*; + +import java.util.Date; + +import com.ruoyi.common.core.domain.BaseEntity; +import org.springframework.web.multipart.MultipartFile; + +/** + * 文件分片记录业务对象 file_chunk_param + * + * @author ruoyi + * @date 2024-07-10 + */ + +@Data +@EqualsAndHashCode(callSuper = true) +public class FileChunkParamBo extends BaseEntity { + + /** + * 主键 + */ + private Long id; + /** + * 文件块编号 + */ + + private Long chunkNumber; + /** + * 分块大小 + */ + + private Long chunkSize; + /** + * 当前分块大小 + */ + + private Long currentChunkSize; + /** + * 总块数 + */ + + private Long totalChunks; + + /** + * 总大小 + */ + + private Long totalSize; + + /** + * 文件标识,MD5 + */ + + private String identifier; + + /** + * 文件名 + */ + + private String fileName; + + /** + * 相对路径 + */ + + private String relativePath; + + /** + * 二进制文件 + */ + private MultipartFile file; + + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/official/domain/vo/CheckSysChunkVo.java b/ruoyi-system/src/main/java/com/ruoyi/official/domain/vo/CheckSysChunkVo.java new file mode 100644 index 0000000..c84067b --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/official/domain/vo/CheckSysChunkVo.java @@ -0,0 +1,23 @@ +package com.ruoyi.official.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import lombok.Data; + +import java.util.List; + +/** + * @author: lx + * @since: 2024/7/10 11:12 + * @description: + */ +@Data +public class CheckSysChunkVo { + + private boolean skipUpload; + + private List uploaded; + + + + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/official/domain/vo/FileChunkFilelistVo.java b/ruoyi-system/src/main/java/com/ruoyi/official/domain/vo/FileChunkFilelistVo.java new file mode 100644 index 0000000..3b39d82 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/official/domain/vo/FileChunkFilelistVo.java @@ -0,0 +1,73 @@ +package com.ruoyi.official.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import com.ruoyi.common.annotation.ExcelDictFormat; +import com.ruoyi.common.convert.ExcelDictConvert; +import lombok.Data; +import java.util.Date; + + + +/** + * 已上传文件记录视图对象 file_chunk_filelist + * + * @author ruoyi + * @date 2024-07-10 + */ +@Data +@ExcelIgnoreUnannotated +public class FileChunkFilelistVo { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @ExcelProperty(value = "主键") + private Long id; + + /** + * 链接 + */ + @ExcelProperty(value = "链接") + private String url; + + /** + * 本地地址 + */ + @ExcelProperty(value = "本地地址") + private String locationAddress; + + /** + * 文件标识,MD5 + */ + @ExcelProperty(value = "文件标识,MD5") + private String identifier; + + /** + * 文件名 + */ + @ExcelProperty(value = "文件名") + private String filename; + + /** + * 相对路径 + */ + @ExcelProperty(value = "相对路径") + private String relativePath; + + /** + * 创建者id + */ + @ExcelProperty(value = "创建者id") + private Long createUserId; + + /** + * 更新者id + */ + @ExcelProperty(value = "更新者id") + private Long updateUserId; + + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/official/domain/vo/FileChunkParamVo.java b/ruoyi-system/src/main/java/com/ruoyi/official/domain/vo/FileChunkParamVo.java new file mode 100644 index 0000000..a8da3aa --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/official/domain/vo/FileChunkParamVo.java @@ -0,0 +1,91 @@ +package com.ruoyi.official.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import com.ruoyi.common.annotation.ExcelDictFormat; +import com.ruoyi.common.convert.ExcelDictConvert; +import lombok.Data; +import java.util.Date; + + + +/** + * 文件分片记录视图对象 file_chunk_param + * + * @author ruoyi + * @date 2024-07-10 + */ +@Data +@ExcelIgnoreUnannotated +public class FileChunkParamVo { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @ExcelProperty(value = "主键") + private Long id; + + /** + * 文件块编号 + */ + @ExcelProperty(value = "文件块编号") + private Long chunkNumber; + + /** + * 分块大小 + */ + @ExcelProperty(value = "分块大小") + private Long chunkSize; + + /** + * 当前分块大小 + */ + @ExcelProperty(value = "当前分块大小") + private Long currentChunkSize; + + /** + * 总块数 + */ + @ExcelProperty(value = "总块数") + private Long totalChunks; + + /** + * 总大小 + */ + @ExcelProperty(value = "总大小") + private Long totalSize; + + /** + * 文件标识,MD5 + */ + @ExcelProperty(value = "文件标识,MD5") + private String identifier; + + /** + * 文件名 + */ + @ExcelProperty(value = "文件名") + private String fileName; + + /** + * 相对路径 + */ + @ExcelProperty(value = "相对路径") + private String relativePath; + + /** + * 创建者id + */ + @ExcelProperty(value = "创建者id") + private Long createUserId; + + /** + * 更新者id + */ + @ExcelProperty(value = "更新者id") + private Long updateUserId; + + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/official/mapper/FileChunkFilelistMapper.java b/ruoyi-system/src/main/java/com/ruoyi/official/mapper/FileChunkFilelistMapper.java new file mode 100644 index 0000000..276186b --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/official/mapper/FileChunkFilelistMapper.java @@ -0,0 +1,15 @@ +package com.ruoyi.official.mapper; + +import com.ruoyi.official.domain.FileChunkFilelist; +import com.ruoyi.official.domain.vo.FileChunkFilelistVo; +import com.ruoyi.common.core.mapper.BaseMapperPlus; + +/** + * 已上传文件记录Mapper接口 + * + * @author ruoyi + * @date 2024-07-10 + */ +public interface FileChunkFilelistMapper extends BaseMapperPlus { + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/official/mapper/FileChunkParamMapper.java b/ruoyi-system/src/main/java/com/ruoyi/official/mapper/FileChunkParamMapper.java new file mode 100644 index 0000000..6abe540 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/official/mapper/FileChunkParamMapper.java @@ -0,0 +1,15 @@ +package com.ruoyi.official.mapper; + +import com.ruoyi.official.domain.FileChunkParam; +import com.ruoyi.official.domain.vo.FileChunkParamVo; +import com.ruoyi.common.core.mapper.BaseMapperPlus; + +/** + * 文件分片记录Mapper接口 + * + * @author ruoyi + * @date 2024-07-10 + */ +public interface FileChunkParamMapper extends BaseMapperPlus { + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/official/service/IFileChunkFilelistService.java b/ruoyi-system/src/main/java/com/ruoyi/official/service/IFileChunkFilelistService.java new file mode 100644 index 0000000..7464b1d --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/official/service/IFileChunkFilelistService.java @@ -0,0 +1,60 @@ +package com.ruoyi.official.service; + +import com.ruoyi.official.domain.FileChunkFilelist; +import com.ruoyi.official.domain.FileChunkParam; +import com.ruoyi.official.domain.bo.FileChunkParamBo; +import com.ruoyi.official.domain.vo.CheckSysChunkVo; +import com.ruoyi.official.domain.vo.FileChunkFilelistVo; +import com.ruoyi.official.domain.bo.FileChunkFilelistBo; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.core.domain.PageQuery; + +import javax.servlet.http.HttpServletResponse; +import java.util.Collection; +import java.util.List; + +/** + * 已上传文件记录Service接口 + * + * @author ruoyi + * @date 2024-07-10 + */ +public interface IFileChunkFilelistService { + + /** + * 查询已上传文件记录 + */ + FileChunkFilelistVo queryById(Long id); + + /** + * 查询已上传文件记录列表 + */ + TableDataInfo queryPageList(FileChunkFilelistBo bo, PageQuery pageQuery); + + /** + * 查询已上传文件记录列表 + */ + List queryList(FileChunkFilelistBo bo); + + /** + * 新增已上传文件记录 + */ + Boolean insertByBo(FileChunkFilelistBo bo); + + /** + * 修改已上传文件记录 + */ + Boolean updateByBo(FileChunkFilelistBo bo); + + /** + * 校验并批量删除已上传文件记录信息 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); + + + boolean postFileUpload(FileChunkParamBo fileChunkParam, HttpServletResponse response); + + CheckSysChunkVo getFileUpload(FileChunkParamBo fileChunkParam, HttpServletResponse response); + + String mergeFile(FileChunkFilelistBo FileChunkFilelistBo); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/official/service/IFileChunkParamService.java b/ruoyi-system/src/main/java/com/ruoyi/official/service/IFileChunkParamService.java new file mode 100644 index 0000000..247a057 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/official/service/IFileChunkParamService.java @@ -0,0 +1,52 @@ +package com.ruoyi.official.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.ruoyi.official.domain.FileChunkParam; +import com.ruoyi.official.domain.vo.FileChunkParamVo; +import com.ruoyi.official.domain.bo.FileChunkParamBo; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.core.domain.PageQuery; + +import java.util.Collection; +import java.util.List; + +/** + * 文件分片记录Service接口 + * + * @author ruoyi + * @date 2024-07-10 + */ +public interface IFileChunkParamService { + + /** + * 查询文件分片记录 + */ + FileChunkParamVo queryById(Long id); + + /** + * 查询文件分片记录列表 + */ + TableDataInfo queryPageList(FileChunkParamBo bo, PageQuery pageQuery); + + /** + * 查询文件分片记录列表 + */ + List queryList(FileChunkParamBo bo); + + /** + * 新增文件分片记录 + */ + Boolean insertByBo(FileChunkParamBo bo); + + /** + * 修改文件分片记录 + */ + Boolean updateByBo(FileChunkParamBo bo); + + /** + * 校验并批量删除文件分片记录信息 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); + + void remove(LambdaQueryWrapper wrapper); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/official/service/impl/FileChunkFilelistServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/official/service/impl/FileChunkFilelistServiceImpl.java new file mode 100644 index 0000000..4ee16a0 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/official/service/impl/FileChunkFilelistServiceImpl.java @@ -0,0 +1,318 @@ +package com.ruoyi.official.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.core.domain.PageQuery; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.ruoyi.official.domain.FileChunkParam; +import com.ruoyi.official.domain.bo.FileChunkParamBo; +import com.ruoyi.official.domain.vo.CheckSysChunkVo; +import com.ruoyi.official.domain.vo.FileChunkParamVo; +import com.ruoyi.official.mapper.FileChunkParamMapper; +import com.ruoyi.official.service.IFileChunkParamService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import com.ruoyi.official.domain.bo.FileChunkFilelistBo; +import com.ruoyi.official.domain.vo.FileChunkFilelistVo; +import com.ruoyi.official.domain.FileChunkFilelist; +import com.ruoyi.official.mapper.FileChunkFilelistMapper; +import com.ruoyi.official.service.IFileChunkFilelistService; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Serializable; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.security.Security; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Collection; +import java.util.stream.Collectors; + +/** + * 已上传文件记录Service业务层处理 + * + * @author ruoyi + * @date 2024-07-10 + */ +@RequiredArgsConstructor +@Service +@Slf4j +public class FileChunkFilelistServiceImpl implements IFileChunkFilelistService { + + private final FileChunkFilelistMapper baseMapper; + private final FileChunkParamMapper fileChunkParamMapper; + private final IFileChunkParamService iFileChunkParamService; + + @Value("${ruoyi.profile}") + private String filePath; + private final static String folderPath = "/file"; + /** + * 查询已上传文件记录 + */ + @Override + public FileChunkFilelistVo queryById(Long id){ + return baseMapper.selectVoById(id); + } + + /** + * 查询已上传文件记录列表 + */ + @Override + public TableDataInfo queryPageList(FileChunkFilelistBo bo, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(result); + } + + /** + * 查询已上传文件记录列表 + */ + @Override + public List queryList(FileChunkFilelistBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + return baseMapper.selectVoList(lqw); + } + + private LambdaQueryWrapper buildQueryWrapper(FileChunkFilelistBo bo) { + Map params = bo.getParams(); + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(StringUtils.isNotBlank(bo.getUrl()), FileChunkFilelist::getUrl, bo.getUrl()); + lqw.eq(StringUtils.isNotBlank(bo.getLocationAddress()), FileChunkFilelist::getLocationAddress, bo.getLocationAddress()); + lqw.eq(StringUtils.isNotBlank(bo.getIdentifier()), FileChunkFilelist::getIdentifier, bo.getIdentifier()); + lqw.like(StringUtils.isNotBlank(bo.getFilename()), FileChunkFilelist::getFilename, bo.getFilename()); + lqw.eq(StringUtils.isNotBlank(bo.getRelativePath()), FileChunkFilelist::getRelativePath, bo.getRelativePath()); + lqw.eq(bo.getCreateUserId() != null, FileChunkFilelist::getCreateUserId, bo.getCreateUserId()); + lqw.eq(bo.getUpdateUserId() != null, FileChunkFilelist::getUpdateUserId, bo.getUpdateUserId()); + return lqw; + } + + /** + * 新增已上传文件记录 + */ + @Override + public Boolean insertByBo(FileChunkFilelistBo bo) { + FileChunkFilelist add = BeanUtil.toBean(bo, FileChunkFilelist.class); + validEntityBeforeSave(add); + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setId(add.getId()); + } + return flag; + } + + /** + * 修改已上传文件记录 + */ + @Override + public Boolean updateByBo(FileChunkFilelistBo bo) { + FileChunkFilelist update = BeanUtil.toBean(bo, FileChunkFilelist.class); + validEntityBeforeSave(update); + return baseMapper.updateById(update) > 0; + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(FileChunkFilelist entity){ + //TODO 做一些数据校验,如唯一约束 + } + + /** + * 批量删除已上传文件记录 + */ + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if(isValid){ + //TODO 做一些业务上的校验,判断是否需要校验 + } + return baseMapper.deleteBatchIds(ids) > 0; + } + + /** + * 每一个上传块都会包含如下分块信息: + * chunkNumber: 当前块的次序,第一个块是 1,注意不是从 0 开始的。 + * totalChunks: 文件被分成块的总数。 + * chunkSize: 分块大小,根据 totalSize 和这个值你就可以计算出总共的块数。注意最后一块的大小可能会比这个要大。 + * currentChunkSize: 当前块的大小,实际大小。 + * totalSize: 文件总大小。 + * identifier: 这个就是每个文件的唯一标示,md5码 + * fileName: 文件名。 + * relativePath: 文件夹上传的时候文件的相对路径属性。 + * 一个分块可以被上传多次,当然这肯定不是标准行为,,这种重传也但是在实际上传过程中是可能发生这种事情的是本库的特性之一。 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public boolean postFileUpload(FileChunkParamBo fileChunkParam, HttpServletResponse response) { + + MultipartFile file = fileChunkParam.getFile(); + log.debug("file originName: {}, chunkNumber: {}", file.getOriginalFilename(), fileChunkParam.getChunkNumber()); + Path path = Paths.get(generatePath(filePath + folderPath, fileChunkParam)); + try { + Files.write(path, fileChunkParam.getFile().getBytes()); + log.debug("文件 {} 写入成功, md5:{}", fileChunkParam.getFileName(), fileChunkParam.getIdentifier()); + return iFileChunkParamService.insertByBo(fileChunkParam); + //写入数据库 + } catch (IOException e) { + throw new RuntimeException("上传失败" + e.getMessage()); + } + } + + @Override + public CheckSysChunkVo getFileUpload(FileChunkParamBo fileChunkParam, HttpServletResponse response) { + // 检查该文件是否存在于fileList中吗,直接返回skipUpload为true,执行闪传 + //fileChunkParamMapper + String identifier = fileChunkParam.getIdentifier(); + + // 检查该文件是否存在于fileList中吗,直接返回skipUpload为true,执行闪传 + CheckSysChunkVo checkSysChunkVo = new CheckSysChunkVo(); + + // 先查询文件分片管理表和已上传文件记录表 + // List sysChunkList = fileChunkParamMapper.list(new LambdaQueryWrapper().eq(SysChunk::getIdentifier, identifier)); + List sysChunkList = fileChunkParamMapper.selectVoList(Wrappers.lambdaQuery().eq(FileChunkParam::getIdentifier, identifier)); + List sysFilelistList = baseMapper.selectVoList(Wrappers.lambdaQuery().eq(FileChunkFilelist::getIdentifier, identifier)); + + // 检查文件中是否存在于sysFilelistList中 + if (sysFilelistList != null && !sysFilelistList.isEmpty()) { + checkSysChunkVo.setSkipUpload(true); + return checkSysChunkVo; + } + + // 获取已存在的块的chunkNumber列表并返回给前端 + if (sysChunkList != null && !sysChunkList.isEmpty()) { + List uploadedChunks = sysChunkList.stream() + .map(FileChunkParamVo::getChunkNumber) + .collect(Collectors.toList()); + checkSysChunkVo.setUploaded(uploadedChunks); + } + + return checkSysChunkVo; + } + + /** + * 合并请求 + * @param sysFilelist 已上传文件实体 + * @return 合并是否成功 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public String mergeFile(FileChunkFilelistBo sysFilelist) { + // 获取文件的名称 + String fileName = sysFilelist.getFilename(); + String file = filePath + folderPath + "/" + sysFilelist.getIdentifier() + "/" + fileName; + String folder = filePath + folderPath + "/" + sysFilelist.getIdentifier(); + String url = folderPath + "/" + sysFilelist.getIdentifier() + "/" + fileName; + merge(file, folder, fileName); + + //当前文件已存在数据库中时,返回已存在标识 + Long selectCount = baseMapper.selectCount(new LambdaQueryWrapper() + .eq(FileChunkFilelist::getFilename, sysFilelist.getFilename()) + .eq(FileChunkFilelist::getIdentifier, sysFilelist.getIdentifier())); + + + if (selectCount > 0) { + return url; + } + + sysFilelist.setLocationAddress(file); + sysFilelist.setUrl(url); + sysFilelist.setCreateTime(new Date()); + Boolean flag = insertByBo(sysFilelist); + if (flag) { + + // 插入文件记录成功后,删除chunk表中的对应记录,释放空间 + + LambdaQueryWrapper chunkLambdaQueryWrapper = new LambdaQueryWrapper() + .eq(FileChunkParam::getFileName, sysFilelist.getFilename()) + .eq(FileChunkParam::getIdentifier, sysFilelist.getIdentifier()); + + iFileChunkParamService.remove(chunkLambdaQueryWrapper); + } + return url; + } + + /** + * 生成块文件所在地址 + */ + private String generatePath(String uploadFolder, FileChunkParamBo fileChunkParam) { + StringBuilder stringBuilder = new StringBuilder(); + // 文件夹地址md5 + stringBuilder.append(uploadFolder).append("/").append(fileChunkParam.getIdentifier()); + //判断uploadFolder/identifier 路径是否存在,不存在则创建 + if (!Files.isWritable(Paths.get(stringBuilder.toString()))) { + log.info("path not exist,create path: {}", stringBuilder.toString()); + try { + Files.createDirectories(Paths.get(stringBuilder.toString())); + } catch (IOException e) { + log.error("生成时出现问题" + e.getMessage(), e); + throw new RuntimeException("生成时出现问题" + e.getMessage()); + } + } + + //文件夹地址/md5/文件名-1 + return stringBuilder.append("/") + .append(fileChunkParam.getFileName()) + .append("-") + .append(fileChunkParam.getChunkNumber()).toString(); + } + + /** + * 文件合并 + * + * @param targetFile 要形成的文件名 + * @param folder 要形成的文件夹地址 + * @param fileName 文件的名称 + */ + public static void merge(String targetFile, String folder, String fileName) { + try { + // 创建目标文件 + Files.createFile(Paths.get(targetFile)); + + // 收集需要合并的文件路径并排序 + List filesToMerge = Files.list(Paths.get(folder)) + .filter(path -> !path.getFileName().toString().equals(fileName)) + .sorted((o1, o2) -> { + String p1 = o1.getFileName().toString(); + String p2 = o2.getFileName().toString(); + int i1 = p1.lastIndexOf("-"); + int i2 = p2.lastIndexOf("-"); + return Integer.valueOf(p2.substring(i2)).compareTo(Integer.valueOf(p1.substring(i1))); + }) + .collect(Collectors.toList()); + + // 注释掉的逻辑中 是以追加的形式写入文件 这样做在传送大文件时会出现合并失败的情况 + // 我的推断:可能是因为每次循环迭代中都会执行文件的读取和写入操作,这种方式对于大文件来说效率不高,并且可能会导致内存不足或者文件操作异常 + // 所以这里使用缓冲流逐个合并文件, 不一次性读取整个文件内容,而是使用缓冲区逐段读取和写入,以降低内存使用量 + try (OutputStream out = Files.newOutputStream(Paths.get(targetFile), StandardOpenOption.APPEND)) { + for (Path path : filesToMerge) { + try (InputStream in = Files.newInputStream(path)) { + byte[] buffer = new byte[8192]; // 8KB缓冲区 + int len; + while ((len = in.read(buffer)) > 0) { + out.write(buffer, 0, len); + } + } + // 合并后删除该块 + Files.delete(path); + } + } + + } catch (IOException e) { + log.error("合并出现错误:" + e.getMessage(), e); + throw new RuntimeException("合并出现错误," + e.getMessage()); + } + } + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/official/service/impl/FileChunkParamServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/official/service/impl/FileChunkParamServiceImpl.java new file mode 100644 index 0000000..81f4507 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/official/service/impl/FileChunkParamServiceImpl.java @@ -0,0 +1,121 @@ +package com.ruoyi.official.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.core.domain.PageQuery; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import com.ruoyi.official.domain.bo.FileChunkParamBo; +import com.ruoyi.official.domain.vo.FileChunkParamVo; +import com.ruoyi.official.domain.FileChunkParam; +import com.ruoyi.official.mapper.FileChunkParamMapper; +import com.ruoyi.official.service.IFileChunkParamService; + +import java.util.List; +import java.util.Map; +import java.util.Collection; + +/** + * 文件分片记录Service业务层处理 + * + * @author ruoyi + * @date 2024-07-10 + */ +@RequiredArgsConstructor +@Service +public class FileChunkParamServiceImpl implements IFileChunkParamService { + + private final FileChunkParamMapper baseMapper; + + /** + * 查询文件分片记录 + */ + @Override + public FileChunkParamVo queryById(Long id){ + return baseMapper.selectVoById(id); + } + + /** + * 查询文件分片记录列表 + */ + @Override + public TableDataInfo queryPageList(FileChunkParamBo bo, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(result); + } + + /** + * 查询文件分片记录列表 + */ + @Override + public List queryList(FileChunkParamBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + return baseMapper.selectVoList(lqw); + } + + private LambdaQueryWrapper buildQueryWrapper(FileChunkParamBo bo) { + Map params = bo.getParams(); + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(bo.getChunkNumber() != null, FileChunkParam::getChunkNumber, bo.getChunkNumber()); + lqw.eq(bo.getChunkSize() != null, FileChunkParam::getChunkSize, bo.getChunkSize()); + lqw.eq(bo.getCurrentChunkSize() != null, FileChunkParam::getCurrentChunkSize, bo.getCurrentChunkSize()); + lqw.eq(bo.getTotalChunks() != null, FileChunkParam::getTotalChunks, bo.getTotalChunks()); + lqw.eq(bo.getTotalSize() != null, FileChunkParam::getTotalSize, bo.getTotalSize()); + lqw.eq(StringUtils.isNotBlank(bo.getIdentifier()), FileChunkParam::getIdentifier, bo.getIdentifier()); + lqw.like(StringUtils.isNotBlank(bo.getFileName()), FileChunkParam::getFileName, bo.getFileName()); + lqw.eq(StringUtils.isNotBlank(bo.getRelativePath()), FileChunkParam::getRelativePath, bo.getRelativePath()); + return lqw; + } + + /** + * 新增文件分片记录 + */ + @Override + public Boolean insertByBo(FileChunkParamBo bo) { + FileChunkParam add = BeanUtil.toBean(bo, FileChunkParam.class); + validEntityBeforeSave(add); + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setId(add.getId()); + } + return flag; + } + + /** + * 修改文件分片记录 + */ + @Override + public Boolean updateByBo(FileChunkParamBo bo) { + FileChunkParam update = BeanUtil.toBean(bo, FileChunkParam.class); + validEntityBeforeSave(update); + return baseMapper.updateById(update) > 0; + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(FileChunkParam entity){ + //TODO 做一些数据校验,如唯一约束 + } + + /** + * 批量删除文件分片记录 + */ + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if(isValid){ + //TODO 做一些业务上的校验,判断是否需要校验 + } + return baseMapper.deleteBatchIds(ids) > 0; + } + + @Override + public void remove(LambdaQueryWrapper wrapper) { + baseMapper.delete(wrapper); + } +} diff --git a/ruoyi-system/src/main/resources/mapper/official/FileChunkFilelistMapper.xml b/ruoyi-system/src/main/resources/mapper/official/FileChunkFilelistMapper.xml new file mode 100644 index 0000000..ff6521f --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/official/FileChunkFilelistMapper.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/ruoyi-system/src/main/resources/mapper/official/FileChunkParamMapper.xml b/ruoyi-system/src/main/resources/mapper/official/FileChunkParamMapper.xml new file mode 100644 index 0000000..ed69a21 --- /dev/null +++ b/ruoyi-system/src/main/resources/mapper/official/FileChunkParamMapper.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + +