package com.ruoyi.other.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.other.mapper.TempFileMapper; import com.ruoyi.other.pojo.TempFile; import com.ruoyi.other.service.TempFileService; import com.ruoyi.sales.mapper.CommonFileMapper; import com.ruoyi.sales.pojo.CommonFile; import lombok.extern.slf4j.Slf4j; import org.apache.catalina.util.URLEncoder; import org.apache.commons.io.FilenameUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.List; import java.util.UUID; @Service @Slf4j public class TempFileServiceImpl extends ServiceImpl implements TempFileService { @Autowired private TempFileMapper tempFileMapper; @Autowired private CommonFileMapper commonFileMapper; @Value("${file.upload-dir}") private String uploadDir; @Value("${file.temp-dir}") private String tempDir; // 上传到临时目录 @Override public TempFile uploadFile(MultipartFile file,Integer type) throws IOException { // 1. 生成临时文件ID和路径 String tempId = UUID.randomUUID().toString(); String originalFilename = file.getOriginalFilename(); if(originalFilename == null) throw new IOException("文件名不能为空"); // URLEncoder urlEncoder = new URLEncoder(); // String encodedFilename = urlEncoder.encode(originalFilename, StandardCharsets.UTF_8); // encodedFilename = encodedFilename.replaceAll("%2E","."); // Path tempFilePath = Paths.get(tempDir, tempId + "_" + encodedFilename); Path tempFilePath = Paths.get(tempDir, tempId + "_" + file.getOriginalFilename()); // 2. 确保目录存在 Path parentDir = tempFilePath.getParent(); if (parentDir != null) { Files.createDirectories(parentDir); // 递归创建目录 } // 3. 保存文件到临时目录 file.transferTo(tempFilePath.toFile()); // 4. 保存临时文件记录 TempFile tempFileRecord = new TempFile(); tempFileRecord.setTempId(tempId); tempFileRecord.setOriginalName(file.getOriginalFilename()); tempFileRecord.setTempPath(tempFilePath.toString()); tempFileRecord.setExpireTime(LocalDateTime.now().plusHours(2)); // 2小时后过期 tempFileRecord.setType(type); tempFileMapper.insert(tempFileRecord); return tempFileRecord; } /** * 将临时文件迁移到正式目录 * * @param businessId 业务ID(销售台账ID) * @param tempFileIds 临时文件ID列表 * @throws IOException 文件操作异常 */ public void migrateTempFilesToFormal(Long businessId, List tempFileIds, Integer fileType) throws IOException { if (com.baomidou.mybatisplus.core.toolkit.CollectionUtils.isEmpty(tempFileIds)) { return; } // 构建正式目录路径(按业务类型和日期分组) String formalDir = uploadDir + LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE); Path formalDirPath = Paths.get(formalDir); // 确保正式目录存在(递归创建) if (!Files.exists(formalDirPath)) { Files.createDirectories(formalDirPath); } for (String tempFileId : tempFileIds) { // 查询临时文件记录 TempFile tempFile = tempFileMapper.selectById(tempFileId); if (tempFile == null) { log.warn("临时文件不存在,跳过处理: {}", tempFileId); continue; } // 构建正式文件名(包含业务ID和时间戳,避免冲突) String originalFilename = tempFile.getOriginalName(); String fileExtension = FilenameUtils.getExtension(originalFilename); String formalFilename = businessId + "_" + System.currentTimeMillis() + "_" + UUID.randomUUID().toString().substring(0, 8) + (StringUtils.hasText(fileExtension) ? "." + fileExtension : ""); Path formalFilePath = formalDirPath.resolve(formalFilename); try { // 执行文件迁移(使用原子操作确保安全性) Files.move( Paths.get(tempFile.getTempPath()), formalFilePath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE ); log.info("文件迁移成功: {} -> {}", tempFile.getTempPath(), formalFilePath); // 更新文件记录(关联到业务ID) CommonFile fileRecord = new CommonFile(); fileRecord.setCommonId(businessId); fileRecord.setName(originalFilename); fileRecord.setUrl(formalFilePath.toString()); fileRecord.setCreateTime(LocalDateTime.now()); fileRecord.setType(fileType); commonFileMapper.insert(fileRecord); // 删除临时文件记录 tempFileMapper.deleteById(tempFile); log.info("文件迁移成功: {} -> {}", tempFile.getTempPath(), formalFilePath); } catch (IOException e) { log.error("文件迁移失败: {}", tempFile.getTempPath(), e); // 可选择回滚事务或记录失败文件 throw new IOException("文件迁移异常", e); } } } @Scheduled(cron = "0 0 3 * * ?") // 每天凌晨3点执行 public void cleanupExpiredTempFiles() { LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.lt(TempFile::getExpireTime, LocalDateTime.now()); // expireTime < 当前时间 List expiredFiles = tempFileMapper.selectList(wrapper); for (TempFile file : expiredFiles) { try { // 删除物理文件 Files.deleteIfExists(Paths.get(file.getTempPath())); // 删除数据库记录 tempFileMapper.deleteById(file); log.info("已清理过期临时文件: {}", file.getTempPath()); } catch (IOException e) { log.error("删除文件失败: {}", file.getTempPath(), e); // 可选择记录失败日志或重试 } } log.info("过期临时文件清理完成,共清理 {} 个文件", expiredFiles.size()); } }