Browse Source

feat: 更新文档

master
yuanjs@qutke.com 2 weeks ago
parent
commit
9b22077a58
  1. 38
      src/main/java/org/ycloud/aipan/controller/FileController.java
  2. 13
      src/main/java/org/ycloud/aipan/controller/req/FileDelReq.java
  3. 37
      src/main/java/org/ycloud/aipan/controller/req/FileSecondUploadReq.java
  4. 4
      src/main/java/org/ycloud/aipan/mapper/AccountFileMapper.java
  5. 36
      src/main/java/org/ycloud/aipan/service/AccountFileService.java
  6. 184
      src/main/java/org/ycloud/aipan/service/impl/AccountFileServiceImpl.java
  7. 8
      src/main/resources/mapper/AccountFileMapper.xml

38
src/main/java/org/ycloud/aipan/controller/FileController.java

@ -5,14 +5,11 @@ import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.ycloud.aipan.controller.req.FileBatchReq; import org.ycloud.aipan.controller.req.*;
import org.ycloud.aipan.controller.req.FileUpdateReq;
import org.ycloud.aipan.controller.req.FileUploadReq;
import org.ycloud.aipan.dto.AccountFileDTO; import org.ycloud.aipan.dto.AccountFileDTO;
import org.ycloud.aipan.dto.FolderTreeNodeDTO; import org.ycloud.aipan.dto.FolderTreeNodeDTO;
import org.ycloud.aipan.interceptor.LoginInterceptor; import org.ycloud.aipan.interceptor.LoginInterceptor;
import org.ycloud.aipan.service.AccountFileService; import org.ycloud.aipan.service.AccountFileService;
import org.ycloud.aipan.controller.req.FolderCreateReq;
import org.ycloud.aipan.util.JsonData; import org.ycloud.aipan.util.JsonData;
import java.util.List; import java.util.List;
@ -94,4 +91,37 @@ public class FileController {
accountFileService.moveBatch(req); accountFileService.moveBatch(req);
return JsonData.buildSuccess(); return JsonData.buildSuccess();
} }
/**
* 文件批量删除
*/
@PostMapping("del_batch")
public JsonData delBatch(@RequestBody FileDelReq req) {
req.setAccountId(LoginInterceptor.threadLocal.get().getId());
accountFileService.delBatch(req);
return JsonData.buildSuccess();
}
/**
* 文件复制接口
*/
@PostMapping("copy_batch")
@Operation(summary = "文件批量复制", description = "文件批量复制")
public JsonData copyBatch(
@Parameter(description = "文件批量复制请求对象", required = true) @RequestBody FileBatchReq req) {
req.setAccountId(LoginInterceptor.threadLocal.get().getId());
accountFileService.copyBatch(req);
return JsonData.buildSuccess();
}
/**
* 文件秒传接口, true就是文件秒传成功false失败需要重新调用上传接口
*/
@PostMapping("second_upload")
public JsonData secondUpload(@RequestBody FileSecondUploadReq req) {
req.setAccountId(LoginInterceptor.threadLocal.get().getId());
Boolean flag = accountFileService.secondUpload(req);
return JsonData.buildSuccess(flag);
}
} }

13
src/main/java/org/ycloud/aipan/controller/req/FileDelReq.java

@ -0,0 +1,13 @@
package org.ycloud.aipan.controller.req;
import lombok.Data;
import java.util.List;
@Data
public class FileDelReq {
private List<Long> fileIds;
private Long accountId;
}

37
src/main/java/org/ycloud/aipan/controller/req/FileSecondUploadReq.java

@ -0,0 +1,37 @@
package org.ycloud.aipan.controller.req;
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
public class FileSecondUploadReq {
/**
* 文件名
*/
private String filename;
/**
* 文件唯一标识md5
*/
private String identifier;
/**
* 用户id
*/
private Long accountId;
/**
* 父级目录id
*/
private Long parentId;
/**
* 文件大小
*/
//private Long fileSize;
}

4
src/main/java/org/ycloud/aipan/mapper/AccountFileMapper.java

@ -1,8 +1,11 @@
package org.ycloud.aipan.mapper; package org.ycloud.aipan.mapper;
import org.apache.ibatis.annotations.Param;
import org.ycloud.aipan.model.AccountFileDO; import org.ycloud.aipan.model.AccountFileDO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import java.util.List;
/** /**
* <p> * <p>
* 用户文件表 Mapper 接口 * 用户文件表 Mapper 接口
@ -13,4 +16,5 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
*/ */
public interface AccountFileMapper extends BaseMapper<AccountFileDO> { public interface AccountFileMapper extends BaseMapper<AccountFileDO> {
void insertFileBatch(@Param("newAccountFileDOList") List<AccountFileDO> newAccountFileDOList);
} }

