package cn.teammodel.service.impl; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.lang.UUID; import cn.teammodel.common.ErrorCode; import cn.teammodel.common.FiveEducations; import cn.teammodel.common.IdRequest; import cn.teammodel.common.PK; import cn.teammodel.config.exception.ServiceException; import cn.teammodel.model.dto.Appraise.*; import cn.teammodel.model.entity.User; import cn.teammodel.model.entity.appraise.*; import cn.teammodel.model.entity.school.ClassInfo; import cn.teammodel.model.entity.school.School; import cn.teammodel.model.entity.school.Student; import cn.teammodel.model.vo.appraise.AppraiseRecordVo; import cn.teammodel.model.vo.appraise.StudentReportVo; import cn.teammodel.repository.*; import cn.teammodel.security.utils.SecurityUtil; import cn.teammodel.service.EvaluationService; import cn.teammodel.utils.ChartUtil; import cn.teammodel.utils.PdfUtil; import cn.teammodel.utils.RepositoryUtil; import cn.teammodel.utils.SchoolDateUtil; import com.azure.cosmos.models.CosmosPatchOperations; import com.azure.spring.data.cosmos.core.query.CosmosPageRequest; import com.itextpdf.text.DocumentException; import com.itextpdf.text.pdf.PdfReader; import com.itextpdf.text.pdf.PdfStamper; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.jfree.chart.ChartUtils; import org.jfree.chart.JFreeChart; import org.jfree.data.general.DefaultPieDataset; import org.springframework.core.io.ClassPathResource; import org.springframework.data.domain.Page; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import javax.annotation.Resource; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.time.Instant; import java.time.LocalDate; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; 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) { 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(); User loginUser = SecurityUtil.getLoginUser(); String schoolId = loginUser.getSchoolId(); Appraise appraise = appraiseRepository.findAppraiseBySchoolIdAndPeriodIdAndCode(schoolId, periodId, PK.PK_APPRAISE); // 如果传参 period 所在没有 tree 的话,则复制一份模板 tree 给他 (处理节点 id) if (appraise == null) { appraise = RepositoryUtil.findOne(appraiseRepository.findTemplateTree(), "获取模板评价树失败"); if (appraise == null) { throw new ServiceException("模板评价树不存在"); } // refresh refreshAppraiseTree(appraise); appraise.setPeriodId(periodId); appraise.setSchoolId(schoolId); appraise.setId(null); appraise = appraiseRepository.save(appraise); } return this.buildTree(appraise); } /** * 将 appraise 进行替换(天才) */ private void refreshAppraiseTree(Appraise appraise) { List nodes = appraise.getNodes(); List rules = appraise.getAchievementRules(); 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"); }); // refresh rules rules.forEach(item -> item.setId(UUID.randomUUID().toString())); } @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); } // 排序 parents = parents.stream().sorted(Comparator.comparing(AppraiseTreeNode::getOrder)).collect(Collectors.toList()); 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) { String periodId = insertNodeDto.getPeriodId(); String pid = insertNodeDto.getPid(); String name = insertNodeDto.getName(); String logo = insertNodeDto.getLogo(); Integer order = insertNodeDto.getOrder(); Integer score = insertNodeDto.getScore(); Boolean isPraise = insertNodeDto.getIsPraise(); Appraise appraise = findAppraise(periodId); User loginUser = SecurityUtil.getLoginUser(); List originNodes = appraise.getNodes(); AppraiseTreeNode newNode = new AppraiseTreeNode(); // 根节点直接添加 if (StringUtils.isNotEmpty(pid)) { boolean invalid = originNodes.stream().noneMatch(item -> pid.equals(item.getId())); if (invalid) { throw new ServiceException(ErrorCode.PARAMS_ERROR.getCode(), "父节点不存在"); } } if (StringUtils.isNotEmpty(pid)) { AppraiseTreeNode parent = originNodes.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]; AppraiseTreeNode root = originNodes.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.setPath(path); newNode.setPraise(isPraise); newNode.setLogo(logo); newNode.setScore(score); } } newNode.setId(UUID.randomUUID().toString()); newNode.setPid(pid); newNode.setName(name); newNode.setOrder(order); newNode.setCreatorId(loginUser.getId()); newNode.setCreator(loginUser.getName()); newNode.setCreateTime(Instant.now().toEpochMilli()); originNodes.add(newNode); return buildTree(appraiseRepository.save(appraise)); } @Override public Appraise updateNode(UpdateNodeDto updateNodeDto) { String updateNodeId = updateNodeDto.getId(); String newNodeName = updateNodeDto.getName(); 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(), "更新节点不存在")); String originNodeName = updateNode.getName(); updateNode.setName(newNodeName); updateNode.setOrder(updateNodeDto.getOrder()); // 一级/二级 if (updateNode.getPath() == null || updateNode.getPath().length == 0) { int index = StringUtils.isEmpty(updateNode.getPid()) ? 0 : 1; originNodes.forEach(x -> { // 修改三级节点的 path if (x.getPath() == null) { return; } String[] path = x.getPath(); if (originNodeName.equals(path[index])) { path[index] = newNodeName; x.setPath(path); } }); } else { // 三级 updateNode.setLogo(updateNodeDto.getLogo()); updateNode.setPraise(updateNodeDto.isPraise()); updateNode.getPath()[2] = newNodeName; } 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); break; } } 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(); boolean pushParent = appraiseVoteDto.isPushParent(); String targetType = appraiseVoteDto.getTargetType(); String appraiseId = appraiseVoteDto.getAppraiseId(); User loginUser = SecurityUtil.getLoginUser(); String schoolId = loginUser.getSchoolId(); String classId; String className; String periodId; String name; String avatar = null; String gender = null; // 分别对班级和学生的关键信息取值 if (targetType.equals(TARGET_STUDENT)) { List list = studentRepository.findByIdAndCode(targetId, String.format(PK.STUDENT, schoolId)); Student student = RepositoryUtil.findOne(list, "该学生不存在"); classId = student.getClassId(); periodId = student.getPeriodId(); name = student.getName(); avatar = student.getPicture(); gender = student.getGender(); // 获取班级信息 ClassInfo classInfo = classRepository.findClassByIdAndCode(classId, String.format(PK.CLASS, schoolId)); className = classInfo.getName(); } 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(); className = classInfo.getName(); } else { throw new ServiceException(ErrorCode.PARAMS_ERROR.getCode(), "不受支持的评价对象"); } // 获取评价项节点 List nodes = appraiseRepository.findNodeById(PK.PK_APPRAISE, appraiseId); AppraiseTreeNode appraiseTreeNode = RepositoryUtil.findOne(nodes, "该评价项不存在"); if (appraiseTreeNode.getPath() == null) { throw new ServiceException(ErrorCode.PARAMS_ERROR.getCode(), "仅能评价三级评价项"); } // 通过 periodId 获取 semesters List periodById = schoolRepository.findPeriodById(schoolId, periodId); School.Period period = RepositoryUtil.findOne(periodById, "获取学段失败"); List semesters = period.getSemesters(); // 获取当前学年学期组合 ID String academicYearId = SchoolDateUtil.calculateAcademicYearId(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(Instant.now().toEpochMilli()); // 处理学校与学生的差异 if (targetType.equals(TARGET_CLASS)) { item.setSpread(spread); } else { item.setPushParent(pushParent); } // 不存在或者学生的班级不一样则创建一条新的,存在则处理一下分值后再向其 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.setClassName(className); record.setName(name); record.setAvatar(avatar); record.setGender(gender); 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 { CosmosPatchOperations operations = CosmosPatchOperations.create(); operations.add("/nodes/-", item); // 表扬 (待改进不会减少表扬数) if (appraiseTreeNode.isPraise()) { operations.increment("/praiseCount", 1); } // 加分 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(); Boolean isPraise = findVoteRecordDto.getIsPraise(); User loginUser = SecurityUtil.getLoginUser(); String teacherId = loginUser.getId(); String schoolId = loginUser.getSchoolId(); List semesters = schoolRepository.findSemestersById(schoolId, periodId); String academicYearId = SchoolDateUtil.calculateAcademicYearId(semesters, LocalDate.now()); // 分页 Sort sort = Sort.by("createTime").descending(); final CosmosPageRequest pageRequest = new CosmosPageRequest(findVoteRecordDto.getCurrent(), findVoteRecordDto.getSize(), null, sort); Page appraiseRecordItemPage = appraiseRecordRepository.searchNodesByCondition( targetId, targetType, classId, teacherId, academicYearId, isPraise, String.format(PK.PK_APPRAISE_RECORD, schoolId), pageRequest ); List content = appraiseRecordItemPage.getContent(); return content; } @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 (!loginUser.getRoles().contains("admin") && !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); } public List Reports(ReportDto reportDto) { // 获取当前登录用户和学校信息 User user = SecurityUtil.getLoginUser(); String schoolId = user.getSchoolId(); // 获取学生 ID 列表 List studentIds = reportDto.getIds(); if (CollectionUtils.isEmpty(studentIds)) { throw new ServiceException(ErrorCode.PARAM_ERROR.getCode(), "学生 ID 列表不能为空"); } Set studentIdSet = new HashSet<>(studentIds); // 批量查询学生信息 List students = studentRepository.findAllByCodeAndIdIn(String.format(PK.STUDENT, schoolId),studentIdSet); if (CollectionUtils.isEmpty(students)) { throw new ServiceException(ErrorCode.OPERATION_ERROR.getCode(), "未找到对应的学生信息"); } // 获取学段 ID 和班级 ID String periodId = students.get(0).getPeriodId(); // 假设所有学生属于同一个学段 List classIds = students.stream().map(Student::getClassId).collect(Collectors.toList()); // 查询学期信息 List semesters = schoolRepository.findSemestersById(schoolId, periodId); String academicYearId = SchoolDateUtil.calculateAcademicYearId(semesters, LocalDate.now()); // 批量查询评价记录 List appraiseRecords = appraiseRecordRepository.findAllRecord( studentIds, classIds.get(0), academicYearId, String.format(PK.PK_APPRAISE_RECORD, schoolId) ); if (CollectionUtils.isEmpty(appraiseRecords)) { throw new ServiceException(ErrorCode.OPERATION_ERROR.getCode(), "未找到对应的评价记录"); } // 查询班级内学生总数 Map classStudentCountMap = new HashMap<>(); for (String classId : classIds) { int count = studentRepository.countByClassIdAndCode(classId, String.format(PK.STUDENT, schoolId)); classStudentCountMap.put(classId, count); } // 查询成就规则 Appraise appraise = RepositoryUtil.findOne(appraiseRepository.findRulesById(schoolId, periodId), "当前成就规则还未创建"); List rules = appraise.getAchievementRules(); if (CollectionUtils.isEmpty(rules)) { throw new ServiceException(ErrorCode.OPERATION_ERROR.getCode(), "当前成就规则为空"); } // 创建线程池 ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2); List> futures = new ArrayList<>(); // 并行处理每个学生的报告生成任务 for (Student student : students) { CompletableFuture future = CompletableFuture.supplyAsync(() -> { try { return generateStudentReport(student, appraiseRecords, classStudentCountMap, rules, academicYearId, schoolId); } catch (Exception e) { System.out.println("生成报告失败:" + e.getMessage()); return null; } }, executor); futures.add(future); } // 等待所有任务完成并收集结果 List reportVos = futures.stream() .map(CompletableFuture::join) .filter(Objects::nonNull) .collect(Collectors.toList()); // 关闭线程池 executor.shutdown(); return reportVos; } private StudentReportVo generateStudentReport(Student student, List appraiseRecords, Map classStudentCountMap, List rules, String academicYearId, String schoolId) { String studentId = student.getId(); String classId = student.getClassId(); // 查找当前学生的评价记录 Optional appraiseRecordOpt = appraiseRecords.stream() .filter(record -> record.getTargetId().equals(studentId)) .findFirst(); // 初始化默认值 Map praiseDistribution = StudentReportVo.ofFiveEducation(); Map criticalDistribution = StudentReportVo.ofFiveEducation(); List records = Collections.emptyList(); int praiseCount = 0; float beyondPercent = 0.0f; AchievementRule curAchievement = rules.get(rules.size() - 1); // 默认使用最低成就规则 int n = 1; // 如果有评价记录,则填充数据 if (appraiseRecordOpt.isPresent()) { AppraiseRecord appraiseRecord = appraiseRecordOpt.get(); // 处理评价记录 if (appraiseRecord.getNodes() != null) { // 计算雷达图数据 for (AppraiseRecordItem record : appraiseRecord.getNodes()) { AppraiseTreeNode appraiseNode = record.getAppraiseNode(); String[] path = appraiseNode.getPath(); if (path == null || path.length == 0) { continue; } String root = path[0]; String key = FiveEducations.getCodeByName(root); if (key != null) { if (appraiseNode.isPraise()) { praiseDistribution.put(key, praiseDistribution.getOrDefault(key, 0) + 1); } else { criticalDistribution.put(key, criticalDistribution.getOrDefault(key, 0) + 1); } } } // 计算学生排名 praiseCount = appraiseRecord.getPraiseCount(); int greaterCount = appraiseRecordRepository.findClassRecord(String.format(PK.PK_APPRAISE_RECORD, schoolId), academicYearId, classId, praiseCount); int stuInClassCount = classStudentCountMap.get(classId); beyondPercent = (float) (stuInClassCount - greaterCount) / stuInClassCount; // 计算成就项 (排序 120(20x3) 60(10x4) 25(5x5) 121 rules = rules.stream().sorted(Comparator.comparing(AchievementRule::getLevel).reversed()).collect(Collectors.toList()); int ruleSize = rules.size(); curAchievement = rules.get(rules.size() - 1); for (int i = 0; i <= rules.size() - 1; i++) { AchievementRule curRule = rules.get(i); int flag = praiseCount / curRule.getPromotionCount(); if (ruleSize == 1) { n = praiseCount / curRule.getLevelCount() + 1; break; } if (flag >= 1) { if (i == 0) { curAchievement = curRule; AchievementRule lastRule = rules.get(i + 1); n = (praiseCount - lastRule.getPromotionCount()) / curRule.getLevelCount() + 1; } else { AchievementRule nextRule = rules.get(i - 1); curAchievement = nextRule; n = (praiseCount - curRule.getPromotionCount()) / nextRule.getLevelCount() + 1; } break; } if (i == rules.size() - 1) { n = praiseCount / curRule.getLevelCount() + 1; } } // 获取记录 records = appraiseRecord.getNodes().stream() .sorted(Comparator.comparing(AppraiseRecordItem::getCreateTime).reversed()) .collect(Collectors.toList()); } } // 构建报告 return StudentReportVo.builder() .name(student.getName()) .studentId(studentId) .className(student.getClassId()) // 假设班级名可以从学生信息中获取 .praiseCount(praiseCount) .score(appraiseRecordOpt.isPresent() ? appraiseRecordOpt.get().getScore() : 0) // 默认分数为 0 .beyondPercent(beyondPercent) .topPraiseTeacher(new StudentReportVo.Teacher(null, null)) // 默认值为 null .topCriticalTeacher(new StudentReportVo.Teacher(null, null)) // 默认值为 null .topPraiseNode(null) // 默认值为 null .topCriticalNode(null) // 默认值为 null .praiseDistribution(praiseDistribution) .criticalDistribution(criticalDistribution) .curAchievement(curAchievement) .achievementN(n) .records(records) .build(); } @Override public StudentReportVo studentReport(IdRequest idRequest) { String studentId = idRequest.getId(); User user = SecurityUtil.getLoginUser(); String schoolId = user.getSchoolId(); // 查询学生信息,学段等 Student student = RepositoryUtil.findOne(studentRepository.findByIdAndCode(studentId, String.format(PK.STUDENT, schoolId)), "当前学生不存在"); String periodId = student.getPeriodId(); String classId = student.getClassId(); List semesters = schoolRepository.findSemestersById(schoolId, periodId); // 生成学年 id String academicYearId = SchoolDateUtil.calculateAcademicYearId(semesters, LocalDate.now()); // 查询评价文档 AppraiseRecord appraiseRecord = appraiseRecordRepository.findAppraiseRecordByTargetIdAndClassIdAndAcademicYearIdAndCode( studentId, classId, academicYearId, String.format(PK.PK_APPRAISE_RECORD, schoolId) ); // 班级内学生总数 int stuInClassCount = studentRepository.countByClassIdAndCode(classId, String.format(PK.STUDENT, schoolId)); if (appraiseRecord == null || appraiseRecord.getNodes() == null) { throw new ServiceException(ErrorCode.OPERATION_ERROR.getCode(), "当前学生暂未产生评价数据,无法生成报告"); } Integer praiseCount = appraiseRecord.getPraiseCount(); List records = appraiseRecord.getNodes(); records = records.stream().sorted(Comparator.comparing(AppraiseRecordItem::getCreateTime).reversed()).collect(Collectors.toList()); // 查询成就规则 Appraise appraise = RepositoryUtil.findOne(appraiseRepository.findRulesById(schoolId, periodId), "当前成就规则还未创建"); List rules = appraise.getAchievementRules(); if (CollectionUtil.isEmpty(rules)) { throw new ServiceException(ErrorCode.OPERATION_ERROR.getCode(), "当前成就规则为空"); } // 计算雷达图 Map praiseDistribution = StudentReportVo.ofFiveEducation(); Map criticalDistribution = StudentReportVo.ofFiveEducation(); // 计算数据 Map praiseTeacherMap = new HashMap<>(); Map criticalTeacherMap = new HashMap<>(); Map praiseNodeMap = new HashMap<>(); Map criticalNodeMap = new HashMap<>(); // 计算当前学生排名在全班次位 int greaterCount = appraiseRecordRepository.findClassRecord(String.format(PK.PK_APPRAISE_RECORD, schoolId), academicYearId, classId, praiseCount); float beyondPercent; long currentRank = stuInClassCount - greaterCount; beyondPercent = (float) currentRank / stuInClassCount; // 根据全部数据计算结果 for (AppraiseRecordItem record : records) { AppraiseTreeNode appraiseNode = record.getAppraiseNode(); String[] path = appraiseNode.getPath(); // 数据异常则跳过 if (path == null || path.length == 0) { continue; } String root = path[0]; // 一级评价项不在预期中 String key = FiveEducations.getCodeByName(root); // 计算雷达图 if (key != null) { if (appraiseNode.isPraise()) { praiseDistribution.put(key, praiseDistribution.getOrDefault(key, 0) + 1); } else { criticalDistribution.put(key, criticalDistribution.getOrDefault(key, 0) + 1); } } String teacherId = record.getCreatorId(); String nodeName = appraiseNode.getName(); // 根据评价是否为表扬计算数据 if (appraiseNode.isPraise()) { praiseTeacherMap.put(teacherId, praiseTeacherMap.getOrDefault(teacherId, 0) + 1); praiseNodeMap.put(nodeName, praiseNodeMap.getOrDefault(nodeName, 0) + 1); } else { criticalTeacherMap.put(teacherId, criticalTeacherMap.getOrDefault(teacherId, 0) + 1); criticalNodeMap.put(nodeName, criticalNodeMap.getOrDefault(nodeName, 0) + 1); } } // 计算成就项 (排序 120(20x3) 60(10x4) 25(5x5) 121 rules = rules.stream().sorted(Comparator.comparing(AchievementRule::getLevel).reversed()).collect(Collectors.toList()); int ruleSize = rules.size(); AchievementRule curAchievement = rules.get(ruleSize - 1); int n = 1; for (int i = 0; i <= ruleSize - 1; i++) { // 头尾的情况要特判(理清逻辑就很简单) AchievementRule curRule = rules.get(i); int flag = praiseCount / curRule.getPromotionCount(); // 只有一个节点也特殊处理 if (ruleSize == 1) { n = praiseCount / curRule.getLevelCount() + 1; break; } if (flag >= 1) { if (i == 0) { curAchievement = curRule; AchievementRule lastRule = rules.get(i + 1); n = (praiseCount - lastRule.getPromotionCount()) / curRule.getLevelCount() + 1; } else { AchievementRule nextRule = rules.get(i - 1); curAchievement = nextRule; n = (praiseCount - curRule.getPromotionCount()) / nextRule.getLevelCount() + 1; } break; } if (i == ruleSize - 1) { n = praiseCount / curRule.getLevelCount() + 1; } } // 汇总数据 String topPraiseTeacherId = praiseTeacherMap.entrySet().stream().max(Map.Entry.comparingByValue()).map(Map.Entry::getKey).orElse(null); AppraiseRecordItem tmp1 = records.stream().filter(item -> item.getCreatorId().equals(topPraiseTeacherId)).findFirst().orElse(null); String topPraiseTeacherName = tmp1 == null ? null : tmp1.getCreator(); String topCriticalTeacherId = criticalTeacherMap.entrySet().stream().max(Map.Entry.comparingByValue()).map(Map.Entry::getKey).orElse(null); AppraiseRecordItem tmp2 = records.stream().filter(item -> item.getCreatorId().equals(topCriticalTeacherId)).findFirst().orElse(null); String topCriticalTeacherName = tmp2 == null ? null : tmp2.getCreator(); String topPraiseNode = praiseNodeMap.entrySet().stream().max(Map.Entry.comparingByValue()).map(Map.Entry::getKey).orElse(null); String topCriticalNode = criticalNodeMap.entrySet().stream().max(Map.Entry.comparingByValue()).map(Map.Entry::getKey).orElse(null); return StudentReportVo.builder() .name(student.getName()) .studentId(studentId) .className(appraiseRecord.getClassName()) .praiseCount(praiseCount) .score(appraiseRecord.getScore()) .beyondPercent(beyondPercent) .topPraiseTeacher(new StudentReportVo.Teacher(topPraiseTeacherId, topPraiseTeacherName)) .topCriticalTeacher(new StudentReportVo.Teacher(topCriticalTeacherId, topCriticalTeacherName)) .topPraiseNode(topPraiseNode) .topCriticalNode(topCriticalNode) .praiseDistribution(praiseDistribution) .criticalDistribution(criticalDistribution) .curAchievement(curAchievement) .achievementN(n) .records(records) .build(); } @Override public void exportStuReportPdf(IdRequest idRequest, HttpServletResponse response) throws IOException, DocumentException { // 设置response参数 response.reset(); response.setContentType("application/pdf"); response.setHeader("Content-disposition", "attachment;filename=report_student_" + System.currentTimeMillis() + ".pdf"); ClassPathResource resource = new ClassPathResource("templates/pdf_templates/student_report.pdf"); InputStream in = resource.getInputStream(); ServletOutputStream os = response.getOutputStream(); // 获取报告数据 StudentReportVo reportVo = this.studentReport(idRequest); // 处理 stampter PdfReader pdfReader = new PdfReader(in); PdfStamper stamper = new PdfStamper(pdfReader, os); int criticalCount = reportVo.getCriticalDistribution().values().stream().mapToInt(Integer::intValue).sum(); Map data = new HashMap<>(); data.put("name", reportVo.getName()); data.put("studentId", reportVo.getStudentId()); data.put("className", reportVo.getClassName()); data.put("totalCount", String.valueOf(reportVo.getPraiseCount() + criticalCount)); data.put("praiseCount", String.valueOf(reportVo.getPraiseCount())); data.put("criticalCount", String.valueOf(criticalCount)); data.put("beyondPercent", String.valueOf(reportVo.getBeyondPercent())); data.put("topPraiseTeacher", StringUtils.isBlank(reportVo.getTopPraiseTeacher().getName()) ? "暂无" : reportVo.getTopPraiseTeacher().getName()); data.put("topCriticalTeacher", StringUtils.isBlank(reportVo.getTopCriticalTeacher().getName()) ? "暂无" : reportVo.getTopCriticalTeacher().getName()); data.put("topPraiseNode", StringUtils.isBlank(reportVo.getTopPraiseNode()) ? "暂无" : reportVo.getTopPraiseNode()); data.put("topCriticalNode", StringUtils.isBlank(reportVo.getTopCriticalNode()) ? "暂无" : reportVo.getTopCriticalNode()); DefaultPieDataset praiseDistributionDataset = new DefaultPieDataset(); praiseDistributionDataset.setValue(FiveEducations.VIRTUE.getName(), reportVo.getPraiseDistribution().get(FiveEducations.VIRTUE.getCode())); praiseDistributionDataset.setValue(FiveEducations.INTELLIGENCE.getName(), reportVo.getPraiseDistribution().get(FiveEducations.INTELLIGENCE.getCode())); praiseDistributionDataset.setValue(FiveEducations.SPORTS.getName(), reportVo.getPraiseDistribution().get(FiveEducations.SPORTS.getCode())); praiseDistributionDataset.setValue(FiveEducations.ART.getName(), reportVo.getPraiseDistribution().get(FiveEducations.ART.getCode())); praiseDistributionDataset.setValue(FiveEducations.LABOUR.getName(), reportVo.getPraiseDistribution().get(FiveEducations.LABOUR.getCode())); JFreeChart praisePieChart = ChartUtil.pieChart("五育表扬指标分布", praiseDistributionDataset); ByteArrayOutputStream praiseBos = new ByteArrayOutputStream(); ChartUtils.writeChartAsJPEG(praiseBos, praisePieChart, 340, 330); // 如果分布为空,则填充默认值 1 DefaultPieDataset criticalDistributionDataset = new DefaultPieDataset(); criticalDistributionDataset.setValue(FiveEducations.VIRTUE.getName(), reportVo.getCriticalDistribution().get(FiveEducations.VIRTUE.getCode())); criticalDistributionDataset.setValue(FiveEducations.INTELLIGENCE.getName(), reportVo.getCriticalDistribution().get(FiveEducations.INTELLIGENCE.getCode())); criticalDistributionDataset.setValue(FiveEducations.SPORTS.getName(), reportVo.getCriticalDistribution().get(FiveEducations.SPORTS.getCode())); criticalDistributionDataset.setValue(FiveEducations.ART.getName(), reportVo.getCriticalDistribution().get(FiveEducations.ART.getCode())); criticalDistributionDataset.setValue(FiveEducations.LABOUR.getName(), reportVo.getCriticalDistribution().get(FiveEducations.LABOUR.getCode())); JFreeChart criticalPieChart = ChartUtil.pieChart("五育待改进指标分布", criticalDistributionDataset); ByteArrayOutputStream criticalBos = new ByteArrayOutputStream(); ChartUtils.writeChartAsJPEG(criticalBos, criticalPieChart, 340, 330); // 填充表单 PdfUtil.fillPdfForm(stamper, data); PdfUtil.fillImage(stamper, "praiseDistribution", praiseBos.toByteArray()); PdfUtil.fillImage(stamper, "criticalDistribution", criticalBos.toByteArray()); // 关闭流 stamper.setFormFlattening(true); stamper.close(); os.close(); } /** * 递归收集 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; } }