diff --git a/src/main/java/com/project/appeal/domain/service/Impl/SaveAppealDomainServiceImpl.java b/src/main/java/com/project/appeal/domain/service/Impl/SaveAppealDomainServiceImpl.java index 73de86f..c68e6ca 100644 --- a/src/main/java/com/project/appeal/domain/service/Impl/SaveAppealDomainServiceImpl.java +++ b/src/main/java/com/project/appeal/domain/service/Impl/SaveAppealDomainServiceImpl.java @@ -123,6 +123,7 @@ public class SaveAppealDomainServiceImpl implements SaveAppealDomainService { case SINGLE_CHOICE-> taskEntity.getSingleChoiceScore(); case MULTIPLE_CHOICE -> taskEntity.getMultipleChoiceScore(); case TRUE_FALSE -> taskEntity.getTrueFalseScore(); + default -> throw new BusinessErrorException("不支持的题目类型:" + type); }; //总分+单题分数 diff --git a/src/main/java/com/project/statistics/application/StatisticsApplicationService.java b/src/main/java/com/project/statistics/application/StatisticsApplicationService.java new file mode 100644 index 0000000..7b9d774 --- /dev/null +++ b/src/main/java/com/project/statistics/application/StatisticsApplicationService.java @@ -0,0 +1,9 @@ +package com.project.statistics.application; + +import com.project.base.domain.result.Result; +import com.project.statistics.domain.dto.ExamTaskStatisticsDTO; + +public interface StatisticsApplicationService { + + Result examTaskStatistics(Long taskId); +} diff --git a/src/main/java/com/project/statistics/application/impl/StatisticsApplicationServiceImpl.java b/src/main/java/com/project/statistics/application/impl/StatisticsApplicationServiceImpl.java new file mode 100644 index 0000000..f7d6841 --- /dev/null +++ b/src/main/java/com/project/statistics/application/impl/StatisticsApplicationServiceImpl.java @@ -0,0 +1,23 @@ +package com.project.statistics.application.impl; + +import com.project.base.domain.result.Result; +import com.project.statistics.application.StatisticsApplicationService; +import com.project.statistics.domain.dto.ExamTaskStatisticsDTO; +import com.project.statistics.domain.service.ExamTaskStatisticsDomainService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class StatisticsApplicationServiceImpl implements StatisticsApplicationService { + + @Autowired + private ExamTaskStatisticsDomainService examTaskStatisticsDomainService; + + /** + * 统计考试任务数据 + */ + @Override + public Result examTaskStatistics(Long taskId) { + return examTaskStatisticsDomainService.calculateExamTaskStatistics(taskId); + } +} diff --git a/src/main/java/com/project/statistics/controller/StatisticsController.java b/src/main/java/com/project/statistics/controller/StatisticsController.java new file mode 100644 index 0000000..5e0d405 --- /dev/null +++ b/src/main/java/com/project/statistics/controller/StatisticsController.java @@ -0,0 +1,25 @@ +package com.project.statistics.controller; + +import com.project.base.domain.result.Result; +import com.project.statistics.application.StatisticsApplicationService; +import com.project.statistics.domain.dto.ExamTaskStatisticsDTO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/admin/statistics") +public class StatisticsController { + + @Autowired + private StatisticsApplicationService statisticsApplicationService; + + /** + * 统计考试任务数据 + */ + @GetMapping("/examTask") + public Result examTask(Long taskId) { + return statisticsApplicationService.examTaskStatistics(taskId); + } + + +} diff --git a/src/main/java/com/project/statistics/domain/dto/ExamTaskStatisticsDTO.java b/src/main/java/com/project/statistics/domain/dto/ExamTaskStatisticsDTO.java new file mode 100644 index 0000000..09c753e --- /dev/null +++ b/src/main/java/com/project/statistics/domain/dto/ExamTaskStatisticsDTO.java @@ -0,0 +1,22 @@ +package com.project.statistics.domain.dto; + +import lombok.Data; + +@Data +public class ExamTaskStatisticsDTO { + + /** + * 参与率(%) + */ + private String participationRate; + + /** + * 平均得分 + */ + private String averageScore; + + /** + * 通过率(%) + */ + private String passRate; +} diff --git a/src/main/java/com/project/statistics/domain/service/ExamTaskStatisticsDomainService.java b/src/main/java/com/project/statistics/domain/service/ExamTaskStatisticsDomainService.java new file mode 100644 index 0000000..a4ccabb --- /dev/null +++ b/src/main/java/com/project/statistics/domain/service/ExamTaskStatisticsDomainService.java @@ -0,0 +1,9 @@ +package com.project.statistics.domain.service; + +import com.project.base.domain.result.Result; +import com.project.statistics.domain.dto.ExamTaskStatisticsDTO; + +public interface ExamTaskStatisticsDomainService { + + Result calculateExamTaskStatistics(Long taskId); +} diff --git a/src/main/java/com/project/statistics/domain/service/impl/ExamTaskStatisticsDomainServiceImpl.java b/src/main/java/com/project/statistics/domain/service/impl/ExamTaskStatisticsDomainServiceImpl.java new file mode 100644 index 0000000..4257176 --- /dev/null +++ b/src/main/java/com/project/statistics/domain/service/impl/ExamTaskStatisticsDomainServiceImpl.java @@ -0,0 +1,112 @@ +package com.project.statistics.domain.service.impl; + +import com.project.base.domain.result.Result; +import com.project.exam.domain.entity.ExamRecordEntity; +import com.project.exam.domain.service.ExamRecordBaseService; +import com.project.statistics.domain.dto.ExamTaskStatisticsDTO; +import com.project.statistics.domain.service.ExamTaskStatisticsDomainService; +import com.project.task.domain.entity.TaskUserEntity; +import com.project.task.domain.service.TaskUserBaseService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +@Service +public class ExamTaskStatisticsDomainServiceImpl implements ExamTaskStatisticsDomainService { + + @Autowired + private TaskUserBaseService taskUserBaseService; + + @Autowired + private ExamRecordBaseService examRecordBaseService; + + @Override + public Result calculateExamTaskStatistics(Long taskId) { + ExamTaskStatisticsDTO dto = new ExamTaskStatisticsDTO(); + + // 1. 查询该考试任务下的所有任务用户 + List taskUserList = taskUserBaseService.lambdaQuery() + .eq(TaskUserEntity::getTaskId, taskId) + .list(); + + int totalUserCount = taskUserList.size(); + if (totalUserCount == 0) { + dto.setParticipationRate("0"); + dto.setAverageScore("0"); + dto.setPassRate("0"); + return Result.success(dto); + } + + // 2. 获取任务用户ID列表 + List taskUserIdList = taskUserList.stream() + .map(TaskUserEntity::getId) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + // 3. 查询关联的考试记录 + List examRecordList = examRecordBaseService.lambdaQuery() + .in(ExamRecordEntity::getTaskUserId, taskUserIdList) + .list(); + + // 4. 筛选已提交的考试记录(提交时间不为空) + List submittedRecordList = examRecordList.stream() + .filter(record -> record.getSubmitTime() != null) + .toList(); + + if (submittedRecordList.isEmpty()) { + dto.setParticipationRate("0"); + dto.setAverageScore("0"); + dto.setPassRate("0"); + return Result.success(dto); + } + + int submittedCount = (int) submittedRecordList.stream() + .map(ExamRecordEntity::getTaskUserId) + .distinct() // 对 userId 本身去重 + .count(); + // 5. 计算参与率 = 已提交人数 / 总人数 * 100 + BigDecimal participationRate = BigDecimal.valueOf(submittedCount) + .multiply(BigDecimal.valueOf(100)) + .divide(BigDecimal.valueOf(totalUserCount), 2, RoundingMode.HALF_UP); + dto.setParticipationRate(formatValue(participationRate.doubleValue())); + + // 6. 计算平均得分 = 累加考试得分 / 已提交记录数 + double totalScore = submittedRecordList.stream() + .mapToDouble(record -> record.getScore() != null ? record.getScore() : 0.0) + .sum(); + BigDecimal averageScore = BigDecimal.valueOf(totalScore) + .divide(BigDecimal.valueOf(submittedRecordList.size()), 2, RoundingMode.HALF_UP); + dto.setAverageScore(formatValue(averageScore.doubleValue())); + + // 7. 计算通过率 = 通过的记录数 / 已提交记录数 * 100 + long passCount = submittedRecordList.stream() + .filter(record -> Boolean.TRUE.equals(record.getPass())) + .count(); + BigDecimal passRate = BigDecimal.valueOf(passCount) + .multiply(BigDecimal.valueOf(100)) + .divide(BigDecimal.valueOf(submittedRecordList.size()), 2, RoundingMode.HALF_UP); + dto.setPassRate(formatValue(passRate.doubleValue())); + + return Result.success(dto); + } + + /** + * 格式化数值:整数保留整数,有小数保留两位 + */ + private String formatValue(Double value) { + if (value == null) { + return "0"; + } + BigDecimal bd = BigDecimal.valueOf(value); + BigDecimal rounded = bd.setScale(0, RoundingMode.HALF_UP); + if (bd.compareTo(rounded) == 0) { + return rounded.toPlainString(); + } + return bd.setScale(2, RoundingMode.HALF_UP).toPlainString(); + } +}