This commit is contained in:
zt
2025-10-14 18:42:02 +08:00
parent 1ceef7f1d1
commit b9507e1fd7
17 changed files with 1111 additions and 68 deletions

View File

@ -1,5 +1,6 @@
package org.dromara.project.domain.dto.leave; package org.dromara.project.domain.dto.leave;
import com.alibaba.excel.annotation.ExcelProperty;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.Data; import lombok.Data;
@ -26,12 +27,14 @@ public class BusLeaveManagerReviewReq implements Serializable {
* 管理员意见1未读 2同意 3拒绝 * 管理员意见1未读 2同意 3拒绝
*/ */
@NotNull(message = "管理员意见不能为空") @NotNull(message = "管理员意见不能为空")
private String managerOpinion; private String gangerOpinion;
/** /**
* 管理员说明 * 班组长说明
*/ */
private String managerExplain; @ExcelProperty(value = "班组长说明")
private String gangerExplain;
/** /**
* 备注 * 备注

View File

@ -6,8 +6,11 @@ import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data; import lombok.Data;
import org.dromara.common.excel.annotation.ExcelDictFormat; import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.excel.convert.ExcelDictConvert; import org.dromara.common.excel.convert.ExcelDictConvert;
import org.dromara.common.translation.annotation.Translation;
import org.dromara.common.translation.constant.TransConstant;
import org.dromara.project.domain.BusLeave; import org.dromara.project.domain.BusLeave;
import org.dromara.project.domain.vo.reissuecard.AuditUserVo; import org.dromara.project.domain.vo.reissuecard.AuditUserVo;
import org.springframework.transaction.annotation.Transactional;
import java.io.Serial; import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
@ -65,6 +68,9 @@ public class BusLeaveVo implements Serializable {
@ExcelDictFormat(dictType = "user_leave_type") @ExcelDictFormat(dictType = "user_leave_type")
private String leaveType; private String leaveType;
@Translation(type = TransConstant.DICT_TYPE_TO_LABEL, mapper = "leaveType",other = "user_leave_type")
private String leaveTypeName;
/** /**
* 请假开始时间 * 请假开始时间
*/ */

View File

@ -760,6 +760,7 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
.eq(BusAttendance::getUserId, userId) .eq(BusAttendance::getUserId, userId)
.eq(b, BusAttendance::getProjectId, projectId) .eq(b, BusAttendance::getProjectId, projectId)
.in(BusAttendance::getClockStatus, abnormalList) .in(BusAttendance::getClockStatus, abnormalList)
.ge(BusAttendance::getClockDate, LocalDate.now().minusDays(29))
.orderByDesc(BusAttendance::getClockDate) .orderByDesc(BusAttendance::getClockDate)
.orderByDesc(BusAttendance::getRuleTime) .orderByDesc(BusAttendance::getRuleTime)
); );

View File

