diff --git a/src/main/java/cn/lihongjie/coal/aop/ControllerAop.java b/src/main/java/cn/lihongjie/coal/aop/ControllerAop.java index 88ef0199..69970ded 100644 --- a/src/main/java/cn/lihongjie/coal/aop/ControllerAop.java +++ b/src/main/java/cn/lihongjie/coal/aop/ControllerAop.java @@ -1,6 +1,8 @@ package cn.lihongjie.coal.aop; import cn.lihongjie.coal.annotation.SysLog; +import cn.lihongjie.coal.base.dto.CommonDto; +import cn.lihongjie.coal.base.entity.CommonEntity; import cn.lihongjie.coal.common.RequestUtils; import cn.lihongjie.coal.ip.IpQueryService; import cn.lihongjie.coal.session.SessionService; @@ -137,20 +139,34 @@ public class ControllerAop { SysLog sysLog, Method method, Object[] args, HttpServletRequest request) { String message = sysLog.message(); if (StringUtils.isEmpty(sysLog.message())) { - message = "(id?:'') + ' ' + (name?:'') + ' ' + (code?:'')"; + if (args != null + && args.length == 1 + && args[0] != null + && (args[0] instanceof CommonDto || args[0] instanceof CommonEntity)) { + + message = "(id?:'') + ' ' + (name?:'') + ' ' + (code?:'')"; + } else { + + message = ""; + } } try { - MethodBasedEvaluationContext context = - new MethodBasedEvaluationContext( - ArrayUtils.isEmpty(args) ? null : args[0], - method, - args, - new DefaultParameterNameDiscoverer()); - context.setVariable("request", request); - Object value = parser.parseExpression(message).getValue(context); - return value + ""; + if (StringUtils.isNotBlank(message)) { + + MethodBasedEvaluationContext context = + new MethodBasedEvaluationContext( + ArrayUtils.isEmpty(args) ? null : args[0], + method, + args, + new DefaultParameterNameDiscoverer()); + context.setVariable("request", request); + Object value = parser.parseExpression(message).getValue(context); + return value + ""; + } else { + return ""; + } } catch (Exception e) { log.error("解析日志表达式失败: {}", sysLog.message(), e); return ""; diff --git a/src/main/java/cn/lihongjie/coal/base/mapper/CommonMapper.java b/src/main/java/cn/lihongjie/coal/base/mapper/CommonMapper.java index 3f8ccfef..95fd7f3f 100644 --- a/src/main/java/cn/lihongjie/coal/base/mapper/CommonMapper.java +++ b/src/main/java/cn/lihongjie/coal/base/mapper/CommonMapper.java @@ -11,6 +11,7 @@ import cn.lihongjie.coal.dictionary.entity.DictionaryItemEntity; import cn.lihongjie.coal.file.entity.FileEntity; import cn.lihongjie.coal.meter.entity.MeterEntity; import cn.lihongjie.coal.meterLog.entity.MeterLogEntity; +import cn.lihongjie.coal.netDisk.entity.NetDiskEntity; import cn.lihongjie.coal.noteBook.entity.NoteBookEntity; import cn.lihongjie.coal.noteBookChapter.entity.NoteBookChapterEntity; import cn.lihongjie.coal.organization.entity.OrganizationEntity; @@ -52,7 +53,14 @@ public interface CommonMapper { return null; } } + default Long toLong(String s) { + try { + return Long.valueOf(s); + } catch (Exception e) { + return null; + } + } default UserEntity createUser(String id) { if (StringUtils.isEmpty(id)) { @@ -244,6 +252,17 @@ public interface CommonMapper { return e; } + + default NetDiskEntity createNetDiskEntity(String id) { + + if (StringUtils.isEmpty(id)) { + + return null; + } + var e = new NetDiskEntity(); + e.setId(id); + return e; + } default NoteBookEntity createNoteBook(String id) { if (StringUtils.isEmpty(id)) { diff --git a/src/main/java/cn/lihongjie/coal/netDisk/controller/NetDiskController.java b/src/main/java/cn/lihongjie/coal/netDisk/controller/NetDiskController.java new file mode 100644 index 00000000..9695e0e7 --- /dev/null +++ b/src/main/java/cn/lihongjie/coal/netDisk/controller/NetDiskController.java @@ -0,0 +1,173 @@ +package cn.lihongjie.coal.netDisk.controller; + +import cn.lihongjie.coal.annotation.OrgScope; +import cn.lihongjie.coal.annotation.SysLog; +import cn.lihongjie.coal.base.dto.IdRequest; +import cn.lihongjie.coal.netDisk.dto.*; +import cn.lihongjie.coal.netDisk.service.NetDiskService; + +import jakarta.servlet.http.HttpServletResponse; + +import lombok.extern.slf4j.Slf4j; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +@RestController +@RequestMapping("/netDisk") +@SysLog(module = "网盘") +@Slf4j +@OrgScope +public class NetDiskController { + @Autowired private NetDiskService service; + + + + @PostMapping("/getById") + public NetDiskDto getById(@RequestBody IdRequest request) { + return this.service.getById(request.getId()); + } + + @PostMapping("/ls") + @SysLog(action = "ls", message = "id") + public List ls(@RequestBody IdRequest request) { + return this.service.ls(request); + } + + + @PostMapping("/createDir") + @SysLog(action = "createDir", message = "name") + public NetDiskDto createDir (@RequestBody CreateDirDto request) { + return this.service.createDir(request); + } + + + @PostMapping("/batchCreateDir") + @SysLog(action = "createDir", message = "names.join(',')") + public List batchCreateDir (@RequestBody BatchCreateDirDto request) { + return this.service.batchCreateDir(request); + } + + @PostMapping("/preCreateFile") + @SysLog(action = "preCreateFile", message = "name") + + public PreCreateFileDto preCreateFile (@RequestBody CreateFileDto request) { + return this.service.preCreateFile(request); + } + + + @PostMapping("/createFileSlice") + public NetDiskDto createFileSlice ( + @RequestParam("parent") String parent, + @RequestParam("size") Long size, + @RequestParam("sha256") String sha256, + @RequestParam("mineType") String mineType, + @RequestParam("multipartFile") MultipartFile multipartFile, + @RequestParam("ossKey") String ossKey, + @RequestParam("name") String name, + @RequestParam("code") String code, + @RequestParam("remarks") String remarks, + @RequestParam("sortKey") String sortKey + + + + ) { + CreateFileDto request = new CreateFileDto(); + request.setParent(parent); + request.setSize(size); + request.setSha256(sha256); + request.setMineType(mineType); + request.setMultipartFile(multipartFile); + request.setOssKey(ossKey); + request.setName(name); + request.setCode(code); + request.setRemarks(remarks); + try{ + + request.setSortKey(Integer.valueOf(sortKey)); + }catch (Exception e){ + + } + + return this.service.createFileSlice(request); + } + + @PostMapping("/createFile") + public NetDiskDto createFile ( + @RequestParam("parent") String parent, + @RequestParam("size") Long size, + @RequestParam("sha256") String sha256, + @RequestParam("mineType") String mineType, + @RequestParam("multipartFile") MultipartFile multipartFile, + @RequestParam("ossKey") String ossKey, + @RequestParam("name") String name, + @RequestParam("code") String code, + @RequestParam("remarks") String remarks, + @RequestParam("sortKey") String sortKey + + + + ) { + CreateFileDto request = new CreateFileDto(); + request.setParent(parent); + request.setSize(size); + request.setSha256(sha256); + request.setMineType(mineType); + request.setMultipartFile(multipartFile); + request.setOssKey(ossKey); + request.setName(name); + request.setCode(code); + request.setRemarks(remarks); + try{ + + request.setSortKey(Integer.valueOf(sortKey)); + }catch (Exception e){ + + } + + return this.service.createFile(request); + } + + @PostMapping("/move") + @SysLog(action = "move", message = "srcId + ' -> ' + targetId") + + public NetDiskDto move(@RequestBody MoveDto request) { + return this.service.move(request); + } + + + @PostMapping("/copy") + @SysLog(action = "copy", message = "srcId + ' -> ' + targetId") + public NetDiskDto copy (@RequestBody CopyDto request) { + return this.service.copy(request); + } + + @PostMapping("/remove") + @SysLog(action = "remove", message = "id") + public Object remove (@RequestBody IdRequest request) { + this.service.remove(request); + return null; + } + + @PostMapping("/downloadDir") + @SysLog(action = "downloadDir", message = "id") + public void remove (@RequestBody IdRequest request, HttpServletResponse response) { + + this.service.downloadDir(request, response); + } + + @GetMapping("/downloadFile") + + public ResponseEntity downloadFile(@RequestParam("id") String id, @RequestParam(value = "attachment", defaultValue = "false") Boolean attachment) { + + + return ResponseEntity.status(HttpStatus.TEMPORARY_REDIRECT).header("Location", this.service.downloadFile(id, attachment)).body(null); + } + + +} diff --git a/src/main/java/cn/lihongjie/coal/netDisk/dto/BatchCreateDirDto.java b/src/main/java/cn/lihongjie/coal/netDisk/dto/BatchCreateDirDto.java new file mode 100644 index 00000000..32eca90f --- /dev/null +++ b/src/main/java/cn/lihongjie/coal/netDisk/dto/BatchCreateDirDto.java @@ -0,0 +1,15 @@ +package cn.lihongjie.coal.netDisk.dto; + +import lombok.Data; + +import java.util.*; + +@Data +public class BatchCreateDirDto { + + private String parent; + + + private List names; + +} diff --git a/src/main/java/cn/lihongjie/coal/netDisk/dto/CopyDto.java b/src/main/java/cn/lihongjie/coal/netDisk/dto/CopyDto.java new file mode 100644 index 00000000..e02206bd --- /dev/null +++ b/src/main/java/cn/lihongjie/coal/netDisk/dto/CopyDto.java @@ -0,0 +1,14 @@ +package cn.lihongjie.coal.netDisk.dto; + +import lombok.Data; + +@Data +public class CopyDto { + + private String srcId; + private String newName; + private String targetId; + + + +} diff --git a/src/main/java/cn/lihongjie/coal/netDisk/dto/CreateDirDto.java b/src/main/java/cn/lihongjie/coal/netDisk/dto/CreateDirDto.java new file mode 100644 index 00000000..36a5b06e --- /dev/null +++ b/src/main/java/cn/lihongjie/coal/netDisk/dto/CreateDirDto.java @@ -0,0 +1,22 @@ +package cn.lihongjie.coal.netDisk.dto; + +import cn.lihongjie.coal.base.dto.OrgCommonDto; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.*; + +@Data +@NoArgsConstructor +public class CreateDirDto extends OrgCommonDto { + + private String parent; + + + public CreateDirDto(String parent, String name) { + super(); + this.parent = parent; + setName(name); + } +} diff --git a/src/main/java/cn/lihongjie/coal/netDisk/dto/CreateFileDto.java b/src/main/java/cn/lihongjie/coal/netDisk/dto/CreateFileDto.java new file mode 100644 index 00000000..d7da8e75 --- /dev/null +++ b/src/main/java/cn/lihongjie/coal/netDisk/dto/CreateFileDto.java @@ -0,0 +1,47 @@ +package cn.lihongjie.coal.netDisk.dto; + +import cn.lihongjie.coal.base.dto.OrgCommonDto; + +import lombok.Data; + +import org.hibernate.annotations.Comment; +import org.springframework.web.multipart.MultipartFile; + +import java.util.*; + +@Data +public class CreateFileDto extends OrgCommonDto { + + + private String parent; + + + @Comment("文件/文件夹 大小, 单位 byte") + private Long size; + + + @Comment("文件 sha256 值, 文件夹没有") + private String sha256; + + + @Comment("文件 mime 类型, 文件夹没有") + private String mineType; + + + private MultipartFile multipartFile; + + + private String ossKey; + + + @Comment("分片序号") + private Long sliceIndex; + + + @Comment("分片 sha256 值") + private String sliceSha256; + + + @Comment("分片文件大小") + private String sliceSize; +} diff --git a/src/main/java/cn/lihongjie/coal/netDisk/dto/CreateNetDiskDto.java b/src/main/java/cn/lihongjie/coal/netDisk/dto/CreateNetDiskDto.java new file mode 100644 index 00000000..38d444fa --- /dev/null +++ b/src/main/java/cn/lihongjie/coal/netDisk/dto/CreateNetDiskDto.java @@ -0,0 +1,8 @@ +package cn.lihongjie.coal.netDisk.dto; + +import cn.lihongjie.coal.base.dto.OrgCommonDto; + +import lombok.Data; + +@Data +public class CreateNetDiskDto extends OrgCommonDto {} diff --git a/src/main/java/cn/lihongjie/coal/netDisk/dto/MoveDto.java b/src/main/java/cn/lihongjie/coal/netDisk/dto/MoveDto.java new file mode 100644 index 00000000..bf577bff --- /dev/null +++ b/src/main/java/cn/lihongjie/coal/netDisk/dto/MoveDto.java @@ -0,0 +1,16 @@ +package cn.lihongjie.coal.netDisk.dto; + +import lombok.Data; + +import java.util.*; + +@Data +public class MoveDto { + + private String srcId; + private String newName; + private String targetId; + + + +} diff --git a/src/main/java/cn/lihongjie/coal/netDisk/dto/NetDiskDto.java b/src/main/java/cn/lihongjie/coal/netDisk/dto/NetDiskDto.java new file mode 100644 index 00000000..512db06d --- /dev/null +++ b/src/main/java/cn/lihongjie/coal/netDisk/dto/NetDiskDto.java @@ -0,0 +1,42 @@ +package cn.lihongjie.coal.netDisk.dto; + +import cn.lihongjie.coal.base.dto.OrgCommonDto; + +import lombok.Data; + +import org.hibernate.annotations.Comment; +import org.hibernate.annotations.Formula; + +@Data +public class NetDiskDto extends OrgCommonDto { + + + + @Comment("条目类型, 文件夹/目录") + private String entryType; + + @Formula( + "(select i.name\n" + + "from t_dictionary d,\n" + + " t_dictionary_item i\n" + + "where d.id = i.dictionary_id\n" + + " and d.code = 'netDisk.entryType'\n" + + " and i.code = entry_type)") + private String entryTypeName; + + + + + @Comment("文件/文件夹 大小, 单位 byte") + private Long size; + + private Long sizeHumanReadable; + + + @Comment("文件 sha256 值, 文件夹没有") + private String sha256; + + + @Comment("文件 mime 类型, 文件夹没有") + private String mineType; +} diff --git a/src/main/java/cn/lihongjie/coal/netDisk/dto/PreCreateFileDto.java b/src/main/java/cn/lihongjie/coal/netDisk/dto/PreCreateFileDto.java new file mode 100644 index 00000000..a0432f96 --- /dev/null +++ b/src/main/java/cn/lihongjie/coal/netDisk/dto/PreCreateFileDto.java @@ -0,0 +1,22 @@ +package cn.lihongjie.coal.netDisk.dto; + +import lombok.Data; + +import org.hibernate.annotations.Comment; + +import java.util.*; + +@Data +public class PreCreateFileDto { + + @Comment("如果这个存在, 就意味着文件已经上传过, 可以直接创建") + private String ossKey; + + @Comment("分片明细") + private List slices = new ArrayList<>(); + + @Comment("分片大小") + private Long sliceSize; + + +} diff --git a/src/main/java/cn/lihongjie/coal/netDisk/dto/RenameDto.java b/src/main/java/cn/lihongjie/coal/netDisk/dto/RenameDto.java new file mode 100644 index 00000000..bc5bc18f --- /dev/null +++ b/src/main/java/cn/lihongjie/coal/netDisk/dto/RenameDto.java @@ -0,0 +1,14 @@ +package cn.lihongjie.coal.netDisk.dto; + +import lombok.Data; + +import java.util.*; + +@Data public class RenameDto { + + private String id; + + private String name; + + +} diff --git a/src/main/java/cn/lihongjie/coal/netDisk/dto/UpdateNetDiskDto.java b/src/main/java/cn/lihongjie/coal/netDisk/dto/UpdateNetDiskDto.java new file mode 100644 index 00000000..10cb9b3c --- /dev/null +++ b/src/main/java/cn/lihongjie/coal/netDisk/dto/UpdateNetDiskDto.java @@ -0,0 +1,8 @@ +package cn.lihongjie.coal.netDisk.dto; + +import cn.lihongjie.coal.base.dto.OrgCommonDto; + +import lombok.Data; + +@Data +public class UpdateNetDiskDto extends OrgCommonDto {} diff --git a/src/main/java/cn/lihongjie/coal/netDisk/entity/NetDiskEntity.java b/src/main/java/cn/lihongjie/coal/netDisk/entity/NetDiskEntity.java new file mode 100644 index 00000000..cc0f33a9 --- /dev/null +++ b/src/main/java/cn/lihongjie/coal/netDisk/entity/NetDiskEntity.java @@ -0,0 +1,81 @@ +package cn.lihongjie.coal.netDisk.entity; + +import cn.lihongjie.coal.base.entity.OrgCommonEntity; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Entity; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; + +import lombok.Data; + +import org.hibernate.annotations.Comment; +import org.hibernate.annotations.Formula; + +import java.util.List; + +@Data +@Entity +public class NetDiskEntity extends OrgCommonEntity { + + @ManyToOne + private NetDiskEntity parent; + + @Comment("条目类型, 文件夹/目录/分片文件") + private String entryType; + + @Formula( + "(select i.name\n" + + "from t_dictionary d,\n" + + " t_dictionary_item i\n" + + "where d.id = i.dictionary_id\n" + + " and d.code = 'netDisk.entryType'\n" + + " and i.code = entry_type)") + private String entryTypeName; + + + + @OneToMany(mappedBy = "parent", cascade = {CascadeType.REMOVE}) + private List children; + + @Comment("文件/文件夹 大小, 单位 byte") + private Long size; + + + @Formula("(pg_size_pretty(size))") + private Long sizeHumanReadable; + + + @Comment("文件 sha256 值, 文件夹没有") + private String sha256; + + + @Comment("文件 mime 类型, 文件夹没有") + private String mineType; + + + private String ossKey; + + + @Comment("分片序号") + private Long sliceIndex; + + + @Comment("分片 sha256 值") + private String sliceSha256; + + + @Comment("分片文件大小") + private Long sliceSize; + + + @Comment("分片上传任务ID") + private String sliceTaskId; + + + + + + + +} diff --git a/src/main/java/cn/lihongjie/coal/netDisk/mapper/NetDiskMapper.java b/src/main/java/cn/lihongjie/coal/netDisk/mapper/NetDiskMapper.java new file mode 100644 index 00000000..e6718c81 --- /dev/null +++ b/src/main/java/cn/lihongjie/coal/netDisk/mapper/NetDiskMapper.java @@ -0,0 +1,35 @@ +package cn.lihongjie.coal.netDisk.mapper; + +import cn.lihongjie.coal.base.mapper.BaseMapper; +import cn.lihongjie.coal.base.mapper.CommonMapper; +import cn.lihongjie.coal.netDisk.dto.*; +import cn.lihongjie.coal.netDisk.entity.NetDiskEntity; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.Named; +import org.mapstruct.control.DeepClone; + +import java.util.List; + +@Mapper( + componentModel = org.mapstruct.MappingConstants.ComponentModel.SPRING, + uses = {CommonMapper.class}, + mappingControl = DeepClone.class) +public interface NetDiskMapper + extends BaseMapper { + NetDiskEntity toEntity(CreateDirDto request); + + @Mappings({ + @org.mapstruct.Mapping(target = "id", ignore = true), + @Mapping(target = "parent", ignore = true, qualifiedByName = "copyChildren"), + @Mapping(source = "children", target = "children", ignore = false, qualifiedByName = "copyChildren" ) + }) + @Named("copyChildren") + NetDiskEntity copyChildren(NetDiskEntity src); + + NetDiskEntity toEntity(CreateFileDto request); + + List toDto(List children); +} diff --git a/src/main/java/cn/lihongjie/coal/netDisk/repository/NetDiskRepository.java b/src/main/java/cn/lihongjie/coal/netDisk/repository/NetDiskRepository.java new file mode 100644 index 00000000..9eece739 --- /dev/null +++ b/src/main/java/cn/lihongjie/coal/netDisk/repository/NetDiskRepository.java @@ -0,0 +1,25 @@ +package cn.lihongjie.coal.netDisk.repository; + +import cn.lihongjie.coal.base.dao.BaseRepository; +import cn.lihongjie.coal.netDisk.entity.NetDiskEntity; + +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface NetDiskRepository extends BaseRepository { + NetDiskEntity findBySha256(String sha256); + + List findAllBySha256(String sha256); + + long countByNameAndParentNull(String name); + + boolean existsByOssKey(String ossKey); + + NetDiskEntity findByParentNull(); + + List findALlByParentNullAndOrganizationId(String organizationId); + + long countByParentNullAndOrganizationIdAndName(String organizationId, String name); +} diff --git a/src/main/java/cn/lihongjie/coal/netDisk/service/NetDiskService.java b/src/main/java/cn/lihongjie/coal/netDisk/service/NetDiskService.java new file mode 100644 index 00000000..dcea85e3 --- /dev/null +++ b/src/main/java/cn/lihongjie/coal/netDisk/service/NetDiskService.java @@ -0,0 +1,744 @@ +package cn.lihongjie.coal.netDisk.service; + +import cn.lihongjie.coal.base.dto.CommonQuery; +import cn.lihongjie.coal.base.dto.IdRequest; +import cn.lihongjie.coal.base.service.BaseService; +import cn.lihongjie.coal.common.Ctx; +import cn.lihongjie.coal.exception.BizException; +import cn.lihongjie.coal.netDisk.dto.*; +import cn.lihongjie.coal.netDisk.entity.NetDiskEntity; +import cn.lihongjie.coal.netDisk.mapper.NetDiskMapper; +import cn.lihongjie.coal.netDisk.repository.NetDiskRepository; +import cn.lihongjie.coal.spring.config.AliyunProperty; + +import com.aliyun.oss.HttpMethod; +import com.aliyun.oss.OSSClient; +import com.aliyun.oss.model.*; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.criteria.Root; +import jakarta.servlet.http.HttpServletResponse; + +import lombok.Cleanup; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; + +import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; +import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.time.DateUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.convert.ConversionService; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StopWatch; + +import java.io.InputStream; +import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Collectors; + +@Service +@Slf4j +@Transactional +public class NetDiskService extends BaseService { + public static final int DEFAULT_SLICE_SIZE = 10 * 1024 * 1024; + @Autowired ObjectMapper objectMapper; + @Autowired OSSClient ossClient; + @Autowired AliyunProperty aliyunProperty; + @Autowired private NetDiskRepository repository; + @Autowired private NetDiskMapper mapper; + @Autowired private ConversionService conversionService; + + private static void checkEntry(CreateFileDto request, List allSlice) { + if (allSlice.isEmpty() && request.getSliceIndex() != 0) { + throw new BizException("第一个分片的index必须是0"); + } + + if (request.getSliceIndex() != allSlice.size()) { + throw new BizException("分片index必须是连续的"); + } + } + + private static NetDiskEntity findExistEntry( + CreateFileDto request, List allSlice, NetDiskEntity existEntry) { + for (NetDiskEntity entity : allSlice) { + + if (entity.getSliceIndex().equals(request.getSliceIndex())) { + + if (StringUtils.equals(entity.getSliceSha256(), request.getSliceSha256())) { + log.info("分片 {} 已经存在, 不用上传", request.getName() + "-" + request.getSliceIndex()); + existEntry = entity; + break; + } else { + throw new BizException("分片已经存在, 但是sha256不一致, 请检查"); + } + } + } + return existEntry; + } + + private static String getOssKey(String sha256) { + return "netDisk" + + "/" + + sha256.substring(0, 2) + + "/" + + sha256.substring(2, 4) + + "/" + + sha256.substring(4); + } + + public List ls(IdRequest request) { + + handlerRootDir(request); + + if (StringUtils.isEmpty(request.getId())) { + return this.mapper.toDto( + repository.findALlByParentNullAndOrganizationId( + Ctx.currentUser().getOrganizationId())); + } + + NetDiskEntity entity = get(request.getId()); + + return this.mapper.toDto(entity.getChildren()); + } + + public List batchCreateDir(BatchCreateDirDto request) { + + request.getNames().forEach(x -> validateDirName(x)); + + ArrayList ans = new ArrayList<>(); + for (Object o : request.getNames()) { + String name = (String) o; + NetDiskDto dto = createDir(new CreateDirDto(request.getParent(), name)); + ans.add(dto); + } + + return ans; + } + + private void handlerRootDir(IdRequest request) { + if (StringUtils.equals(request.getId(), "/")) { + + List rootDir = + this.repository.findAll( + new Specification() { + @Override + public Predicate toPredicate( + Root root, + CriteriaQuery query, + CriteriaBuilder criteriaBuilder) { + + return criteriaBuilder.and( + criteriaBuilder.isNull(root.get("parent")), + criteriaBuilder.equal( + root.get("organizationId"), + Ctx.currentUser().getOrganizationId()), + criteriaBuilder.equal(root.get("entryType"), "0"), + criteriaBuilder.equal(root.get("name"), "/")); + } + }); + + if (rootDir.isEmpty()) { + NetDiskEntity entity = new NetDiskEntity(); + entity.setEntryType("0"); + entity.setName("/"); + entity.setOrganizationId(Ctx.currentUser().getOrganizationId()); + save(entity); + + request.setId(entity.getId()); + } else { + request.setId(rootDir.get(0).getId()); + } + } + } + + public NetDiskDto createDir(CreateDirDto request) { + + validateDirName(request.getName()); + + checkParentWithEntryName(request.getParent(), request.getName()); + + NetDiskEntity entity = this.mapper.toEntity(request); + entity.setEntryType("0"); + entity.setSize(0L); + save(entity); + return this.mapper.toDto(entity); + } + + private void checkParentWithEntryName(String parent, String name) { + + if (StringUtils.isEmpty(parent)) { + + long count = + repository.countByParentNullAndOrganizationIdAndName( + Ctx.currentUser().getOrganizationId(), name); + + if (count > 0) { + throw new BizException("根目录已经存在同名文件/文件夹"); + } + } else { + + NetDiskEntity disk = get(parent); + + if (disk.getChildren().stream().anyMatch(x -> x.getName().equals(name))) { + throw new BizException("目录已经存在同名文件/文件夹"); + } + } + } + + private void validateDirName(String name) { + + if (StringUtils.isEmpty(name) || StringUtils.isBlank(name)) { + throw new BizException("文件夹名字不能为空"); + } + + if (name.length() > 255) { + throw new BizException("文件夹名字不能超过255个字符"); + } + } + + @SneakyThrows + public NetDiskDto createFile(CreateFileDto request) { + + validateFileName(request.getName()); + checkParentWithEntryName(request.getParent(), request.getName()); + + if (StringUtils.isNotBlank(request.getOssKey())) { + + if (!repository.existsByOssKey(request.getOssKey())) { + throw new BizException("OSSKey 不存在"); + } + } else { + + try { + + String ossKey = getOssKey(request.getSha256()); + + PutObjectResult objectResult = + ossClient.putObject( + aliyunProperty.getOSS().getBucketName(), + ossKey, + request.getMultipartFile().getInputStream()); + request.setOssKey(ossKey); + log.info("上传文件 {} 到 OSS {}", request.getName(), objectResult.getResponse()); + + } catch (Exception e) { + log.warn("上传文件失败", e); + throw new BizException("上传文件失败 " + e.getMessage()); + } + } + + NetDiskEntity entity = this.mapper.toEntity(request); + entity.setEntryType("1"); + entity.setSize(request.getSize()); + save(entity); + + if (entity.getParent() != null) { + + updateDirSize(entity.getParent().getId()); + } + + return this.mapper.toDto(entity); + } + + @SneakyThrows + public NetDiskDto createFileSlice(CreateFileDto request) { + + validateFileName(request.getName()); + checkParentWithEntryName(request.getParent(), request.getName()); + NetDiskEntity existEntry = null; + String ossKey = getOssKey(request.getSha256()); + + @Cleanup InputStream inputStream = request.getMultipartFile().getInputStream(); + + try { + + List allSlice = findSlice(request.getSha256()); + + // 如果分片已经存在, 就不用上传了 + + existEntry = findExistEntry(request, allSlice, existEntry); + + checkEntry(request, allSlice); + + if (existEntry == null) { + + UploadPartResult uploadPartResult = + doUploadPart(request, ossKey, allSlice, inputStream); + + existEntry = saveEntry(request, ossKey, allSlice, uploadPartResult); + log.info("上传文件 {} 到 OSS {}", request.getName(), uploadPartResult.getResponse()); + } + + } catch (Exception e) { + log.warn("上传文件失败", e); + throw new BizException("上传文件失败 " + e.getMessage()); + } + + List slice = findSlice(request.getSha256()); + + long totalSize = slice.stream().mapToLong(NetDiskEntity::getSliceSize).sum(); + + if (totalSize == request.getSize()) { + log.info("文件 {} 已经上传完成, 开始合并", request.getName()); + + CompleteMultipartUploadResult completeMultipartUploadResult = + doCompleteSliceUpload(slice, ossKey); + + log.info("合并完成 {}", completeMultipartUploadResult.getResponse()); + + // 生成新的文件信息 + + NetDiskEntity netDiskEntity = buildNewEntry(slice, ossKey); + + // 删除分片信息 + + this.repository.deleteAll(slice); + + if (netDiskEntity.getParent() != null) { + + updateDirSize(netDiskEntity.getParent().getId()); + } + + } else { + log.info( + "文件 {} 还没有上传完成, 已经上传 {} 个分片, 分片总大小 {} , 文件总大小 {} ", + request.getName(), + slice.size(), + totalSize, + request.getSize()); + } + + return this.mapper.toDto(existEntry); + } + + private NetDiskEntity buildNewEntry(List slice, String ossKey) { + NetDiskEntity completeFile = mapper.copy(slice.get(0)); + + completeFile.setEntryType("1"); + completeFile.setOssKey(ossKey); + completeFile.setSliceSize(null); + completeFile.setSliceIndex(null); + completeFile.setSliceSha256(null); + + this.repository.save(completeFile); + return completeFile; + } + + private CompleteMultipartUploadResult doCompleteSliceUpload( + List slice, String ossKey) { + List partETags = + slice.stream() + .map( + x -> { + try { + return objectMapper.readValue( + x.getRemarks(), PartETag.class); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + }) + .collect(Collectors.toList()); + CompleteMultipartUploadRequest completeMultipartUploadRequest = + new CompleteMultipartUploadRequest( + aliyunProperty.getOSS().getBucketName(), + ossKey, + slice.get(0).getSliceTaskId(), + partETags); + CompleteMultipartUploadResult completeMultipartUploadResult = + ossClient.completeMultipartUpload(completeMultipartUploadRequest); + return completeMultipartUploadResult; + } + + private NetDiskEntity saveEntry( + CreateFileDto request, + String ossKey, + List allSlice, + UploadPartResult uploadPartResult) + throws JsonProcessingException { + NetDiskEntity existEntry; + existEntry = this.mapper.toEntity(request); + existEntry.setEntryType("2"); + existEntry.setSliceTaskId(getUploadId(request, ossKey, allSlice)); + existEntry.setRemarks(objectMapper.writeValueAsString(uploadPartResult.getPartETag())); + save(existEntry); + return existEntry; + } + + private UploadPartResult doUploadPart( + CreateFileDto request, + String ossKey, + List allSlice, + InputStream inputStream) { + UploadPartRequest uploadPartRequest = new UploadPartRequest(); + uploadPartRequest.setBucketName(aliyunProperty.getOSS().getBucketName()); + uploadPartRequest.setKey(ossKey); + uploadPartRequest.setUploadId(getUploadId(request, ossKey, allSlice)); + uploadPartRequest.setPartNumber(Math.toIntExact(request.getSliceIndex()) + 1); + uploadPartRequest.setInputStream(inputStream); + UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest); + return uploadPartResult; + } + + private String getUploadId(CreateFileDto request, String ossKey, List allSlice) { + String uploadId; + if (request.getSliceIndex() == 0) { + + uploadId = initPartTask(ossKey); + + log.info("初始化分片上传 {}", uploadId); + } else { + + uploadId = allSlice.get(0).getSliceTaskId(); + } + return uploadId; + } + + private String initPartTask(String ossKey) { + String uploadId; + InitiateMultipartUploadRequest multipartUploadRequest = + new InitiateMultipartUploadRequest(aliyunProperty.getOSS().getBucketName(), ossKey); + + InitiateMultipartUploadResult multipartUpload = + ossClient.initiateMultipartUpload(multipartUploadRequest); + uploadId = multipartUpload.getUploadId(); + return uploadId; + } + + private void validateFileName(String name) { + + if (StringUtils.isEmpty(name) || StringUtils.isBlank(name)) { + throw new BizException("文件名字不能为空"); + } + + if (name.length() > 255) { + throw new BizException("文件名字不能超过255个字符"); + } + } + + public NetDiskDto rename(RenameDto request) { + + NetDiskEntity netDiskEntity = get(request.getId()); + + if (netDiskEntity.getParent().getChildren().stream() + .anyMatch(x -> x.getName().equals(request.getName()))) { + throw new BizException("目录已经存在同名文件/文件夹"); + } + + netDiskEntity.setName(request.getName()); + + save(netDiskEntity); + + return this.mapper.toDto(netDiskEntity); + } + + public void remove(IdRequest request) { + + this.repository.findAllById(request.getIds()).forEach(this::remove); + } + + private void remove(NetDiskEntity netDiskEntity) { + + this.repository.delete(netDiskEntity); + + if (netDiskEntity.getParent() != null) { + + updateDirSize(netDiskEntity.getParent().getId()); + } + } + + public NetDiskDto move(MoveDto request) { + + NetDiskEntity src = get(request.getSrcId()); + NetDiskEntity target = get(request.getTargetId()); + if (StringUtils.isNotEmpty(request.getNewName())) { + src.setName(request.getNewName()); + } + + if (src.getParent().getId().equals(target.getId())) { + throw new BizException("不能移动到自己的子目录"); + } + + if (!target.getEntryType().equals("0")) { + throw new BizException("只能移动到目录"); + } + + if (target.getChildren().stream().filter(x -> x.getName().equals(src.getName())).count() + > 0) { + throw new BizException("目标目录已经存在同名文件/文件夹"); + } + + src.setParent(target); + + this.save(src); + + if (src.getParent() != null) { + + updateDirSize(src.getParent().getId()); + } + + if (target.getParent() != null) { + + updateDirSize(target.getParent().getId()); + } + + return this.mapper.toDto(src); + } + + public NetDiskDto copy(CopyDto request) { + + NetDiskEntity srcEntity = get(request.getSrcId()); + + NetDiskEntity src = this.mapper.copyChildren(srcEntity); + + NetDiskEntity target = get(request.getTargetId()); + if (StringUtils.isNotEmpty(request.getNewName())) { + src.setName(request.getNewName()); + } + + if (src.getParent().getId().equals(target.getId())) { + throw new BizException("不能复制到自己的子目录"); + } + + if (!target.getEntryType().equals("0")) { + throw new BizException("只能复制到目录"); + } + + if (target.getChildren().stream().filter(x -> x.getName().equals(src.getName())).count() + > 0) { + throw new BizException("目标目录已经存在同名文件/文件夹"); + } + + src.setParent(target); + + this.save(src); + + if (src.getParent() != null) { + + updateDirSize(src.getParent().getId()); + } + + return this.mapper.toDto(src); + } + + public String downloadFile(String id, boolean attachment) { + + String bucketName = aliyunProperty.getOSS().getBucketName(); + NetDiskEntity disk = get(id); + + if (!disk.getEntryType().equals("1")) { + throw new BizException("只能下载文件"); + } + + String ossKey = disk.getOssKey(); + Date addedMinutes = + DateUtils.addMinutes( + new Date(), + Math.toIntExact( + ObjectUtils.defaultIfNull( + aliyunProperty.getOSS().getPreSignedUrlExpire(), 30L))); + GeneratePresignedUrlRequest request = + new GeneratePresignedUrlRequest(bucketName, ossKey, HttpMethod.GET); + request.setExpiration(addedMinutes); + + ResponseHeaderOverrides responseHeaders = new ResponseHeaderOverrides(); + responseHeaders.setContentType(disk.getMineType()); + if (attachment) { + responseHeaders.setContentDisposition( + "attachment;filename=" + + URLEncoder.encode(disk.getName(), StandardCharsets.UTF_8)); + + } else { + responseHeaders.setContentDisposition( + "inline;filename=" + URLEncoder.encode(disk.getName(), StandardCharsets.UTF_8)); + } + request.setResponseHeaders(responseHeaders); + + URL url = ossClient.generatePresignedUrl(request); + + return url.toString(); + } + + @SneakyThrows + public void downloadDir(IdRequest id, HttpServletResponse response) { + + NetDiskEntity dir = get(id.getId()); + + if (!dir.getEntryType().equals("0")) { + throw new BizException("只能下载目录"); + } + + StopWatch stopWatch = new StopWatch("下载文件夹 " + dir.getName() + " " + dir.getId()); + // 设置响应类型为ZIP + response.setContentType("application/zip"); + + // 设置响应头,指定ZIP文件名 + String zipName = URLEncoder.encode(dir.getName() + ".zip", StandardCharsets.UTF_8); + response.setHeader("Content-Disposition", "attachment; filename=" + zipName); + + @Cleanup("close") + ZipArchiveOutputStream zipArchiveOutputStream = + new ZipArchiveOutputStream(response.getOutputStream()); + + zipDir(dir, zipArchiveOutputStream, dir, stopWatch); + + log.info(stopWatch.prettyPrint()); + } + + @SneakyThrows + private void zipDir( + NetDiskEntity dir, + ZipArchiveOutputStream zipArchiveOutputStream, + NetDiskEntity root, + StopWatch stopWatch) { + + if (dir.getEntryType().equals("0")) { + + stopWatch.start("创建目录 " + dir.getName() + " " + dir.getId()); + zipArchiveOutputStream.putArchiveEntry(new ZipArchiveEntry(getFullName(root, dir))); + + zipArchiveOutputStream.closeArchiveEntry(); + + stopWatch.stop(); + } else { + + zipArchiveOutputStream.putArchiveEntry(new ZipArchiveEntry(getFullName(root, dir))); + + stopWatch.start("下载文件 " + dir.getName() + " " + dir.getId()); + OSSObject ossObject = + ossClient.getObject(aliyunProperty.getOSS().getBucketName(), dir.getOssKey()); + + IOUtils.copy(ossObject.getObjectContent(), zipArchiveOutputStream); + + stopWatch.stop(); + zipArchiveOutputStream.closeArchiveEntry(); + + for (NetDiskEntity child : dir.getChildren()) { + + zipDir(child, zipArchiveOutputStream, root, stopWatch); + } + } + } + + private String getFullName(NetDiskEntity root, NetDiskEntity entry) { + + if (root == entry) { + return entry.getName() + "/"; + } + + LinkedList parts = new LinkedList<>(); + boolean isDir = entry.getEntryType().equals("0"); + + while (entry != root) { + parts.addFirst(entry.getName()); + entry = entry.getParent(); + } + + return StringUtils.join(parts, "/") + (isDir ? "/" : ""); + } + + public NetDiskDto create(CreateNetDiskDto request) { + NetDiskEntity entity = mapper.toEntity(request); + + this.repository.save(entity); + return getById(entity.getId()); + } + + public NetDiskDto update(UpdateNetDiskDto request) { + NetDiskEntity entity = this.repository.get(request.getId()); + this.mapper.updateEntity(entity, request); + + this.repository.save(entity); + + return getById(entity.getId()); + } + + public void delete(IdRequest request) { + this.repository.deleteAllById(request.getIds()); + } + + public NetDiskDto getById(String id) { + NetDiskEntity entity = repository.get(id); + + return mapper.toDto(entity); + } + + public Page list(CommonQuery query) { + Page page = + repository.findAll( + query.specification(conversionService), + PageRequest.of( + query.getPageNo(), + query.getPageSize(), + Sort.by(query.getOrders()))); + + return page.map(this.mapper::toDto); + } + + public PreCreateFileDto preCreateFile(CreateFileDto request) { + + PreCreateFileDto dto = new PreCreateFileDto(); + validateFileName(request.getName()); + + checkParentWithEntryName(request.getParent(), request.getName()); + + List sha256 = repository.findAllBySha256(request.getSha256()); + + String ossKey = sha256.stream().findAny().map(NetDiskEntity::getOssKey).orElse(null); + + dto.setOssKey(ossKey); + + dto.setSliceSize((long) DEFAULT_SLICE_SIZE); + + if (request.getSize() > DEFAULT_SLICE_SIZE) { + + if (StringUtils.isEmpty(ossKey)) { + + // 查找分片记录 + List slice = findSlice(request.getSha256()); + + dto.setSlices(this.mapper.toDto(slice)); + } + } + + return dto; + } + + private List findSlice(String sha256) { + return repository.findAll( + (root, query, criteriaBuilder) -> { + return criteriaBuilder.and( + criteriaBuilder.equal(root.get("sha256"), sha256), + criteriaBuilder.equal(root.get("entryType"), "2")); + }, + Sort.by(Sort.Direction.ASC, "sliceIndex")); + } + + public void updateDirSize(String id) { + NetDiskEntity entity = get(id); + entity.setSize(entity.getChildren().stream().mapToLong(NetDiskEntity::getSize).sum()); + save(entity); + + while (entity.getParent() != null) { + entity = entity.getParent(); + entity.setSize(entity.getChildren().stream().mapToLong(NetDiskEntity::getSize).sum()); + save(entity); + } + } +} diff --git a/src/main/java/cn/lihongjie/coal/spring/config/AliyunProperty.java b/src/main/java/cn/lihongjie/coal/spring/config/AliyunProperty.java index e1786d43..e5c87b62 100644 --- a/src/main/java/cn/lihongjie/coal/spring/config/AliyunProperty.java +++ b/src/main/java/cn/lihongjie/coal/spring/config/AliyunProperty.java @@ -19,6 +19,8 @@ public class AliyunProperty { private List regions; + private Long preSignedUrlExpire; + private String ak; private String sk; diff --git a/src/main/java/cn/lihongjie/coal/spring/config/RResponseBodyAdvice.java b/src/main/java/cn/lihongjie/coal/spring/config/RResponseBodyAdvice.java index 5d601f15..e873dd23 100644 --- a/src/main/java/cn/lihongjie/coal/spring/config/RResponseBodyAdvice.java +++ b/src/main/java/cn/lihongjie/coal/spring/config/RResponseBodyAdvice.java @@ -5,6 +5,7 @@ import cn.lihongjie.coal.base.dto.R; import org.springframework.core.MethodParameter; import org.springframework.data.domain.Page; import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; @@ -48,6 +49,10 @@ public class RResponseBodyAdvice implements ResponseBodyAdvice { return body; } + if (body instanceof ResponseEntity) { + return body; + } + if (body instanceof Page p) { R> r = R.success(p.getContent()); r.setPageNo(p.getNumber()); diff --git a/src/main/resources/config/dictionary.json b/src/main/resources/config/dictionary.json index 4eb8d5e3..4db70e30 100644 --- a/src/main/resources/config/dictionary.json +++ b/src/main/resources/config/dictionary.json @@ -1675,6 +1675,24 @@ } ] }, + { + "code": "netDisk.entryType", + "name": "网盘文件类型", + "item": [ + { + "code": "0", + "name": "文件夹" + }, + { + "code": "1", + "name": "文件" + }, + { + "code": "2", + "name": "文件分片" + } + ] + }, { "code": "notebook.contentType", "name": "章节内容类型",