diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/contractor/service/impl/SubConstructionUserServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/contractor/service/impl/SubConstructionUserServiceImpl.java index 1356eb34..2126c602 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/contractor/service/impl/SubConstructionUserServiceImpl.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/contractor/service/impl/SubConstructionUserServiceImpl.java @@ -1517,6 +1517,7 @@ public class SubConstructionUserServiceImpl extends ServiceImpl numberVos = validMaterials.stream().map(m -> { + + List numberVos = new ArrayList<>(); + + for (MatMaterials m : validMaterials) { MatMaterialsNumberVo numberVo = new MatMaterialsNumberVo(); BeanUtils.copyProperties(m, numberVo); MatMaterialsInventory inv = inventoryMap.get(m.getId()); @@ -501,8 +505,10 @@ public class MatMaterialsServiceImpl extends ServiceImpl> list(BusWorkWageQueryReq req) { + return R.ok(busWorkWageService.queryList(req)); + } + } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/impl/BusAttendanceServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/impl/BusAttendanceServiceImpl.java index ce001c29..7a39868f 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/impl/BusAttendanceServiceImpl.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/impl/BusAttendanceServiceImpl.java @@ -119,7 +119,7 @@ public class BusAttendanceServiceImpl extends ServiceImpl ATTENDANCE_STATUS = new HashSet<>(Arrays.asList(BusAttendanceClockStatusEnum.NORMAL.getValue(), BusAttendanceClockStatusEnum.LATE.getValue(), BusAttendanceClockStatusEnum.LEAVEEARLY.getValue() - ,BusAttendanceClockStatusEnum.REISSUE.getValue())); + , BusAttendanceClockStatusEnum.REISSUE.getValue())); /** @@ -320,7 +320,7 @@ public class BusAttendanceServiceImpl extends ServiceImpl getTodayAttendance(Long projectId,Long userId) { + public List getTodayAttendance(Long projectId, Long userId) { if (userId == null) { userId = LoginHelper.getUserId(); @@ -915,8 +915,8 @@ public class BusAttendanceServiceImpl extends ServiceImpl byUserId = projectTeamService.getByUserId(userId, dto.getProjectId()); - if(CollectionUtil.isEmpty(byUserId)){ - return new AttendanceCountVo(0,0); + if (CollectionUtil.isEmpty(byUserId)) { + return new AttendanceCountVo(0, 0); } list2 = byUserId.stream().map(BusProjectTeamAppVo::getId).toList(); } @@ -962,7 +962,7 @@ public class BusAttendanceServiceImpl extends ServiceImpl byUserId = projectTeamService.getByUserId(userId, dto.getProjectId()); - if(CollectionUtil.isEmpty(byUserId)){ + if (CollectionUtil.isEmpty(byUserId)) { return TableDataInfo.build(new ArrayList<>()); } list1 = byUserId.stream().map(BusProjectTeamAppVo::getId).toList(); @@ -1192,10 +1192,10 @@ public class BusAttendanceServiceImpl extends ServiceImpl allUserIds = constructionUserService.list(Wrappers.lambdaQuery() .eq(SubConstructionUser::getUserRole, "0") - .eq(SubConstructionUser::getProjectId,dto.getProjectId()) - .eq(dto.getTeamId()!=null,SubConstructionUser::getTeamId,dto.getTeamId()) - .eq(StrUtil.isNotBlank(dto.getTypeOfWork()),SubConstructionUser::getTypeOfWork,dto.getTypeOfWork()) - .like(StrUtil.isNotBlank(dto.getUserName()),SubConstructionUser::getUserName,dto.getUserName()) + .eq(SubConstructionUser::getProjectId, dto.getProjectId()) + .eq(dto.getTeamId() != null, SubConstructionUser::getTeamId, dto.getTeamId()) + .eq(StrUtil.isNotBlank(dto.getTypeOfWork()), SubConstructionUser::getTypeOfWork, dto.getTypeOfWork()) + .like(StrUtil.isNotBlank(dto.getUserName()), SubConstructionUser::getUserName, dto.getUserName()) .isNotNull(SubConstructionUser::getTeamId) ).stream().map(SubConstructionUser::getSysUserId).toList(); @@ -1203,9 +1203,9 @@ public class BusAttendanceServiceImpl extends ServiceImpl list; - if(CollectionUtil.isEmpty(allUserIds)){ + if (CollectionUtil.isEmpty(allUserIds)) { list = new ArrayList<>(); - }else { + } else { list = list(Wrappers.lambdaQuery() .in(BusAttendance::getUserId, allUserIds) .between(BusAttendance::getClockDate, startDate, now) @@ -1255,6 +1255,21 @@ public class BusAttendanceServiceImpl extends ServiceImpllambdaQuery() + .eq(BusLeave::getProjectId, dto.getProjectId()) + .in(BusLeave::getUserId, allUserIds) + .eq(dto.getTeamId() != null, BusLeave::getTeamId, dto.getTeamId()) + // 关键:给日期字符串加单引号,避免 SQL 语法错误 + .apply("DATE(start_time) <= {0}", todayStr) + .apply("DATE(end_time) >= {0}", todayStr) + ); + } + vo.setLeave(count); result.add(vo); currentDate = currentDate.plusDays(1); } @@ -1276,10 +1291,10 @@ public class BusAttendanceServiceImpl extends ServiceImpl allUserIds = constructionUserService.list(Wrappers.lambdaQuery() .eq(SubConstructionUser::getUserRole, "0") - .eq(SubConstructionUser::getProjectId,dto.getProjectId()) - .eq(dto.getTeamId()!=null,SubConstructionUser::getTeamId,dto.getTeamId()) - .eq(StrUtil.isNotBlank(dto.getTypeOfWork()),SubConstructionUser::getTypeOfWork,dto.getTypeOfWork()) - .like(StrUtil.isNotBlank(dto.getUserName()),SubConstructionUser::getUserName,dto.getUserName()) + .eq(SubConstructionUser::getProjectId, dto.getProjectId()) + .eq(dto.getTeamId() != null, SubConstructionUser::getTeamId, dto.getTeamId()) + .eq(StrUtil.isNotBlank(dto.getTypeOfWork()), SubConstructionUser::getTypeOfWork, dto.getTypeOfWork()) + .like(StrUtil.isNotBlank(dto.getUserName()), SubConstructionUser::getUserName, dto.getUserName()) .isNotNull(SubConstructionUser::getTeamId) ).stream().map(SubConstructionUser::getSysUserId).toList(); @@ -1287,19 +1302,24 @@ public class BusAttendanceServiceImpl extends ServiceImpllambdaQuery() - .eq(BusLeave::getTimeType, "2") - // 关键:给日期字符串加单引号,避免 SQL 语法错误 - .apply("DATE(start_time) <= {0}", todayStr) - .apply("DATE(end_time) >= {0}", todayStr) - ); + int count = 0; + if (CollectionUtil.isNotEmpty(allUserIds)) { + count = (int) leaveService.count(Wrappers.lambdaQuery() + .eq(BusLeave::getProjectId, dto.getProjectId()) + .in(BusLeave::getUserId, allUserIds) + .eq(dto.getTeamId() != null, BusLeave::getTeamId, allUserIds) + // 关键:给日期字符串加单引号,避免 SQL 语法错误 + .apply("DATE(start_time) <= {0}", todayStr) + .apply("DATE(end_time) >= {0}", todayStr) + ); + } List list; - if(CollectionUtil.isEmpty(allUserIds)){ + if (CollectionUtil.isEmpty(allUserIds)) { list = new ArrayList<>(); - }else { + } else { list = list(Wrappers.lambdaQuery() - .in(CollectionUtil.isNotEmpty(allUserIds),BusAttendance::getUserId, allUserIds) + .in(CollectionUtil.isNotEmpty(allUserIds), BusAttendance::getUserId, allUserIds) .eq(BusAttendance::getClockDate, now) .orderByAsc(BusAttendance::getClockDate) ); @@ -1332,10 +1352,11 @@ public class BusAttendanceServiceImpl extends ServiceImpl> listAll(QltListQueryDto dto) { + return R.ok(qltFileFolderService.listAll(dto)); + } + + /** + * 查询指定目录的树形结构(一次性加载所有层级) + * @return 树形结构列表 + */ + @GetMapping("/tree-all") + public R> treeAll(QltListQueryDto dto) { + + // 1. 查询所有子项(利用path前缀匹配,一次性加载所有层级) + List allItems = qltFileFolderService.list(new LambdaQueryWrapper() + .eq(QltFileFolder::getProjectId, dto.getProjectId()) + .like(QltFileFolder::getPath, "," + dto.getParentId() + ",") // 包含父ID的所有子项 + .eq(dto.getType()!=null,QltFileFolder::getType, dto.getType()) + .orderByDesc(QltFileFolder::getId) + .orderByAsc(QltFileFolder::getSort) + ); + + // 2. 构建树形结构 + List treeVOS = buildTree(allItems, dto.getParentId()); + return R.ok(treeVOS); + } + + /** + * 递归构建树形结构 + */ + private List buildTree(List allItems, Long parentId) { + return allItems.stream() + .filter(item -> parentId.equals(item.getParentId())) + .map(item -> { + QltFileFolderTreeVO vo = new QltFileFolderTreeVO(); + // 复制基本属性(可使用BeanUtils.copyProperties) + vo.setId(item.getId()); + vo.setName(item.getName()); + vo.setParentId(item.getParentId()); + vo.setType(item.getType()); + vo.setLevel(item.getLevel()); + vo.setSort(item.getSort()); + vo.setFileSuffix(item.getFileSuffix()); + + // 递归查询子节点 + vo.setChildren(buildTree(allItems, item.getId())); + return vo; + }) + .collect(Collectors.toList()); + } + + /** + * 创建文件或文件夹 + */ + @PostMapping("/create") + public R create(@RequestBody QltFileFolderCreateDTO dto) { + return R.ok(qltFileFolderService.createFileOrFolder(dto)); + } + + /** + * 删除文件或文件夹(级联删除子项) + */ + @DeleteMapping("/{ids}") + @Transactional + public R delete(@PathVariable List ids) { + for (Long id : ids) { + qltFileFolderService.deleteFileOrFolder(id); + } + return R.ok(true); + } + + /** + * 移动文件或文件夹到指定目录 + */ + @PostMapping("/move") + @Transactional + public R move(@RequestBody QltFileFolderMoveDTO dto) { + return R.ok(qltFileFolderService.moveFileOrFolder(dto.getId(), dto.getTargetParentId())); + } + + /** + * 上传ZIP文件并自动解压到指定目录(支持多层级) + */ + @PostMapping("/uploadAndUnzip") + public R uploadAndUnzip( + @RequestParam("file") MultipartFile file, + @RequestParam("parentId") Long parentId, + @RequestParam("projectId") Long projectId) { + + try { + // 实际应用中,userId和deptId通常从登录信息中获取 + String result = qltFileFolderService.uploadAndUnzip(file, parentId, projectId); + return R.ok(result); + } catch (IllegalArgumentException e) { + return R.fail(e.getMessage()); + } catch (Exception e) { + return R.fail("文件处理失败:" + e.getMessage()); + } + } + + @PostMapping("/unzip") + public R unzip(@RequestParam("id") Long id, + @RequestParam("parentId") Long parentId) { + return R.ok(qltFileFolderService.unzip(id, parentId)); + } + + @PutMapping("/reName") + public R reName(@RequestParam("id") Long id, + @RequestParam("name") String name) { + return R.ok(qltFileFolderService.reName(id,name)); + } + + + @GetMapping("/download/{id}") + public void download(@PathVariable Long id, HttpServletResponse response) throws IOException { + qltFileFolderService.download(id, response); + } + + +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/quality/domain/QltFileFolder.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/quality/domain/QltFileFolder.java new file mode 100644 index 00000000..25d831ed --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/quality/domain/QltFileFolder.java @@ -0,0 +1,92 @@ +package org.dromara.quality.domain; + +import org.dromara.common.mybatis.core.domain.BaseEntity; +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serial; + +/** + * 质量会议纪要对象 qlt_file_folder + * + * @author Lion Li + * @date 2025-10-16 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("qlt_file_folder") +public class QltFileFolder extends BaseEntity { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @TableId(value = "id") + private Long id; + + /** + * 项目id + */ + private Long projectId; + + /** + * 名称(文件名或文件夹名) + */ + private String name; + + /** + * 父级ID(0表示根目录) + */ + private Long parentId; + + /** + * 类型(1-文件夹,2-文件) + */ + private Integer type; + + /** + * 层级(根目录为1,子级+1) + */ + private Integer level; + + /** + * 同层级排序号 + */ + private Integer sort; + + /** + * 层级路径(如:1,2,3 表示ID为1→2→3的层级) + */ + private String path; + + /** + * 文件id + */ + private Long fileId; + + /** + * 文件后缀 + */ + private String fileSuffix; + + /** + * 存储路径 + */ + private String filePath; + + /** + * + */ + private String remark; + + /** + * 删除标志(0代表存在 1代表删除) + */ + @TableLogic + private String delFlag; + + +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/quality/domain/bo/QltFileFolderBo.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/quality/domain/bo/QltFileFolderBo.java new file mode 100644 index 00000000..492d5426 --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/quality/domain/bo/QltFileFolderBo.java @@ -0,0 +1,90 @@ +package org.dromara.quality.domain.bo; + +import org.dromara.quality.domain.QltFileFolder; +import org.dromara.common.mybatis.core.domain.BaseEntity; +import org.dromara.common.core.validate.AddGroup; +import org.dromara.common.core.validate.EditGroup; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import lombok.EqualsAndHashCode; +import jakarta.validation.constraints.*; + +/** + * 质量会议纪要业务对象 qlt_file_folder + * + * @author Lion Li + * @date 2025-10-16 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = QltFileFolder.class, reverseConvertGenerate = false) +public class QltFileFolderBo extends BaseEntity { + + /** + * 主键ID + */ + @NotNull(message = "主键ID不能为空", groups = { EditGroup.class }) + private Long id; + + /** + * 项目id + */ + @NotNull(message = "项目id不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long projectId; + + /** + * 名称(文件名或文件夹名) + */ + @NotBlank(message = "名称(文件名或文件夹名)不能为空", groups = { AddGroup.class, EditGroup.class }) + private String name; + + /** + * 父级ID(0表示根目录) + */ + private Long parentId; + + /** + * 类型(1-文件夹,2-文件) + */ + @NotNull(message = "类型(1-文件夹,2-文件)不能为空", groups = { AddGroup.class, EditGroup.class }) + private Integer type; + + /** + * 层级(根目录为1,子级+1) + */ + @NotNull(message = "层级(根目录为1,子级+1)不能为空", groups = { AddGroup.class, EditGroup.class }) + private Integer level; + + /** + * 同层级排序号 + */ + private Integer sort; + + /** + * 层级路径(如:1,2,3 表示ID为1→2→3的层级) + */ + @NotBlank(message = "层级路径(如:1,2,3 表示ID为1→2→3的层级)不能为空", groups = { AddGroup.class, EditGroup.class }) + private String path; + + /** + * 文件id + */ + private Long fileId; + + /** + * 文件后缀 + */ + private String fileSuffix; + + /** + * 存储路径 + */ + private String filePath; + + /** + * + */ + private String remark; + + +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/quality/domain/dto/fileFolder/QltFileFolderCreateDTO.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/quality/domain/dto/fileFolder/QltFileFolderCreateDTO.java new file mode 100644 index 00000000..b7b216c7 --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/quality/domain/dto/fileFolder/QltFileFolderCreateDTO.java @@ -0,0 +1,58 @@ +package org.dromara.quality.domain.dto.fileFolder; + + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + + +@Data +public class QltFileFolderCreateDTO { + + /** + * 名称 + */ + @NotBlank(message = "名称不能为空") + private String name; + + private Long projectId; + + /** + * 父级ID(0表示根目录) + */ + private Long parentId = 0L; + + /** + * 文件类型 + */ + @NotNull(message = "类型不能为空") + private Integer type; + + /** + * 排序号 + */ + private Integer sort=0; + + + /** + * 文件id + */ + private Long fileId; + + /** + * 文件后缀 + */ + private String fileSuffix; + + /** + * 文件存储路径(仅文件有效) + */ + private String filePath; + + /** + * 备注 + */ + private String remark; + +} + diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/quality/domain/dto/fileFolder/QltFileFolderMoveDTO.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/quality/domain/dto/fileFolder/QltFileFolderMoveDTO.java new file mode 100644 index 00000000..0b0c8752 --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/quality/domain/dto/fileFolder/QltFileFolderMoveDTO.java @@ -0,0 +1,14 @@ +package org.dromara.quality.domain.dto.fileFolder; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class QltFileFolderMoveDTO { + + @NotNull(message = "文件/文件夹ID不能为空") + private Long id; + + @NotNull(message = "目标父目录ID不能为空") + private Long targetParentId; +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/safety/domain/dto/fileFolder/ListQueryDto.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/quality/domain/dto/fileFolder/QltListQueryDto.java similarity index 82% rename from xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/safety/domain/dto/fileFolder/ListQueryDto.java rename to xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/quality/domain/dto/fileFolder/QltListQueryDto.java index fa43cee7..aa681c3d 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/safety/domain/dto/fileFolder/ListQueryDto.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/quality/domain/dto/fileFolder/QltListQueryDto.java @@ -1,11 +1,11 @@ -package org.dromara.safety.domain.dto.fileFolder; +package org.dromara.quality.domain.dto.fileFolder; import jakarta.validation.constraints.NotNull; import lombok.Data; @Data -public class ListQueryDto { +public class QltListQueryDto { /** * 父目录ID,默认0(根目录) */ diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/quality/domain/vo/QltFileFolderVo.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/quality/domain/vo/QltFileFolderVo.java new file mode 100644 index 00000000..b30dd11d --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/quality/domain/vo/QltFileFolderVo.java @@ -0,0 +1,109 @@ +package org.dromara.quality.domain.vo; + +import org.dromara.quality.domain.QltFileFolder; +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import org.dromara.common.excel.annotation.ExcelDictFormat; +import org.dromara.common.excel.convert.ExcelDictConvert; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + + + +/** + * 质量会议纪要视图对象 qlt_file_folder + * + * @author Lion Li + * @date 2025-10-16 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = QltFileFolder.class) +public class QltFileFolderVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @ExcelProperty(value = "主键ID") + private Long id; + + /** + * 项目id + */ + @ExcelProperty(value = "项目id") + private Long projectId; + + /** + * 名称(文件名或文件夹名) + */ + @ExcelProperty(value = "名称", converter = ExcelDictConvert.class) + @ExcelDictFormat(readConverterExp = "文=件名或文件夹名") + private String name; + + /** + * 父级ID(0表示根目录) + */ + @ExcelProperty(value = "父级ID", converter = ExcelDictConvert.class) + @ExcelDictFormat(readConverterExp = "0=表示根目录") + private Long parentId; + + /** + * 类型(1-文件夹,2-文件) + */ + @ExcelProperty(value = "类型", converter = ExcelDictConvert.class) + @ExcelDictFormat(readConverterExp = "1=-文件夹,2-文件") + private Integer type; + + /** + * 层级(根目录为1,子级+1) + */ + @ExcelProperty(value = "层级", converter = ExcelDictConvert.class) + @ExcelDictFormat(readConverterExp = "根=目录为1,子级+1") + private Integer level; + + /** + * 同层级排序号 + */ + @ExcelProperty(value = "同层级排序号") + private Integer sort; + + /** + * 层级路径(如:1,2,3 表示ID为1→2→3的层级) + */ + @ExcelProperty(value = "层级路径", converter = ExcelDictConvert.class) + @ExcelDictFormat(readConverterExp = "如=:1,2,3,表=示ID为1→2→3的层级") + private String path; + + /** + * 文件id + */ + @ExcelProperty(value = "文件id") + private Long fileId; + + /** + * 文件后缀 + */ + @ExcelProperty(value = "文件后缀") + private String fileSuffix; + + /** + * 存储路径 + */ + @ExcelProperty(value = "存储路径") + private String filePath; + + /** + * + */ + @ExcelProperty(value = "") + private String remark; + + +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/quality/domain/vo/fileFolder/QltFileFolderTreeVO.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/quality/domain/vo/fileFolder/QltFileFolderTreeVO.java new file mode 100644 index 00000000..b33f86fd --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/quality/domain/vo/fileFolder/QltFileFolderTreeVO.java @@ -0,0 +1,53 @@ +package org.dromara.quality.domain.vo.fileFolder; + +import lombok.Data; + +import java.util.List; + +@Data +public class QltFileFolderTreeVO { + + + private Long id; + + /** + * 名称(文件名或文件夹名) + */ + private String name; + + + /** + * 父级ID(0表示根目录) + */ + private Long parentId; + + /** + * 1-文件夹,2-文件 + */ + private Integer type; + + /** + * 层级(根目录为1,子级+1) + */ + private Integer level; + + /** + * 同层级排序号 + */ + private Integer sort; + + /** + * 文件id + */ + private Long fileId; + + /** + * 文件后缀 + */ + private String fileSuffix; + + /** + * 子级 + */ + private List children; +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/quality/mapper/QltFileFolderMapper.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/quality/mapper/QltFileFolderMapper.java new file mode 100644 index 00000000..92861b57 --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/quality/mapper/QltFileFolderMapper.java @@ -0,0 +1,22 @@ +package org.dromara.quality.mapper; + +import org.apache.ibatis.annotations.Param; +import org.dromara.quality.domain.QltFileFolder; +import org.dromara.quality.domain.vo.QltFileFolderVo; +import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; + +/** + * 质量会议纪要Mapper接口 + * + * @author Lion Li + * @date 2025-10-16 + */ +public interface QltFileFolderMapper extends BaseMapperPlus { + + + void batchUpdateChildPaths( + @Param("oldPath") String oldPath, + @Param("newPath") String newPath, + @Param("levelDiff") int levelDiff, + @Param("parentId") Long parentId); +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/quality/service/IQltFileFolderService.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/quality/service/IQltFileFolderService.java new file mode 100644 index 00000000..ed317752 --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/quality/service/IQltFileFolderService.java @@ -0,0 +1,115 @@ +package org.dromara.quality.service; + +import jakarta.servlet.http.HttpServletResponse; +import org.dromara.quality.domain.dto.fileFolder.QltFileFolderCreateDTO; +import org.dromara.quality.domain.dto.fileFolder.QltListQueryDto; +import org.dromara.quality.domain.vo.QltFileFolderVo; +import org.dromara.quality.domain.bo.QltFileFolderBo; +import org.dromara.quality.domain.QltFileFolder; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.mybatis.core.page.PageQuery; + +import com.baomidou.mybatisplus.extension.service.IService; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.Collection; +import java.util.List; + +/** + * 质量会议纪要Service接口 + * + * @author Lion Li + * @date 2025-10-16 + */ +public interface IQltFileFolderService extends IService{ + + /** + * 查询质量会议纪要 + * + * @param id 主键 + * @return 质量会议纪要 + */ + QltFileFolderVo queryById(Long id); + + /** + * 分页查询质量会议纪要列表 + * + * @param bo 查询条件 + * @param pageQuery 分页参数 + * @return 质量会议纪要分页列表 + */ + TableDataInfo queryPageList(QltFileFolderBo bo, PageQuery pageQuery); + + /** + * 查询符合条件的质量会议纪要列表 + * + * @param bo 查询条件 + * @return 质量会议纪要列表 + */ + List queryList(QltFileFolderBo bo); + + /** + * 新增质量会议纪要 + * + * @param bo 质量会议纪要 + * @return 是否新增成功 + */ + Boolean insertByBo(QltFileFolderBo bo); + + /** + * 修改质量会议纪要 + * + * @param bo 质量会议纪要 + * @return 是否修改成功 + */ + Boolean updateByBo(QltFileFolderBo bo); + + /** + * 校验并批量删除质量会议纪要信息 + * + * @param ids 待删除的主键集合 + * @param isValid 是否进行有效性校验 + * @return 是否删除成功 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); + + List listAll(QltListQueryDto dto); + + /** + * 创建文件或文件夹 + */ + QltFileFolder createFileOrFolder(QltFileFolderCreateDTO dto); + + /** + * 删除文件或文件夹(级联删除子项) + */ + boolean deleteFileOrFolder(Long id); + + /** + * 移动文件或文件夹到指定目录 + */ + boolean moveFileOrFolder(Long id, Long targetParentId); + + /** + * 上传并解压ZIP文件 + */ + String uploadAndUnzip(MultipartFile file, Long parentId, Long projectId) throws Exception; + + /** + * 获取指定目录的树形结构(一次性加载所有层级) + * @return 树形结构列表 + */ + String unzip(Long id, Long parentId); + + /** + * 重命名文件或文件夹 + */ + Boolean reName(Long id,String name); + + /** + * 下载 + */ + void download(@PathVariable Long id, HttpServletResponse response) throws IOException; +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/quality/service/impl/QltFileFolderServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/quality/service/impl/QltFileFolderServiceImpl.java new file mode 100644 index 00000000..ab862757 --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/quality/service/impl/QltFileFolderServiceImpl.java @@ -0,0 +1,585 @@ +package org.dromara.quality.service.impl; + +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import jakarta.servlet.http.HttpServletResponse; +import org.dromara.common.core.exception.ServiceException; +import org.dromara.common.core.utils.MapstructUtils; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.core.utils.file.FileUtils; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.mybatis.core.page.PageQuery; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import lombok.RequiredArgsConstructor; +import org.dromara.common.oss.core.OssClient; +import org.dromara.common.oss.factory.OssFactory; +import org.dromara.quality.domain.dto.fileFolder.QltFileFolderCreateDTO; +import org.dromara.quality.domain.dto.fileFolder.QltListQueryDto; +import org.dromara.safety.domain.HseFileFolder; +import org.dromara.safety.domain.dto.fileFolder.FileFolderCreateDTO; +import org.dromara.safety.domain.vo.HseFileFolderVo; +import org.dromara.system.domain.vo.SysOssVo; +import org.dromara.system.service.ISysOssService; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Service; +import org.dromara.quality.domain.bo.QltFileFolderBo; +import org.dromara.quality.domain.vo.QltFileFolderVo; +import org.dromara.quality.domain.QltFileFolder; +import org.dromara.quality.mapper.QltFileFolderMapper; +import org.dromara.quality.service.IQltFileFolderService; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.io.*; +import java.nio.charset.Charset; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.Collection; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +/** + * 质量会议纪要Service业务层处理 + * + * @author Lion Li + * @date 2025-10-16 + */ +@RequiredArgsConstructor +@Service +public class QltFileFolderServiceImpl extends ServiceImpl implements IQltFileFolderService { + + private final QltFileFolderMapper baseMapper; + + private final ISysOssService ossService; + + /** + * 查询质量会议纪要 + * + * @param id 主键 + * @return 质量会议纪要 + */ + @Override + public QltFileFolderVo queryById(Long id){ + return baseMapper.selectVoById(id); + } + + /** + * 分页查询质量会议纪要列表 + * + * @param bo 查询条件 + * @param pageQuery 分页参数 + * @return 质量会议纪要分页列表 + */ + @Override + public TableDataInfo queryPageList(QltFileFolderBo bo, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(result); + } + + /** + * 查询符合条件的质量会议纪要列表 + * + * @param bo 查询条件 + * @return 质量会议纪要列表 + */ + @Override + public List queryList(QltFileFolderBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + return baseMapper.selectVoList(lqw); + } + + private LambdaQueryWrapper buildQueryWrapper(QltFileFolderBo bo) { + Map params = bo.getParams(); + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.orderByDesc(QltFileFolder::getId); + lqw.eq(bo.getProjectId() != null, QltFileFolder::getProjectId, bo.getProjectId()); + lqw.like(StringUtils.isNotBlank(bo.getName()), QltFileFolder::getName, bo.getName()); + lqw.eq(bo.getParentId() != null, QltFileFolder::getParentId, bo.getParentId()); + lqw.eq(bo.getType() != null, QltFileFolder::getType, bo.getType()); + lqw.eq(bo.getLevel() != null, QltFileFolder::getLevel, bo.getLevel()); + lqw.eq(bo.getSort() != null, QltFileFolder::getSort, bo.getSort()); + lqw.eq(StringUtils.isNotBlank(bo.getPath()), QltFileFolder::getPath, bo.getPath()); + lqw.eq(bo.getFileId() != null, QltFileFolder::getFileId, bo.getFileId()); + lqw.eq(StringUtils.isNotBlank(bo.getFileSuffix()), QltFileFolder::getFileSuffix, bo.getFileSuffix()); + lqw.eq(StringUtils.isNotBlank(bo.getFilePath()), QltFileFolder::getFilePath, bo.getFilePath()); + return lqw; + } + + /** + * 新增质量会议纪要 + * + * @param bo 质量会议纪要 + * @return 是否新增成功 + */ + @Override + public Boolean insertByBo(QltFileFolderBo bo) { + QltFileFolder add = MapstructUtils.convert(bo, QltFileFolder.class); + validEntityBeforeSave(add); + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setId(add.getId()); + } + return flag; + } + + /** + * 修改质量会议纪要 + * + * @param bo 质量会议纪要 + * @return 是否修改成功 + */ + @Override + public Boolean updateByBo(QltFileFolderBo bo) { + QltFileFolder update = MapstructUtils.convert(bo, QltFileFolder.class); + validEntityBeforeSave(update); + return baseMapper.updateById(update) > 0; + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(QltFileFolder entity){ + //TODO 做一些数据校验,如唯一约束 + } + + /** + * 校验并批量删除质量会议纪要信息 + * + * @param ids 待删除的主键集合 + * @param isValid 是否进行有效性校验 + * @return 是否删除成功 + */ + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if(isValid){ + //TODO 做一些业务上的校验,判断是否需要校验 + } + return baseMapper.deleteByIds(ids) > 0; + } + + @Override + public List listAll(QltListQueryDto dto) { + + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.orderByDesc(QltFileFolder::getId); + lqw.orderByAsc(QltFileFolder::getSort); + + lqw.eq(QltFileFolder::getProjectId, dto.getProjectId()); + lqw.eq(QltFileFolder::getParentId, dto.getParentId()); + lqw.eq(dto.getType() != null, QltFileFolder::getType, dto.getType()); + + return baseMapper.selectVoList(lqw); + } + + @Override + public QltFileFolder createFileOrFolder(QltFileFolderCreateDTO dto) { + // 1. 验证父目录是否存在 + if (dto.getParentId() != 0) { + QltFileFolder parent = getById(dto.getParentId()); + if (parent == null || parent.getType() != 1) { // 父级必须是文件夹 + throw new ServiceException("父目录不存在或不是文件夹"); + } + } + + // 2. 构建新文件/文件夹对象 + QltFileFolder entity = new QltFileFolder(); + entity.setProjectId(dto.getProjectId()); + entity.setName(dto.getName()); + entity.setParentId(dto.getParentId()); + entity.setType(dto.getType()); + entity.setSort(dto.getSort() != null ? dto.getSort() : 0); + entity.setRemark(dto.getRemark()); + entity.setFileId(dto.getFileId()); + + + // 3. 设置层级和路径 + if (dto.getParentId() == 0) { // 根目录 + entity.setLevel(1); + entity.setPath("," + entity.getId() + ","); // ID会在插入后自动生成,这里先占位 + } else { // 子目录/文件 + QltFileFolder parent = getById(dto.getParentId()); + entity.setLevel(parent.getLevel() + 1); + entity.setPath(parent.getPath() + entity.getId() + ","); // ID会在插入后更新 + } + + // 4. 处理文件特有属性 + if (dto.getType() == 2) { // 如果是文件 + entity.setFileSuffix(dto.getFileSuffix()); + entity.setFilePath(dto.getFilePath()); + } + + // 5. 保存并更新路径(因为ID是自增的,需要先保存再更新路径) + save(entity); + + // 6. 修正路径中的ID + if (dto.getParentId() == 0) { + entity.setPath("," + entity.getId() + ","); + } else { + QltFileFolder parent = getById(dto.getParentId()); + entity.setPath(parent.getPath() + entity.getId() + ","); + } + updateById(entity); + + return entity; + } + + @Override + @Transactional + public boolean deleteFileOrFolder(Long id) { + QltFileFolder entity = getById(id); + if (entity == null) { + throw new ServiceException("文件/文件夹不存在"); + } + + // 1. 删除自身及所有子项(通过path匹配) + LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery() + .like(QltFileFolder::getPath, "," + id + ","); + + return remove(queryWrapper); + } + + @Override + @Transactional + public boolean moveFileOrFolder(Long id, Long targetParentId) { + // 1. 验证源文件和目标目录是否存在 + QltFileFolder source = getById(id); + if (source == null) { + throw new ServiceException("源文件/文件夹不存在"); + } + + QltFileFolder targetParent = getById(targetParentId); + if (targetParent == null || targetParent.getType() != 1) { + throw new ServiceException("目标目录不存在或不是文件夹"); + } + + // 2. 防止循环移动(不能移动到自身或子目录下) + if (targetParent.getPath().contains(source.getPath())) { + throw new ServiceException("不能将文件夹移动到其子目录下"); + } + + // 3. 获取原路径和新路径的前缀 + String oldPath = source.getPath(); + String oldParentPath = source.getParentId() == 0 ? ",0," : getById(source.getParentId()).getPath(); + String newParentPath = targetParent.getPath(); + int oldLevel = source.getLevel(); + + // 4. 计算新路径和新层级 + String newPath = newParentPath + id + ","; + int newLevel = targetParent.getLevel() + 1; + + // 5. 更新自身信息 + source.setParentId(targetParentId); + source.setLevel(newLevel); + source.setPath(newPath); + updateById(source); + + // 6. 更新所有子项的路径和层级 + if (source.getType() == 1) { // 如果是文件夹,需要更新其子项 + // 计算路径替换的前后缀 + String pathReplaceFrom = oldPath; + String pathReplaceTo = newPath; + int levelDiff = newLevel - oldLevel; // 层级变化量 + + // 批量更新子项 + baseMapper.batchUpdateChildPaths(pathReplaceFrom, pathReplaceTo, levelDiff, id); + } + + return true; + } + + + @Override + @Transactional + public String uploadAndUnzip(MultipartFile file, Long parentId, Long projectId) throws Exception { + // 1. 参数验证 + if (file == null || file.isEmpty()) { + throw new ServiceException("上传文件不能为空"); + } + + // 2. 验证文件类型是否为ZIP + String originalFilename = file.getOriginalFilename(); + if (originalFilename == null || !originalFilename.toLowerCase().endsWith(".zip")) { + throw new ServiceException("只支持上传ZIP格式的文件"); + } + + // 3. 验证父目录是否存在且为文件夹 + QltFileFolder parentFolder = null; + if (parentId != null && parentId != 0) { + parentFolder = getById(parentId); + if (parentFolder == null || parentFolder.getType() != 1) { + throw new ServiceException("指定的父目录不存在或不是文件夹"); + } + } + + // 4. 创建临时文件用于解压 + File tempZipFile = File.createTempFile("upload_", ".zip"); + try { + // 5. 保存上传的文件到临时位置 + file.transferTo(tempZipFile); + + // 6. 解压ZIP文件 + String unzipResult = doUnzipFile(tempZipFile, parentId, projectId, parentFolder); + + return unzipResult; + } finally { + // 7. 清理临时文件 + if (tempZipFile.exists()) { + tempZipFile.delete(); + } + } + } + + /** + * 实际执行解压操作的方法 + * + * @param zipFile ZIP文件 + * @param parentId 父目录ID + * @param projectId 项目ID + * @param parentFolder 父目录对象 + * @return 解压结果信息 + * @throws Exception 解压过程中可能出现的异常 + */ + private String doUnzipFile(File zipFile, Long parentId, Long projectId, QltFileFolder parentFolder) throws Exception { + // 用于记录解压的文件数量 + int fileCount = 0; + int folderCount = 0; + + // 获取父级路径信息 + String parentPath = parentId == null || parentId == 0 ? "," : parentFolder.getPath(); + int parentLevel = parentId == null || parentId == 0 ? 0 : parentFolder.getLevel(); + + try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile), Charset.forName("GBK"))) { + ZipEntry entry; + while ((entry = zis.getNextEntry()) != null) { + // 跳过目录项(目录会根据文件路径自动创建) + if (entry.isDirectory()) { + continue; + } + + String entryName = entry.getName(); + // 处理中文文件名乱码问题 + if (!Charset.forName("GBK").newEncoder().canEncode(entryName)) { + // 尝试用UTF-8编码解析 + entryName = new String(entryName.getBytes("ISO-8859-1"), "UTF-8"); + } + + // 分离路径和文件名 + String[] pathParts = entryName.split("/"); + String fileName = pathParts[pathParts.length - 1]; + + // 构造文件在系统中的虚拟路径 + Long currentParentId = parentId == null ? 0L : parentId; + String currentPath = parentPath; + + // 如果ZIP中有目录结构,则需要逐级创建文件夹 + for (int i = 0; i < pathParts.length - 1; i++) { + String folderName = pathParts[i]; + if (folderName.isEmpty()) continue; + + // 查找是否已存在同名文件夹 + LambdaQueryWrapper folderQuery = Wrappers.lambdaQuery(); + folderQuery.eq(QltFileFolder::getProjectId, projectId) + .eq(QltFileFolder::getParentId, currentParentId) + .eq(QltFileFolder::getName, folderName) + .eq(QltFileFolder::getType, 1); // 文件夹类型 + + QltFileFolder existingFolder = getOne(folderQuery); + + if (existingFolder == null) { + // 创建新的文件夹 + QltFileFolder newFolder = new QltFileFolder(); + newFolder.setProjectId(projectId); + newFolder.setName(folderName); + newFolder.setParentId(currentParentId); + newFolder.setType(1); // 文件夹 + newFolder.setLevel(parentLevel + i + 1); + newFolder.setSort(0); + newFolder.setPath(currentPath); + + // 先保存获取ID + save(newFolder); + + // 更新路径,格式为 ,parentId,childId, + newFolder.setPath(currentPath + newFolder.getId() + ","); + updateById(newFolder); + + currentParentId = newFolder.getId(); + currentPath = newFolder.getPath(); + folderCount++; + } else { + currentParentId = existingFolder.getId(); + currentPath = existingFolder.getPath(); + } + } + + // 创建文件记录 + QltFileFolder fileRecord = new QltFileFolder(); + fileRecord.setProjectId(projectId); + fileRecord.setName(fileName); + fileRecord.setParentId(currentParentId); + fileRecord.setType(2); // 文件类型 + fileRecord.setLevel(parentLevel + pathParts.length); + // 文件路径格式为 ,parentId,fileId, + fileRecord.setPath(currentPath + fileRecord.getId() + ","); + + // 设置文件扩展名 + int dotIndex = fileName.lastIndexOf("."); + if (dotIndex > 0 && dotIndex < fileName.length() - 1) { + fileRecord.setFileSuffix(fileName.substring(dotIndex + 1)); + } + // 上传文件内容到OSS并设置文件路径 + // 上传文件内容到OSS并设置文件路径 + try { + // 从ZIP流中读取文件内容 + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buffer = new byte[32768]; + int bytesRead; + while ((bytesRead = zis.read(buffer)) != -1) { + baos.write(buffer, 0, bytesRead); + } + byte[] fileContent = baos.toByteArray(); + + // 检查文件长度,如果为0则跳过保存 + if (fileContent.length > 0) { + // 直接使用字节流上传到OSS,避免创建临时文件 + try (ByteArrayInputStream bais = new ByteArrayInputStream(fileContent)) { + String contentType = FileUtils.getMimeType(fileName); + SysOssVo ossVo = ossService.upload(bais, fileName, contentType, (long) fileContent.length); + + // 设置真实的文件路径 + fileRecord.setFilePath(ossVo.getUrl()); + fileRecord.setFileId(ossVo.getOssId()); + } + + save(fileRecord); + + // 更新文件的路径(因为ID是自增的,需要先保存再更新路径) + fileRecord.setPath(currentPath + fileRecord.getId() + ","); + updateById(fileRecord); + + fileCount++; + } + // 如果文件长度为0,则跳过保存操作 + } catch (Exception e) { + throw new ServiceException("文件上传失败: " + e.getMessage()); + } + + zis.closeEntry(); + } + } + + return String.format("解压完成,共创建 %d 个文件夹,%d 个文件", folderCount, fileCount); + } + + + @Override + @Transactional + public String unzip(Long id, Long parentId) { + // 1. 参数验证 + if (id == null) { + throw new ServiceException("文件ID不能为空"); + } + + // 2. 获取要解压的ZIP文件 + QltFileFolder zipFile = getById(id); + if (zipFile == null) { + throw new ServiceException("指定的文件不存在"); + } + + // 3. 验证是否为ZIP文件 + if (!"zip".equalsIgnoreCase(zipFile.getFileSuffix())) { + throw new ServiceException("只能解压ZIP格式的文件"); + } + + // 4. 验证文件路径是否存在 + if (StringUtils.isBlank(zipFile.getFilePath())) { + throw new ServiceException("文件存储路径不存在"); + } + + // 5. 验证目标目录是否存在且为文件夹 + QltFileFolder parentFolder = null; + if (parentId != null && parentId != 0) { + parentFolder = getById(parentId); + if (parentFolder == null || parentFolder.getType() != 1) { + throw new ServiceException("指定的目标目录不存在或不是文件夹"); + } + } + + // 6. 下载ZIP文件到临时位置 + try { + // 从URL下载文件 + Long fileId = zipFile.getFileId(); + File tempZipFile = downloadFileToTemp(fileId); + + if (tempZipFile == null || !tempZipFile.exists()) { + throw new ServiceException("下载文件失败"); + } + + try { + // 7. 解压ZIP文件 + String unzipResult = doUnzipFile(tempZipFile, parentId, zipFile.getProjectId(), parentFolder); + return unzipResult; + } finally { + // 8. 清理临时文件 + if (tempZipFile.exists()) { + tempZipFile.delete(); + } + } + } catch (Exception e) { + throw new ServiceException("解压文件失败: " + e.getMessage()); + } + } + + /** + * 从URL下载文件到临时位置 + * + * @param fileId 文件id + * @return 临时文件 + * @throws Exception 下载过程中的异常 + */ + private File downloadFileToTemp(Long fileId) throws Exception { + SysOssVo sysOss = ossService.getById(fileId); + if (ObjectUtil.isNull(sysOss)) { + throw new ServiceException("文件数据不存在!"); + } + + OssClient storage = OssFactory.instance(sysOss.getService()); + // 直接使用OssClient返回的临时文件路径 + Path tempPath = storage.fileDownload(sysOss.getUrl()); + + return tempPath.toFile(); + } + + @Override + public Boolean reName(Long id, String name) { + QltFileFolder fileFolder = getById(id); + if (fileFolder != null) { + fileFolder.setName(name); + return updateById(fileFolder); + } + return false; + } + + @Override + public void download(Long id, HttpServletResponse response) throws IOException { + + QltFileFolder fileFolder = getById(id); + + if(fileFolder == null){ + throw new ServiceException("文件数据不存在!"); + } + Long ossId = fileFolder.getFileId(); + SysOssVo sysOss = ossService.getById(ossId); + if (ObjectUtil.isNull(sysOss)) { + throw new ServiceException("文件数据不存在!"); + } + FileUtils.setAttachmentResponseHeader(response, fileFolder.getName()+"."+fileFolder.getFileSuffix()); + response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE + "; charset=UTF-8"); + OssClient storage = OssFactory.instance(sysOss.getService()); + storage.download(sysOss.getFileName(), response.getOutputStream(), response::setContentLengthLong); + } +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/safety/controller/HseFileFolderController.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/safety/controller/HseFileFolderController.java index f808546b..9b58e1f3 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/safety/controller/HseFileFolderController.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/safety/controller/HseFileFolderController.java @@ -1,35 +1,24 @@ package org.dromara.safety.controller; +import java.io.IOException; import java.util.List; import java.util.stream.Collectors; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import lombok.RequiredArgsConstructor; import jakarta.servlet.http.HttpServletResponse; -import jakarta.validation.constraints.*; -import cn.dev33.satoken.annotation.SaCheckPermission; +import org.dromara.quality.domain.dto.fileFolder.SafeListQueryDto; import org.dromara.safety.domain.HseFileFolder; import org.dromara.safety.domain.dto.fileFolder.FileFolderCreateDTO; import org.dromara.safety.domain.dto.fileFolder.FileFolderMoveDTO; -import org.dromara.safety.domain.dto.fileFolder.ListQueryDto; import org.dromara.safety.domain.vo.fileFolder.FileFolderTreeVO; -import org.springframework.http.ResponseEntity; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; import org.springframework.validation.annotation.Validated; -import org.dromara.common.idempotent.annotation.RepeatSubmit; -import org.dromara.common.log.annotation.Log; import org.dromara.common.web.core.BaseController; -import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.core.domain.R; -import org.dromara.common.core.validate.AddGroup; -import org.dromara.common.core.validate.EditGroup; -import org.dromara.common.log.enums.BusinessType; -import org.dromara.common.excel.utils.ExcelUtil; import org.dromara.safety.domain.vo.HseFileFolderVo; -import org.dromara.safety.domain.bo.HseFileFolderBo; import org.dromara.safety.service.IHseFileFolderService; -import org.dromara.common.mybatis.core.page.TableDataInfo; import org.springframework.web.multipart.MultipartFile; /** @@ -50,7 +39,7 @@ public class HseFileFolderController extends BaseController { * 查询会议纪要列表 */ @GetMapping("/list-all") - public R> listAll(ListQueryDto dto) { + public R> listAll(SafeListQueryDto dto) { return R.ok(hseFileFolderService.listAll(dto)); } @@ -59,7 +48,7 @@ public class HseFileFolderController extends BaseController { * @return 树形结构列表 */ @GetMapping("/tree-all") - public R> treeAll(ListQueryDto dto) { + public R> treeAll(SafeListQueryDto dto) { // 1. 查询所有子项(利用path前缀匹配,一次性加载所有层级) List allItems = hseFileFolderService.list(new LambdaQueryWrapper() @@ -110,10 +99,13 @@ public class HseFileFolderController extends BaseController { /** * 删除文件或文件夹(级联删除子项) */ - @DeleteMapping("/{id}") + @DeleteMapping("/{ids}") @Transactional - public R delete(@PathVariable Long id) { - return R.ok(hseFileFolderService.deleteFileOrFolder(id)); + public R delete(@PathVariable List ids) { + for (Long id : ids) { + hseFileFolderService.deleteFileOrFolder(id); + } + return R.ok(true); } /** @@ -145,14 +137,23 @@ public class HseFileFolderController extends BaseController { } } + @PostMapping("/unzip") + public R unzip(@RequestParam("id") Long id, + @RequestParam("parentId") Long parentId) { + return R.ok(hseFileFolderService.unzip(id, parentId)); + } + + @PutMapping("/reName") + public R reName(@RequestParam("id") Long id, + @RequestParam("name") String name) { + return R.ok(hseFileFolderService.reName(id,name)); + } - - - - - - + @GetMapping("/download/{id}") + public void download(@PathVariable Long id, HttpServletResponse response) throws IOException { + hseFileFolderService.download(id, response); + } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/safety/domain/dto/fileFolder/SafeListQueryDto.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/safety/domain/dto/fileFolder/SafeListQueryDto.java new file mode 100644 index 00000000..fe1c80f9 --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/safety/domain/dto/fileFolder/SafeListQueryDto.java @@ -0,0 +1,26 @@ +package org.dromara.quality.domain.dto.fileFolder; + + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class SafeListQueryDto { + /** + * 父目录ID,默认0(根目录) + */ + @NotNull(message = "父目录ID不能为空") + private Long parentId; + + + /** + * 类型:1-文件夹,2-文件,null-全部 + */ + private Integer type; + + + @NotNull(message = "项目不能为空") + private Long projectId; + + +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/safety/service/IHseFileFolderService.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/safety/service/IHseFileFolderService.java index 07fb38de..d4e1724b 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/safety/service/IHseFileFolderService.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/safety/service/IHseFileFolderService.java @@ -1,7 +1,8 @@ package org.dromara.safety.service; +import jakarta.servlet.http.HttpServletResponse; +import org.dromara.quality.domain.dto.fileFolder.SafeListQueryDto; import org.dromara.safety.domain.dto.fileFolder.FileFolderCreateDTO; -import org.dromara.safety.domain.dto.fileFolder.ListQueryDto; import org.dromara.safety.domain.vo.HseFileFolderVo; import org.dromara.safety.domain.bo.HseFileFolderBo; import org.dromara.safety.domain.HseFileFolder; @@ -9,8 +10,10 @@ import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.common.mybatis.core.page.PageQuery; import com.baomidou.mybatisplus.extension.service.IService; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; import java.util.Collection; import java.util.List; @@ -73,7 +76,7 @@ public interface IHseFileFolderService extends IService{ Boolean deleteWithValidByIds(Collection ids, Boolean isValid); - List listAll(ListQueryDto dto); + List listAll(SafeListQueryDto dto); /** * 创建文件或文件夹 @@ -100,4 +103,14 @@ public interface IHseFileFolderService extends IService{ * @return 树形结构列表 */ String unzip(Long id, Long parentId); + + /** + * 重命名文件或文件夹 + */ + Boolean reName(Long id,String name); + + /** + * 下载 + */ + void download(@PathVariable Long id, HttpServletResponse response) throws IOException; } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/safety/service/impl/HseFileFolderServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/safety/service/impl/HseFileFolderServiceImpl.java index 9375b72e..18dd7378 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/safety/service/impl/HseFileFolderServiceImpl.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/safety/service/impl/HseFileFolderServiceImpl.java @@ -1,17 +1,25 @@ package org.dromara.safety.service.impl; +import cn.hutool.core.util.ObjectUtil; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import jakarta.servlet.http.HttpServletResponse; import org.dromara.common.core.exception.ServiceException; import org.dromara.common.core.utils.MapstructUtils; import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.core.utils.file.FileUtils; import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.common.mybatis.core.page.PageQuery; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import lombok.RequiredArgsConstructor; +import org.dromara.common.oss.core.OssClient; +import org.dromara.common.oss.factory.OssFactory; +import org.dromara.quality.domain.dto.fileFolder.SafeListQueryDto; import org.dromara.safety.domain.dto.fileFolder.FileFolderCreateDTO; -import org.dromara.safety.domain.dto.fileFolder.ListQueryDto; +import org.dromara.system.domain.vo.SysOssVo; +import org.dromara.system.service.ISysOssService; +import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import org.dromara.safety.domain.bo.HseFileFolderBo; import org.dromara.safety.domain.vo.HseFileFolderVo; @@ -21,9 +29,9 @@ import org.dromara.safety.service.IHseFileFolderService; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; -import java.io.File; -import java.io.FileInputStream; +import java.io.*; import java.nio.charset.Charset; +import java.nio.file.Path; import java.util.*; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; @@ -39,6 +47,7 @@ import java.util.zip.ZipInputStream; public class HseFileFolderServiceImpl extends ServiceImpl implements IHseFileFolderService { private final HseFileFolderMapper baseMapper; + private final ISysOssService ossService; /** * 查询会议纪要 @@ -148,7 +157,7 @@ public class HseFileFolderServiceImpl extends ServiceImpl listAll(ListQueryDto dto) { + public List listAll(SafeListQueryDto dto) { LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); lqw.orderByDesc(HseFileFolder::getId); @@ -416,17 +425,42 @@ public class HseFileFolderServiceImpl extends ServiceImpl 0 && dotIndex < fileName.length() - 1) { fileRecord.setFileSuffix(fileName.substring(dotIndex + 1)); } + // 上传文件内容到OSS并设置文件路径 + // 上传文件内容到OSS并设置文件路径 + try { + // 从ZIP流中读取文件内容 + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buffer = new byte[32768]; + int bytesRead; + while ((bytesRead = zis.read(buffer)) != -1) { + baos.write(buffer, 0, bytesRead); + } + byte[] fileContent = baos.toByteArray(); - // TODO: 这里应该与实际文件存储服务集成,设置真实的文件路径 - fileRecord.setFilePath(""); // 实际应用中应设置真实文件存储路径 + // 检查文件长度,如果为0则跳过保存 + if (fileContent.length > 0) { + // 直接使用字节流上传到OSS,避免创建临时文件 + try (ByteArrayInputStream bais = new ByteArrayInputStream(fileContent)) { + String contentType = FileUtils.getMimeType(fileName); + SysOssVo ossVo = ossService.upload(bais, fileName, contentType, (long) fileContent.length); - save(fileRecord); + // 设置真实的文件路径 + fileRecord.setFilePath(ossVo.getUrl()); + fileRecord.setFileId(ossVo.getOssId()); + } - // 更新文件的路径(因为ID是自增的,需要先保存再更新路径) - fileRecord.setPath(currentPath + fileRecord.getId() + ","); - updateById(fileRecord); + save(fileRecord); - fileCount++; + // 更新文件的路径(因为ID是自增的,需要先保存再更新路径) + fileRecord.setPath(currentPath + fileRecord.getId() + ","); + updateById(fileRecord); + + fileCount++; + } + // 如果文件长度为0,则跳过保存操作 + } catch (Exception e) { + throw new ServiceException("文件上传失败: " + e.getMessage()); + } zis.closeEntry(); } @@ -472,8 +506,8 @@ public class HseFileFolderServiceImpl extends ServiceImpl + + + + + + UPDATE hse_file_folder + SET + path = REPLACE(path, #{oldPath}, #{newPath}), + level = level + #{levelDiff}, + update_time = NOW() + WHERE + path LIKE CONCAT(#{oldPath}, '%') + AND id != #{parentId} + +