@ -59,6 +59,8 @@ import java.util.*;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.dromara.common.core.constant.TenantConstants.SUPER_ADMIN_ID;
/** /**
* 施工人员请假申请Service业务层处理 * 施工人员请假申请Service业务层处理
* *
@ -236,73 +238,79 @@ public class BusLeaveServiceImpl extends ServiceImpl<BusLeaveMapper, BusLeave>
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public Boolean managerReview(BusLeaveManagerReviewReq req) { public Boolean managerReview(BusLeaveManagerReviewReq req) {
Long id = req.getId();
String managerOpinion = req.getManagerOpinion(); BusLeave busLeave = baseMapper.selectById(req.getId());
// 判断该请假记录是否存在 if (busLeave == null) {
BusLeave oldLeave = this.getById(id); throw new ServiceException("未找到该申请");
if (oldLeave == null) {
throw new ServiceException("施工人员请假申请不存在", HttpStatus.NOT_FOUND);
} }
// 如果已经审核过,则返回 String gangerOpinion = req.getGangerOpinion();
if (!BusOpinionStatusEnum.UNREAD.getValue().equals(oldLeave.getManagerOpinion())) { busLeave.setGangerOpinion(gangerOpinion);
throw new ServiceException("该请假已审核,请勿重复操作", HttpStatus.BAD_REQUEST); busLeave.setGangerExplain(req.getGangerExplain());
if(busLeave.getGangerId() == null){
LoginUser loginUser = LoginHelper.getLoginUser();
busLeave.setGangerName(loginUser.getNickname());
busLeave.setGangerId(loginUser.getUserId());
} }
// 判断班组长是否审核通过
String gangerOpinion = oldLeave.getGangerOpinion(); busLeave.setGangerTime(LocalDateTime.now());
if (!BusOpinionStatusEnum.PASS.getValue().equals(gangerOpinion)) {
throw new ServiceException("请等待班组长审核通过后再进行操作", HttpStatus.BAD_REQUEST); int i = baseMapper.updateById(busLeave);
if(gangerOpinion.equals("2")){
if(busLeave.getTimeType().equals("1")){
LocalDateTime startTime = busLeave.getStartTime();
LocalDateTime endTime = busLeave.getEndTime();
LocalDate startDate = startTime.toLocalDate();
LocalDate endDate = endTime.toLocalDate();
List<BusAttendance> list = attendanceService.list(Wrappers.lambdaQuery(BusAttendance.class)
.eq(BusAttendance::getProjectId, busLeave.getProjectId())
.le(BusAttendance::getClockDate, endDate)
.ge(BusAttendance::getClockDate, startDate)
.eq(BusAttendance::getUserId, busLeave.getUserId())
);
for (BusAttendance attendance : list) {
attendance.setClockStatus(BusAttendanceClockStatusEnum.LATE.getValue());
}
if(!list.isEmpty()){
attendanceService.updateBatchById(list);
}
}else if(busLeave.getTimeType().equals("2")){
if(busLeave.getPeriodType().equals("1")){
LocalDateTime startTime = busLeave.getStartTime();
LocalDate localDate = startTime.toLocalDate();
List<BusAttendance> list = attendanceService.list(Wrappers.lambdaQuery(BusAttendance.class)
.eq(BusAttendance::getProjectId, busLeave.getProjectId())
.eq(BusAttendance::getClockDate, localDate)
.eq(BusAttendance::getClockType, "1")
.eq(BusAttendance::getUserId, busLeave.getUserId())
);
for (BusAttendance attendance : list) {
attendance.setClockStatus(BusAttendanceClockStatusEnum.LATE.getValue());
}
if(!list.isEmpty()){
attendanceService.updateBatchById(list);
}
}else if(busLeave.getPeriodType().equals("2")){
LocalDateTime endTime = busLeave.getEndTime();
LocalDate localDate = endTime.toLocalDate();
List<BusAttendance> list = attendanceService.list(Wrappers.lambdaQuery(BusAttendance.class)
.eq(BusAttendance::getProjectId, busLeave.getProjectId())
.eq(BusAttendance::getClockDate, localDate)
.eq(BusAttendance::getClockType, "2")
.eq(BusAttendance::getUserId, busLeave.getUserId())
);
for (BusAttendance attendance : list) {
attendance.setClockStatus(BusAttendanceClockStatusEnum.LATE.getValue());
}
if(!list.isEmpty()){
attendanceService.updateBatchById(list);
}
}
}
} }
// todo 判断当前用户是否为项目管理员 return i>0;
// 判断是否为分包公司管理员
Long contractorId = oldLeave.getContractorId();
SubConstructionUser constructionUser = constructionUserService.lambdaQuery()
.eq(SubConstructionUser::getSysUserId, LoginHelper.getUserId())
.eq(SubConstructionUser::getContractorId, contractorId)
.eq(SubConstructionUser::getUserRole, SubConstructionUserRoleEnum.ADMIN.getValue())
.one();
if (constructionUser == null) {
throw new ServiceException("您无权审核该请假申请", HttpStatus.FORBIDDEN);
}
// 填充默认值,更新数据
BusLeave leave = new BusLeave();
leave.setId(id);
leave.setManagerId(constructionUser.getId());
leave.setManagerOpinion(managerOpinion);
leave.setManagerExplain(req.getManagerExplain());
leave.setManagerTime(LocalDateTime.now());
leave.setRemark(req.getRemark());
boolean result = this.updateById(leave);
if (!result) {
throw new ServiceException("更新管理员审核操作失败", HttpStatus.ERROR);
}
// 更新考勤表记录
LocalDateTime startTime = oldLeave.getStartTime();
LocalDateTime endTime = oldLeave.getEndTime();
// 计算相差的时间
// long diffInMillis = endTime.getTime() - startTime.getTime();
// long day = TimeUnit.MILLISECONDS.toDays(diffInMillis) + 1;
// Long userId = oldLeave.getUserId();
// String userName = oldLeave.getUserName();
// Long projectId = oldLeave.getProjectId();
// 遍历每一天
List<BusAttendance> attendanceList = new ArrayList<>();
// for (long i = 0; i < day; i++) {
// BusAttendance attendance = new BusAttendance();
// attendance.setUserId(userId);
// attendance.setUserName(userName);
// attendance.setProjectId(projectId);
// Date date = DateUtils.addDays(startTime, (int) i);
// attendance.setLeaveId(id);
// attendance.setClockDate(date);
// attendance.setClockStatus(BusAttendanceClockStatusEnum.LEAVE.getValue());
// attendance.setCommuter(BusAttendanceCommuterEnum.ALLDAY.getValue());
// attendanceList.add(attendance);
// }
boolean saveBatch = attendanceService.saveBatch(attendanceList);
if (!saveBatch) {
throw new ServiceException("更新考勤记录失败", HttpStatus.ERROR);
}
return true;
} }
/** /**
@ -375,7 +383,7 @@ public class BusLeaveServiceImpl extends ServiceImpl<BusLeaveMapper, BusLeave>
// 精确查询 // 精确查询
lqw.eq(ObjectUtils.isNotEmpty(id), BusLeave::getId, id); lqw.eq(ObjectUtils.isNotEmpty(id), BusLeave::getId, id);
lqw.eq(ObjectUtils.isNotEmpty(userId), BusLeave::getUserId, userId); lqw.eq(ObjectUtils.isNotEmpty(userId), BusLeave::getUserId, userId);
lqw.eq(ObjectUtils.isNotEmpty(gangerId), BusLeave::getGangerId, gangerId); lqw.eq(ObjectUtils.isNotEmpty(gangerId) && !Objects.equals(gangerId, SUPER_ADMIN_ID), BusLeave::getGangerId, gangerId);
lqw.eq(StringUtils.isNotBlank(gangerOpinion), BusLeave::getGangerOpinion, gangerOpinion); lqw.eq(StringUtils.isNotBlank(gangerOpinion), BusLeave::getGangerOpinion, gangerOpinion);
lqw.eq(StringUtils.isNotBlank(managerOpinion), BusLeave::getManagerOpinion, managerOpinion); lqw.eq(StringUtils.isNotBlank(managerOpinion), BusLeave::getManagerOpinion, managerOpinion);
lqw.eq(ObjectUtils.isNotEmpty(projectId), BusLeave::getProjectId, projectId); lqw.eq(ObjectUtils.isNotEmpty(projectId), BusLeave::getProjectId, projectId);
@ -418,6 +426,28 @@ public class BusLeaveServiceImpl extends ServiceImpl<BusLeaveMapper, BusLeave>
// 添加审核状态 // 添加审核状态
String status = BusReviewStatusEnum.getEnumByOpinionStatus(leave.getGangerOpinion(), leave.getManagerOpinion()); String status = BusReviewStatusEnum.getEnumByOpinionStatus(leave.getGangerOpinion(), leave.getManagerOpinion());
leaveVo.setAuditStatus(status); leaveVo.setAuditStatus(status);
//添加审核人
if(StrUtil.isBlank(leaveVo.getGangerName())){
String userType = leaveVo.getUserType();
List<SysUser> sysUsers = new ArrayList<>();
if(leaveVo.getGangerId()==null){
if("1".equals(userType)){
sysUsers = userService.selectUserByRoleIdAndProjectId(6L, leaveVo.getProjectId());
} else if ("2".equals(userType)) {
sysUsers = userService.selectUserByRoleIdAndProjectId(7L, leaveVo.getProjectId());
}
}else {
SysUserVo sysUserVo = userService.selectUserById(leaveVo.getGangerId());
if(sysUserVo != null){
leaveVo.setGangerName(sysUserVo.getNickName());
}
}
if(CollectionUtil.isNotEmpty(sysUsers)){
String collect = sysUsers.stream().map(SysUser::getNickName).collect(Collectors.joining());
leaveVo.setGangerName(collect);
}
}
return leaveVo; return leaveVo;
}).toList(); }).toList();
leaveVoPage.setRecords(leaveVoList); leaveVoPage.setRecords(leaveVoList);
@ -462,6 +492,12 @@ public class BusLeaveServiceImpl extends ServiceImpl<BusLeaveMapper, BusLeave>
// 5. 保存新记录(需补充完整字段赋值) // 5. 保存新记录(需补充完整字段赋值)
leave.setUserTime(LocalDateTime.now()); leave.setUserTime(LocalDateTime.now());
//插入班组
SubConstructionUser bySysUserId = constructionUserService.getBySysUserId(userId);
if (bySysUserId != null) {
leave.setTeamId(bySysUserId.getTeamId());
leave.setContractorId(bySysUserId.getContractorId());
}
this.save(leave); this.save(leave);
return leave.getId(); return leave.getId();
} }

View File

@ -359,6 +359,28 @@ public class BusReissueCardServiceImpl extends ServiceImpl<BusReissueCardMapper,
// 添加审核状态 // 添加审核状态
String status = BusReviewStatusEnum.getEnumByOpinionStatus(reissueCard.getGangerOpinion(), reissueCard.getManagerOpinion()); String status = BusReviewStatusEnum.getEnumByOpinionStatus(reissueCard.getGangerOpinion(), reissueCard.getManagerOpinion());
reissueCardVo.setStatus(status); reissueCardVo.setStatus(status);
//添加审核人
if(StrUtil.isBlank(reissueCardVo.getGangerName())){
String userType = reissueCardVo.getUserType();
List<SysUser> sysUsers = new ArrayList<>();
if(reissueCardVo.getGangerId()==null){
if("1".equals(userType)){
sysUsers = userService.selectUserByRoleIdAndProjectId(6L, reissueCardVo.getProjectId());
} else if ("2".equals(userType)) {
sysUsers = userService.selectUserByRoleIdAndProjectId(7L, reissueCardVo.getProjectId());
}
}else {
SysUserVo sysUserVo = userService.selectUserById(reissueCardVo.getGangerId());
if(sysUserVo != null){
reissueCardVo.setGangerName(sysUserVo.getNickName());
}
}
if(CollectionUtil.isNotEmpty(sysUsers)){
String collect = sysUsers.stream().map(SysUser::getNickName).collect(Collectors.joining());
reissueCardVo.setGangerName(collect);
}
}
return reissueCardVo; return reissueCardVo;
}).toList(); }).toList();
reissueCardVoPage.setRecords(reissueCardVoList); reissueCardVoPage.setRecords(reissueCardVoList);

View File

@ -0,0 +1,140 @@
package org.dromara.safety.controller;
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.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.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;
/**
* 会议纪要
*
* @author Lion Li
* @date 2025-10-14
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/safety/fileFolder")
public class HseFileFolderController extends BaseController {
private final IHseFileFolderService hseFileFolderService;
/**
* 查询会议纪要列表
*/
@GetMapping("/list-all")
public R<List<HseFileFolderVo>> listAll(ListQueryDto dto) {
return R.ok(hseFileFolderService.listAll(dto));
}
/**
* 查询指定目录的树形结构(一次性加载所有层级)
* @return 树形结构列表
*/
@GetMapping("/tree-all")
public R<List<FileFolderTreeVO>> treeAll(ListQueryDto dto) {
// 1. 查询所有子项利用path前缀匹配一次性加载所有层级
List<HseFileFolder> allItems = hseFileFolderService.list(new LambdaQueryWrapper<HseFileFolder>()
.like(HseFileFolder::getPath, "," + dto.getParentId() + ",") // 包含父ID的所有子项
.eq(dto.getType()!=null,HseFileFolder::getType, dto.getType())
.orderByDesc(HseFileFolder::getId)
.orderByAsc(HseFileFolder::getSort)
);
// 2. 构建树形结构
List<FileFolderTreeVO> treeVOS = buildTree(allItems, dto.getParentId());
return R.ok(treeVOS);
}
/**
* 递归构建树形结构
*/
private List<FileFolderTreeVO> buildTree(List<HseFileFolder> allItems, Long parentId) {
return allItems.stream()
.filter(item -> parentId.equals(item.getParentId()))
.map(item -> {
FileFolderTreeVO vo = new FileFolderTreeVO();
// 复制基本属性可使用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<HseFileFolder> create(@RequestBody FileFolderCreateDTO dto) {
return R.ok(hseFileFolderService.createFileOrFolder(dto));
}
/**
* 删除文件或文件夹(级联删除子项)
*/
@DeleteMapping("/{id}")
@Transactional
public R<Boolean> delete(@PathVariable Long id) {
return R.ok(hseFileFolderService.deleteFileOrFolder(id));
}
/**
* 移动文件或文件夹到指定目录
*/
@PostMapping("/move")
@Transactional
public R<Boolean> move(@RequestBody FileFolderMoveDTO dto) {
return R.ok(hseFileFolderService.moveFileOrFolder(dto.getId(), dto.getTargetParentId()));
}
}

View File

@ -0,0 +1,92 @@
package org.dromara.safety.domain;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
/**
* 会议纪要对象 hse_file_folder
*
* @author Lion Li
* @date 2025-10-14
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("hse_file_folder")
public class HseFileFolder extends BaseEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id")
private Long id;
/**
* 项目id
*/
private Long projectId;
/**
* 名称(文件名或文件夹名)
*/
private String name;
/**
* 父级ID0表示根目录
*/
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;
}