36
src/main/java/org/ycloud/aipan/service/AccountFileService.java

@ -1,10 +1,7 @@
package org.ycloud.aipan.service; package org.ycloud.aipan.service;
import org.ycloud.aipan.controller.req.FileBatchReq; import org.ycloud.aipan.controller.req.*;
import org.ycloud.aipan.controller.req.FileUpdateReq;
import org.ycloud.aipan.controller.req.FileUploadReq;
import org.ycloud.aipan.controller.req.FolderCreateReq;
import org.ycloud.aipan.dto.AccountFileDTO; import org.ycloud.aipan.dto.AccountFileDTO;
import org.ycloud.aipan.dto.FolderTreeNodeDTO; import org.ycloud.aipan.dto.FolderTreeNodeDTO;
@ -39,13 +36,40 @@ public interface AccountFileService {
/** /**
* 普通小文件上传 * 普通小文件上传
* @param req
*/ */
void fileUpload(FileUploadReq req); void fileUpload(FileUploadReq req);
/** /**
* 批量移动目标文件夹 * 批量移动目标文件夹
* @param req
*/ */
void moveBatch(FileBatchReq req); void moveBatch(FileBatchReq req);
/**
* 文件的批量删除
* 步骤一检查是否满足1文件ID数量是否合法2文件是否属于当前用户
* 步骤二判断文件是否是文件夹文件夹的话需要递归获取里面子文件ID然后进行批量删除
* 步骤三需要更新账号存储空间使用情况
* 步骤四批量删除账号映射文件考虑回收站如何设计
*/
void delBatch(FileDelReq req);
/**
* 文件复制
* * 检查被转移的文件ID是否合法
* * 检查目标文件夹ID是否合法
* * 执行拷贝递归查找差异点ID是全新的
* * 计算存储空间大小检查是否足够差异点空间需要检查
* * 存储相关记录
*/
void copyBatch(FileBatchReq req);
/**
* 文件秒传
* 1检查文件是否存在
* 2检查空间是否足够
* 3建立关系
*/
Boolean secondUpload(FileSecondUploadReq req);
} }

184
src/main/java/org/ycloud/aipan/service/impl/AccountFileServiceImpl.java

