You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
IESExtension/src/main/java/cn/teammodel/service/impl/EvaluationServiceImpl.java

942 lines
44 KiB

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 的方法: 判断参数,判断数据是否为空 <br/>
* 从 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<AppraiseTreeNode> nodes = appraise.getNodes();
List<AchievementRule> rules = appraise.getAchievementRules();
List<AppraiseTreeNode> children = nodes.stream().filter(item -> item.getPid() != null).collect(Collectors.toList());
// 将非 root 的 nodes 通过 pid 收集成 list, 再遍历 nodes, 将其每一个 id 对应 map 中的 list 的 pid修改成新的 id 即可
Map<String, List<AppraiseTreeNode>> pidNodeMap = children.stream().collect(Collectors.groupingBy(AppraiseTreeNode::getPid));
nodes.forEach(item -> {
String newId = UUID.randomUUID().toString();
String oldId = item.getId();
List<AppraiseTreeNode> 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<AppraiseTreeNode> nodes = appraise.getNodes();
List<AppraiseTreeNode> 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<AppraiseTreeNode> trees, List<AppraiseTreeNode> 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<AppraiseTreeNode> 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<AppraiseTreeNode> 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<AppraiseTreeNode> nodes = appraise.getNodes();
List<AppraiseTreeNode> 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();
String from = appraiseVoteDto.getFrom();
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<Student> 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<AppraiseTreeNode> nodes = appraiseRepository.findNodeById(PK.PK_APPRAISE, appraiseId,periodId);
AppraiseTreeNode appraiseTreeNode = RepositoryUtil.findOne(nodes, "该评价项不存在");
if (appraiseTreeNode.getPath() == null) {
throw new ServiceException(ErrorCode.PARAMS_ERROR.getCode(), "仅能评价三级评价项");
}
// 通过 periodId 获取 semesters
List<School.Period> periodById = schoolRepository.findPeriodById(schoolId, periodId);
School.Period period = RepositoryUtil.findOne(periodById, "获取学段失败");
List<School.Semester> 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());
item.setFrom(from);
// 处理学校与学生的差异
if (targetType.equals(TARGET_CLASS)) {
item.setSpread(spread);
} else {
item.setPushParent(pushParent);
}
// 不存在或者学生的班级不一样则创建一条新的,存在则处理一下分值后再向其 nodes 中插入一条 item
if (record == null || !classId.equals(record.getClassId())) {
List<AppraiseRecordItem> 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 : 0);
record.setCriticizeCount(appraiseTreeNode.isPraise() ? 0 : 1);
record.setScore(appraiseTreeNode.isPraise() ? 1 : 0);
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);
}else {
operations.increment("/criticizeCount", 1);
}
// 加分
//nt scoreToPlus = ObjectUtils.isEmpty(appraiseTreeNode.getScore()) ? 0 : appraiseTreeNode.getScore();
int incrementValue = appraiseTreeNode.isPraise() ? 1 : -1;
if (record.getScore() + incrementValue < 0) {
operations.set("/score", 0);
}else {
operations.increment("/score", incrementValue);
}
// patch doc
appraiseRecordRepository.save(record.getId(), PK.buildOf(PK.PK_APPRAISE_RECORD, schoolId), AppraiseRecord.class, operations);
}
}
@Override
public List<AppraiseRecordVo> 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<School.Semester> 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<AppraiseRecordVo> appraiseRecordItemPage = appraiseRecordRepository.searchNodesByCondition(
targetId,
targetType,
classId,
teacherId,
academicYearId,
isPraise,
String.format(PK.PK_APPRAISE_RECORD, schoolId),
pageRequest
);
List<AppraiseRecordVo> 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<AppraiseRecord> 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();
if (praise) {
appraiseRecord.setPraiseCount(appraiseRecord.getPraiseCount() - 1);
appraiseRecord.setScore(appraiseRecord.getScore() - 1);
}else {
appraiseRecord.setCriticizeCount(appraiseRecord.getCriticizeCount() - 1);
appraiseRecord.setScore(appraiseRecord.getScore() + 1);
}
if (appraiseRecord.getScore() < 0) {
appraiseRecord.setScore(0);
}
//int score = record.getAppraiseNode().getScore() == null ? 0 : record.getAppraiseNode().getScore();
//Integer newScore = appraiseRecord.getScore() - score;
//appraiseRecord.setScore(newScore);
// 保存
appraiseRecordRepository.save(appraiseRecord);
}
public List<StudentReportVo> Reports(ReportDto reportDto) {
// 获取当前登录用户和学校信息
User user = SecurityUtil.getLoginUser();
String schoolId = user.getSchoolId();
// 获取学生 ID 列表
List<String> studentIds = reportDto.getIds();
if (CollectionUtils.isEmpty(studentIds)) {
throw new ServiceException(ErrorCode.PARAM_ERROR.getCode(), "学生 ID 列表不能为空");
}
Set<String> studentIdSet = new HashSet<>(studentIds);
// 批量查询学生信息
List<Student> 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<String> classIds = students.stream().map(Student::getClassId).collect(Collectors.toList());
// 查询学期信息
List<School.Semester> semesters = schoolRepository.findSemestersById(schoolId, periodId);
String academicYearId = SchoolDateUtil.calculateAcademicYearId(semesters, LocalDate.now());
// 批量查询评价记录
List<AppraiseRecord> appraiseRecords = appraiseRecordRepository.findAllRecord(
studentIds, classIds, academicYearId, String.format(PK.PK_APPRAISE_RECORD, schoolId)
);
if (CollectionUtils.isEmpty(appraiseRecords)) {
throw new ServiceException(ErrorCode.OPERATION_ERROR.getCode(), "未找到对应的评价记录");
}
// 查询班级内学生总数
Map<String, Integer> 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<AchievementRule> rules = appraise.getAchievementRules();
if (CollectionUtils.isEmpty(rules)) {
throw new ServiceException(ErrorCode.OPERATION_ERROR.getCode(), "当前成就规则为空");
}
// 创建线程池
ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);
List<CompletableFuture<StudentReportVo>> futures = new ArrayList<>();
// 并行处理每个学生的报告生成任务
for (Student student : students) {
CompletableFuture<StudentReportVo> 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<StudentReportVo> reportVos = futures.stream()
.map(CompletableFuture::join)
.filter(Objects::nonNull)
.collect(Collectors.toList());
// 关闭线程池
executor.shutdown();
return reportVos;
}
private StudentReportVo generateStudentReport(Student student, List<AppraiseRecord> appraiseRecords,
Map<String, Integer> classStudentCountMap, List<AchievementRule> rules,
String academicYearId, String schoolId) {
String studentId = student.getId();
String classId = student.getClassId();
// 查找当前学生的评价记录
Optional<AppraiseRecord> appraiseRecordOpt = appraiseRecords.stream()
.filter(record -> record.getTargetId().equals(studentId))
.findFirst();
// 初始化默认值
Map<String, Integer> praiseDistribution = StudentReportVo.ofFiveEducation();
Map<String, Integer> criticalDistribution = StudentReportVo.ofFiveEducation();
List<AppraiseRecordItem> records = Collections.emptyList();
int praiseCount = 0;
int criticizeCount = 0;
int score = 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();
criticizeCount = appraiseRecord.getCriticizeCount();
score = appraiseRecord.getScore();
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)
.criticizeCount(criticizeCount)
.score(score) // 默认分数为 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<School.Semester> 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<AppraiseRecordItem> records = appraiseRecord.getNodes();
records = records.stream().sorted(Comparator.comparing(AppraiseRecordItem::getCreateTime).reversed()).collect(Collectors.toList());
// 查询成就规则
Appraise appraise = RepositoryUtil.findOne(appraiseRepository.findRulesById(schoolId, periodId), "当前成就规则还未创建");
List<AchievementRule> rules = appraise.getAchievementRules();
if (CollectionUtil.isEmpty(rules)) {
throw new ServiceException(ErrorCode.OPERATION_ERROR.getCode(), "当前成就规则为空");
}
// 计算雷达图
Map<String, Integer> praiseDistribution = StudentReportVo.ofFiveEducation();
Map<String, Integer> criticalDistribution = StudentReportVo.ofFiveEducation();
// 计算数据
Map<String, Integer> praiseTeacherMap = new HashMap<>();
Map<String, Integer> criticalTeacherMap = new HashMap<>();
Map<String, Integer> praiseNodeMap = new HashMap<>();
Map<String, Integer> 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<String, String> 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<AppraiseTreeNode> nodes, List<AppraiseTreeNode> nodesToDelete) {
for (AppraiseTreeNode node : nodes) {
if (id.equals(node.getPid())) {
// 收集
nodesToDelete.add(node);
// 递归收集
collectNodesToDelete(node.getId(), nodes, nodesToDelete);
}
}
}
/**
* 递归的构建父亲节点的孩子,以及孩子的孩子 (理论支持无极树,但应该考虑是否增加递归深度)
*/
private void buildChildren(AppraiseTreeNode parent, List<AppraiseTreeNode> nodes) {
List<AppraiseTreeNode> 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<AppraiseTreeNode> getChildren(AppraiseTreeNode parent, List<AppraiseTreeNode> nodes) {
List<AppraiseTreeNode> children = new ArrayList<>();
for (AppraiseTreeNode node : nodes) {
if (StringUtils.isNotBlank(node.getPid()) && node.getPid().equals(parent.getId())) {
children.add(node);
}
}
return children;
}
}