View File

@ -0,0 +1,90 @@
package org.dromara.safety.domain.bo;
import org.dromara.safety.domain.HseFileFolder;
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.*;
/**
* 会议纪要业务对象 hse_file_folder
*
* @author Lion Li
* @date 2025-10-14
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = HseFileFolder.class, reverseConvertGenerate = false)
public class HseFileFolderBo 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;
/**
* 父级ID0表示根目录
*/
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;
}

View File

@ -0,0 +1,51 @@
package org.dromara.safety.domain.dto.fileFolder;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Data
public class FileFolderCreateDTO {
/**
* 名称
*/
@NotBlank(message = "名称不能为空")
private String name;
/**
* 父级ID0表示根目录
*/
private Long parentId = 0L;
/**
* 文件类型
*/
@NotNull(message = "类型不能为空")
private Integer type;
/**
* 排序号
*/
private Integer sort=0;
/**
* 文件后缀
*/
private String fileSuffix;
/**
* 文件存储路径(仅文件有效)
*/
private String filePath;
/**
* 备注
*/
private String remark;
}

View File

@ -0,0 +1,14 @@
package org.dromara.safety.domain.dto.fileFolder;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Data
public class FileFolderMoveDTO {
@NotNull(message = "文件/文件夹ID不能为空")
private Long id;
@NotNull(message = "目标父目录ID不能为空")
private Long targetParentId;
}