@ -1,5 +1,6 @@
package org.ycloud.aipan.service.impl; package org.ycloud.aipan.service.impl;
import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -9,10 +10,7 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.ycloud.aipan.component.StoreEngine; import org.ycloud.aipan.component.StoreEngine;
import org.ycloud.aipan.config.MinioConfig; import org.ycloud.aipan.config.MinioConfig;
import org.ycloud.aipan.controller.req.FileBatchReq; import org.ycloud.aipan.controller.req.*;
import org.ycloud.aipan.controller.req.FileUpdateReq;
import org.ycloud.aipan.controller.req.FileUploadReq;
import org.ycloud.aipan.controller.req.FolderCreateReq;
import org.ycloud.aipan.dto.AccountFileDTO; import org.ycloud.aipan.dto.AccountFileDTO;
import org.ycloud.aipan.dto.FolderTreeNodeDTO; import org.ycloud.aipan.dto.FolderTreeNodeDTO;
import org.ycloud.aipan.enums.BizCodeEnum; import org.ycloud.aipan.enums.BizCodeEnum;
@ -21,8 +19,10 @@ import org.ycloud.aipan.enums.FolderFlagEnum;
import org.ycloud.aipan.exception.BizException; import org.ycloud.aipan.exception.BizException;
import org.ycloud.aipan.mapper.AccountFileMapper; import org.ycloud.aipan.mapper.AccountFileMapper;
import org.ycloud.aipan.mapper.FileMapper; import org.ycloud.aipan.mapper.FileMapper;
import org.ycloud.aipan.mapper.StorageMapper;
import org.ycloud.aipan.model.AccountFileDO; import org.ycloud.aipan.model.AccountFileDO;
import org.ycloud.aipan.model.FileDO; import org.ycloud.aipan.model.FileDO;
import org.ycloud.aipan.model.StorageDO;
import org.ycloud.aipan.service.AccountFileService; import org.ycloud.aipan.service.AccountFileService;
import org.ycloud.aipan.util.CommonUtil; import org.ycloud.aipan.util.CommonUtil;
import org.ycloud.aipan.util.SpringBeanUtil; import org.ycloud.aipan.util.SpringBeanUtil;
@ -45,6 +45,8 @@ public class AccountFileServiceImpl implements AccountFileService {
private AccountFileMapper accountFileMapper; private AccountFileMapper accountFileMapper;
@Autowired @Autowired
private FileMapper fileMapper; private FileMapper fileMapper;
@Autowired
private StorageMapper storageMapper;
@Override @Override
public List<AccountFileDTO> listFile(Long accountId, Long parentId) { public List<AccountFileDTO> listFile(Long accountId, Long parentId) {
@ -261,6 +263,80 @@ public class AccountFileServiceImpl implements AccountFileService {
} }
@Override
@Transactional(rollbackFor = Exception.class)
public void delBatch(FileDelReq req) {
//步骤一:检查是否满足:1、文件ID数量是否合法,2、文件是否属于当前用户
List<AccountFileDO> accountFileDOList = checkFileIdLegal(req.getFileIds(), req.getAccountId());
//步骤二:判断文件是否是文件夹,文件夹的话需要递归获取里面子文件ID,然后进行批量删除
List<AccountFileDO> storeAccountFileDOList = new ArrayList<>();
findAllAccountFileDOWithRecur(storeAccountFileDOList, accountFileDOList, false);
//拿到全部文件ID列表
List<Long> allFileIdList = storeAccountFileDOList.stream().map(AccountFileDO::getId).collect(Collectors.toList());
//步骤三:需要更新账号存储空间使用情况 可以加个分布式锁,redission 作业,提示可以用account_id锁粒度
long allFileSize = storeAccountFileDOList.stream()
.filter(file -> file.getIsDir().equals(FolderFlagEnum.NO.getCode()))
.mapToLong(AccountFileDO::getFileSize).sum();
StorageDO storageDO = storageMapper.selectOne(new QueryWrapper<StorageDO>().eq("account_id", req.getAccountId()));
storageDO.setUsedSize(storageDO.getUsedSize() - allFileSize);
storageMapper.updateById(storageDO);
// 步骤四:批量删除账号映射文件,考虑回收站如何设计
accountFileMapper.deleteBatchIds(allFileIdList);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void copyBatch(FileBatchReq req) {
//检查被转移的文件ID是否合法
List<AccountFileDO> accountFileDOList = checkFileIdLegal(req.getFileIds(), req.getAccountId());
//检查目标文件夹ID是否合法
checkTargetParentIdLegal(req);
//执行拷贝,递归查找【差异点,ID是全新的】
List<AccountFileDO> newAccountFileDOList = findBatchCopyFileWithRecur(accountFileDOList, req.getTargetParentId());
//计算存储空间大小,检查是否足够【差异点,空间需要检查】
long totalFileSize = newAccountFileDOList.stream().filter(file -> file.getIsDir().equals(FolderFlagEnum.NO.getCode()))
.mapToLong(AccountFileDO::getFileSize).sum();
if (!checkAndUpdateCapacity(req.getAccountId(), totalFileSize)) {
throw new BizException(BizCodeEnum.FILE_STORAGE_NOT_ENOUGH);
}
//存储
accountFileMapper.insertFileBatch(newAccountFileDOList);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean secondUpload(FileSecondUploadReq req) {
//检查文件是否存在
FileDO fileDO = fileMapper.selectOne(new QueryWrapper<FileDO>().eq("identifier", req.getIdentifier()));
//检查空间是否足够
if (fileDO != null && checkAndUpdateCapacity(req.getAccountId(), fileDO.getFileSize())) {
//处理文件秒传
AccountFileDTO accountFileDTO = new AccountFileDTO();
accountFileDTO.setAccountId(req.getAccountId());
accountFileDTO.setFileId(fileDO.getId());
accountFileDTO.setParentId(req.getParentId());
accountFileDTO.setFileName(req.getFilename());
accountFileDTO.setFileSize(fileDO.getFileSize());
accountFileDTO.setDel(false);
accountFileDTO.setIsDir(FolderFlagEnum.NO.getCode());
//保存关联文件关系,里面有做相关检查
saveAccountFile(accountFileDTO);
return true;
}
return false;
}
/** /**
* 检查目标文件夹ID是否合法,包括子文件夹 * 检查目标文件夹ID是否合法,包括子文件夹
* 1目标的文件ID不能是文件 * 1目标的文件ID不能是文件
@ -414,8 +490,108 @@ public class AccountFileServiceImpl implements AccountFileService {
accountFileDO.setFileName(split[0] + "_" + System.currentTimeMillis() + "." + split[1]); accountFileDO.setFileName(split[0] + "_" + System.currentTimeMillis() + "." + split[1]);
} }
} }
}
/**
* 递归查找
*
* @param allAccountFileDOList 容器存储查询到到全部文件或者文件夹
* @param prepareAccountFileDOList 待查询的文件和文件夹
* @param onlyFolder 控制是否只存储文件
*/
public void findAllAccountFileDOWithRecur(List<AccountFileDO> allAccountFileDOList, List<AccountFileDO> prepareAccountFileDOList, boolean onlyFolder) {
for (AccountFileDO accountFileDO : prepareAccountFileDOList) {
if (Objects.equals(accountFileDO.getIsDir(), FolderFlagEnum.YES.getCode())) {
//递归查找
List<AccountFileDO> childAccountFileDOList = accountFileMapper.selectList(new QueryWrapper<AccountFileDO>()
.eq("parent_id", accountFileDO.getId()));
findAllAccountFileDOWithRecur(allAccountFileDOList, childAccountFileDOList, onlyFolder);
}
//如果通过onlyFolder是true,只存储文件夹到allAccountFileDOList,否则都存储到allAccountFileDOList
if (!onlyFolder || Objects.equals(accountFileDO.getIsDir(), FolderFlagEnum.YES.getCode())) {
allAccountFileDOList.add(accountFileDO);
}
}
} }
/**
* 包括递归处理生成新的ID
*
* @param accountFileDOList
* @param targetParentId
* @return
*/
public List<AccountFileDO> findBatchCopyFileWithRecur(List<AccountFileDO> accountFileDOList, Long targetParentId) {
List<AccountFileDO> newAccountFileDOList = new ArrayList<>();
accountFileDOList.forEach(accountFileDO -> doCopyChildRecord(newAccountFileDOList, accountFileDO, targetParentId));
return newAccountFileDOList;
}
/**
* 递归处理包括子文件夹
*
* @param newAccountFileDOList
* @param accountFileDO
* @param targetParentId
*/
private void doCopyChildRecord(List<AccountFileDO> newAccountFileDOList, AccountFileDO accountFileDO, Long targetParentId) {
//保存旧的ID,方便查找子文件夹
Long oldAccountFileId = accountFileDO.getId();
//创建新记录
accountFileDO.setId(IdUtil.getSnowflakeNextId());
accountFileDO.setParentId(targetParentId);
accountFileDO.setGmtModified(null);
accountFileDO.setGmtCreate(null);
//处理重复文件夹
processFileNameDuplicate(accountFileDO);
//纳入容器存储
newAccountFileDOList.add(accountFileDO);
//判断是文件还是文件夹,递归处理
if (Objects.equals(accountFileDO.getIsDir(), FolderFlagEnum.YES.getCode())) {
//继续获取子文件夹列表
List<AccountFileDO> childAccountFileDOList = findChildAccountFile(accountFileDO.getAccountId(), oldAccountFileId);
if (CollectionUtils.isEmpty(childAccountFileDOList)) {
return;
}
//递归处理
childAccountFileDOList
.forEach(childAccountFileDO -> doCopyChildRecord(newAccountFileDOList, childAccountFileDO, accountFileDO.getId()));
}
}
/**
* 查找文件记录只查询下一级不递归
*
* @param accountId
* @param parentId
* @return
*/
private List<AccountFileDO> findChildAccountFile(Long accountId, Long parentId) {
return accountFileMapper.selectList(new QueryWrapper<AccountFileDO>()
.eq("account_id", accountId).eq("parent_id", parentId));
}
/**
* 检查存储空间和更新存储空间
*
* @param accountId
* @param fileSize
* @return
*/
public boolean checkAndUpdateCapacity(Long accountId, Long fileSize) {
StorageDO storageDO = storageMapper.selectOne(new QueryWrapper<StorageDO>().eq("account_id", accountId));
Long totalSize = storageDO.getTotalSize();
if (storageDO.getUsedSize() + fileSize <= totalSize) {
storageDO.setUsedSize(storageDO.getUsedSize() + fileSize);
storageMapper.updateById(storageDO);
return true;
} else {
return false;
}
}
} }

8
src/main/resources/mapper/AccountFileMapper.xml

@ -23,5 +23,13 @@
<sql id="Base_Column_List"> <sql id="Base_Column_List">
id, account_id, is_dir, parent_id, file_id, file_name, file_type, file_suffix, file_size, del, del_time, gmt_modified, gmt_create id, account_id, is_dir, parent_id, file_id, file_name, file_type, file_suffix, file_size, del, del_time, gmt_modified, gmt_create
</sql> </sql>
<insert id="insertFileBatch" parameterType="java.util.List">
INSERT INTO account_file (id,account_id, is_dir, parent_id, file_id, file_name, file_type, file_suffix, file_size, del, del_time)
VALUES
<foreach collection="newAccountFileDOList" item="item" separator=",">
(#{item.id},#{item.accountId}, #{item.isDir}, #{item.parentId}, #{item.fileId}, #{item.fileName}, #{item.fileType},
#{item.fileSuffix}, #{item.fileSize}, #{item.del}, #{item.delTime})
</foreach>
</insert>
</mapper> </mapper>

Loading…
Cancel
Save