diff --git a/src/main/java/cn/teammodel/common/ErrorCode.java b/src/main/java/cn/teammodel/common/ErrorCode.java index f75bd0a..67fb782 100644 --- a/src/main/java/cn/teammodel/common/ErrorCode.java +++ b/src/main/java/cn/teammodel/common/ErrorCode.java @@ -10,7 +10,8 @@ public enum ErrorCode { FORBIDDEN_ERROR(40300, "禁止访问"), SYSTEM_ERROR(50000, "系统内部异常"), OPERATION_ERROR(50001, "操作失败"), - DATA_FORMAT_ERROR(40001, "数据格式错误"); + DATA_FORMAT_ERROR(40001, "数据格式错误"), + PARAM_ERROR(40002, "数据为空"); /** * 状态码 diff --git a/src/main/java/cn/teammodel/controller/admin/service/BlobService.java b/src/main/java/cn/teammodel/controller/admin/service/BlobService.java index 93318c4..75fa250 100644 --- a/src/main/java/cn/teammodel/controller/admin/service/BlobService.java +++ b/src/main/java/cn/teammodel/controller/admin/service/BlobService.java @@ -9,3 +9,4 @@ public interface BlobService { Map getBlobSas(BlobSasDto blobSasDto, HttpServletRequest request); Map getSasR99(BlobSasDto blobSasDto, HttpServletRequest request); } + diff --git a/src/main/java/cn/teammodel/controller/frontend/AppraiseController.java b/src/main/java/cn/teammodel/controller/frontend/AppraiseController.java index 36439a7..b722c48 100644 --- a/src/main/java/cn/teammodel/controller/frontend/AppraiseController.java +++ b/src/main/java/cn/teammodel/controller/frontend/AppraiseController.java @@ -86,9 +86,17 @@ public class AppraiseController { return R.success(res); } + @PostMapping("Reports") + @ApiOperation(value = "查看多个学生当前的学期的实时评价报告") + public R> Reports(@Valid @RequestBody ReportDto reportDto) { + List res = evaluationService.Reports(reportDto); + return R.success(res); + } + @PostMapping("studentReportPDF") @ApiOperation(value = "导出学生当前的学期的实时评价报告 PDF") public void exportStuReportPdf(@Valid @RequestBody IdRequest idRequest, HttpServletResponse response) throws DocumentException, IOException { evaluationService.exportStuReportPdf(idRequest, response); } + } diff --git a/src/main/java/cn/teammodel/model/dto/Appraise/ReportDto.java b/src/main/java/cn/teammodel/model/dto/Appraise/ReportDto.java new file mode 100644 index 0000000..b0350fe --- /dev/null +++ b/src/main/java/cn/teammodel/model/dto/Appraise/ReportDto.java @@ -0,0 +1,13 @@ +package cn.teammodel.model.dto.Appraise; + +import lombok.Data; +import javax.validation.constraints.Size; +import java.util.List; + +@Data +public class ReportDto { + + @Size(min = 1, message = "ids cannot be empty") + public List ids; + +} diff --git a/src/main/java/cn/teammodel/repository/AppraiseRecordRepository.java b/src/main/java/cn/teammodel/repository/AppraiseRecordRepository.java index ad45191..465e7f6 100644 --- a/src/main/java/cn/teammodel/repository/AppraiseRecordRepository.java +++ b/src/main/java/cn/teammodel/repository/AppraiseRecordRepository.java @@ -34,6 +34,9 @@ public interface AppraiseRecordRepository extends CosmosRepository findScoreAndPraise(String targetId,String academicYearId,String code); + @Query("select * from Student as c where c.targetId in (@targetId) and c.classId =@classId and c.academicYearId = @academicYearId and c.code = @code") + List findAllRecord(List targetId,String classId,String academicYearId,String code); + /** * 条件查询 */ diff --git a/src/main/java/cn/teammodel/repository/StudentRepository.java b/src/main/java/cn/teammodel/repository/StudentRepository.java index bc4a566..9f22b6e 100644 --- a/src/main/java/cn/teammodel/repository/StudentRepository.java +++ b/src/main/java/cn/teammodel/repository/StudentRepository.java @@ -18,7 +18,7 @@ public interface StudentRepository extends CosmosRepository { @Query("select c.pk, c.code, c.id, c.name, c.gender, c.schoolId, c.periodId, c.year, c.createTime, c.picture, c.mail, c.mobile, c.country, c.classId, c.no, c.groupId, c.groupName, c.guardians, c.irs, c.salt from Student as c where c.id = @id and c.code = @code") List findByIdAndCode(String id, String code); - @Query("select c.id, c.name, c.classId, c.picture from Student as c where c.code = @code and c.id in (@ids)") + @Query("select c.id, c.name, c.classId, c.picture, c.periodId from Student as c where c.code = @code and c.id in (@ids)") List findAllByCodeAndIdIn(String code, Set ids); int countByClassIdAndCode(String classId, String code); diff --git a/src/main/java/cn/teammodel/service/EvaluationService.java b/src/main/java/cn/teammodel/service/EvaluationService.java index 2e6a6c3..b02f65e 100644 --- a/src/main/java/cn/teammodel/service/EvaluationService.java +++ b/src/main/java/cn/teammodel/service/EvaluationService.java @@ -62,6 +62,7 @@ public interface EvaluationService { * 学生评价报告 */ StudentReportVo studentReport(IdRequest idRequest); + List Reports(ReportDto reportDto); void exportStuReportPdf(IdRequest idRequest, HttpServletResponse response) throws IOException, DocumentException; } diff --git a/src/main/java/cn/teammodel/service/impl/EvaluationServiceImpl.java b/src/main/java/cn/teammodel/service/impl/EvaluationServiceImpl.java index ab0255b..4347446 100644 --- a/src/main/java/cn/teammodel/service/impl/EvaluationServiceImpl.java +++ b/src/main/java/cn/teammodel/service/impl/EvaluationServiceImpl.java @@ -36,6 +36,7 @@ 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; @@ -46,6 +47,9 @@ 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; /** @@ -476,6 +480,190 @@ public class EvaluationServiceImpl implements EvaluationService { appraiseRecordRepository.save(appraiseRecord); } + + public List Reports(ReportDto reportDto) { + // 获取当前登录用户和学校信息 + //User user = SecurityUtil.getLoginUser(); + String schoolId = "hbcn"; + + // 获取学生 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();