Files
senmesh-zc/src/main/java/com/senmesh/sfile/service/impl/UserFileServiceImpl.java

442 lines
15 KiB
Java
Raw Normal View History

2025-12-31 18:10:10 +08:00
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.config.FileProperties;
2026-01-05 16:35:46 +08:00
import com.senmesh.component.file.service.FileService;
2026-01-06 16:37:39 +08:00
import com.senmesh.component.file.util.FileUtils;
import com.senmesh.sfile.constant.FileLocationEnum;
import com.senmesh.sfile.constant.FileRecordTypeEnum;
2026-01-05 16:35:46 +08:00
import com.senmesh.sfile.constant.FileStatusEnum;
2025-12-31 18:10:10 +08:00
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;
2026-01-05 16:35:46 +08:00
import com.senmesh.sfile.dto.ExpandUserFileDTO;
2025-12-31 18:10:10 +08:00
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.dto.UserFileRecordDTO;
import com.senmesh.sfile.dto.UserFileRecordQueryDTO;
2026-01-08 14:33:33 +08:00
import com.senmesh.sfile.dto.UserFileStatisticsDTO;
2025-12-31 18:10:10 +08:00
import com.senmesh.sfile.entity.UserFile;
import com.senmesh.sfile.service.UserFileService;
import com.google.common.collect.Sets;
2026-01-05 16:35:46 +08:00
import lombok.extern.slf4j.Slf4j;
2025-12-31 18:10:10 +08:00
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
2026-01-05 16:35:46 +08:00
import java.io.InputStream;
2025-12-31 18:10:10 +08:00
import java.text.SimpleDateFormat;
2026-01-05 16:35:46 +08:00
import java.util.ArrayList;
import java.util.Collections;
2025-12-31 18:10:10 +08:00
import java.util.Date;
import java.util.List;
2026-01-05 16:35:46 +08:00
import java.util.Map;
import java.util.Set;
2025-12-31 18:10:10 +08:00
/**
* 用户文件ServiceImpl
*/
2026-01-05 16:35:46 +08:00
@Slf4j
2025-12-31 18:10:10 +08:00
@Service
public class UserFileServiceImpl implements UserFileService {
@Autowired
private UserFileMapper userFileMapper;
2026-01-05 16:35:46 +08:00
@Autowired
private FileService fileService;
@Autowired
private FileProperties fileProperties;
2025-12-31 18:10:10 +08:00
@Override
@Transactional
2026-01-05 16:35:46 +08:00
public UserFileDTO create(UserFileCreateDTO createDTO) {
2025-12-31 18:10:10 +08:00
Long parentId = createDTO.getParentId();
String parentPath = createDTO.getParentPath();
Long userId = createDTO.getUserId();
String rootType = createDTO.getRootType();
2026-01-05 16:35:46 +08:00
// 1. 校验 userId 和 rootType 不能为空
Assert.notNull(userId, "用户ID不能为空");
Assert.notBlank(rootType, "根类型不能为空");
// 2. 校验文件类型时fileCode不能为空
2025-12-31 18:10:10 +08:00
if (FileTypeEnum.isFile(createDTO.getFileType())) {
Assert.notBlank(createDTO.getFileCode(), "文件编码不能为空");
}
2026-01-05 16:35:46 +08:00
// 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();
2025-12-31 18:10:10 +08:00
}
2026-01-05 16:35:46 +08:00
// 如果提供了 parentPath,直接信任调用方,不做校验
2025-12-31 18:10:10 +08:00
2026-01-05 16:35:46 +08:00
// 4. 插入数据获取ID
2025-12-31 18:10:10 +08:00
UserFile userFile = UserFileConvert.INSTANCE.toEntity(createDTO);
userFileMapper.saveEntity(userFile);
Long newId = userFile.getId();
2026-01-05 16:35:46 +08:00
// 5. 构建path
2025-12-31 18:10:10 +08:00
String path = StringUtils.isNotBlank(parentPath)
? parentPath + CommonConstants.COMMA + newId
: String.valueOf(newId);
2026-01-05 16:35:46 +08:00
// 6. 更新path
2025-12-31 18:10:10 +08:00
userFile.setPath(path);
userFileMapper.updateEntitySelective(userFile);
2026-01-05 16:35:46 +08:00
// 7. 转换为DTO返回
return UserFileConvert.INSTANCE.toDto(userFile);
2025-12-31 18:10:10 +08:00
}
@Override
@Transactional
2026-01-05 16:35:46 +08:00
public ExpandUserFileDTO create(ExpandUserFileCreateDTO expandCreateDTO) {
2025-12-31 18:10:10 +08:00
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());
// 2. 创建文件夹,文件名 = 原文件名 + 当前时间
String timestamp = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
2026-01-06 16:37:39 +08:00
String baseFileName = FileTypeEnum.isFile(normalFile.getFileType()) ? FileUtils.getFileNameNoSuffix(normalFile.getFileName()) : normalFile.getFileName();
String folderName = baseFileName + "_" + timestamp;
2025-12-31 18:10:10 +08:00
UserFileCreateDTO folderCreateDTO = UserFileCreateDTO.builder()
.userId(userId)
.rootType(rootType)
2026-01-05 16:35:46 +08:00
.fileStatus(expandCreateDTO.getFileStatus())
2025-12-31 18:10:10 +08:00
.fileType(FileTypeEnum.FOLDER.getType())
.fileName(folderName)
.parentId(0L)
.endTime(expandCreateDTO.getEndTime())
.accessCode(expandCreateDTO.getAccessCode())
.build();
2026-01-05 16:35:46 +08:00
UserFileDTO folderDTO = this.create(folderCreateDTO);
Long folderId = folderDTO.getId();
2025-12-31 18:10:10 +08:00
String folderPath = String.valueOf(folderId);
2026-01-06 16:37:39 +08:00
// 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);
2025-12-31 18:10:10 +08:00
// 4. 在文件夹下创建文件对象
2026-01-05 16:35:46 +08:00
List<UserFileDTO> createdFiles = new java.util.ArrayList<>();
2025-12-31 18:10:10 +08:00
for (UserFileDTO fileDTO : filesToCopy) {
UserFileCreateDTO fileCreateDTO = UserFileCreateDTO.builder()
.userId(userId)
.rootType(rootType)
2026-01-05 16:35:46 +08:00
.fileStatus(expandCreateDTO.getFileStatus())
2025-12-31 18:10:10 +08:00
.fileType(FileTypeEnum.FILE.getType())
.fileName(fileDTO.getFileName())
.fileCode(fileDTO.getFileCode())
2026-01-05 16:35:46 +08:00
.parentId(folderId)
2025-12-31 18:10:10 +08:00
.parentPath(folderPath)
.build();
2026-01-05 16:35:46 +08:00
UserFileDTO createdFile = this.create(fileCreateDTO);
createdFiles.add(createdFile);
2025-12-31 18:10:10 +08:00
}
2026-01-05 16:35:46 +08:00
// 6. 返回结果
return ExpandUserFileDTO.builder()
.folder(folderDTO)
.files(createdFiles)
.build();
2025-12-31 18:10:10 +08:00
}
2026-01-05 16:35:46 +08:00
@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)
2026-01-08 14:33:33 +08:00
.fileStatus(folder.getFileStatus())
2026-01-05 16:35:46 +08:00
.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)
2026-01-08 14:33:33 +08:00
.fileStatus(folder.getFileStatus())
2026-01-05 16:35:46 +08:00
.build();
UserFileDTO createdFile = this.create(fileCreateDTO);
createdFiles.add(createdFile);
}
// 5. 返回结果
return ExpandUserFileDTO.builder()
.folder(folderDTO)
.files(createdFiles)
.build();
}
2025-12-31 18:10:10 +08:00
@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)
2026-01-24 23:22:21 +08:00
userFileMapper.updatePathByParentIdChange(id, newParentId, modifyDTO.getOperateUserId());
2025-12-31 18:10:10 +08:00
}
// 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);
}
2026-01-24 23:22:21 +08:00
@Override
@Transactional
public void removeByIds(List<Long> ids, Long userId) {
Assert.notEmpty(ids, "文件ID列表不能为空");
Assert.notNull(userId, "用户ID不能为空");
// 循环删除每个文件
for (Long id : ids) {
remove(id, userId);
}
}
2025-12-31 18:10:10 +08:00
@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;
}
2026-01-08 14:33:33 +08:00
@Override
public List<UserFileStatisticsDTO> statisticsByCategory(UserFileQueryDTO queryDTO) {
queryDTO.disablePage();
return userFileMapper.statisticsByCategory(queryDTO);
}
@Override
public void pageRecordByPhone(UserFileRecordQueryDTO queryDTO) {
Assert.isTrue(UserContextHolder.isSuperior(), "当前用户无操作权限");
Assert.notBlank(queryDTO.getPhone(), "手机号不能为空");
queryDTO.setPhone(queryDTO.getPhone().trim());
if (queryDTO.getRecordType() != null) {
Assert.notNull(FileRecordTypeEnum.getByType(queryDTO.getRecordType()),
"记录类型不合法,仅支持1上传/2发送/3接收");
}
queryDTO.setRootTypeIn(resolveRootTypes(queryDTO.getRecordType()));
List<UserFileRecordDTO> records = userFileMapper.listRecordByPhone(queryDTO);
fillRecordType(records);
}
/**
* 记录类型 -> root_type 条件
* 上传记录固定为普通(1);发送/接收方向与本端部署位置有关:
* 内网侧发送为内穿外(2)、接收为外传内(3);外网侧相反
*
* @param recordType 记录类型,为空查全部
*/
private Set<String> resolveRootTypes(Integer recordType) {
if (recordType == null) {
return Sets.newHashSet(RootTypeEnum.NORMAL.getType(), getSendRootType(), getReceiveRootType());
}
switch (FileRecordTypeEnum.getByType(recordType)) {
case UPLOAD:
return Collections.singleton(RootTypeEnum.NORMAL.getType());
case SEND:
return Collections.singleton(getSendRootType());
case RECEIVE:
return Collections.singleton(getReceiveRootType());
default:
return resolveRootTypes(null);
}
}
/**
* 本端发送方向:内网侧为内穿外(2),外网侧为外传内(3),与TransferUserFileServiceImpl保持一致
*/
private String getSendRootType() {
return FileLocationEnum.isInternal(fileProperties.getTransfer().getLocation())
? RootTypeEnum.INTERNAL_TO_EXTERNAL.getType()
: RootTypeEnum.EXTERNAL_TO_INTERNAL.getType();
}
/**
* 本端接收方向:内网侧为外传内(3),外网侧为内穿外(2),与TransferFileMonitorServiceImpl保持一致
*/
private String getReceiveRootType() {
return FileLocationEnum.isInternal(fileProperties.getTransfer().getLocation())
? RootTypeEnum.EXTERNAL_TO_INTERNAL.getType()
: RootTypeEnum.INTERNAL_TO_EXTERNAL.getType();
}
/**
* 按本端部署位置回填记录类型与描述
*/
private void fillRecordType(List<UserFileRecordDTO> records) {
if (records == null || records.isEmpty()) {
return;
}
for (UserFileRecordDTO record : records) {
String rootType = record.getRootType();
if (RootTypeEnum.NORMAL.getType().equals(rootType)) {
record.setRecordType(FileRecordTypeEnum.UPLOAD.getType());
} else if (getSendRootType().equals(rootType)) {
record.setRecordType(FileRecordTypeEnum.SEND.getType());
} else if (getReceiveRootType().equals(rootType)) {
record.setRecordType(FileRecordTypeEnum.RECEIVE.getType());
}
if (record.getRecordType() != null) {
record.setRecordTypeDesc(FileRecordTypeEnum.getByType(record.getRecordType()).getDesc());
} else {
RootTypeEnum rootTypeEnum = RootTypeEnum.getByType(rootType);
record.setRecordTypeDesc(rootTypeEnum == null ? rootType : rootTypeEnum.getDesc());
}
}
}
2025-12-31 18:10:10 +08:00
}