This commit is contained in:
wangxk 2025-05-28 16:15:51 +08:00
commit 307a21acac
12 changed files with 513 additions and 197 deletions

View File

@ -35,18 +35,14 @@ public class TaskStatusHolder {
// 原子性检查并标记任务开始
public boolean startTaskIfAbsent(String key) {
return taskStatusMap.putIfAbsent(key, "IN_PROGRESS") == null;
}
// 标记任务结束
public void finishTask(String key) {
taskStatusMap.remove(key);
}
// 获取任务状态
public String getStatus(String key) {
return taskStatusMap.getOrDefault(key, "TASK_NOT_FOUND");

View File

@ -65,11 +65,13 @@ public class TsFilesController {
public ResponseResult getTsFilesPage(String id, String fileName, String startDate, String endDate, String keywords, String nodeId, String taskId, String childNode, Page<TsFiles> page) throws Exception {
//分页查询
int currentPage = (int) page.getCurrent();
// 先尝试从缓存获取
if(!StrUtil.isEmpty(id)){
IPage<TsFiles> cachedPage = tsFilesService.getCachedTsFilesPage(taskId, nodeId, currentPage,id);
if (cachedPage != null) {
return ResponseResult.successData(cachedPage);
// 先尝试从缓存获取 如果搜索条件为空 从Redis获取
if(StrUtil.isBlank(fileName)&&StrUtil.isBlank(keywords) &&StrUtil.isBlank(startDate) &&StrUtil.isBlank(endDate)) {
if (!StrUtil.isEmpty(id)) {
IPage<TsFiles> cachedPage = tsFilesService.getCachedTsFilesPage(taskId, nodeId, currentPage, id);
if (cachedPage != null) {
return ResponseResult.successData(cachedPage);
}
}
}
Page<TsFiles> tsfilesPage = tsFilesService.getTsFilesPage(id, fileName, startDate, endDate, keywords, nodeId, taskId, fileName, childNode, page);
@ -238,8 +240,6 @@ public class TsFilesController {
if (StrUtil.isBlank(id)) {
return ResponseResult.error("参数为空");
}
return ResponseResult.success(tsFilesService.decompressionFolder(id, decompressionPath, parentId, path));
} catch (Exception e) {
System.out.print("解压缩异常原因" + e);
@ -619,14 +619,14 @@ public class TsFilesController {
@Log(module = "实验数据管理", value = "文件自动备份!")
@PostMapping("/automaticFileBackupByIds")
@ApiOperation("自动备份本地文件到备份空间通过ID")
public ResponseResult automaticFileBackupByIds(String id) throws IOException {
public ResponseResult automaticFileBackupByIds(String id,String taskId, String nodeId) throws IOException {
if (StrUtil.isEmpty(id)) {
return ResponseResult.error("参数为空");
}
List<String> dataset = StrUtil.split(id, ",");
// 生成唯一Key
String asyncKey = taskStatusHolder.generateKeybyId(dataset);
String asyncKey = taskStatusHolder.generateKey(taskId, nodeId);
// 检查任务是否已存在
String existingStatus = taskStatusHolder.getStatus(asyncKey);
@ -638,7 +638,7 @@ public class TsFilesController {
// 原子性启动新任务
if (taskStatusHolder.startTaskIfAbsent(asyncKey)) {
// 直接异步执行并推送结果
tsFilesService.automaticFileBackupAsyncByIds(dataset);
tsFilesService.automaticFileBackupAsyncByIds(dataset,taskId,nodeId);
return ResponseResult.success("任务开始处理!");
} else {
return ResponseResult.success("任务已由其他请求启动");

View File

@ -4,6 +4,7 @@ package com.yfd.platform.modules.experimentalData.controller;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.yfd.platform.annotation.Log;
import com.yfd.platform.component.TaskStatusHolder;
import com.yfd.platform.config.ResponseResult;
@ -183,4 +184,28 @@ public class TsNodesController {
return null;
}
/**********************************
* 用途说明: 查询可不可以初始化试验任务扫描
* 参数说明 taskId 试验任务ID
* 返回值说明: com.yfd.platform.config.ResponseResult
***********************************/
@Log(module = "查询可不可以初始化试验任务扫描", value = "查询可不可以初始化试验任务扫描!")
@PostMapping("/selectTsNodesByTskeId")
@ApiOperation("查询可不可以初始化试验任务扫描")
@PreAuthorize("@el.check('del:tsnodes')")
public ResponseResult selectTsNodesByTskeId( String taskId) {
if (StrUtil.isBlank(taskId)) {
return ResponseResult.error("参数为空");
}
List<TsNodes> tsNodesList = tsNodesService.list(new QueryWrapper<TsNodes>().eq("task_id", taskId));
//如果节点不为空 就不能初始化了
if (tsNodesList.size()>0) {
return ResponseResult.success("该项目已经初始化完成,局部更新请选择节点上传文件!");
} else {
return ResponseResult.success("可以初始化!");
}
}
}

View File

@ -247,7 +247,7 @@ public interface ITsFilesService extends IService<TsFiles> {
Object compareMd5List(List<String> dataset, String nodeId, String taskId);
void automaticFileBackupAsyncByIds(List<String> dataset) throws IOException;
void automaticFileBackupAsyncByIds(List<String> dataset,String taskId, String nodeId) throws IOException;
IPage<TsFiles> getCachedTsFilesPage(String taskId, String nodeId, int currentPage,String id);
}

View File

@ -267,15 +267,14 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
tsFilesPage.setRecords(records); // 同步到 tsFilesPage
int currentPage = (int) page.getCurrent();
// 如果是前五页将结果存入Redis有效期建议30分钟
if (!StrUtil.isEmpty(id)) {
if (currentPage >= 1 && currentPage <= 5) {
String redisKey = "tsfiles_" + taskId + "_" + nodeId + "_parentId" + id + "_page_" + currentPage + "";
redisTemplate.opsForValue().set(redisKey, tsFilesPage, 2, TimeUnit.HOURS);
// 如果是前五页将结果存入Redis有效期建议30分钟如果搜索条件为空 从Redis获取
if (StrUtil.isBlank(fileName) && StrUtil.isBlank(keywords) && StrUtil.isBlank(startDate) && StrUtil.isBlank(endDate)) {
if (!StrUtil.isEmpty(id)) {
if (currentPage >= 1 && currentPage <= 5) {
String redisKey = "tsfiles_" + taskId + "_" + nodeId + "_parentId" + id + "_page_" + currentPage + "";
redisTemplate.opsForValue().set(redisKey, tsFilesPage, 2, TimeUnit.HOURS);
}
}
} else {
String redisKey = "tsfiles_" + taskId + "_" + nodeId + "_page_" + currentPage;
redisTemplate.delete(redisKey);
}
return tsFilesPage;
@ -292,27 +291,50 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
@Override
public IPage<TsFiles> getCachedTsFilesPage(String taskId, String nodeId, int currentPage, String id) {
String redisKey = "tsfiles_" + taskId + "_" + nodeId + "_parentId" + id + "_page_" + currentPage;
Object data = redisTemplate.opsForValue().get(redisKey);
try {
if (data instanceof IPage<?>) {
@SuppressWarnings("unchecked")
IPage<TsFiles> page = (IPage<TsFiles>) data;
if (data instanceof IPage<?>) {
// 由于启用了类型信息可以直接强制转换
@SuppressWarnings("unchecked")
IPage<TsFiles> page = (IPage<TsFiles>) data;
// 检查 records 是否已正确反序列化
if (page.getRecords() != null && !page.getRecords().isEmpty()
&& page.getRecords().get(0) instanceof TsFiles) {
return page;
} else {
// 处理可能的反序列化异常
throw new IllegalStateException("反序列化失败records 类型不正确");
// 类型校验
if (isValidPage(page)) {
return page;
}
}
} catch (Exception e) {
// 记录异常日志关键
LOGGER.error("反序列化失败,删除缓存: {}", redisKey, e);
// 异步删除缓存避免阻塞主流程
CompletableFuture.runAsync(() -> redisTemplate.delete(redisKey));
}
return null;
return null; // 或返回空分页对象 new Page<>(0)
// if (data instanceof IPage<?>) {
// // 由于启用了类型信息可以直接强制转换
// @SuppressWarnings("unchecked")
// IPage<TsFiles> page = (IPage<TsFiles>) data;
// // 检查 records 是否已正确反序列化
// if (page.getRecords() != null && !page.getRecords().isEmpty()
// && page.getRecords().get(0) instanceof TsFiles) {
// return page;
// } else {
// // 处理可能的反序列化异常
// throw new IllegalStateException("反序列化失败records 类型不正确");
// }
// }
// return null;
}
private boolean isValidPage(IPage<TsFiles> page) {
return page.getRecords() != null
&& !page.getRecords().isEmpty()
&& page.getRecords().get(0) instanceof TsFiles;
}
public boolean hasValidExtension(String name, List<SysDictionaryItems> sysDictionaryItems) {
// 如果传入的文件名为空返回 false
if (name == null) {
@ -417,8 +439,6 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
//todo 新增成功以后 删除redis
for (int page = 1; page <= 5; page++) {
String redisKey = "tsfiles_" + tsFiles.getTaskId() + "_" + tsFiles.getNodeId() + "_parentId" + tsFiles.getParentId() + "_page_" + page;
redisTemplate.delete(redisKey);
}
LOGGER.info("已清理缓存taskid={}, node={}, pages=1-5", tsFiles.getTaskId(), tsFiles.getNodeId());
@ -658,7 +678,7 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
@Transactional(rollbackFor = Exception.class)// 添加事务注解遇到异常时回滚
public ResponseResult updateTsFiles(TsFiles tsFiles) {
//todo 新增成功以后 删除redis
//todo 修改成功以后 删除redis
for (int page = 1; page <= 5; page++) {
String redisKey = "tsfiles_" + tsFiles.getTaskId() + "_" + tsFiles.getNodeId() + "_parentId" + tsFiles.getParentId() + "_page_" + page;
redisTemplate.delete(redisKey);
@ -939,10 +959,19 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
public String deleteTsFilesByIds(List<String> dataset, String type) {
List<TsFiles> filesList = tsFilesMapper.selectBatchIds(dataset);
//todo 删除的时候成功以后 删除redis
for (int page = 1; page <= 5; page++) {
String redisKey = "tsfiles_" + filesList.get(0).getTaskId() + "_" + filesList.get(0).getNodeId() + "_parentId" + filesList.get(0).getParentId() + "_page_" + page;
redisTemplate.delete(redisKey);
}
LOGGER.info("已清理缓存taskid={}, node={}, pages=1-5", filesList.get(0).getTaskId(), filesList.get(0).getNodeId());
int LocalSuccessCount = 0, LocalFailCount = 0, Localtotal = CollUtil.size(dataset);
//Todo 最直接的办法 循环出来 一条一条删除
for (TsFiles files : filesList) {
//判断是文件还是文件夹
if ("FOLDER".equals(files.getIsFile())) {
//如果是文件夹
@ -1233,6 +1262,15 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
try {
String finalParentId = ensureFullPathExists(compressedPath, filesList.get(0).getNodeId(), filesList.get(0).getTaskId(), path);
parentId = finalParentId;
LOGGER.info("压缩的时候删除Redis");
//todo 压缩的时候删除Redis
if (!parentId.equals("00")) {
for (int page = 1; page <= 5; page++) {
String redisKey = "tsfiles_" + filesList.get(0).getTaskId() + "_" + filesList.get(0).getNodeId() + "_parentId" + parentId + "_page_" + page;
redisTemplate.delete(redisKey);
}
}
LOGGER.info("路径已全部存在最终目录ID: {}", finalParentId);
} catch (RuntimeException e) {
LOGGER.error("路径创建失败: {}", e.getMessage());
@ -1840,6 +1878,16 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
try {
String finalParentId = ensureFullPathExists(decompressionPath, zipFileRecord.getNodeId(), zipFileRecord.getTaskId(), path);
parentId = finalParentId;
LOGGER.info("解压缩的时候删除Redis");
//todo 解压缩的时候删除Redis
if (!parentId.equals("00")) {
for (int page = 1; page <= 5; page++) {
String redisKey = "tsfiles_" + zipFileRecord.getTaskId() + "_" + zipFileRecord.getNodeId() + "_parentId" + parentId + "_page_" + page;
redisTemplate.delete(redisKey);
}
}
LOGGER.info("路径已全部存在最终目录ID: {}", finalParentId);
} catch (RuntimeException e) {
LOGGER.error("路径创建失败: {}", e.getMessage());
@ -1910,7 +1958,7 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
TsFiles fileRecord = createFileRecord(
file.getName(),
parentId,
buildFolderPath(decompressionPath),
buildFolderPath(extractPath),
zipFileRecord.getTaskId(),
zipFileRecord.getNodeId(),
zipFileRecord.getUploader(),
@ -3375,22 +3423,22 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
String asyncKey = taskStatusHolder.generateKey(taskId, nodeId);
// 无论成功失败都标记完成
taskStatusHolder.finishTask(asyncKey);
WebSocketServer.sendMessageTo("文件自动备份完成", "backups");
WebSocketServer.sendMessageTo("文件自动备份完成", "taskId_" + taskId + "_" + "nodeId_" + nodeId);
}
}
@Override
@Async("asyncExecutor")
public void automaticFileBackupAsyncByIds(List<String> dataset) throws IOException {
public void automaticFileBackupAsyncByIds(List<String> dataset, String taskId, String nodeId) throws IOException {
try {
// 执行实际备份逻辑
this.automaticFileBackupByIds(dataset);
} finally {
// 生成唯一Key
String asyncKey = taskStatusHolder.generateKeybyId(dataset);
String asyncKey = taskStatusHolder.generateKey(taskId, nodeId);
// 无论成功失败都标记完成
taskStatusHolder.finishTask(asyncKey);
WebSocketServer.sendMessageTo("文件自动备份完成", "backups");
WebSocketServer.sendMessageTo("文件自动备份完成", "taskId_" + taskId + "_" + "nodeId_" + nodeId);
}
}
@ -3891,6 +3939,16 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
*/
private void updateDatabase(String parentId, String oldpaths, String fileName,
String newPath, String targetFileName, String type) {
TsFiles tsFilesData = tsFilesMapper.selectById(parentId);
LOGGER.info("移动的时候删除Redis");
//todo 移动的时候成功以后 删除redis
if (!parentId.equals("00")) {
for (int page = 1; page <= 5; page++) {
String redisKey = "tsfiles_" + tsFilesData.getTaskId() + "_" + tsFilesData.getNodeId() + "_parentId" + parentId + "_page_" + page;
redisTemplate.delete(redisKey);
}
LOGGER.info("已清理缓存taskid={}, node={}, pages=1-5", tsFilesData.getTaskId(), tsFilesData.getNodeId());
}
// 1. 构建完整路径
String oldWorkPath = oldpaths.endsWith("/") ? oldpaths : oldpaths + "/";
String newWorkPath = newPath.endsWith("/") ? newPath : newPath + "/";
@ -4127,6 +4185,17 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
*/
private void insertDatabaseRecord(String parentId, String oldpaths, String fileName,
String newPath, String targetFileName, String type) {
TsFiles tsFilesData = tsFilesMapper.selectById(parentId);
LOGGER.info("复制的时候删除Redis");
//todo 复制的时候成功以后 删除redis
if (!parentId.equals("00")) {
for (int page = 1; page <= 5; page++) {
String redisKey = "tsfiles_" + tsFilesData.getTaskId() + "_" + tsFilesData.getNodeId() + "_parentId" + parentId + "_page_" + page;
redisTemplate.delete(redisKey);
}
LOGGER.info("已清理缓存taskid={}, node={}, pages=1-5", tsFilesData.getTaskId(), tsFilesData.getNodeId());
}
// 获取用户信息
// 获取当前用户和时间
UsernamePasswordAuthenticationToken authentication =

View File

@ -477,11 +477,23 @@ public class TsNodesServiceImpl extends ServiceImpl<TsNodesMapper, TsNodes> impl
//批量修改
LambdaUpdateWrapper<TsFiles> updateWrapper1 = new LambdaUpdateWrapper<>();
updateWrapper1.in(TsFiles::getId,dataset).isNotNull(TsFiles::getBackupPath).ne(TsFiles::getBackupPath,"").set(TsFiles::getWorkPath, "");
updateWrapper1.in(TsFiles::getId, dataset).set(TsFiles::getWorkPath, "");
tsFilesMapper.update(null, updateWrapper1);
//批量删除
// LambdaQueryWrapper<TsFiles> deleteWrapper = new LambdaQueryWrapper<>();
// deleteWrapper.in(TsFiles::getId, dataset).isNull(TsFiles::getBackupPath).eq(TsFiles::getBackupPath, "").isNull(TsFiles::getWorkPath).eq(TsFiles::getWorkPath, "");
// tsFilesMapper.delete(deleteWrapper);
//
// LambdaQueryWrapper<TsFiles> deleteWrapper = new LambdaQueryWrapper<>();
// deleteWrapper.in(TsFiles::getId, dataset)
// .and(wrapper -> wrapper.isNull(TsFiles::getBackupPath).or().eq(TsFiles::getBackupPath, ""))
// .and(wrapper -> wrapper.isNull(TsFiles::getWorkPath).or().eq(TsFiles::getWorkPath, ""));
// tsFilesMapper.delete(deleteWrapper);
LambdaQueryWrapper<TsFiles> deleteWrapper = new LambdaQueryWrapper<>();
deleteWrapper.in(TsFiles::getId,dataset).isNull(TsFiles::getBackupPath).eq(TsFiles::getBackupPath,"").isNull(TsFiles::getWorkPath).eq(TsFiles::getWorkPath,"");
deleteWrapper.in(TsFiles::getId, dataset)
.and(wrapper -> wrapper.isNull(TsFiles::getBackupPath).or().eq(TsFiles::getBackupPath, ""))
.and(wrapper -> wrapper.isNull(TsFiles::getWorkPath).or().eq(TsFiles::getWorkPath, ""));
tsFilesMapper.delete(deleteWrapper);
// //批量修改TsFiles表结构
@ -777,17 +789,13 @@ public class TsNodesServiceImpl extends ServiceImpl<TsNodesMapper, TsNodes> impl
LocalDateTime now = LocalDateTime.now();
// 转换为 Timestamp
Timestamp currentTime = Timestamp.valueOf(now);
// 查询本地文件路径根目录 E:\yun
QueryWrapper<StorageSourceConfig> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name", "filePath");
queryWrapper.eq("type", "local");
StorageSourceConfig storageSourceConfig = storageSourceConfigMapper.selectOne(queryWrapper);
//获取文件列表 这个地方path+name
File projectDir = new File(storageSourceConfig.getValue() + path + nodeName);
// 获取所有子目录
List<File> allSubDirs = new ArrayList<>();
getAllSubDirectories(projectDir, allSubDirs);
@ -899,88 +907,6 @@ public class TsNodesServiceImpl extends ServiceImpl<TsNodesMapper, TsNodes> impl
long costTimeFiles = endTimeFiles - startTimeFiles;
// 打印日志
LOGGER.info("文件表中的节点ID更新完成影响 {} 行,总耗时 {} 毫秒", affectedLevelFilesRows, costTimeFiles);
// //获取文件列表
// String absolutePath =path + "/" + nodeName + "/";
// //获取当前登录用户 上传人是当前登录人
// UsernamePasswordAuthenticationToken authentication =
// (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
// LoginUser loginuser = null;
// if (authentication != null) {
// loginuser = (LoginUser) authentication.getPrincipal();
// }
// //登录人
// String uploader = null;
// if (loginuser != null) {
// uploader = loginuser.getUsername();
// }
// // 设置当前时间
// LocalDateTime now = LocalDateTime.now();
// // 转换为 Timestamp
// Timestamp currentTime = Timestamp.valueOf(now);
//
//
//
//
//
// //获取文件列表
// FileListRequest fileListRequest = buildFileRequest(absolutePath);
// String storageKey = fileListRequest.getStorageKey();
// Integer storageId = storageSourceService.findIdByKey(storageKey);
// if (storageId == null) {
// throw new InvalidStorageSourceException("通过存储源 key 未找到存储源, key: " + storageKey);
// }
// // 处理请求参数默认值
// fileListRequest.handleDefaultValue();
// AbstractBaseFileService<?> fileService = storageSourceContext.getByStorageId(storageId);
// //todo 首先获取两个集合 对比出数据库中没有的文件夹以及文件递归增加
// List<FileItemResult> fileItemList = fileService.fileList(fileListRequest.getPath());
//
// for (FileItemResult item : fileItemList) {
// //思路就是 如果是文件夹 就查询一下 没有就新增 新的的时候递归往下走
// if (item.getType() == FileTypeEnum.FOLDER) {
// //先查询有没有 如果没有就新增 条件 节点ID 任务ID 上级ID 工作空间路径 文件名称
// LambdaQueryWrapper<TsFiles> queryWrapper1 = new LambdaQueryWrapper<>();
// queryWrapper1.eq(TsFiles::getTaskId, taskId);
// queryWrapper1.eq(TsFiles::getNodeId, nodeId);
// queryWrapper1.eq(TsFiles::getParentId, parentId);
// queryWrapper1.eq(TsFiles::getFileName, item.getName());
// queryWrapper1.eq(TsFiles::getWorkPath, item.getPath());
// TsFiles tsFiles = tsFilesMapper.selectOne(queryWrapper1);
// //如果没有 新增 并且递归
// if (tsFiles == null) {
// //保存文件
// TsFiles tsFilesData = savetsFiles(taskId, nodeId, item.getName(), item.getPath(), parentId, "FOLDER", String.valueOf(item.getSize()));
// otherLevelsData(taskId, nodeId, item.getName(), item.getPath(), tsFilesData.getId());
//
// } else {
// otherLevelsData(taskId, nodeId, item.getName(), item.getPath(), tsFiles.getId());
// }
// } else {
// //todo 如果是文件 直接新增就可以了 不需要其他的操作
// //先查询有没有 如果没有就新增 条件 节点ID 任务ID 上级ID 工作空间路径 文件名称
// LambdaQueryWrapper<TsFiles> queryWrapper2 = new LambdaQueryWrapper<>();
// queryWrapper2.eq(TsFiles::getTaskId, taskId);
// queryWrapper2.eq(TsFiles::getNodeId, nodeId);
// queryWrapper2.eq(TsFiles::getParentId, parentId);
// queryWrapper2.eq(TsFiles::getFileName, item.getName());
// queryWrapper2.eq(TsFiles::getWorkPath, item.getPath());
// TsFiles tsFiles = tsFilesMapper.selectOne(queryWrapper2);
// if (tsFiles == null) {
//
// // 获取文件大小字节
// long fileSizeInBytes = item.getSize();
// // 转换为 MB 并保留两位小数
// double fileSizeInMB = fileSizeInBytes / (1024.0 * 1024.0);
// String fileSizeFormatted = String.format("%.2f", fileSizeInMB); // 保留两位小数
//
// //保存文件信息
// TsFiles tsFilesData = savetsFiles(taskId, nodeId, item.getName(), item.getPath(), parentId, "FILE", fileSizeFormatted);
// LOGGER.info("保存文件信息:{}", item.getPath() + item.getName());
// }
// }
// }
}
/**

View File

@ -56,11 +56,13 @@ public class FilesController {
public ResponseResult getFilesPage(String fileName, String startDate, String endDate, String keywords, String nodeId,String projectId, Page<Files> page) throws Exception {
//分页查询
int currentPage = (int) page.getCurrent();
// 先尝试从缓存获取
IPage<Files> cachedFilesPage = filesService.getCachedFilesPage(projectId, nodeId,currentPage );
if (cachedFilesPage != null){
return ResponseResult.successData(cachedFilesPage);
}
// 先尝试从缓存获取 如果搜索条件为空 从Redis获取
if(StrUtil.isBlank(fileName)&&StrUtil.isBlank(keywords) &&StrUtil.isBlank(startDate) &&StrUtil.isBlank(endDate)){
IPage<Files> cachedFilesPage = filesService.getCachedFilesPage(projectId, nodeId,currentPage);
if (cachedFilesPage != null){
return ResponseResult.successData(cachedFilesPage);
}
}
Page<Files> filesPage = filesService.getFilesPage(fileName, startDate, endDate, keywords, nodeId, projectId, fileName, page);
return ResponseResult.successData(filesPage);
@ -71,6 +73,8 @@ public class FilesController {
* 用途说明新增专项文档管理-文档内容
* 参数说明
* Files 文档内容
* sourcePath 源路径
* targetPath 目标路径
* 返回值说明: com.yfd.platform.config.ResponseResult 返回新增成功或者失败
***********************************/
@Log(module = "专项文档管理", value = "新增专项文档管理文档内容!")
@ -78,7 +82,7 @@ public class FilesController {
@ApiOperation("新增专项文档管理文档内容")
@ResponseBody
@PreAuthorize("@el.check('add:files')")
public ResponseResult addFiles(@RequestBody Files files) {
public ResponseResult addFiles(@RequestBody Files files) throws Exception {
//对象不能为空
if (ObjUtil.isEmpty(files)) {
return ResponseResult.error("参数为空");

View File

@ -5,10 +5,12 @@ import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.yfd.platform.annotation.Log;
import com.yfd.platform.component.TaskStatusHolder;
import com.yfd.platform.component.WebSocketServer;
import com.yfd.platform.config.ResponseResult;
import com.yfd.platform.modules.experimentalData.domain.TsNodes;
import com.yfd.platform.modules.specialDocument.domain.Nodes;
import com.yfd.platform.modules.specialDocument.service.INodesService;
import io.swagger.annotations.ApiOperation;
@ -246,4 +248,25 @@ public class NodesController {
}
/**********************************
* 用途说明: 查询可不可以初始化专项文档扫描
* 参数说明 projectId 所属项目ID
* 返回值说明: com.yfd.platform.config.ResponseResult
***********************************/
@Log(module = "查询可不可以初始化专项文档扫描", value = "查询可不可以初始化专项文档扫描!")
@PostMapping("/selectNodesByProjectId")
@ApiOperation("查询可不可以初始化专项文档扫描")
public ResponseResult selectNodesByProjectId( String projectId) {
if (StrUtil.isBlank(projectId)) {
return ResponseResult.error("参数为空");
}
List<Nodes> nodesList = nodesService.list(new QueryWrapper<Nodes>().eq("project_id", projectId));
//如果节点不为空 就不能初始化了
if (nodesList.size()>0) {
return ResponseResult.success("该项目已经初始化完成,局部更新请选择节点上传文件!");
} else {
return ResponseResult.success("可以初始化!");
}
}
}

View File

@ -111,4 +111,16 @@ public class Files implements Serializable {
@TableField(exist = false)
private String type;
/**
* 源路径TODO 增加用于前端展示
*/
@TableField(exist = false)
private String sourcePath;
/**
* 目标路径TODO 增加用于前端展示
*/
@TableField(exist = false)
private String targetPath;
}

View File

@ -39,9 +39,11 @@ public interface IFilesService extends IService<Files> {
* 用途说明新增专项文档管理-文档内容
* 参数说明
* Files 文档内容
* sourcePath 源路径
* targetPath 目标路径
* 返回值说明: com.yfd.platform.config.ResponseResult 返回新增成功或者失败
***********************************/
ResponseResult addFiles(Files files);
ResponseResult addFiles(Files files) throws Exception;
/**********************************
* 用途说明: 修改专项文档管理-文档内容

View File

@ -3,6 +3,8 @@ package com.yfd.platform.modules.specialDocument.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
@ -20,6 +22,8 @@ import com.yfd.platform.modules.storage.chain.FileChain;
import com.yfd.platform.modules.storage.chain.FileContext;
import com.yfd.platform.modules.storage.context.StorageSourceContext;
import com.yfd.platform.modules.storage.controller.file.FileController;
import com.yfd.platform.modules.storage.mapper.StorageSourceConfigMapper;
import com.yfd.platform.modules.storage.model.entity.StorageSourceConfig;
import com.yfd.platform.modules.storage.model.enums.FileTypeEnum;
import com.yfd.platform.modules.storage.model.request.BatchDeleteRequest;
import com.yfd.platform.modules.storage.model.request.RenameFileRequest;
@ -30,6 +34,10 @@ import com.yfd.platform.modules.storage.service.base.AbstractBaseFileService;
import com.yfd.platform.system.domain.LoginUser;
import com.yfd.platform.utils.StringUtils;
import io.netty.channel.ChannelInboundHandlerAdapter;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@ -41,8 +49,17 @@ import org.springframework.transaction.annotation.Transactional;
import com.fasterxml.jackson.core.type.TypeReference;
import javax.annotation.Resource;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@ -82,6 +99,9 @@ public class FilesServiceImpl extends ServiceImpl<FilesMapper, Files> implements
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Resource
private StorageSourceConfigMapper storageSourceConfigMapper;
/**********************************
* 用途说明: 分页查询专项文档管理-文档内容
@ -189,9 +209,11 @@ public class FilesServiceImpl extends ServiceImpl<FilesMapper, Files> implements
filesPage.setRecords(records);
int currentPage = (int) page.getCurrent();
// 如果是前五页将结果存入Redis有效期建议30分钟
if (currentPage >= 1 && currentPage <= 5) {
String redisKey = "sdfiles_" + projectId + "_" + nodeId + "_page_" + currentPage;
redisTemplate.opsForValue().set(redisKey, filesPage, 2, TimeUnit.HOURS);
if (StrUtil.isBlank(fileName) && StrUtil.isBlank(keywords) && StrUtil.isBlank(startDate) && StrUtil.isBlank(endDate)) {
if (currentPage >= 1 && currentPage <= 5) {
String redisKey = "sdfiles_" + projectId + "_" + nodeId + "_page_" + currentPage;
redisTemplate.opsForValue().set(redisKey, filesPage, 2, TimeUnit.HOURS);
}
}
return filesPage;
}
@ -229,6 +251,7 @@ public class FilesServiceImpl extends ServiceImpl<FilesMapper, Files> implements
return null; // 或返回空分页对象 new Page<>(0)
}
private boolean isValidPage(IPage<Files> page) {
return page.getRecords() != null
&& !page.getRecords().isEmpty()
@ -240,15 +263,14 @@ public class FilesServiceImpl extends ServiceImpl<FilesMapper, Files> implements
* 用途说明新增专项文档管理-文档内容
* 参数说明
* Files 文档内容
* sourcePath 源路径
* targetPath 目标路径
* 返回值说明: com.yfd.platform.config.ResponseResult 返回新增成功或者失败
***********************************/
@Override
public ResponseResult addFiles(Files files) {
public ResponseResult addFiles(Files files) throws Exception {
Boolean value = false;
List<String> names = Arrays.asList(files.getFileName().split(","));
List<String> sizes = Arrays.asList(files.getFileSize().split(","));
//todo 新增成功以后 删除redis
for (int page = 1; page <= 5; page++) {
String redisKey = "sdfiles_" + files.getProjectId() + "_" + files.getNodeId() + "_page_" + page;
@ -256,73 +278,56 @@ public class FilesServiceImpl extends ServiceImpl<FilesMapper, Files> implements
}
LOGGER.info("已清理缓存project={}, node={}, pages=1-5", files.getProjectId(), files.getNodeId());
// 差不多的流程 就是提出来 然后判断 如果两个 中有一个是true
//获取当前登录用户 上传人是当前登录人
UsernamePasswordAuthenticationToken authentication =
(UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
LoginUser loginuser = (LoginUser) authentication.getPrincipal();
// 数据校验
if (names.size() != sizes.size()) {
return ResponseResult.error("文件名称和文件大小的列表长度不一致!");
LoginUser loginuser = null;
if (authentication != null) {
loginuser = (LoginUser) authentication.getPrincipal();
}
List<Files> filesToSave = new ArrayList<>();
// 设置当前时间
LocalDateTime now = LocalDateTime.now();
// 转换为 Timestamp
Timestamp currentTime = Timestamp.valueOf(now);
files.setUploadTime(currentTime);
//新增之前从处理一下源路径的数据 ,String sourcePath,String targetPath
// 查询本地文件路径根目录 E:\yun
QueryWrapper<StorageSourceConfig> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name", "filePath");
queryWrapper.eq("type", "sdlocal");
StorageSourceConfig storageSourceConfig = storageSourceConfigMapper.selectOne(queryWrapper);
//源路径 临时路径
String sourceFilePath = files.getSourcePath();
//目标路径
String targetFilePath = files.getTargetPath();
for (int i = 0; i < names.size(); i++) {
String name = names.get(i).trim();
String sizeStr = sizes.get(i).trim();
List<Files> filesToSave = this.documentUploadById(files, sourceFilePath, targetFilePath, storageSourceConfig.getValue());
// 校验文件名是否包含非法字符
if (containsInvalidCharacters(name)) {
return ResponseResult.error("文件名包含非法字符!");
}
//校验是否真正上传
String pathAndName = files.getFilePath() + "/" + name;
//准备获取文件的信息
AbstractBaseFileService<?> fileService = storageSourceContext.getByStorageKey("sdlocal");
FileItemResult fileItemResult = fileService.getFileItem(pathAndName);
if (fileItemResult == null || fileItemResult.getName() == null) {
return ResponseResult.error(name + "文件没有上传到空间,请重新选择上传!");
}
// 校验文件大小是否可以转换成数值
try {
Files files1 = new Files();
files1.setProjectId(files.getProjectId());
files1.setNodeId(files.getNodeId());
files1.setFilePath(files.getFilePath());
files1.setKeywords(files.getKeywords());
files1.setDescription(files.getDescription());
files1.setUploadTime(files.getUploadTime());
files1.setUploader(loginuser.getUsername());
files1.setFileName(name);
files1.setFileSize(sizeStr);
filesToSave.add(files1);
} catch (NumberFormatException e) {
return ResponseResult.error("文件大小必须是有效的数字!");
}
}
if (filesToSave.size() > 0) {
//循环新增
for (Files filess : filesToSave) {
if (loginuser != null) {
filess.setUploader(loginuser.getUsername());
}
//校验是否真正上传
String pathAndName = filess.getFilePath() + filess.getFileName();
//准备获取文件的信息
AbstractBaseFileService<?> fileService = storageSourceContext.getByStorageKey("sdlocal");
FileItemResult fileItemResult = fileService.getFileItem(pathAndName);
if (fileItemResult == null || fileItemResult.getName() == null) {
return ResponseResult.error(filess.getFileName() + "文件没有上传到空间,请重新选择上传!");
}
int valueAdded = filesMapper.insert(filess);
if (valueAdded == 1) {
value = true;
} else {
value = false;
}
}
} else {
value = true;
LOGGER.info("文件名相同覆盖 {}", files.getFileName());
}
File target = new File(storageSourceConfig.getValue() + String.valueOf(sourceFilePath));
//删除临时文件
deleteDirectory(target);
if (value) {
return ResponseResult.success("新增文件成功!");
} else {
@ -330,6 +335,251 @@ public class FilesServiceImpl extends ServiceImpl<FilesMapper, Files> implements
}
}
public static void deleteDirectory(File directory) throws Exception {
if (!directory.exists()) {
return; // 路径不存在直接返回
}
File[] files = directory.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
deleteDirectory(file); // 递归删除子目录
} else {
if (!file.delete()) { // 删除文件
throw new Exception("无法删除文件: " + file.getAbsolutePath());
}
}
}
}
if (!directory.delete()) { // 删除空目录
throw new Exception("无法删除目录: " + directory.getAbsolutePath());
}
}
public List<Files> documentUploadById(Files files, String sourceFilePath, String targetFilePath, String value) throws Exception {
List<Files> filesList = new ArrayList<>();
// 设置当前时间
LocalDateTime now = LocalDateTime.now();
// 转换为 Timestamp
Timestamp currentTime = Timestamp.valueOf(now);
FileListRequest sourcefileListRequest = buildFileRequest(sourceFilePath);
FileListRequest targetfileListRequest = buildFileRequest(targetFilePath);
//获取源文件列表 包含文件和文件夹
List<FileItemResult> sourceFileItemList = obtainFileItemResultData(sourcefileListRequest);
//获取目标文件列表 包含文件和文件夹
List<FileItemResult> targetFileItemList = obtainFileItemResultData(targetfileListRequest);
for (FileItemResult fileItemResult : sourceFileItemList) {
String a1 = fileItemResult.getType() + fileItemResult.getName();
FileItemResult targetFileItem = targetFileItemList.stream()
.filter(t -> (t.getType() + t.getName()).equals(a1))
.findFirst() // 获取第一个匹配项返回 Optional
.orElse(null);
// 检查对象存在
if (targetFileItem != null) {
// 循环处理文件 对比文件的MD5值
if (fileItemResult.getType() == FileTypeEnum.FILE) {
//源路径
String sourcePath = value + sourceFilePath + "/" + fileItemResult.getName();
//目标路径
String targetPath = value + targetFilePath + "/" + fileItemResult.getName();
//根据路径获取MD5值比较 如果相同则不进行操作 如果不同则重命名目标路径下的文件名称以及表结构 把现在源路径下的文件拷贝到目标路径下 新增数据库文件记录
// 计算源文件MD5
File sourceLocalFile = new File(sourcePath);
String sourceLocalMD5 = calculateMD5Data(new FileInputStream(sourceLocalFile));
// 计算源文件MD5
File targetLocalFile = new File(targetPath);
String targetLocalMD5 = calculateMD5Data(new FileInputStream(targetLocalFile));
// 如果MD5值不相同则进行操作 //如果一致 则直接复制源文件到目标文件
if (StringUtils.isNoneEmpty(sourceLocalMD5, targetLocalMD5) && !sourceLocalMD5.equals(targetLocalMD5)) {
//更改目标路径下的文件名称
String newTargetPath = renameFile(sourcePath);
//将源目录文件 复制到 目标目录文件
copyToDirectoryFile(newTargetPath, value + targetFilePath + "/");
String normalizedPath = newTargetPath
.replaceAll("[/\\\\]+", "/") // "\" 或混合分隔符统一为 "/"
.replaceAll("/+$", "");
String fileName = normalizedPath.substring(normalizedPath.lastIndexOf("/") + 1);
Files files1 = new Files();
files1.setProjectId(files.getProjectId());
files1.setNodeId(files.getNodeId());
files1.setFileName(fileName);
//todo
files1.setFilePath(targetFilePath);
long fileSizeInBytes = fileItemResult.getSize();
// 转换为 MB 并保留两位小数
double fileSizeInMB = fileSizeInBytes / (1024.0 * 1024.0);
String fileSizeFormatted = String.format("%.2f", fileSizeInMB); // 保留两位小数
files1.setFileSize(fileSizeFormatted);
files1.setUploadTime(currentTime);
filesList.add(files1);
} else {
//将源目录文件 复制到 目标目录文件 todo这个地方是覆盖
copyWithOverride(sourcePath, targetPath);
}
}
} else {
//如果为空对象不存在 则直接拷贝
String sourcePath = value + sourceFilePath;
//目标路径
String targetPath = value + targetFilePath;
//将源目录文件 复制到 目标目录文件
copyFolderWithOverride(sourcePath, targetPath);
//这个时候要新增
Files files1 = new Files();
files1.setProjectId(files.getProjectId());
files1.setNodeId(files.getNodeId());
files1.setFileName(fileItemResult.getName());
//todo
files1.setFilePath(targetFilePath);
long fileSizeInBytes = fileItemResult.getSize();
// 转换为 MB 并保留两位小数
double fileSizeInMB = fileSizeInBytes / (1024.0 * 1024.0);
String fileSizeFormatted = String.format("%.2f", fileSizeInMB); // 保留两位小数
files1.setFileSize(fileSizeFormatted);
files1.setUploadTime(currentTime);
filesList.add(files1);
}
}
return filesList;
}
/**
* 确保路径格式为以 "/" 开头和结尾例如 "/data/test/"
* 若路径为空或非法返回根路径 "/"
*/
private String ensurePathFormat(String path) {
if (StringUtils.isBlank(path)) {
return "/";
}
// 统一替换反斜杠为斜杠兼容 Windows 路径
String normalized = path.replaceAll("\\\\", "/");
// 去掉多余的斜杠
normalized = normalized.replaceAll("/{2,}", "/");
// 确保以 "/" 开头
if (!normalized.startsWith("/")) {
normalized = "/" + normalized;
}
// 确保以 "/" 结尾
if (!normalized.endsWith("/")) {
normalized += "/";
}
return normalized;
}
// 优化后的 MD5 计算方法线程安全
public String calculateMD5Data(InputStream inputStream) throws IOException, NoSuchAlgorithmException {
try (InputStream is = inputStream) { // 确保流关闭
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = is.read(buffer)) != -1) {
md.update(buffer, 0, bytesRead);
}
return Hex.encodeHexString(md.digest());
}
}
public static String renameFile(String originalPath) throws IOException {
// JSONObject jsonObject = new JSONObject()
File originalFile = new File(originalPath);
if (!originalFile.exists()) {
throw new IOException("原文件不存在: " + originalPath);
}
// 生成新文件名保留扩展名
String baseName = FilenameUtils.getBaseName(originalPath);
String extension = FilenameUtils.getExtension(originalPath);
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
String newFileName = baseName + "_" + timestamp + "." + extension;
Path originalFilePath = originalFile.toPath();
Path newFilePath = Paths.get(originalFile.getParent(), newFileName);
// 处理目标文件已存在的情况
if (java.nio.file.Files.exists(newFilePath)) {
java.nio.file.Files.delete(newFilePath); // 删除已存在的目标文件
}
// 移动文件
java.nio.file.Files.move(originalFilePath, newFilePath);
System.out.println("重命名成功: " + originalPath + "" + newFilePath.toString());
return newFilePath.toString();
// jsonObject.set("paht",newFilePath.toString());
// jsonObject.set("name",newFileName);
}
private static void copyWithOverride(String source, String target) throws IOException {
File srcFile = new File(source);
File destFile = new File(target);
// 强制覆盖已存在的文件
FileUtils.copyFile(srcFile, destFile, true);
System.out.println("成功覆盖文件: " + target);
}
private static void copyToDirectoryFile(String source, String targetDirectory) throws IOException {
File srcFile = new File(source);
File destDir = new File(targetDirectory);
// 确保目标目录存在如果不存在则创建
if (!destDir.exists()) {
destDir.mkdirs();
}
// 创建目标文件对象在目标目录下使用源文件名
File destFile = new File(destDir, srcFile.getName());
// 复制文件到目标目录
FileUtils.copyFile(srcFile, destFile);
System.out.println("成功复制文件到: " + destFile.getAbsolutePath());
}
private static void copyFolderWithOverride(String source, String target) throws IOException {
File srcDir = new File(source);
File destDir = new File(target);
// 递归拷贝文件夹及内容
FileUtils.copyDirectory(srcDir, destDir);
System.out.println("成功覆盖文件夹: " + target);
}
private List<FileItemResult> obtainFileItemResultData(FileListRequest fileListRequest) throws Exception {
String storageKey = fileListRequest.getStorageKey();
Integer storageId = storageSourceService.findIdByKey(storageKey);
if (storageId == null) {
throw new InvalidStorageSourceException("通过存储源 key 未找到存储源, key: " + storageKey);
}
// 处理请求参数默认值
fileListRequest.handleDefaultValue();
AbstractBaseFileService<?> fileService = storageSourceContext.getByStorageId(storageId);
//todo 首先获取两个集合 对比出数据库中没有的文件夹以及文件递归增加
List<FileItemResult> fileItemList = fileService.fileList(fileListRequest.getPath());
return fileItemList;
}
private FileListRequest buildFileRequest(String path) {
FileListRequest fileListRequest = new FileListRequest();
fileListRequest.setOrderBy("time");
fileListRequest.setOrderDirection("desc");
fileListRequest.setPassword("");
fileListRequest.setPath(path);
fileListRequest.setStorageKey("sdlocal");
return fileListRequest;
}
/**********************************
* 用途说明: 修改专项文档管理-文档内容
* 参数说明

View File

@ -19,6 +19,7 @@ import com.yfd.platform.modules.storage.service.base.AbstractBaseFileService;
import com.yfd.platform.system.mapper.SysDictionaryItemsMapper;
import com.yfd.platform.utils.StringUtils;
import io.netty.channel.ChannelInboundHandlerAdapter;
import net.bytebuddy.implementation.bytecode.Throw;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
@ -124,6 +125,15 @@ public class ProjectServiceImpl extends ServiceImpl<ProjectMapper, Project> impl
// 转换为 Timestamp
Timestamp currentTime = Timestamp.valueOf(now);
project.setCreateTime(currentTime);
LambdaQueryWrapper<Project> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Project::getProjectName,project.getProjectName());
Project project1 = projectMapper.selectOne(queryWrapper);
if (project1 != null){
throw new RuntimeException("专项文档管理项目已存在!");
}
int valueAdded = projectMapper.insert(project);
if (valueAdded == 1) {
@ -226,7 +236,6 @@ public class ProjectServiceImpl extends ServiceImpl<ProjectMapper, Project> impl
if (project.getProjectTime() == null || project.getProjectTime().equals("")) {
project.setProjectTime(null);
}
int valueUpdate = projectMapper.updateById(project);
if (valueUpdate == 1) {
return true;