From 66c187f3de2c39a71e9bd06218fc8717a44524d7 Mon Sep 17 00:00:00 2001 From: dk <1260500659@qq.com> Date: Mon, 6 Jul 2026 16:50:52 +0800 Subject: [PATCH] =?UTF-8?q?feat(file=E3=80=81requirement):=20=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E6=96=87=E4=BB=B6=E6=9C=8D=E5=8A=A1=E5=92=8C=E9=9C=80?= =?UTF-8?q?=E6=B1=82=E7=AE=A1=E7=90=86=E5=8A=9F=E8=83=BD=20-=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E6=8C=89=E9=85=8D=E7=BD=AEID=E5=92=8C=E8=B7=AF?= =?UTF-8?q?=E5=BE=84=E8=8E=B7=E5=8F=96=E6=96=87=E4=BB=B6=E7=9A=84=E6=96=B9?= =?UTF-8?q?=E6=B3=95=20-=20=E5=AE=9E=E7=8E=B0=E6=96=87=E4=BB=B6=E4=B8=8D?= =?UTF-8?q?=E5=AD=98=E5=9C=A8=E6=97=B6=E7=9A=84404=E5=93=8D=E5=BA=94?= =?UTF-8?q?=E5=A4=84=E7=90=86=20-=20=E4=BC=98=E5=8C=96=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E8=B7=AF=E5=BE=84=E7=94=9F=E6=88=90=E9=80=BB?= =?UTF-8?q?=E8=BE=91=EF=BC=8C=E4=BD=BF=E7=94=A8UUID=E7=A1=AE=E4=BF=9D?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=90=8D=E5=AE=89=E5=85=A8=E6=80=A7=20-=20?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=9C=80=E6=B1=82=E6=89=A7=E8=A1=8C=E9=80=89?= =?UTF-8?q?=E9=A1=B9=E6=9F=A5=E8=AF=A2=E6=8E=A5=E5=8F=A3=E5=92=8C=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=20-=20=E6=94=AF=E6=8C=81=E6=8E=92=E9=99=A4=E7=89=B9?= =?UTF-8?q?=E5=AE=9A=E7=8A=B6=E6=80=81=E7=A0=81=E7=9A=84=E9=9C=80=E6=B1=82?= =?UTF-8?q?=E5=88=86=E9=A1=B5=E6=9F=A5=E8=AF=A2=20-=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E9=9C=80=E6=B1=82=E6=A8=A1=E5=9D=97=E8=BF=87=E6=BB=A4=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E5=B9=B6=E6=8F=90=E5=8F=96=E5=85=AC=E5=85=B1=E6=96=B9?= =?UTF-8?q?=E6=B3=95=20-=20=E4=BF=AE=E5=A4=8DS3=E5=AE=A2=E6=88=B7=E7=AB=AF?= =?UTF-8?q?=E5=AF=B9=E8=B1=A1=E9=94=AE=E8=A7=A3=E6=9E=90=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../project/ProjectRequirementController.java | 7 +++ .../project/ProjectRequirementMapper.java | 21 +++++++-- .../project/ProjectRequirementService.java | 2 + .../ProjectRequirementServiceImpl.java | 47 +++++++++++++------ .../controller/admin/file/FileController.java | 9 +++- .../file/core/client/s3/S3FileClient.java | 6 +-- .../system/service/file/FileService.java | 9 ++++ .../system/service/file/FileServiceImpl.java | 46 ++++++++---------- 8 files changed, 98 insertions(+), 49 deletions(-) diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/project/ProjectRequirementController.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/project/ProjectRequirementController.java index 57116aa..750e9cb 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/project/ProjectRequirementController.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/project/ProjectRequirementController.java @@ -76,6 +76,13 @@ public class ProjectRequirementController { return success(requirementService.getRequirementTree(pageReqVO)); } + @GetMapping("/execution-options") + @Operation(summary = "获取执行可关联的项目需求下拉选项") + public CommonResult> getExecutionRequirementOptions( + @Valid ProjectRequirementPageReqVO pageReqVO) { + return success(requirementService.getExecutionRequirementOptions(pageReqVO)); + } + @PostMapping("/change-status") @Operation(summary = "变更需求状态") public CommonResult changeRequirementStatus(@Valid @RequestBody ProjectRequirementStatusActionReqVO reqVO) { diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/project/ProjectRequirementMapper.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/project/ProjectRequirementMapper.java index 0e15379..049f59e 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/project/ProjectRequirementMapper.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/project/ProjectRequirementMapper.java @@ -22,20 +22,28 @@ public interface ProjectRequirementMapper extends BaseMapperX selectPage(ProjectRequirementPageReqVO reqVO) { - return selectPage(reqVO, buildQueryWrapper(reqVO)); + return selectPage(reqVO, buildQueryWrapper(reqVO, null)); + } + + /** + * 分页查询需求列表,并排除指定状态集合。 + */ + default PageResult selectPage(ProjectRequirementPageReqVO reqVO, Collection excludeStatusCodes) { + return selectPage(reqVO, buildQueryWrapper(reqVO, excludeStatusCodes)); } /** * 查询所有符合条件的需求列表,用于树查询 */ default List selectList(ProjectRequirementPageReqVO reqVO) { - return selectList(buildQueryWrapper(reqVO)); + return selectList(buildQueryWrapper(reqVO, null)); } /** * 构建查询条件 */ - private LambdaQueryWrapperX buildQueryWrapper(ProjectRequirementPageReqVO reqVO) { + private LambdaQueryWrapperX buildQueryWrapper(ProjectRequirementPageReqVO reqVO, + Collection excludeStatusCodes) { LambdaQueryWrapperX queryWrapper = new LambdaQueryWrapperX<>(); if (StringUtils.hasText(reqVO.getTitle())) { queryWrapper.like(ProjectRequirementDO::getTitle, reqVO.getTitle()); @@ -49,8 +57,11 @@ public interface ProjectRequirementMapper extends BaseMapperX getRequirementTree(ProjectRequirementPageReqVO pageReqVO); + PageResult getExecutionRequirementOptions(ProjectRequirementPageReqVO pageReqVO); + void changeRequirementStatus(ProjectRequirementStatusActionReqVO reqVO); void changeRequirementStatusForReview(ProjectRequirementStatusActionReqVO reqVO); diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/project/ProjectRequirementServiceImpl.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/project/ProjectRequirementServiceImpl.java index 4d3fc84..5e7d387 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/project/ProjectRequirementServiceImpl.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/project/ProjectRequirementServiceImpl.java @@ -255,21 +255,9 @@ public class ProjectRequirementServiceImpl implements ProjectRequirementService @Override @CheckObjectPermission(objectType = PROJECT_OBJECT_TYPE, objectId = "#pageReqVO.projectId", accessible = true) public PageResult getRequirementPage(ProjectRequirementPageReqVO pageReqVO) { - if (pageReqVO.getModuleId() != null) { - if (isAllRequirementsModule(pageReqVO.getModuleId())) { - pageReqVO.setModuleId(null); - } else { - List moduleIds = getAllModuleIdsWithChildren(pageReqVO.getModuleId(), pageReqVO.getProjectId()); - pageReqVO.setModuleIds(moduleIds); - pageReqVO.setModuleId(null); - } - } + normalizePageModuleFilter(pageReqVO); PageResult pageResult = requirementMapper.selectPage(pageReqVO); - Map statusModelMap = getStatusModelMap(); - List list = pageResult.getList().stream() - .map(requirement -> buildRequirementRespVO(requirement, statusModelMap)) - .collect(Collectors.toList()); - return new PageResult<>(list, pageResult.getTotal()); + return toRequirementRespPage(pageResult); } @Override @@ -325,6 +313,16 @@ public class ProjectRequirementServiceImpl implements ProjectRequirementService return new PageResult<>(list, (long) total); } + @Override + @CheckObjectPermission(objectType = PROJECT_OBJECT_TYPE, objectId = "#pageReqVO.projectId", accessible = true) + public PageResult getExecutionRequirementOptions(ProjectRequirementPageReqVO pageReqVO) { + normalizePageModuleFilter(pageReqVO); + Set excludedStatusCodes = new HashSet<>(getTerminalStatusCodes()); + excludedStatusCodes.add(STATUS_REVIEW_REJECTED); + PageResult pageResult = requirementMapper.selectPage(pageReqVO, excludedStatusCodes); + return toRequirementRespPage(pageResult); + } + @Override @Transactional(rollbackFor = Exception.class) @CheckObjectPermission(objectType = PROJECT_OBJECT_TYPE, objectId = "#reqVO.projectId", @@ -749,6 +747,27 @@ public class ProjectRequirementServiceImpl implements ProjectRequirementService return childrenMap; } + private void normalizePageModuleFilter(ProjectRequirementPageReqVO pageReqVO) { + if (pageReqVO.getModuleId() == null) { + return; + } + if (isAllRequirementsModule(pageReqVO.getModuleId())) { + pageReqVO.setModuleId(null); + return; + } + List moduleIds = getAllModuleIdsWithChildren(pageReqVO.getModuleId(), pageReqVO.getProjectId()); + pageReqVO.setModuleIds(moduleIds); + pageReqVO.setModuleId(null); + } + + private PageResult toRequirementRespPage(PageResult pageResult) { + Map statusModelMap = getStatusModelMap(); + List list = pageResult.getList().stream() + .map(requirement -> buildRequirementRespVO(requirement, statusModelMap)) + .collect(Collectors.toList()); + return new PageResult<>(list, pageResult.getTotal()); + } + /** * 一次性加载当前对象类型下的状态模型,供列表和树查询复用。 */ diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/file/FileController.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/file/FileController.java index 4da0e93..6176446 100644 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/file/FileController.java +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/file/FileController.java @@ -131,6 +131,13 @@ public class FileController { // https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/1432/ path = URLUtil.decode(path, StandardCharsets.UTF_8, false); + FileDO file = fileService.getFileByConfigIdAndPath(configId, path); + if (file == null) { + log.warn("[getFileContent][configId({}) path({}) 文件不存在]", configId, path); + response.setStatus(HttpStatus.NOT_FOUND.value()); + return; + } + // 读取内容 byte[] content = fileService.getFileContent(configId, path); if (content == null) { @@ -138,7 +145,7 @@ public class FileController { response.setStatus(HttpStatus.NOT_FOUND.value()); return; } - writeAttachment(response, path, content); + writeAttachment(response, StrUtil.blankToDefault(file.getName(), file.getPath()), content); } @GetMapping("/page") diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/framework/file/core/client/s3/S3FileClient.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/framework/file/core/client/s3/S3FileClient.java index 3efffd1..9683c34 100644 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/framework/file/core/client/s3/S3FileClient.java +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/framework/file/core/client/s3/S3FileClient.java @@ -136,11 +136,11 @@ public class S3FileClient extends AbstractFileClient { } private String resolveObjectKey(String urlOrPath) { - String cleaned = HttpUtils.removeUrlQuery(urlOrPath); - if (!HttpUtil.isHttp(cleaned) && !HttpUtil.isHttps(cleaned)) { - return cleaned; + if (!HttpUtil.isHttp(urlOrPath) && !HttpUtil.isHttps(urlOrPath)) { + return urlOrPath; } + String cleaned = HttpUtils.removeUrlQuery(urlOrPath); String path = StrUtil.removePrefix(cleaned, config.getDomain() + "/"); if (StrUtil.equals(path, cleaned)) { URI uri = URI.create(cleaned); diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/file/FileService.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/file/FileService.java index a1493a8..a64e841 100644 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/file/FileService.java +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/file/FileService.java @@ -63,6 +63,15 @@ public interface FileService { Long createFile(FileCreateReqVO createReqVO); FileDO getFile(Long id); + /** + * 按 (configId, path) 获取文件记录。 + * + * @param configId 文件配置编号 + * @param path 文件存储路径 + * @return 文件记录,不存在返回 null + */ + FileDO getFileByConfigIdAndPath(Long configId, String path); + /** * 通过文件 URL 获得文件。 * diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/file/FileServiceImpl.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/file/FileServiceImpl.java index a9ebc94..8432844 100644 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/file/FileServiceImpl.java +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/file/FileServiceImpl.java @@ -3,6 +3,7 @@ package com.njcn.rdms.module.system.service.file; import cn.hutool.core.date.LocalDateTimeUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.crypto.digest.DigestUtil; import com.google.common.annotations.VisibleForTesting; @@ -42,14 +43,6 @@ public class FileServiceImpl implements FileService { * 目的:按照日期,进行分目录 */ static boolean PATH_PREFIX_DATE_ENABLE = true; - /** - * 上传文件的后缀,是否包含时间戳 - *

- * 目的:保证文件的唯一性,避免覆盖 - * 定制:可按需调整成 UUID、或者其他方式 - */ - static boolean PATH_SUFFIX_TIMESTAMP_ENABLE = true; - @Resource private FileConfigService fileConfigService; @@ -103,34 +96,27 @@ public class FileServiceImpl implements FileService { @VisibleForTesting String generateUploadPath(String name, String directory) { - // 1. 生成前缀、后缀 + // 1. 生成前缀,并将存储文件名切换为安全 key: + // path 仅承担对象存储 key / 路径职责,不再复用原文件名,避免 %、&、中文等字符进入 URI 路径。 String prefix = null; if (PATH_PREFIX_DATE_ENABLE) { prefix = LocalDateTimeUtil.format(LocalDateTimeUtil.now(), PURE_DATE_PATTERN); } - String suffix = null; - if (PATH_SUFFIX_TIMESTAMP_ENABLE) { - suffix = String.valueOf(System.currentTimeMillis()); + String ext = FileUtil.extName(name); + String storageName = IdUtil.fastSimpleUUID(); + if (StrUtil.isNotEmpty(ext)) { + storageName = storageName + StrUtil.DOT + ext; } - // 2.1 先拼接 suffix 后缀 - if (StrUtil.isNotEmpty(suffix)) { - String ext = FileUtil.extName(name); - if (StrUtil.isNotEmpty(ext)) { - name = FileUtil.mainName(name) + StrUtil.C_UNDERLINE + suffix + StrUtil.DOT + ext; - } else { - name = name + StrUtil.C_UNDERLINE + suffix; - } - } - // 2.2 再拼接 prefix 前缀 + // 2. 先拼接 prefix 前缀 if (StrUtil.isNotEmpty(prefix)) { - name = prefix + StrUtil.SLASH + name; + storageName = prefix + StrUtil.SLASH + storageName; } - // 2.3 最后拼接 directory 目录 + // 3. 最后拼接 directory 目录 if (StrUtil.isNotEmpty(directory)) { - name = directory + StrUtil.SLASH + name; + storageName = directory + StrUtil.SLASH + storageName; } - return name; + return storageName; } @Override @@ -166,6 +152,14 @@ public class FileServiceImpl implements FileService { return validateFileExists(id); } + @Override + public FileDO getFileByConfigIdAndPath(Long configId, String path) { + if (configId == null || StrUtil.isBlank(path)) { + return null; + } + return fileMapper.selectByConfigIdAndPath(configId, path); + } + @Override public FileDO getFileByUrl(String url) { if (StrUtil.isBlank(url)) {