View File

@ -0,0 +1,22 @@
package org.dromara.safety.domain.dto.fileFolder;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Data
public class ListQueryDto {
/**
* 父目录ID默认0根目录
*/
@NotNull(message = "父目录ID不能为空")
private Long parentId;
/**
* 类型1-文件夹2-文件null-全部
*/
private Integer type;
}

View File

@ -0,0 +1,109 @@
package org.dromara.safety.domain.vo;
import org.dromara.safety.domain.HseFileFolder;
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;
/**
* 会议纪要视图对象 hse_file_folder
*
* @author Lion Li
* @date 2025-10-14
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = HseFileFolder.class)
public class HseFileFolderVo 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;
/**
* 父级ID0表示根目录
*/
@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;
}

View File

@ -0,0 +1,53 @@
package org.dromara.safety.domain.vo.fileFolder;
import lombok.Data;
import java.util.List;
@Data
public class FileFolderTreeVO {
private Long id;
/**
* 名称(文件名或文件夹名)
*/
private String name;
/**
* 父级ID0表示根目录
*/
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<FileFolderTreeVO> children;
}

View File

@ -0,0 +1,25 @@
package org.dromara.safety.mapper;
import org.apache.ibatis.annotations.Param;
import org.dromara.safety.domain.HseFileFolder;
import org.dromara.safety.domain.vo.HseFileFolderVo;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
/**
* 会议纪要Mapper接口
*
* @author Lion Li
* @date 2025-10-14
*/
public interface HseFileFolderMapper extends BaseMapperPlus<HseFileFolder, HseFileFolderVo> {
/**
* 批量更新子项的路径和层级
*/
void batchUpdateChildPaths(
@Param("oldPath") String oldPath,
@Param("newPath") String newPath,
@Param("levelDiff") int levelDiff,
@Param("parentId") Long parentId);
}

