@ -11,7 +11,6 @@ import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.AjaxResult ;
import com.ruoyi.common.core.domain.entity.BgtUser ;
import com.ruoyi.common.domain.Annex ;
import com.ruoyi.common.domain.AnnexRecord ;
import com.ruoyi.common.exception.BaseException ;
import com.ruoyi.common.service.IAnnexRecordService ;
import com.ruoyi.common.service.IAnnexService ;
@ -34,15 +33,16 @@ import java.nio.charset.Charset;
import java.time.LocalDateTime ;
import java.time.format.DateTimeFormatter ;
import java.util.ArrayList ;
import java.util.HashMap ;
import java.util.List ;
import java.util.Map ;
import java.util.concurrent.CompletableFuture ;
import java.util.regex.Pattern ;
import java.util.zip.ZipEntry ;
import java.util.zip.ZipInputStream ;
/**
* 登录验证
*
* @author ruoyi
* 压缩文件上传控制器(整合路径收集与验证逻辑)
*/
@Api ( value = " 网页模板上传 " , tags = { " 网页模板上传 " } )
@RequiredArgsConstructor ( onConstructor_ = @Autowired )
@ -57,9 +57,9 @@ public class UploadZipController {
private final IBgtUserService bgtUserService ;
private final IBgtProjectRecruitApplyService recruitApplyService ;
private static final String TEMP_DIR = " ruoyi/uploadPath/temporaryZip " ; //临时解压
private static final String SAVE_DIR = " ruoyi/uploadPath/recruit " ; //保存目录
private static final String RECORD_DIR = " ruoyi/uploadPath/record " ; //记录目录
private static final String TEMP_DIR = " ruoyi/uploadPath/temporaryZip " ; // 临时解压目录
private static final String SAVE_DIR = " ruoyi/uploadPath/recruit " ; // 最终 保存目录
private static final String RECORD_DIR = " ruoyi/uploadPath/record " ; // 记录目录
@ApiOperation ( " 上传压缩文件 " )
@PostMapping ( " /upload-zip " )
@ -71,14 +71,14 @@ public class UploadZipController {
}
String originalFilename = file . getOriginalFilename ( ) ;
if ( originalFilename = = null | | ! originalFilename . toLowerCase ( ) . endsWith ( " .zip " ) ) {
throw new BaseException ( " 上传的文件不是有效的 ZIP 文件! " ) ;
}
String[ ] split = originalFilename . split ( " _ " ) ;
if ( split . length ! = 2 | | ! split [ 0 ] . equals( recruitId. toString( ) ) ) {
throw new BaseException( " 文件名与所选择招工不匹配" ) ;
}
// String[] split = originalFilename.split("_") ;
// if (split.length != 2 || !split[0]. equals( recruitId. toString())) {
// throw new BaseException(" 文件名与所选择招工不匹配") ;
// }
BgtProjectRecruit recruit = recruitService . queryById ( recruitId ) ;
if ( recruit = = null ) {
throw new BaseException ( " 招工信息不存在! " ) ;
@ -90,79 +90,151 @@ public class UploadZipController {
}
String username = bgtUser . getUsername ( ) ;
String firstLevelFolderName = " " ;
String firstLevelFolderName = recruitId + " _ " + DigestUtil . md5Hex ( recruit . getRecruitName ( ) ) ;
try {
// 保存上传的压缩 文件
String s = DigestUtil . md5Hex ( recruit . getRecruitName ( ) ) ;
firstLevelFolderName = recruitId + " _ " + s ;
// 保存并解压ZIP 文件
File zipFile = new File ( TEMP_DIR , firstLevelFolderName + " .zip " ) ;
ensureDirectoryExists ( zipFile . getParentFile ( ) ) ;
try ( OutputStream os = new FileOutputStream ( zipFile ) ) {
os . write ( file . getBytes ( ) ) ;
}
// 解压压缩文件
File extractDir = new File ( TEMP_DIR , firstLevelFolderName ) ;
ensureDirectoryExists ( extractDir ) ;
extractZipFile ( zipFile , extractDir ) ;
// 处理解压后的 文件夹
processExtractedFolder ( extractDir , recruitId ) ;
// 收集目标路径( 仅BaoXian/HeTong目录下的最底层 文件)
// 收集路径( 返回Map和目录列表)
Object [ ] paths = collectLeafPaths ( extractDir ) ;
Map < String , String > leafFileMap = ( Map < String , String > ) paths [ 0 ] ; // 改为Map
List < String > targetDirPaths = ( List < String > ) paths [ 1 ] ;
// 将解压后的文件移动到 SAVE_DIR
move FilesTo SaveDir( extractDir , recruitId , username );
// 处理SAVE_DIR中已有文件的重命名( 关键新增逻辑)
processExisting FilesIn SaveDir( targetDirPaths , recruitId ) ;
// 异步执行 RECORD_DIR 操作和删除临时文件操作
asyncProcessRecordAndDeleteTemp ( extractDir , zipFile , recruitId , username , userId ) ;
// 修改后(新调用方式)
processExtractedFolder ( targetDirPaths , recruitId ) ; // 传递收集的targetDirPaths
// 移动文件到SAVE_DIR( 使用收集的路径)
moveFilesToSaveDir ( extractDir , recruitId , username , leafFileMap ) ;
// 异步清理临时文件(仅保留清理逻辑)
asyncDeleteTempFiles ( extractDir , zipFile ) ;
return AjaxResult . success ( " 文件上传并处理成功 " ) ;
} catch ( Exception e ) {
// 删除临时文件和文件夹
File extractDir = new File ( TEMP_DIR , firstLevelFolderName ) ;
deleteFolder ( extractDir ) ;
File zipFile = new File ( TEMP_DIR , firstLevelFolderName + " .zip " ) ;
deleteFolder ( zipFile ) ;
deleteFolder ( new File ( TEMP_DIR , firstLevelFolderName ) ) ;
deleteFolder ( new File ( TEMP_DIR , firstLevelFolderName + " .zip " ) ) ;
e . printStackTrace ( ) ;
return AjaxResult . error ( " 文件处理过程中出现错误 " ) ;
}
}
@Async
public CompletableFuture < Void > asyncProcessRecordAndDeleteTemp ( File extractDir , File zipFile , Long recruitId , String username , Long userId ) {
return CompletableFuture . runAsync ( ( ) - > {
try {
// 移动到 RECORD_DIR
DateTimeFormatter formatter = DateTimeFormatter . ofPattern ( " yyyyMMddHHmmss " ) ;
String timeStamp = LocalDateTime . now ( ) . format ( formatter ) ;
BgtProjectRecruit recruit = recruitService . queryById ( recruitId ) ;
String s = DigestUtil . md5Hex ( recruit . getRecruitName ( ) ) ;
String firstLevelFolderName = recruitId + " _ " + s ;
File recordDestDir = new File ( RECORD_DIR , firstLevelFolderName ) ;
ensureDirectoryExists ( recordDestDir ) ;
/**
* 收集最底层文件路径( 仅BaoXian/HeTong目录下的文件)
* @param extractDir 解压根目录
* @return [文件路径列表, 目录路径列表]
*/
private Object [ ] collectLeafPaths ( File extractDir ) {
Map < String , String > leafFileMap = new HashMap < > ( ) ; // key: 倒数第二层/倒数第一层/文件名, value: 完整相对路径
List < String > targetDirPaths = new ArrayList < > ( ) ;
collectLeafPathsRecursive ( extractDir , extractDir , leafFileMap , targetDirPaths ) ;
return new Object [ ] { leafFileMap , targetDirPaths } ;
}
List < AnnexRecord > annexRecordList = new ArrayList < > ( ) ;
moveFilesToRecordDirRecursively ( extractDir , recordDestDir , timeStamp , annexRecordList , recruitId , username , userId ) ;
if ( CollectionUtil . isNotEmpty ( annexRecordList ) ) {
annexRecordService . saveBatch ( annexRecordList ) ;
}
/**
* 递归收集路径(核心逻辑:验证倒数第一层目录名)
*/
private void collectLeafPathsRecursive ( File baseDir , File currentDir ,
Map < String , String > leafFileMap , List < String > targetDirPaths ) {
File [ ] files = currentDir . listFiles ( ) ;
if ( files = = null | | files . length = = 0 ) return ;
// 删除临时文件和文件夹
deleteFolder ( extractDir ) ;
if ( ! zipFile . delete ( ) ) {
System . err . println ( " 无法删除压缩文件: " + zipFile . getAbsolutePath ( ) ) ;
}
} catch ( IOException e ) {
e . printStackTrace ( ) ;
boolean hasSubDir = false ;
for ( File file : files ) {
if ( file . isDirectory ( ) ) {
hasSubDir = true ;
collectLeafPathsRecursive ( baseDir , file , leafFileMap , targetDirPaths ) ;
}
} ) ;
}
if ( ! hasSubDir ) {
for ( File file : files ) {
if ( file . isFile ( ) ) {
String relativePath = getRelativePath ( file , baseDir ) ; // 完整相对路径( 如: 张三_123/BaoXian/文件.pdf)
String [ ] pathParts = relativePath . split ( Pattern . quote ( File . separator ) ) ;
if ( pathParts . length > = 3 ) {
String secondLastDir = pathParts [ pathParts . length - 3 ] ; // 倒数第二层( 如: 张三_123)
String lastDir = pathParts [ pathParts . length - 2 ] ; // 倒数第一层( 如: BaoXian)
String fileName = pathParts [ pathParts . length - 1 ] ; // 文件名(如:文件.pdf)
if ( " BaoXian " . equals ( lastDir ) | | " HeTong " . equals ( lastDir ) ) {
// 构造key: 倒数第二层/倒数第一层/文件名( 如: 张三_123/BaoXian/文件.pdf)
String key = secondLastDir + File . separator + lastDir + File . separator + fileName ;
leafFileMap . put ( key , relativePath ) ; // 存入Map
// 收集目录路径(倒数第二层/倒数第一层/)
String dirPath = secondLastDir + File . separator + lastDir + File . separator ;
if ( ! targetDirPaths . contains ( dirPath ) ) {
targetDirPaths . add ( dirPath ) ;
}
}
}
}
}
}
}
/**
* 处理SAVE_DIR中已有文件的重命名( 添加时间戳)
*/
private void processExistingFilesInSaveDir ( List < String > targetDirPaths , Long recruitId ) throws IOException {
BgtProjectRecruit recruit = recruitService . queryById ( recruitId ) ;
String firstLevelFolder = recruitId + " _ " + DigestUtil . md5Hex ( recruit . getRecruitName ( ) ) ;
File saveBaseDir = new File ( SAVE_DIR , firstLevelFolder ) ;
for ( String dirPath : targetDirPaths ) {
File targetDir = new File ( saveBaseDir , dirPath ) ;
if ( targetDir . exists ( ) & & targetDir . isDirectory ( ) ) {
File [ ] existingFiles = targetDir . listFiles ( ) ;
if ( existingFiles ! = null ) {
for ( File existingFile : existingFiles ) {
if ( existingFile . isFile ( ) & & ! isTimestampedFile ( existingFile . getName ( ) ) ) {
String newFileName = addTimestampToFileName ( existingFile . getName ( ) ) ;
File newFile = new File ( targetDir , newFileName ) ;
if ( ! existingFile . renameTo ( newFile ) ) {
throw new IOException ( " 重命名失败: " + existingFile . getAbsolutePath ( ) ) ;
}
}
}
}
}
}
}
/**
* 判断文件名是否已包含时间戳( 格式: xxx_yyyyMMddHHmmss.xxx)
*/
private boolean isTimestampedFile ( String fileName ) {
return fileName . matches ( " ^.*_ \\ d{14} \\ ..*$ " ) ;
}
/**
* 为文件名添加时间戳( 格式: 原文件名_yyyyMMddHHmmss.扩展名)
*/
private String addTimestampToFileName ( String originalName ) {
String timestamp = LocalDateTime . now ( ) . format ( DateTimeFormatter . ofPattern ( " yyyyMMddHHmmss " ) ) ;
int lastDotIndex = originalName . lastIndexOf ( '.' ) ;
if ( lastDotIndex = = - 1 ) {
return originalName + " _ " + timestamp ;
}
return originalName . substring ( 0 , lastDotIndex ) + " _ " + timestamp + originalName . substring ( lastDotIndex ) ;
}
private void ensureDirectoryExists ( File directory ) throws IOException {
if ( ! directory . exists ( ) ) {
if ( ! directory . mkdirs ( ) ) {
throw new IOException ( " 无法创建目录: " + directory . getAbsolutePath ( ) ) ;
}
if ( ! directory . exists ( ) & & ! directory . mkdirs ( ) ) {
throw new IOException ( " 无法创建目录: " + directory . getAbsolutePath ( ) ) ;
}
}
@ -170,20 +242,12 @@ public class UploadZipController {
try ( ZipInputStream zis = new ZipInputStream ( new FileInputStream ( zipFile ) , Charset . forName ( " ISO-8859-15 " ) ) ) {
ZipEntry zipEntry ;
while ( ( zipEntry = zis . getNextEntry ( ) ) ! = null ) {
File destFile = newFile ( extractDir , zipEntry ) ;
if ( zipEntry . isDirectory ( ) ) {
// 是目录,不重命名,直接创建目录
File newDir = new File ( extractDir , zipEntry . getName ( ) ) ;
ensureDirectoryExists ( newDir ) ;
ensureDirectoryExists ( destFile ) ;
} else {
// 是文件,进行重命名
String originalFileName = zipEntry . getName ( ) ;
String newFileName = renameFile ( originalFileName ) ;
File newFile = new File ( extractDir , newFileName ) ;
// 为文件创建父目录
ensureDirectoryExists ( newFile . getParentFile ( ) ) ;
// 写入文件内容
try ( BufferedOutputStream bos = new BufferedOutputStream ( new FileOutputStream ( newFile ) ) ) {
ensureDirectoryExists ( destFile . getParentFile ( ) ) ;
try ( BufferedOutputStream bos = new BufferedOutputStream ( new FileOutputStream ( destFile ) ) ) {
byte [ ] bytesIn = new byte [ 4096 ] ;
int read ;
while ( ( read = zis . read ( bytesIn ) ) ! = - 1 ) {
@ -193,277 +257,181 @@ public class UploadZipController {
}
}
}
}
private String renameFile ( String originalFileName ) {
int lastIndexOfSlash = originalFileName . lastIndexOf ( '/' ) ;
int lastIndexOfDot = originalFileName . lastIndexOf ( '.' ) ;
// 获取文件所在的目录路径
String directoryPath = lastIndexOfSlash ! = - 1 ? originalFileName . substring ( 0 , lastIndexOfSlash + 1 ) : " " ;
// 获取文件扩展名
String fileExtension = lastIndexOfDot ! = - 1 ? originalFileName . substring ( lastIndexOfDot ) : " " ;
// 获取文件名(不包含扩展名)
String fileNameWithoutExtension = lastIndexOfSlash ! = - 1 & & lastIndexOfDot ! = - 1 ?
originalFileName . substring ( lastIndexOfSlash + 1 , lastIndexOfDot ) :
originalFileName ;
// 将 MD5 值转换为一个整数
String s = DigestUtil . md5Hex ( fileNameWithoutExtension ) ;
return directoryPath + s + fileExtension ;
originalFileName . substring ( lastIndexOfSlash + 1 , lastIndexOfDot ) : originalFileName ;
return directoryPath + DigestUtil . md5Hex ( fileNameWithoutExtension ) + fileExtension ;
}
private File newFile ( File destinationDir , ZipEntry zipEntry ) throws IOException {
File destFile = new File ( destinationDir , zipEntry . getName ( ) ) ;
String destDirPath = destinationDir . getCanonicalPath ( ) ;
String destFilePath = destFile . getCanonicalPath ( ) ;
if ( ! destFilePath . startsWith ( destDirPath + File . separator ) ) {
throw new IOException ( " Entry is outside of the target dir: " + zipEntry . getName ( ) ) ;
}
return destFile ;
}
private void processExtractedFolder ( File extractDir , Long recruitId ) {
File [ ] firstLevelFiles = extractDir . listFiles ( ) ;
private void processExtractedFolder ( List < String > targetDirPaths , Long recruitId ) {
List < Long > insuranceUserIds = new ArrayList < > ( ) ; // 存储需要删除保险附件的用户ID
List < Long > contractUserIds = new ArrayList < > ( ) ; // 存储需要删除合同附件的用户ID
List < Long > recruitApplyIds = new ArrayList < > ( ) ; // 存储关联的招工申请ID
// 保险 2
List < Long > insurance = new ArrayList < > ( ) ;
// 劳务合同 1
List < Long > contract = new ArrayList < > ( ) ;
// 招工申请Id
List < Long > recruitApplyIds = new ArrayList < > ( ) ;
if ( firstLevelFiles ! = null ) {
for ( File firstLevelFile : firstLevelFiles ) {
String firstLevelFolderName = firstLevelFile . getName ( ) ;
System . out . println ( " 第一层文件夹名称: " + firstLevelFolderName ) ;
String [ ] split = firstLevelFolderName . split ( " _ " ) ;
String card = split [ 1 ] ;
WgzUser wgzUser = wgzUserService . findByIdentityCard ( card ) ;
if ( wgzUser = = null ) {
throw new BaseException ( " 文件格式错误 " ) ;
}
BgtProjectRecruitApply oneByUserIdAndRecruitId = recruitApplyService . getOneByUserIdAndRecruitId ( wgzUser . getUserId ( ) , recruitId ) ;
if ( oneByUserIdAndRecruitId = = null ) {
throw new BaseException ( " 状态不对 " ) ;
}
recruitApplyIds . add ( oneByUserIdAndRecruitId . getId ( ) ) ;
if ( firstLevelFile . isDirectory ( ) ) {
File [ ] secondLevelFiles = firstLevelFile . listFiles ( ) ;
if ( secondLevelFiles ! = null ) {
for ( File secondLevelFile : secondLevelFiles ) {
String secondLevelFolderName = secondLevelFile . getName ( ) ;
System . out . println ( " 第二层文件夹名称: " + secondLevelFolderName ) ;
if ( secondLevelFile . isDirectory ( ) ) {
File [ ] thirdLevelFiles = secondLevelFile . listFiles ( ) ;
if ( thirdLevelFiles ! = null & & thirdLevelFiles . length > 0 ) {
// 删除数据库里的附件
if ( " BaoXian " . equals ( secondLevelFolderName ) ) {
insurance . add ( wgzUser . getUserId ( ) ) ;
}
if ( " HeTong " . equals ( secondLevelFolderName ) ) {
contract . add ( wgzUser . getUserId ( ) ) ;
}
}
}
}
}
}
// 遍历目标目录路径,解析关键信息
for ( String dirPath : targetDirPaths ) {
// dirPath格式示例: "张三_110101199001011234/BaoXian/" 或 "李四_120101199002024567/HeTong/"
String [ ] pathParts = dirPath . split ( Pattern . quote ( File . separator ) ) ;
if ( pathParts . length < 2 ) {
throw new BaseException ( " 目录路径格式异常: " + dirPath ) ;
}
// 解析倒数第二层目录( 名字_身份证号) 和类型目录( BaoXian/HeTong)
String secondLastDir = pathParts [ 0 ] ; // 格式: 名字_身份证号
String lastDir = pathParts [ 1 ] ; // 格式: BaoXian 或 HeTong( 末尾可能有斜杠, 需处理)
lastDir = lastDir . replaceAll ( " /$ " , " " ) ; // 移除末尾斜杠
// 验证类型目录是否合法
if ( ! " BaoXian " . equals ( lastDir ) & & ! " HeTong " . equals ( lastDir ) ) {
continue ; // 非目标类型目录,跳过
}
// 从"名字_身份证号"中提取身份证号
String [ ] nameIdCard = secondLastDir . split ( " _ " ) ;
if ( nameIdCard . length ! = 2 ) {
throw new BaseException ( " 倒数第二层目录格式错误(需为'名字_身份证号') : " + secondLastDir ) ;
}
String idCard = nameIdCard [ 1 ] ;
// 根据身份证号查询用户
WgzUser wgzUser = wgzUserService . findByIdentityCard ( idCard ) ;
if ( wgzUser = = null ) {
throw new BaseException ( " 身份证号对应的用户不存在: " + idCard ) ;
}
Long userId = wgzUser . getUserId ( ) ;
// 查询用户对应的招工申请记录
BgtProjectRecruitApply apply = recruitApplyService . getOneByUserIdAndRecruitId ( userId , recruitId ) ;
if ( apply = = null ) {
throw new BaseException ( " 用户未参与当前招工或申请记录不存在: 用户ID= " + userId + " , 招工ID= " + recruitId ) ;
}
Long applyId = apply . getId ( ) ;
// 根据类型分类收集数据
if ( " BaoXian " . equals ( lastDir ) ) {
insuranceUserIds . add ( userId ) ;
} else {
contractUserIds . add ( userId ) ;
}
recruitApplyIds . add ( applyId ) ;
}
if ( CollectionUtil . isNotEmpty ( insurance ) ) {
annexService . deleteByUserIdAndRecruitIdAndType ( insurance , recruitId , " 2 " , recruitApplyIds ) ;
// 执行附件删除(与原逻辑一致,仅调整入参)
if ( CollectionUtil . isNotEmpty ( insuranceUserIds ) ) {
annexService . deleteByUserIdAndRecruitIdAndType ( insuranceUserIds , recruitId , " 2 " , recruitApplyIds ) ;
}
if ( CollectionUtil . isNotEmpty ( contract ) ) {
annexService . deleteByUserIdAndRecruitIdAndType ( contract , recruitId , " 1 " , recruitApplyIds ) ;
if ( CollectionUtil . isNotEmpty ( contractUserIds ) ) {
annexService . deleteByUserIdAndRecruitIdAndType ( contractUserIds , recruitId , " 1 " , recruitApplyIds ) ;
}
}
private void moveFilesToSaveDir ( File sourceDir , Long recruitId , String username ) throws IOException {
// 移动到 SAVE_DIR
private void moveFilesToSaveDir ( File sourceDir , Long recruitId , String username , Map < String , String > leafFileMap ) throws IOException {
File saveDestDir = new File ( SAVE_DIR ) ;
ensureDirectoryExists ( saveDestDir ) ;
BgtProjectRecruit recruit = recruitService . queryById ( recruitId ) ;
String s = DigestUtil . md5Hex ( recruit . getRecruitName ( ) ) ;
String firstLevelFolderName = recruit . getId ( ) + " _ " + s ;
String firstLevelFolderName = recruit . getId ( ) + " _ " + DigestUtil . md5Hex ( recruit . getRecruitName ( ) ) ;
File firstLevelDestDir = new File ( saveDestDir , firstLevelFolderName ) ;
ensureDirectoryExists ( firstLevelDestDir ) ;
File [ ] firstLevelFiles = sourceDir . listFiles ( ) ;
List < Annex > annexList = new ArrayList < > ( ) ;
i f ( firstLevelFiles ! = null ) {
for ( File firstLevelFile : firstLevelFiles ) {
if ( firstLevelFile . isDirectory ( ) ) {
for ( Map . Entry < String , String > entry : leafFileMap . entrySet ( ) ) {
String key = entry . getKey ( ) ; // 格式:倒数第二层/倒数第一层/文件名( 如: 张三_123/BaoXian/文件.pdf)
String relativePath = entry . getValue ( ) ; // 完整相对路径( 如: 张三_123/BaoXian/文件.pdf)
String firstLevelName = firstLevelFile . getName ( ) ; //解压目录
File secondLevelDestDir = new File ( firstLevelDestDir , firstLevelName ) ; //保存目录
File [ ] secondLevelFiles = firstLevelFile . listFiles ( ) ;
if ( secondLevelFiles ! = null ) {
for ( File secondLevelfile : secondLevelFiles ) {
if ( secondLevelfile . isDirectory ( ) ) {
String secondLevelName = secondLevelfile . getName ( ) ; //解压目录
File thirdLevelDestDir = new File ( secondLevelDestDir , secondLevelName ) ; //保存目录
File [ ] thirdLevelFiles = secondLevelfile . listFiles ( ) ;
if ( thirdLevelFiles ! = null & & thirdLevelFiles . length > 0 ) {
deleteFolder ( thirdLevelDestDir ) ;
}
}
}
}
moveFilesRecursively ( firstLevelFile , firstLevelDestDir , annexList , recruitId , username ) ;
// 原文件路径:解压目录 + 相对路径
File sourceFile = new File ( sourceDir , relativePath ) ;
if ( ! sourceFile . exists ( ) | | ! sourceFile . isFile ( ) ) {
continue ;
}
}
// 拆分原文件路径为目录部分和文件名部分
String originalFileName = sourceFile . getName ( ) ; // 原文件名(含扩展名)
String parentDirPath = key . substring ( 0 , key . length ( ) - originalFileName . length ( ) ) ; // 目录路径(如 "张三_123/BaoXian/")
// 分离文件名主体和扩展名(例如 "原文件" 和 ".pdf")
int lastDotIndex = originalFileName . lastIndexOf ( '.' ) ;
String fileNameWithoutExt = ( lastDotIndex = = - 1 ) ? originalFileName : originalFileName . substring ( 0 , lastDotIndex ) ;
String fileExtension = ( lastDotIndex = = - 1 ) ? " " : originalFileName . substring ( lastDotIndex ) ;
// 生成MD5文件名( 对文件名主体进行哈希, 保留扩展名)
String md5FileName = DigestUtil . md5Hex ( fileNameWithoutExt ) ; // MD5哈希值
String newFileName = md5FileName + fileExtension ; // 新文件名(如 "d41d8cd98f00b204e9800998ecf8427e.pdf")
// 构建目标文件路径(原目录结构 + MD5文件名)
String newFilePath = parentDirPath + newFileName ;
File destFile = new File ( firstLevelDestDir , newFilePath ) ;
ensureDirectoryExists ( destFile . getParentFile ( ) ) ;
// 复制文件到目标目录
try ( InputStream in = new FileInputStream ( sourceFile ) ;
OutputStream out = new FileOutputStream ( destFile ) ) {
byte [ ] buffer = new byte [ 4096 ] ;
int length ;
while ( ( length = in . read ( buffer ) ) > 0 ) {
out . write ( buffer , 0 , length ) ;
}
}
// 记录附件信息( 更新文件名为MD5后的值)
String newRelativePath = SAVE_DIR + File . separator + newFilePath ;
newRelativePath = newRelativePath . replace ( " \\ " , " / " ) . replace ( " ruoyi/uploadPath " , " /profile " ) ;
String [ ] pathParts = relativePath . split ( Pattern . quote ( File . separator ) ) ;
String parentName = pathParts [ pathParts . length - 3 ] ; // 倒数第二层目录名(用户标识)
String dirName = pathParts [ pathParts . length - 2 ] ; // 倒数第一层目录名( BaoXian/HeTong)
String [ ] userParts = parentName . split ( " _ " ) ;
String card = userParts [ 1 ] ;
WgzUser wgzUser = wgzUserService . findByIdentityCard ( card ) ;
BgtProjectRecruitApply apply = recruitApplyService . getOneByUserIdAndRecruitId ( wgzUser . getUserId ( ) , recruitId ) ;
Annex annex = new Annex ( ) ;
annex . setAnnexName ( newFileName ) ;
annex . setAnnexUrl ( newRelativePath ) ;
annex . setAnnexType ( " BaoXian " . equals ( dirName ) ? " 2 " : " 1 " ) ;
annex . setUserType ( Constants . WGZ ) ;
annex . setUserId ( wgzUser . getUserId ( ) ) ;
annex . setRecruitId ( recruitId ) ;
annex . setRecruitApplyId ( apply . getId ( ) ) ;
annex . setCreateBy ( username ) ;
annex . setUpdateBy ( username ) ;
annexList . add ( annex ) ;
}
if ( CollectionUtil . isNotEmpty ( annexList ) ) {
annexService . saveBatch ( annexList ) ;
}
}
private void moveFilesRecursively ( File source , File destination , List < Annex > annexList , Long recruitId , String username ) throws IOException {
if ( so urc e . isDirectory ( ) ) {
File newDir = new File ( destination , so urc e . getName ( ) ) ;
ensureDirectoryExists ( newDir ) ;
File [ ] files = source . listFiles ( ) ;
if ( files ! = null ) {
for ( File file : files ) {
moveFilesRecursively ( file , newDir , annexList , recruitId , username ) ;
@Async
public CompletableFut ure < Void > asyncDeleteTempFiles ( File extractDir , File zipFile ) {
return CompletableFut ure . runAsync ( ( ) - > {
try {
// 仅清理临时解压目录和ZIP文件
deleteFolder ( extractDir ) ;
i f ( ! zipFile . delete ( ) ) {
System . err . println ( " 无法删除压缩文件: " + zipFile . getAbsolutePath ( ) ) ;
}
} catch ( Exception e ) {
e . printStackTrace ( ) ;
}
} else {
File destFile = new File ( destination , source . getName ( ) ) ;
ensureDirectoryExists ( destFile . getParentFile ( ) ) ;
try ( InputStream in = new FileInputStream ( source ) ;
OutputStream out = new FileOutputStream ( destFile ) ) {
byte [ ] buffer = new byte [ 4096 ] ;
int length ;
while ( ( length = in . read ( buffer ) ) > 0 ) {
out . write ( buffer , 0 , length ) ;
}
}
String relativePath = SAVE_DIR + File . separator + getRelativePath ( source , new File ( TEMP_DIR ) ) ;
relativePath = relativePath . replace ( " \\ " , " / " ) . replace ( " ruoyi/uploadPath " , " /profile " ) ;
System . out . println ( " 文件在项目里的相对目录: " + relativePath ) ;
// 存到数据库
String parentName = destination . getParentFile ( ) . getName ( ) ;
System . out . println ( " 上上一级文件名: " + parentName ) ;
String [ ] split = parentName . split ( " _ " ) ;
String card = split [ 1 ] ;
WgzUser wgzUser = wgzUserService . findByIdentityCard ( card ) ;
BgtProjectRecruitApply oneByUserIdAndRecruitId = recruitApplyService . getOneByUserIdAndRecruitId ( wgzUser . getUserId ( ) , recruitId ) ;
String name = destination . getName ( ) ;
System . out . println ( " 上一级文件名: " + name ) ;
String type = " " ;
if ( " BaoXian " . equals ( name ) ) {
type = " 2 " ;
}
if ( " HeTong " . equals ( name ) ) {
type = " 1 " ;
}
Annex annex = new Annex ( ) ;
annex . setAnnexName ( destFile . getName ( ) ) ;
annex . setAnnexUrl ( relativePath ) ;
annex . setAnnexType ( type ) ;
annex . setUserType ( Constants . WGZ ) ;
annex . setUserId ( wgzUser . getUserId ( ) ) ;
annex . setRecruitId ( recruitId ) ;
annex . setRecruitApplyId ( oneByUserIdAndRecruitId . getId ( ) ) ;
annex . setCreateBy ( username ) ;
annex . setUpdateBy ( username ) ;
annexList . add ( annex ) ;
}
}
private void moveFilesToRecordDirRecursively ( File source , File destination , String timeStamp , List < AnnexRecord > annexRecordList , Long recruitId , String username , Long userId ) throws IOException {
if ( source . isDirectory ( ) ) {
String folderName = source . getName ( ) ;
String [ ] parts = folderName . split ( " _ " ) ;
if ( parts . length > 0 & & parts [ 0 ] . matches ( " \\ d+ " ) & & parts . length > 1 & & parts [ 0 ] . equals ( recruitId . toString ( ) ) ) {
// 如果parts第一部分是数字并且长度大于1, 跳过这一级目录的创建, 直接处理下一级目录
File [ ] files = source . listFiles ( ) ;
if ( files ! = null ) {
for ( File file : files ) {
moveFilesToRecordDirRecursively ( file , destination , timeStamp , annexRecordList , recruitId , username , userId ) ;
}
}
} else {
File newDir = new File ( destination , folderName ) ;
ensureDirectoryExists ( newDir ) ;
File [ ] files = source . listFiles ( ) ;
if ( files ! = null ) {
for ( File file : files ) {
moveFilesToRecordDirRecursively ( file , newDir , timeStamp , annexRecordList , recruitId , username , userId ) ;
}
}
}
} else {
// 获取文件名和扩展名
String fileName = source . getName ( ) ;
int dotIndex = fileName . lastIndexOf ( '.' ) ;
String nameWithoutExtension = dotIndex = = - 1 ? fileName : fileName . substring ( 0 , dotIndex ) ;
String extension = dotIndex = = - 1 ? " " : fileName . substring ( dotIndex ) ;
// 在文件名后面添加时间戳
String newFileName = nameWithoutExtension + " _ " + timeStamp + extension ;
File destFile = new File ( destination , newFileName ) ;
ensureDirectoryExists ( destFile . getParentFile ( ) ) ;
try ( InputStream in = new FileInputStream ( source ) ;
OutputStream out = new FileOutputStream ( destFile ) ) {
byte [ ] buffer = new byte [ 4096 ] ;
int length ;
while ( ( length = in . read ( buffer ) ) > 0 ) {
out . write ( buffer , 0 , length ) ;
}
}
// 记录文件信息到数据库
String relativePath = RECORD_DIR + File . separator + getRelativePath ( destFile , new File ( RECORD_DIR ) ) ;
relativePath = relativePath . replace ( " \\ " , " / " ) . replace ( " ruoyi/uploadPath " , " /profile " ) ;
String parentName = destination . getParentFile ( ) . getName ( ) ;
String [ ] split = parentName . split ( " _ " ) ;
String card = split [ 1 ] ;
WgzUser wgzUser = wgzUserService . findByIdentityCard ( card ) ;
BgtProjectRecruitApply oneByUserIdAndRecruitId = recruitApplyService . getOneByUserIdAndRecruitId ( wgzUser . getUserId ( ) , recruitId ) ;
String name = destination . getName ( ) ;
String type = " " ;
if ( " BaoXian " . equals ( name ) ) {
type = " 2 " ;
}
if ( " HeTong " . equals ( name ) ) {
type = " 1 " ;
}
AnnexRecord annexRecord = new AnnexRecord ( ) ;
annexRecord . setAnnexName ( newFileName ) ;
annexRecord . setAnnexUrl ( relativePath ) ;
annexRecord . setAnnexType ( type ) ;
annexRecord . setUserType ( Constants . WGZ ) ;
annexRecord . setUserId ( wgzUser . getUserId ( ) ) ;
annexRecord . setRecruitId ( recruitId ) ;
annexRecord . setRecruitApplyId ( oneByUserIdAndRecruitId . getId ( ) ) ;
annexRecord . setCreateBy ( username ) ;
annexRecord . setUpdateBy ( username ) ;
annexRecord . setCreateUserId ( userId ) ;
annexRecord . setCreateUserType ( Constants . BGT ) ;
annexRecordList . add ( annexRecord ) ;
}
} ) ;
}
private String getRelativePath ( File file , File baseDir ) {