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.dao.*; import cn.teammodel.model.dto.Appraise.*; import cn.teammodel.model.entity.User; import cn.teammodel.model.entity.appraise.Appraise; import cn.teammodel.model.entity.appraise.AppraiseRecord; import cn.teammodel.model.entity.appraise.AppraiseRecordItem; import cn.teammodel.model.entity.appraise.AppraiseTreeNode; import cn.teammodel.model.entity.school.ClassInfo; import cn.teammodel.model.entity.school.School; import cn.teammodel.model.entity.school.Student; import cn.teammodel.security.utils.SecurityUtil; import cn.teammodel.service.EvaluationService; import cn.teammodel.utils.RepositoryUtil; 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.beans.BeanUtils; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.*; import java.util.stream.Collectors; /** * @author winter * @create 2023-11-20 17:47 */ @Service public class EvaluationServiceImpl implements EvaluationService { /** * 评价记录对象的类型 */ private final static String TARGET_STUDENT = "student"; private final static String TARGET_CLASS = "class"; @Resource private ClassRepository classRepository; @Resource private StudentRepository studentRepository; @Resource private SchoolRepository schoolRepository; @Resource private AppraiseRecordRepository appraiseRecordRepository; @Resource private AppraiseRepository appraiseRepository; /** * 通用的获取用户学校和学段对应的 appraise 的方法: 判断参数,判断数据是否为空
* 从 token 中获取 schoolId */ private Appraise findAppraise(String periodId) { // 默认学区为 default periodId = StringUtils.isEmpty(periodId) ? "default" : periodId; User loginUser = SecurityUtil.getLoginUser(); String schoolId = loginUser.getSchoolId(); // 拿到要新增节点的原始数据 Appraise appraise = appraiseRepository.findAppraiseBySchoolIdAndPeriodIdAndCode(schoolId, periodId, PK.PK_APPRAISE); if (appraise == null) { throw new ServiceException(ErrorCode.PARAMS_ERROR.getCode(), "学校评价数据不存在"); } return appraise; } @Override public Appraise getTree(GetEvaluateTreeDto getEvaluateTreeDto) { String periodId = getEvaluateTreeDto.getPeriodId(); // 默认学区为 default periodId = StringUtils.isEmpty(periodId) ? "default" : periodId; User loginUser = SecurityUtil.getLoginUser(); String schoolId = loginUser.getSchoolId(); Appraise appraise = appraiseRepository.findAppraiseBySchoolIdAndPeriodIdAndCode(schoolId, periodId, PK.PK_APPRAISE); // todo: 是否要对学段进行鉴权 if (appraise != null) { return this.buildTree(appraise); } // 如果传参 period 所在没有 tree 的话,则复制一份模板 tree 给他 (处理节点 id) appraise = RepositoryUtil.findOne(appraiseRepository.findTemplateTree(), "获取模板评价树失败"); if (appraise == null) { throw new ServiceException(); } refreshAppraiseTree(appraise.getNodes()); appraise.setPeriodId(periodId); appraise.setSchoolId(schoolId); appraise.setId(null); appraise = appraiseRepository.save(appraise); return this.buildTree(appraise); } /** * 将树的节点的 id 进行替换(天才) */ private void refreshAppraiseTree(List nodes) { List children = nodes.stream().filter(item -> item.getPid() != null).collect(Collectors.toList()); // 将非 root 的 nodes 通过 pid 收集成 list, 再遍历 nodes, 将其每一个 id 对应 map 中的 list 的 pid修改成新的 id 即可 Map> pidNodeMap = children.stream().collect(Collectors.groupingBy(AppraiseTreeNode::getPid)); nodes.forEach(item -> { String newId = UUID.randomUUID().toString(); String oldId = item.getId(); List appraiseTreeNodes = pidNodeMap.get(oldId); if (appraiseTreeNodes != null) { appraiseTreeNodes.forEach(node -> node.setPid(newId)); } // 处理每个节点属性 item.setId(newId); item.setCreator("template"); }); } @Override public Appraise buildTree(Appraise appraise) { if (appraise == null) { return null; } List nodes = appraise.getNodes(); List parents = new ArrayList<>(); // pid 为 null 或者 "" 则为 parents for (AppraiseTreeNode node : nodes) { if (StringUtils.isBlank(node.getPid())) { parents.add(node); } } // 迭代构建孩子节点 for (AppraiseTreeNode parent : parents) { buildChildren(parent, nodes); } appraise.setNodes(parents); return appraise; } @Override public void flattenTree(List trees, List nodes) { // 递归结束条件 if (ObjectUtils.isEmpty(trees)) return; // 这里的每个 tree 都是组装好的树,区别于单个节点 for (AppraiseTreeNode tree : trees) { // 递归 flattenTree(tree.getChildren(), nodes); // 回溯时删除 children,添加进列表 tree.setChildren(null); nodes.add(tree); } } @Override public Appraise insertNode(InsertNodeDto insertNodeDto) { Appraise appraise = findAppraise(insertNodeDto.getPeriodId()); User loginUser = SecurityUtil.getLoginUser(); List originNodes = appraise.getNodes(); // 拷贝数据到新节点 AppraiseTreeNode newNode = new AppraiseTreeNode(); BeanUtils.copyProperties(insertNodeDto, newNode); String newNodePid = newNode.getPid(); // 根节点直接添加 if (StringUtils.isNotEmpty(newNodePid)) { boolean invalid = originNodes.stream().noneMatch(item -> newNodePid.equals(item.getId())); if (invalid) { throw new ServiceException(ErrorCode.PARAMS_ERROR.getCode(), "父节点不存在"); } } // todo: 可不可以添加默认值 order ? newNode.setId(UUID.randomUUID().toString()); newNode.setCreatorId(loginUser.getId()); newNode.setCreator(loginUser.getName()); newNode.setCreateTime(LocalDateTime.now()); originNodes.add(newNode); return buildTree(appraiseRepository.save(appraise)); } @Override public Appraise updateNode(UpdateNodeDto updateNodeDto) { String updateNodeId = updateNodeDto.getId(); Appraise appraise = findAppraise(updateNodeDto.getPeriodId()); List originNodes = appraise.getNodes(); // 每个节点都有 id, 直接校验是否合法 AppraiseTreeNode updateNode = originNodes.stream() .filter(item -> updateNodeId.equals(item.getId())) .findFirst() .orElseThrow(() -> new ServiceException(ErrorCode.PARAMS_ERROR.getCode(), "更新节点不存在")); // 更新字段,考虑直接 copy properties updateNode.setName(updateNodeDto.getName()); updateNode.setLogo(updateNodeDto.getLogo()); updateNode.setOrder(updateNodeDto.getOrder()); updateNode.setPraise(updateNodeDto.isPraise()); // todo: 为新节点赋值必须参数 (id, creator), 可不可以添加默认值 order ? return buildTree(appraiseRepository.save(appraise)); } @Override public Appraise deleteNode(DeleteNodeDto deleteNodeDto) { // 删除指定节点,可能是(一级,二级,三级),设计一个通用的 Appraise appraise = findAppraise(deleteNodeDto.getPeriodId()); List nodes = appraise.getNodes(); List nodesToDelete = new ArrayList<>(); // 迭代器安全删除 for (AppraiseTreeNode node : nodes) { if (node.getId().equals(deleteNodeDto.getId())) { // 删除当前节点 nodesToDelete.add(node); // 递归删除其孩子节点 this.collectNodesToDelete(node.getId(), nodes, nodesToDelete); } } if (ObjectUtils.isEmpty(nodesToDelete)) { throw new ServiceException(ErrorCode.PARAMS_ERROR.getCode(), "删除节点不存在"); } nodes.removeAll(nodesToDelete); return buildTree(appraiseRepository.save(appraise)); } @Override public void vote(AppraiseVoteDto appraiseVoteDto) { String targetId = appraiseVoteDto.getTargetId(); boolean spread = appraiseVoteDto.isSpread(); String targetType = appraiseVoteDto.getTargetType(); String appraiseId = appraiseVoteDto.getAppraiseId(); User loginUser = SecurityUtil.getLoginUser(); String schoolId = loginUser.getSchoolId(); String classId; String periodId; String name; String avatar = null; // 分别对班级和学生的关键信息取值 if (targetType.equals(TARGET_STUDENT)) { Student student = studentRepository.findStudentByIdAndCode(targetId, String.format(PK.STUDENT, schoolId)); if (student == null) { throw new ServiceException(ErrorCode.PARAMS_ERROR.getCode(), "该学生不存在"); } classId = student.getClassId(); periodId = student.getPeriodId(); name = student.getName(); avatar = student.getPicture(); } else if (targetType.equals(TARGET_CLASS)){ ClassInfo classInfo = classRepository.findClassByIdAndCode(targetId, String.format(PK.CLASS, schoolId)); if (classInfo == null) { throw new ServiceException(ErrorCode.PARAMS_ERROR.getCode(), "班级不存在"); } classId = targetId; periodId = classInfo.getPeriodId(); name = classInfo.getName(); } else { throw new ServiceException(ErrorCode.PARAMS_ERROR.getCode(), "不受支持的评价对象"); } // 获取评价项节点 List nodes = appraiseRepository.findNodeById(PK.PK_APPRAISE, appraiseId); AppraiseTreeNode appraiseTreeNode = RepositoryUtil.findOne(nodes, "获取评价项失败"); // 通过 periodId 获取 semesters List periodById = schoolRepository.findPeriodById(schoolId, periodId); School.Period period = RepositoryUtil.findOne(periodById, "获取学段失败"); List semesters = period.getSemesters(); // 获取当前学年学期组合 ID String academicYearId = SchoolDateUtil.generateAcademicId(semesters, LocalDate.now()); // 查询是否存在记录 AppraiseRecord record = appraiseRecordRepository.findAppraiseRecordByTargetIdAndClassIdAndAcademicYearIdAndCode( targetId, classId, academicYearId, String.format(PK.PK_APPRAISE_RECORD,schoolId) ); // 初始化新的评价节点 AppraiseRecordItem item = new AppraiseRecordItem(); item.setId(UUID.randomUUID().toString()); item.setAppraiseNode(appraiseTreeNode); item.setCreator(loginUser.getName()); item.setCreatorId(loginUser.getId()); item.setCreateTime(LocalDateTime.now()); // 不存在或者学生的班级不一样则创建一条新的,存在则处理一下分值后再向其 nodes 中插入一条 item if (record == null || !classId.equals(record.getClassId())) { List items = Collections.singletonList(item); record = new AppraiseRecord(); record.setTargetId(targetId); record.setTargetType(targetType); record.setClassId(classId); record.setName(name); record.setAvatar(avatar); record.setAcademicYearId(academicYearId); record.setPraiseCount(appraiseTreeNode.isPraise() ? 1 : -1); record.setScore(ObjectUtils.isEmpty(appraiseTreeNode.getScore()) ? 0 : appraiseTreeNode.getScore()); record.setNodes(items); record.setCode(String.format(PK.PK_APPRAISE_RECORD, schoolId)); appraiseRecordRepository.save(record); } else { // 处理学校与学生的差异 if (targetType.equals(TARGET_CLASS)){ item.setSpread(spread); } CosmosPatchOperations operations = CosmosPatchOperations.create(); operations.add("/nodes/0", item); // 表扬 long praise = appraiseTreeNode.isPraise() ? 1 : -1; operations.increment("/praiseCount", praise); // 加分 int scoreToPlus = ObjectUtils.isEmpty(appraiseTreeNode.getScore()) ? 0 : appraiseTreeNode.getScore(); operations.increment("/score", scoreToPlus); // patch doc appraiseRecordRepository.save(record.getId(), PK.buildOf(PK.PK_APPRAISE_RECORD, schoolId), AppraiseRecord.class, operations); } } @Override public List findVoteRecord(FindVoteRecordDto findVoteRecordDto) { String periodId = findVoteRecordDto.getPeriodId(); String targetId = StringUtils.isBlank(findVoteRecordDto.getTargetId()) ? null : findVoteRecordDto.getTargetId(); String targetType = StringUtils.isBlank(findVoteRecordDto.getTargetType()) ? null : findVoteRecordDto.getTargetType(); String classId = StringUtils.isBlank(findVoteRecordDto.getClassId()) ? null : findVoteRecordDto.getClassId(); User loginUser = SecurityUtil.getLoginUser(); String teacherId = loginUser.getId(); String schoolId = loginUser.getSchoolId(); List semesters = schoolRepository.findSemestersById(schoolId, periodId); String academicYearId = SchoolDateUtil.generateAcademicId(semesters, LocalDate.now()); List appraiseRecordItems = appraiseRecordRepository.searchNodesByCondition( targetId, targetType, classId, teacherId, academicYearId, String.format(PK.PK_APPRAISE_RECORD, schoolId) ); return appraiseRecordItems; } @Override public void recallVote(RecallVoteDto recallVoteDto) { String recordId = recallVoteDto.getRecordId(); String nodeId = recallVoteDto.getNodeId(); User loginUser = SecurityUtil.getLoginUser(); String schoolId = loginUser.getSchoolId(); String userId = loginUser.getId(); Optional optional = appraiseRecordRepository.findById(recordId, PK.buildOf(PK.PK_APPRAISE_RECORD, schoolId)); AppraiseRecord appraiseRecord = optional.orElseThrow(() -> new ServiceException("该记录不存在")); AppraiseRecordItem record = appraiseRecord.getNodes() .stream() .filter(item -> nodeId.equals(item.getId())) .findFirst().orElseThrow(() -> new ServiceException("该记录节点不存在")); // 鉴权(不是创建老师不能撤回) if (!userId.equals(record.getCreatorId())) { throw new ServiceException(ErrorCode.NO_AUTH_ERROR.getCode(), "您不是创建老师,不能撤回"); } // 删除评价项并且恢复评分 appraiseRecord.getNodes().removeIf(item -> nodeId.equals(item.getId())); boolean praise = record.getAppraiseNode().isPraise(); Integer newPraiseCount = appraiseRecord.getPraiseCount() + (praise ? -1 : 1); appraiseRecord.setPraiseCount(newPraiseCount); int score = record.getAppraiseNode().getScore() == null ? 0 : record.getAppraiseNode().getScore(); Integer newScore = appraiseRecord.getScore() - score; appraiseRecord.setScore(newScore); // 保存 appraiseRecordRepository.save(appraiseRecord); } /** * 递归收集 id 的节点及 id 节点的孩子节点 (迭代器删除居然也报错) */ private void collectNodesToDelete(String id, List nodes, List nodesToDelete) { for (AppraiseTreeNode node : nodes) { if (id.equals(node.getPid())) { // 收集 nodesToDelete.add(node); // 递归收集 collectNodesToDelete(node.getId(), nodes, nodesToDelete); } } } /** * 递归的构建父亲节点的孩子,以及孩子的孩子 (理论支持无极树,但应该考虑是否增加递归深度) */ private void buildChildren(AppraiseTreeNode parent, List nodes) { List children = getChildren(parent, nodes); if (children.size() == 0) return; parent.setChildren(children); for (AppraiseTreeNode child : children) { // 如果子节点还有孩子的话,就继续递归构建 if (getChildren(child, nodes).size() > 0) { buildChildren(child, nodes); } } } /** * 获取节点的孩子节点列表 */ private List getChildren(AppraiseTreeNode parent, List nodes) { List children = new ArrayList<>(); for (AppraiseTreeNode node : nodes) { if (StringUtils.isNotBlank(node.getPid()) && node.getPid().equals(parent.getId())) { children.add(node); } } return children; } }