package cn.teammodel.service.impl; import cn.hutool.core.lang.UUID; import cn.teammodel.common.ErrorCode; import cn.teammodel.common.PK; import cn.teammodel.config.exception.ServiceException; import cn.teammodel.model.dto.weekDuty.*; import cn.teammodel.model.entity.User; import cn.teammodel.model.entity.school.ClassInfo; import cn.teammodel.model.entity.school.School; import cn.teammodel.model.entity.weekDuty.WeekDuty; import cn.teammodel.model.entity.weekDuty.WeekDutyRecord; import cn.teammodel.model.vo.weekDuty.DutyRecordVo; import cn.teammodel.repository.ClassRepository; import cn.teammodel.repository.DutyRecordRepository; import cn.teammodel.repository.DutyRepository; import cn.teammodel.repository.SchoolRepository; import cn.teammodel.security.utils.SecurityUtil; import cn.teammodel.service.DutyService; import cn.teammodel.utils.SchoolDateUtil; import com.azure.cosmos.models.CosmosPatchOperations; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.time.*; import java.time.temporal.TemporalAdjusters; import java.util.*; import java.util.stream.Collectors; /** * @author winter * @create 2024-01-03 10:08 */ @Service public class DutyServiceImpl implements DutyService { @Resource private ClassRepository classRepository; @Resource private SchoolRepository schoolRepository; @Resource private DutyRecordRepository dutyRecordRepository; @Resource private DutyRepository dutyRepository; @Override public WeekDuty getTree() { User user = SecurityUtil.getLoginUser(); String schoolId = user.getSchoolId(); WeekDuty duty = dutyRepository.findBySchoolIdAndCode(schoolId , PK.WEEK_DUTY); if (duty == null) { // copy from template duty = dutyRepository.findBySchoolIdAndCode("template", PK.WEEK_DUTY); if (duty == null) { throw new RuntimeException("值周模板不存在"); } duty.setId(null); duty.setSchoolId(schoolId); // 刷新值周树和 spots 的 id refreshDuty(duty); duty = dutyRepository.save(duty); } return this.buildTree(duty); } @Override public WeekDuty insertNode(InsertDutyNodeDto insertDutyNodeDto) { String pid = insertDutyNodeDto.getPid(); String name = insertDutyNodeDto.getName(); Integer order = insertDutyNodeDto.getOrder(); Boolean positive = insertDutyNodeDto.getPositive(); Integer score = positive ? insertDutyNodeDto.getScore() : -insertDutyNodeDto.getScore(); String desc = insertDutyNodeDto.getDesc(); User user = SecurityUtil.getLoginUser(); String schoolId = user.getSchoolId(); WeekDuty duty = dutyRepository.findBySchoolIdAndCode(schoolId, PK.WEEK_DUTY); if (duty == null) { throw new ServiceException("值周树不存在"); } WeekDuty.DutyTreeNode newNode = new WeekDuty.DutyTreeNode(); List nodes = duty.getNodes(); // 只有三级节点需要 positive newNode.setPositive(null); // 非根节点(需考虑填充 path) if (StringUtils.isNotEmpty(pid)) { WeekDuty.DutyTreeNode parent = nodes.stream().filter(x -> pid.equals(x.getId())) .findFirst() .orElseThrow(() -> new ServiceException(ErrorCode.PARAMS_ERROR.getCode(), "pid 无效")); String level2 = parent.getName(); // 新节点是三级节点(二级节点不用特殊处理 String ppid = parent.getPid(); if (StringUtils.isNotEmpty(ppid)) { String[] path = new String[3]; WeekDuty.DutyTreeNode root = nodes.stream().filter(x -> ppid.equals(x.getId())) .findFirst() .orElseThrow(() -> new ServiceException(ErrorCode.SYSTEM_ERROR.getCode(), "值周树结构错误")); // 层数不为三级,抛出异常 if (StringUtils.isNotEmpty(root.getPid())) { throw new ServiceException(ErrorCode.SYSTEM_ERROR.getCode(), "值周树结构错误"); } String level1 = root.getName(); path[0] = level1; path[1] = level2; path[2] = name; newNode.setDesc(desc); newNode.setPath(path); newNode.setScore(score); newNode.setPositive(positive); } } newNode.setId(UUID.randomUUID().toString()); newNode.setPid(pid); newNode.setName(name); newNode.setOrder(order); newNode.setCreator(user.getName()); newNode.setCreatorId(user.getId()); newNode.setCreateTime(Instant.now().toEpochMilli()); nodes.add(newNode); return buildTree(dutyRepository.save(duty)); } @Override public WeekDuty deleteNode(DeleteDutyNodeDto deleteDutyNodeDto) { String schoolId = SecurityUtil.getLoginUser().getSchoolId(); String nodeToDeleteId = deleteDutyNodeDto.getNodeId(); WeekDuty duty = dutyRepository.findBySchoolIdAndCode(schoolId, PK.WEEK_DUTY); if (duty == null) { throw new ServiceException("值周树不存在"); } List nodes = duty.getNodes(); List nodesToDelete = new ArrayList<>(); WeekDuty.DutyTreeNode node = nodes.stream() .filter(x -> nodeToDeleteId.equals(x.getId())) .findFirst() .orElseThrow(() -> new ServiceException(ErrorCode.PARAMS_ERROR.getCode(), "删除的节点不存在")); nodesToDelete.add(node); this.collectNodesToDelete(nodeToDeleteId, nodes, nodesToDelete); // 批量删除节点 nodes.removeAll(nodesToDelete); return buildTree(dutyRepository.save(duty)); } @Override public WeekDuty updateNode(UpdateDutyNodeDto updateDutyNodeDto) { String nodeId = updateDutyNodeDto.getId(); String newNodeName = updateDutyNodeDto.getName(); Integer newScore = updateDutyNodeDto.getScore(); String desc = updateDutyNodeDto.getDesc(); Boolean positive = updateDutyNodeDto.getPositive(); String schoolId = SecurityUtil.getLoginUser().getSchoolId(); WeekDuty duty = dutyRepository.findBySchoolIdAndCode(schoolId, PK.WEEK_DUTY); if (duty == null) { throw new ServiceException("值周树不存在"); } List nodes = duty.getNodes(); WeekDuty.DutyTreeNode nodeToUpdate = nodes.stream().filter(x -> nodeId.equals(x.getId())) .findFirst() .orElseThrow(() -> new ServiceException("待更新的节点不存在")); String originNodeName = nodeToUpdate.getName(); // 更新当前节点 nodeToUpdate.setName(newNodeName); nodeToUpdate.setOrder(updateDutyNodeDto.getOrder()); // 一级,二级需同步更新三级节点的 path if (nodeToUpdate.getPath() == null || nodeToUpdate.getPath().length == 0) { int index = StringUtils.isEmpty(nodeToUpdate.getPid()) ? 0 : 1; nodes.forEach(x -> { // 修改三级节点的 path if (x.getPath() == null) { return; } String[] path = x.getPath(); if (originNodeName.equals(path[index])) { path[index] = newNodeName; x.setPath(path); } }); } else { // 三级节点 nodeToUpdate.setScore(positive ? newScore : -newScore); nodeToUpdate.setDesc(desc); nodeToUpdate.setPositive(newScore.equals(0) || positive); nodeToUpdate.getPath()[2] = newNodeName; } return buildTree(dutyRepository.save(duty)); } @Override public void vote(DutyVoteDto dutyVoteDto) { String classId = dutyVoteDto.getClassId(); String spotId = dutyVoteDto.getSpotId(); String dutyNodeId = dutyVoteDto.getDutyNodeId(); String note = dutyVoteDto.getNote(); boolean spread = dutyVoteDto.getSpread(); List attachments = dutyVoteDto.getAttachments(); User user = SecurityUtil.getLoginUser(); String schoolId = user.getSchoolId(); ClassInfo classInfo = classRepository.findClassByIdAndCode(classId, String.format(PK.CLASS, schoolId)); if (classInfo == null) { throw new ServiceException(ErrorCode.PARAMS_ERROR.getCode(), "目标班级不存在"); } String className = classInfo.getName(); String periodId = classInfo.getPeriodId(); List semesters = schoolRepository.findSemestersById(schoolId, periodId); if (ObjectUtils.isEmpty(semesters)) { throw new ServiceException(ErrorCode.SYSTEM_ERROR.getCode(), "内部结构异常"); } String academicYearId = SchoolDateUtil.calculateAcademicYearId(semesters, LocalDate.now()); WeekDuty duty = dutyRepository.findBySchoolIdAndCode(schoolId, PK.WEEK_DUTY); WeekDuty.DutyTreeNode dutyNode = duty.getNodes().stream() .filter(x -> dutyNodeId.equals(x.getId())) .findFirst() .orElseThrow(() -> new ServiceException(ErrorCode.PARAMS_ERROR.getCode(), "评价节点不存在")); if (dutyNode.getPath() == null || dutyNode.getPath().length == 0) { throw new ServiceException(ErrorCode.PARAMS_ERROR.getCode(), "只能评价三级节点"); } // dutynode 剪枝 dutyNode.setCreator(null); dutyNode.setCreatorId(null); dutyNode.setCreateTime(null); WeekDuty.DutySpot spot = duty.getSpots().stream() .filter(x -> spotId.equals(x.getId())) .findFirst() .orElseThrow(() -> new ServiceException(ErrorCode.PARAMS_ERROR.getCode(), "值周评价地点不存在")); WeekDutyRecord weekDutyRecord = dutyRecordRepository.findByClassIdAndAcademicYearIdAndCode( classId, academicYearId, String.format(PK.WEEK_DUTY_RECORD, schoolId) ); WeekDutyRecord.WeekDutyItem newItem = new WeekDutyRecord.WeekDutyItem(); newItem.setId(UUID.randomUUID().toString()); newItem.setDutyTreeNode(dutyNode); newItem.setSpotId(spot.getId()); newItem.setSpotName(spot.getName()); newItem.setSpread(spread); newItem.setAttachments(attachments); newItem.setNote(note); newItem.setCreateTime(Instant.now().toEpochMilli()); newItem.setCreator(user.getName()); newItem.setCreatorId(user.getId()); // 不存在则创建 if (weekDutyRecord == null) { List nodes = Collections.singletonList(newItem); weekDutyRecord = new WeekDutyRecord(); weekDutyRecord.setClassId(classId); weekDutyRecord.setClassName(className); weekDutyRecord.setAcademicYearId(academicYearId); weekDutyRecord.setScore(dutyNode.getScore()); weekDutyRecord.setNodes(nodes); weekDutyRecord.setCode(String.format(PK.WEEK_DUTY_RECORD, schoolId)); weekDutyRecord.setCreateTime(Instant.now().toEpochMilli()); dutyRecordRepository.save(weekDutyRecord); } else { CosmosPatchOperations operations = CosmosPatchOperations.create(); operations.add("/nodes/-", newItem); operations.increment("/score", dutyNode.getScore()); dutyRecordRepository.save( weekDutyRecord.getId(), PK.buildOf(PK.WEEK_DUTY_RECORD, schoolId), WeekDutyRecord.class, operations ); } } @Override public List insertSpot(InsertSpotDto insertSpotDto) { String schoolId = SecurityUtil.getLoginUser().getSchoolId(); WeekDuty duty = dutyRepository.findBySchoolIdAndCode(schoolId, PK.WEEK_DUTY); if (duty == null) { throw new ServiceException("值周树不存在"); } String name = insertSpotDto.getName(); CosmosPatchOperations operations = CosmosPatchOperations.create().add("/spots/-", new WeekDuty.DutySpot(UUID.randomUUID().toString(), name)); duty = dutyRepository.save( duty.getId(), PK.of(PK.WEEK_DUTY), WeekDuty.class, operations ); return duty.getSpots(); } @Override public List deleteSpot(String id) { String schoolId = SecurityUtil.getLoginUser().getSchoolId(); WeekDuty duty = dutyRepository.findBySchoolIdAndCode(schoolId, PK.WEEK_DUTY); if (duty == null) { throw new ServiceException("值周树不存在"); } List spots = duty.getSpots(); WeekDuty.DutySpot spot = spots.stream().filter(x -> id.equals(x.getId())).findFirst().orElseThrow(() -> new ServiceException("值周评价地点不存在")); spots.remove(spot); duty = dutyRepository.save(duty); return duty.getSpots(); } @Override public List findRecords(FindDutyRecordDto findDutyRecordDto) { String classId = StringUtils.isEmpty(findDutyRecordDto.getClassId()) ? null : findDutyRecordDto.getClassId(); String academicYearId = findDutyRecordDto.getAcademicYearId(); Boolean positive = findDutyRecordDto.getPositive(); Long startTime = findDutyRecordDto.getStartTime(); Long endTime = findDutyRecordDto.getEndTime(); User user = SecurityUtil.getLoginUser(); String schoolId = user.getSchoolId(); String userId = user.getId(); if (startTime == null || endTime == null) { LocalDateTime mondayOfCurWeek = LocalDateTime.now().with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)) .withHour(0) .withMinute(0) .withSecond(0) .withNano(0); startTime = mondayOfCurWeek.atZone(ZoneOffset.systemDefault()).toInstant().toEpochMilli(); endTime = Instant.now().toEpochMilli(); } List records = dutyRecordRepository.findRecordsByConditions( String.format(PK.WEEK_DUTY_RECORD, schoolId), academicYearId, userId, classId, positive, startTime, endTime); records = records.stream().sorted((o1, o2) -> o2.getInfo().getCreateTime().compareTo(o1.getInfo().getCreateTime())).collect(Collectors.toList()); return records; } @Override public void recallVote(RecallDutyVoteDto recallDutyVoteDto) { String recordId = recallDutyVoteDto.getRecordId(); String nodeId = recallDutyVoteDto.getNodeId(); User user = SecurityUtil.getLoginUser(); String schoolId = user.getSchoolId(); String userId = user.getId(); Optional optional = dutyRecordRepository.findById(recordId, PK.of(String.format(PK.WEEK_DUTY_RECORD, schoolId))); WeekDutyRecord weekDutyRecord = optional.orElseThrow(() -> new ServiceException(ErrorCode.PARAMS_ERROR.getCode(), "评价记录不存在")); WeekDutyRecord.WeekDutyItem item = weekDutyRecord.getNodes().stream() .filter(x -> nodeId.equals(x.getDutyTreeNode().getId())) .findFirst(). orElseThrow(() -> new ServiceException(ErrorCode.PARAMS_ERROR.getCode(), "评价记录不存在")); if (!userId.equals(item.getCreatorId())) { throw new ServiceException(ErrorCode.PARAMS_ERROR.getCode(), "只能撤回自己的评价"); } weekDutyRecord.setScore(weekDutyRecord.getScore() - item.getDutyTreeNode().getScore()); weekDutyRecord.getNodes().remove(item); dutyRecordRepository.save(weekDutyRecord); } /** * 刷新值周树和 spots 的 id */ private void refreshDuty(WeekDuty duty) { List nodes = duty.getNodes(); List spots = duty.getSpots(); // refresh tree nodes nodes.forEach(x -> { String oldId = x.getId(); String newId = UUID.randomUUID().toString(); x.setId(newId); // 将孩子节点的 pid 改为新的 id nodes.stream().filter(y -> oldId.equals(y.getPid())).forEach(y -> y.setPid(newId)); }); // refresh spot id spots.forEach(x -> x.setId(UUID.randomUUID().toString())); } /** * 递归收集要删除的节点 */ private void collectNodesToDelete(String nodeToDeleteId, List nodes, List nodesToDelete) { List children = nodes.stream() .filter(x -> nodeToDeleteId.equals(x.getPid())) .collect(Collectors.toList()); if (children.size() == 0) return; nodesToDelete.addAll(children); for (WeekDuty.DutyTreeNode child : children) { this.collectNodesToDelete(child.getId(), nodes, nodesToDelete); } } public WeekDuty buildTree(WeekDuty weekDuty) { if (weekDuty == null) { return null; } List nodes = weekDuty.getNodes(); List parents = new ArrayList<>(); // pid 为 null 或者 "" 则为 parents for (WeekDuty.DutyTreeNode node : nodes) { if (StringUtils.isBlank(node.getPid())) { parents.add(node); } } // 迭代构建孩子节点 for (WeekDuty.DutyTreeNode parent : parents) { buildChildren(parent, nodes); } // 根据 order 排序 parents = parents.stream().sorted(Comparator.comparing(WeekDuty.DutyTreeNode::getOrder)).collect(Collectors.toList()); weekDuty.setNodes(parents); return weekDuty; } private void buildChildren(WeekDuty.DutyTreeNode parent, List nodes) { List children = getChildren(parent, nodes); if (children.size() == 0) return; parent.setChildren(children); for (WeekDuty.DutyTreeNode child : children) { // 如果子节点还有孩子的话,就继续递归构建 if (getChildren(child, nodes).size() > 0) { buildChildren(child, nodes); } } } private List getChildren(WeekDuty.DutyTreeNode parent, List nodes) { List children = new ArrayList<>(); for (WeekDuty.DutyTreeNode node : nodes) { if (StringUtils.isNotBlank(node.getPid()) && node.getPid().equals(parent.getId())) { children.add(node); } } return children; } }