View File

@ -0,0 +1,90 @@
package org.dromara.safety.service;
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;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.Collection;
import java.util.List;
/**
* 会议纪要Service接口
*
* @author Lion Li
* @date 2025-10-14
*/
public interface IHseFileFolderService extends IService<HseFileFolder>{
/**
* 查询会议纪要
*
* @param id 主键
* @return 会议纪要
*/
HseFileFolderVo queryById(Long id);
/**
* 分页查询会议纪要列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 会议纪要分页列表
*/
TableDataInfo<HseFileFolderVo> queryPageList(HseFileFolderBo bo, PageQuery pageQuery);
/**
* 查询符合条件的会议纪要列表
*
* @param bo 查询条件
* @return 会议纪要列表
*/
List<HseFileFolderVo> queryList(HseFileFolderBo bo);
/**
* 新增会议纪要
*
* @param bo 会议纪要
* @return 是否新增成功
*/
Boolean insertByBo(HseFileFolderBo bo);
/**
* 修改会议纪要
*
* @param bo 会议纪要
* @return 是否修改成功
*/
Boolean updateByBo(HseFileFolderBo bo);
/**
* 校验并批量删除会议纪要信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
List<HseFileFolderVo> listAll(ListQueryDto dto);
/**
* 创建文件或文件夹
*/
HseFileFolder createFileOrFolder(FileFolderCreateDTO dto);
/**
* 删除文件或文件夹(级联删除子项)
*/
boolean deleteFileOrFolder(Long id);
/**
* 移动文件或文件夹到指定目录
*/
boolean moveFileOrFolder(Long id, Long targetParentId);
}

