This commit is contained in:
2023-12-26 17:40:51 +08:00
parent 8c488efb77
commit ccee9b213b
20 changed files with 1336 additions and 10 deletions

View File

@@ -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 "";

View File

@@ -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)) {

View File

@@ -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<NetDiskDto> 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<NetDiskDto> 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<Object> 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);
}
}

View File

@@ -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<String> names;
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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 {}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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<NetDiskDto> slices = new ArrayList<>();
@Comment("分片大小")
private Long sliceSize;
}

View File

@@ -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;
}

View File

@@ -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 {}

View File

@@ -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<NetDiskEntity> 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;
}

View File

@@ -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, NetDiskDto, CreateNetDiskDto, UpdateNetDiskDto> {
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<NetDiskDto> toDto(List<NetDiskEntity> children);
}

View File

@@ -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> {
NetDiskEntity findBySha256(String sha256);
List<NetDiskEntity> findAllBySha256(String sha256);
long countByNameAndParentNull(String name);
boolean existsByOssKey(String ossKey);
NetDiskEntity findByParentNull();
List<NetDiskEntity> findALlByParentNullAndOrganizationId(String organizationId);
long countByParentNullAndOrganizationIdAndName(String organizationId, String name);
}

View File

@@ -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<NetDiskEntity, NetDiskRepository> {
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<NetDiskEntity> 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<NetDiskEntity> 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<NetDiskDto> 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<NetDiskDto> batchCreateDir(BatchCreateDirDto request) {
request.getNames().forEach(x -> validateDirName(x));
ArrayList<NetDiskDto> 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<NetDiskEntity> rootDir =
this.repository.findAll(
new Specification<NetDiskEntity>() {
@Override
public Predicate toPredicate(
Root<NetDiskEntity> 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<NetDiskEntity> 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<NetDiskEntity> 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<NetDiskEntity> 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<NetDiskEntity> slice, String ossKey) {
List<PartETag> 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<NetDiskEntity> 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<NetDiskEntity> 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<NetDiskEntity> 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<String> 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<NetDiskDto> list(CommonQuery query) {
Page<NetDiskEntity> 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<NetDiskEntity> 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<NetDiskEntity> slice = findSlice(request.getSha256());
dto.setSlices(this.mapper.toDto(slice));
}
}
return dto;
}
private List<NetDiskEntity> 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);
}
}
}

View File

@@ -19,6 +19,8 @@ public class AliyunProperty {
private List<OSSRegion> regions;
private Long preSignedUrlExpire;
private String ak;
private String sk;

View File

@@ -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<Object> {
return body;
}
if (body instanceof ResponseEntity<?>) {
return body;
}
if (body instanceof Page<?> p) {
R<? extends List<?>> r = R.success(p.getContent());
r.setPageNo(p.getNumber());

View File

@@ -1675,6 +1675,24 @@
}
]
},
{
"code": "netDisk.entryType",
"name": "网盘文件类型",
"item": [
{
"code": "0",
"name": "文件夹"
},
{
"code": "1",
"name": "文件"
},
{
"code": "2",
"name": "文件分片"
}
]
},
{
"code": "notebook.contentType",
"name": "章节内容类型",