Files
senmesh-zc/src/main/java/com/senmesh/sfile/service/impl/UserFileServiceImpl.java
2026-01-05 16:35:46 +08:00

332 lines
11 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package com.senmesh.sfile.service.impl;
import com.senmesh.common.constant.CommonConstants;
import com.senmesh.common.exception.util.Assert;
import com.senmesh.common.util.ListUtil;
import com.senmesh.common.util.UserContextHolder;
import com.senmesh.component.file.service.FileService;
import com.senmesh.sfile.constant.FileStatusEnum;
import com.senmesh.sfile.constant.FileTypeEnum;
import com.senmesh.sfile.constant.RootTypeEnum;
import com.senmesh.sfile.convert.UserFileConvert;
import com.senmesh.sfile.dao.UserFileMapper;
import com.senmesh.sfile.dto.ExpandUserFileCreateDTO;
import com.senmesh.sfile.dto.ExpandUserFileDTO;
import com.senmesh.sfile.dto.UserFileCreateDTO;
import com.senmesh.sfile.dto.UserFileDTO;
import com.senmesh.sfile.dto.UserFileModifyDTO;
import com.senmesh.sfile.dto.UserFileModifyStatusDTO;
import com.senmesh.sfile.dto.UserFileQueryDTO;
import com.senmesh.sfile.entity.UserFile;
import com.senmesh.sfile.service.UserFileService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
* 用户文件ServiceImpl
*/
@Slf4j
@Service
public class UserFileServiceImpl implements UserFileService {
@Autowired
private UserFileMapper userFileMapper;
@Autowired
private FileService fileService;
@Override
@Transactional
public UserFileDTO create(UserFileCreateDTO createDTO) {
Long parentId = createDTO.getParentId();
String parentPath = createDTO.getParentPath();
Long userId = createDTO.getUserId();
String rootType = createDTO.getRootType();
// 1. 校验 userId 和 rootType 不能为空
Assert.notNull(userId, "用户ID不能为空");
Assert.notBlank(rootType, "根类型不能为空");
// 2. 校验文件类型时fileCode不能为空
if (FileTypeEnum.isFile(createDTO.getFileType())) {
Assert.notBlank(createDTO.getFileCode(), "文件编码不能为空");
}
// 3. 处理 parentPath(性能优化:如果提供了 parentPath,直接使用,避免查询)
if (StringUtils.isBlank(parentPath) && parentId != null && parentId != 0L) {
// 如果没有提供 parentPath,但提供了 parentId,需要查询父节点获取 parentPath
UserFileDTO parentFile = this.findOne(UserFileQueryDTO.builder()
.id(parentId)
.userId(userId)
.rootType(rootType)
.build());
Assert.isTrue(FileTypeEnum.isFolder(parentFile.getFileType()), "父节点必须是文件夹");
// 获取父路径用于后续拼接
parentPath = parentFile.getPath();
}
// 如果提供了 parentPath,直接信任调用方,不做校验
// 4. 插入数据获取ID
UserFile userFile = UserFileConvert.INSTANCE.toEntity(createDTO);
userFileMapper.saveEntity(userFile);
Long newId = userFile.getId();
// 5. 构建path
String path = StringUtils.isNotBlank(parentPath)
? parentPath + CommonConstants.COMMA + newId
: String.valueOf(newId);
// 6. 更新path
userFile.setPath(path);
userFileMapper.updateEntitySelective(userFile);
// 7. 转换为DTO返回
return UserFileConvert.INSTANCE.toDto(userFile);
}
@Override
@Transactional
public ExpandUserFileDTO create(ExpandUserFileCreateDTO expandCreateDTO) {
Long userFileId = expandCreateDTO.getUserFileId();
Long userId = expandCreateDTO.getUserId();
String rootType = expandCreateDTO.getRootType();
// 1. 查询普通文件(限制rootType为普通)
UserFileDTO normalFile = this.findOne(UserFileQueryDTO.builder()
.id(userFileId)
.userId(userId)
.rootType(RootTypeEnum.NORMAL.getType())
.build());
// 3. 查询需要复制的文件列表
UserFileQueryDTO queryDTO = UserFileQueryDTO.builder()
.userId(userId)
.rootType(RootTypeEnum.NORMAL.getType())
.fileType(FileTypeEnum.FILE.getType())
.pathLike(normalFile.getPath())
.build();
queryDTO.disablePage();
List<UserFileDTO> filesToCopy = this.list(queryDTO);
Assert.notEmpty(filesToCopy, "未查询到需要处理的文件");
// 2. 创建文件夹,文件名 = 原文件名 + 当前时间
String timestamp = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
String folderName = normalFile.getFileName() + "_" + timestamp;
UserFileCreateDTO folderCreateDTO = UserFileCreateDTO.builder()
.userId(userId)
.rootType(rootType)
.fileStatus(expandCreateDTO.getFileStatus())
.fileType(FileTypeEnum.FOLDER.getType())
.fileName(folderName)
.parentId(0L)
.endTime(expandCreateDTO.getEndTime())
.accessCode(expandCreateDTO.getAccessCode())
.build();
UserFileDTO folderDTO = this.create(folderCreateDTO);
Long folderId = folderDTO.getId();
String folderPath = String.valueOf(folderId);
// 4. 在文件夹下创建文件对象
List<UserFileDTO> createdFiles = new java.util.ArrayList<>();
for (UserFileDTO fileDTO : filesToCopy) {
UserFileCreateDTO fileCreateDTO = UserFileCreateDTO.builder()
.userId(userId)
.rootType(rootType)
.fileStatus(expandCreateDTO.getFileStatus())
.fileType(FileTypeEnum.FILE.getType())
.fileName(fileDTO.getFileName())
.fileCode(fileDTO.getFileCode())
.parentId(folderId)
.parentPath(folderPath)
.build();
UserFileDTO createdFile = this.create(fileCreateDTO);
createdFiles.add(createdFile);
}
// 6. 返回结果
return ExpandUserFileDTO.builder()
.folder(folderDTO)
.files(createdFiles)
.build();
}
@Override
@Transactional
public ExpandUserFileDTO create(UserFileDTO folder, List<UserFileDTO> files) {
Long userId = folder.getUserId();
String rootType = folder.getRootType();
// 3. 创建文件夹
UserFileCreateDTO folderCreateDTO = UserFileCreateDTO.builder()
.userId(userId)
.rootType(rootType)
.fileType(FileTypeEnum.FOLDER.getType())
.fileName(folder.getFileName())
.parentId(0L)
.fileStatus(FileStatusEnum.NORMAL.getStatus())
.build();
UserFileDTO folderDTO = this.create(folderCreateDTO);
Long folderId = folderDTO.getId();
String folderPath = String.valueOf(folderId);
log.info("创建文件夹成功: folderId={}, 文件名={}", folderId, folder.getFileName());
// 4. 创建文件列表
List<UserFileDTO> createdFiles = new ArrayList<>();
for (UserFileDTO fileInfo : files) {
// 创建文件记录
UserFileCreateDTO fileCreateDTO = UserFileCreateDTO.builder()
.userId(userId)
.rootType(rootType)
.fileType(FileTypeEnum.FILE.getType())
.fileName(fileInfo.getFileName())
.fileCode(fileInfo.getFileCode())
.parentId(folderId)
.parentPath(folderPath)
.fileStatus(FileStatusEnum.NORMAL.getStatus())
.build();
UserFileDTO createdFile = this.create(fileCreateDTO);
createdFiles.add(createdFile);
}
// 5. 返回结果
return ExpandUserFileDTO.builder()
.folder(folderDTO)
.files(createdFiles)
.build();
}
@Override
@Transactional
public void modify(UserFileModifyDTO modifyDTO) {
Long id = modifyDTO.getId();
Long userId = modifyDTO.getUserId();
Long newParentId = modifyDTO.getParentId();
String rootType = modifyDTO.getRootType();
// 1. 根据id+userId+rootType查询文件是否存在
UserFileDTO existFile = this.findOne(UserFileQueryDTO.builder()
.id(id)
.userId(userId)
.rootType(rootType)
.build());
// 2. 判断是否需要更新path(parentId有值且与现存的parentId不一致)
boolean needUpdatePath = newParentId != null
&& !newParentId.equals(existFile.getParentId());
if (needUpdatePath) {
// 2.1 校验新父节点(如果不是根目录)
if (newParentId != 0L) {
UserFileDTO newParentFile = this.findOne(UserFileQueryDTO.builder()
.id(newParentId)
.userId(userId)
.rootType(rootType)
.build());
Assert.isTrue(FileTypeEnum.isFolder(newParentFile.getFileType()), "父节点必须是文件夹");
// 校验新父节点的rootType必须与当前文件相同
Assert.isTrue(existFile.getRootType().equals(newParentFile.getRootType()),
"不能将文件移动到不同类型的文件夹");
}
// 2.2 在SQL中一次性更新当前节点和所有子孙节点的path(SQL内部会限制相同userId和rootType)
userFileMapper.updatePathByParentIdChange(id, newParentId);
}
// 3. 更新文件信息
UserFile userFile = UserFileConvert.INSTANCE.toEntity(modifyDTO);
userFileMapper.updateEntitySelective(userFile);
}
@Override
@Transactional
public void modifyStatus(UserFileModifyStatusDTO modifyStatusDTO) {
Long id = modifyStatusDTO.getId();
String oldStatus = modifyStatusDTO.getOldStatus();
String newStatus = modifyStatusDTO.getNewStatus();
Long operateUserId = UserContextHolder.getLoginUserId();
// 更新文件状态及其所有子文件状态(SQL内部会校验oldStatus并限制相同userId和rootType)
userFileMapper.updateStatusWithChildren(id, oldStatus, newStatus, operateUserId);
}
@Override
@Transactional
public void modifyStatus(UserFileModifyStatusDTO modifyStatusDTO, Long userId) {
Assert.notNull(userId, "用户ID不能为空");
Long id = modifyStatusDTO.getId();
// 校验文件是否存在且属于该用户
this.findOne(UserFileQueryDTO.builder()
.id(id)
.userId(userId)
.build());
// 调用原方法修改状态
modifyStatus(modifyStatusDTO);
}
@Override
@Transactional
public void remove(Long id) {
// 删除文件及其所有子文件(SQL内部会限制相同userId和rootType)
userFileMapper.deleteFileWithChildren(id, UserContextHolder.getLoginUserId());
}
@Override
@Transactional
public void remove(Long id, Long userId) {
Assert.notNull(id, "文件ID不能为空");
Assert.notNull(userId, "用户ID不能为空");
// 校验文件是否存在且属于该用户
this.findOne(UserFileQueryDTO.builder()
.id(id)
.userId(userId)
.build());
// 删除文件及其所有子文件(SQL内部会限制相同userId和rootType)
remove(id);
}
@Override
public List<UserFileDTO> list(UserFileQueryDTO queryDTO) {
queryDTO.disablePage();
return userFileMapper.list(queryDTO);
}
@Override
public void page(UserFileQueryDTO queryDTO) {
userFileMapper.list(queryDTO);
}
@Override
public UserFileDTO find(UserFileQueryDTO queryDTO) {
List<UserFileDTO> userFileDTOs = this.list(queryDTO);
return ListUtil.first(userFileDTOs);
}
@Override
public UserFileDTO findOne(UserFileQueryDTO queryDTO) {
UserFileDTO userFileDTO = this.find(queryDTO);
Assert.notNull(userFileDTO, "文件不存在");
return userFileDTO;
}
}