View File

@ -0,0 +1,271 @@
package org.dromara.safety.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
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.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.safety.domain.dto.fileFolder.FileFolderCreateDTO;
import org.dromara.safety.domain.dto.fileFolder.ListQueryDto;
import org.springframework.stereotype.Service;
import org.dromara.safety.domain.bo.HseFileFolderBo;
import org.dromara.safety.domain.vo.HseFileFolderVo;
import org.dromara.safety.domain.HseFileFolder;
import org.dromara.safety.mapper.HseFileFolderMapper;
import org.dromara.safety.service.IHseFileFolderService;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Collection;
/**
* 会议纪要Service业务层处理
*
* @author Lion Li
* @date 2025-10-14
*/
@RequiredArgsConstructor
@Service
public class HseFileFolderServiceImpl extends ServiceImpl<HseFileFolderMapper, HseFileFolder> implements IHseFileFolderService {
private final HseFileFolderMapper baseMapper;
/**
* 查询会议纪要
*
* @param id 主键
* @return 会议纪要
*/
@Override
public HseFileFolderVo queryById(Long id){
return baseMapper.selectVoById(id);
}
/**
* 分页查询会议纪要列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return 会议纪要分页列表
*/
@Override
public TableDataInfo<HseFileFolderVo> queryPageList(HseFileFolderBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<HseFileFolder> lqw = buildQueryWrapper(bo);
Page<HseFileFolderVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
/**
* 查询符合条件的会议纪要列表
*
* @param bo 查询条件
* @return 会议纪要列表
*/
@Override
public List<HseFileFolderVo> queryList(HseFileFolderBo bo) {
LambdaQueryWrapper<HseFileFolder> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
private LambdaQueryWrapper<HseFileFolder> buildQueryWrapper(HseFileFolderBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<HseFileFolder> lqw = Wrappers.lambdaQuery();
lqw.orderByDesc(HseFileFolder::getId);
lqw.orderByAsc(HseFileFolder::getSort);
lqw.eq(bo.getProjectId() != null, HseFileFolder::getProjectId, bo.getProjectId());
lqw.like(StringUtils.isNotBlank(bo.getName()), HseFileFolder::getName, bo.getName());
lqw.eq(bo.getParentId() != null, HseFileFolder::getParentId, bo.getParentId());
lqw.eq(bo.getType() != null, HseFileFolder::getType, bo.getType());
lqw.eq(bo.getLevel() != null, HseFileFolder::getLevel, bo.getLevel());
lqw.eq(bo.getSort() != null, HseFileFolder::getSort, bo.getSort());
lqw.eq(StringUtils.isNotBlank(bo.getPath()), HseFileFolder::getPath, bo.getPath());
lqw.eq(bo.getFileId() != null, HseFileFolder::getFileId, bo.getFileId());
lqw.eq(StringUtils.isNotBlank(bo.getFileSuffix()), HseFileFolder::getFileSuffix, bo.getFileSuffix());
lqw.eq(StringUtils.isNotBlank(bo.getFilePath()), HseFileFolder::getFilePath, bo.getFilePath());
return lqw;
}
/**
* 新增会议纪要
*
* @param bo 会议纪要
* @return 是否新增成功
*/
@Override
public Boolean insertByBo(HseFileFolderBo bo) {
HseFileFolder add = MapstructUtils.convert(bo, HseFileFolder.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setId(add.getId());
}
return flag;
}
/**
* 修改会议纪要
*
* @param bo 会议纪要
* @return 是否修改成功
*/
@Override
public Boolean updateByBo(HseFileFolderBo bo) {
HseFileFolder update = MapstructUtils.convert(bo, HseFileFolder.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(HseFileFolder entity){
//TODO 做一些数据校验,如唯一约束
}
/**
* 校验并批量删除会议纪要信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否删除成功
*/
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if(isValid){
//TODO 做一些业务上的校验,判断是否需要校验
}
return baseMapper.deleteByIds(ids) > 0;
}
@Override
public List<HseFileFolderVo> listAll(ListQueryDto dto) {
LambdaQueryWrapper<HseFileFolder> lqw = Wrappers.lambdaQuery();
lqw.orderByDesc(HseFileFolder::getId);
lqw.orderByAsc(HseFileFolder::getSort);
lqw.eq(HseFileFolder::getParentId, dto.getParentId());
lqw.eq(dto.getType()!=null,HseFileFolder::getType, dto.getType());
return baseMapper.selectVoList(lqw);
}
public HseFileFolder createFileOrFolder(FileFolderCreateDTO dto) {
// 1. 验证父目录是否存在
if (dto.getParentId() != 0) {
HseFileFolder parent = getById(dto.getParentId());
if (parent == null || parent.getType() != 1) { // 父级必须是文件夹
throw new ServiceException("父目录不存在或不是文件夹");
}
}
// 2. 构建新文件/文件夹对象
HseFileFolder entity = new HseFileFolder();
entity.setName(dto.getName());
entity.setParentId(dto.getParentId());
entity.setType(dto.getType());
entity.setSort(dto.getSort() != null ? dto.getSort() : 0);
entity.setRemark(dto.getRemark());
// 3. 设置层级和路径
if (dto.getParentId() == 0) { // 根目录
entity.setLevel(1);
entity.setPath("," + entity.getId() + ","); // ID会在插入后自动生成这里先占位
} else { // 子目录/文件
HseFileFolder 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 {
HseFileFolder parent = getById(dto.getParentId());
entity.setPath(parent.getPath() + entity.getId() + ",");
}
updateById(entity);
return entity;
}
@Override
@Transactional
public boolean deleteFileOrFolder(Long id) {
HseFileFolder entity = getById(id);
if (entity == null) {
throw new ServiceException("文件/文件夹不存在");
}
// 1. 删除自身及所有子项通过path匹配
LambdaQueryWrapper<HseFileFolder> queryWrapper = Wrappers.<HseFileFolder>lambdaQuery()
.like(HseFileFolder::getPath, "," + id + ",");
return remove(queryWrapper);
}
@Override
@Transactional
public boolean moveFileOrFolder(Long id, Long targetParentId) {
// 1. 验证源文件和目标目录是否存在
HseFileFolder source = getById(id);
if (source == null) {
throw new ServiceException("源文件/文件夹不存在");
}
HseFileFolder targetParent = getById(targetParentId);
if (targetParent == null || targetParent.getType() != 1) {
throw new ServiceException("目标目录不存在或不是文件夹");
}
// 2. 防止循环移动(不能移动到自身或子目录下)
if (source.getPath().contains("," + targetParentId + ",")) {
throw new ServiceException("不能将文件夹移动到其子目录下");
}
// 3. 获取原路径和新路径的前缀
String oldPath = source.getPath();
String oldParentPath = source.getParentId() == 0 ? ",0," : getById(source.getParentId()).getPath();
String newParentPath = targetParent.getPath();
// 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 - source.getLevel(); // 层级变化量
// 批量更新子项
baseMapper.batchUpdateChildPaths(pathReplaceFrom, pathReplaceTo, levelDiff, id);
}
return true;
}
}

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.safety.mapper.HseFileFolderMapper">
<!-- 批量更新子项的路径和层级 -->
<update id="batchUpdateChildPaths">
UPDATE hse_file_folder
SET
path = REPLACE(path, #{oldPath}, #{newPath}),
level = level + #{levelDiff},
update_time = NOW()
WHERE
path LIKE CONCAT(#{oldPath}, '%')
AND parent_id != #{parentId}
</update>
</mapper>