getSimilarQuestionsByQuestionId(Integer id);
}
\ No newline at end of file
diff --git a/seer-teacher/seer-teacher-service/src/main/java/com/seer/teach/teacher/service/impl/ReportServiceImpl.java b/seer-teacher/seer-teacher-service/src/main/java/com/seer/teach/teacher/service/impl/ReportServiceImpl.java
index 06edd4e..e3cb13d 100644
--- a/seer-teacher/seer-teacher-service/src/main/java/com/seer/teach/teacher/service/impl/ReportServiceImpl.java
+++ b/seer-teacher/seer-teacher-service/src/main/java/com/seer/teach/teacher/service/impl/ReportServiceImpl.java
@@ -4,6 +4,7 @@ import cn.dev33.satoken.stp.StpUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.seer.teach.common.entity.BaseEntity;
import com.seer.teach.common.enums.MakeQuestionTypeEnums;
+import com.seer.teach.common.enums.teacher.ReportTypeEnum;
import com.seer.teach.common.utils.DateUtils;
import com.seer.teach.iot.api.UserDeviceServiceApi;
import com.seer.teach.teacher.common.dto.ReportQuestionDTO;
@@ -18,18 +19,18 @@ import com.seer.teach.teacher.convert.QuestionConvert;
import com.seer.teach.teacher.convert.ReportConvert;
import com.seer.teach.teacher.module.entity.*;
import com.seer.teach.teacher.service.*;
+import com.seer.teach.teacher.util.RateUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.math.RoundingMode;
-import java.time.LocalDate;
-import java.time.LocalDateTime;
-import java.time.LocalTime;
-import java.time.ZoneId;
+import java.time.*;
import java.time.temporal.ChronoUnit;
import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
*
@@ -47,6 +48,20 @@ public class ReportServiceImpl implements ReportService {
private static final String PERCENT_SIGN = "%";
+ private static final String FIRST_STUDY = "你近期首次学习,期待你的持续进步!";
+
+ private static final String NOT_STUDY = "你%s还没有学习哦! 快去学习吧~";
+
+ private static final String VERY_GREAT = "很棒哦! %s你的作业正确率提升了%d%%,继续保持优秀表现~";
+
+ private static final String NOT_BAD = "不错哦! %s你的作业正确率提升了%d%%,请继续加油~";
+
+ private static final String COME_ON = "加油哦! %s你的作业正确率有提升,再接再厉~";
+
+ private static final String CONTINUE_STUDYING = "继续努力哦! %s你的作业正确率还有提升空间~";
+
+ private static final String DON_LOSE_HEART = "别灰心哦!%s你的作业正确率有所下降,坚持学习一定会进步的~";
+
private final EvaluationTasksService evaluationTasksService;
private final UserDeviceServiceApi userDeviceServiceApi;
@@ -161,16 +176,144 @@ public class ReportServiceImpl implements ReportService {
.reduce(0, Integer::sum);
String readingTime = DateUtils.formatSecondsToHourAndMinute(readingSeconds);
+ // 鼓励语句
+ String encourage = generateEncouragement(params.getType(), homeworkCorrectRate, userId);
+
// 日均学习时间
long days = ChronoUnit.DAYS.between(startTime.toLocalDate(), endTime.toLocalDate()) + 1;
String dailyStudyTime = (studySeconds / 60 / days) + MINUTE;
// 返回最终AI报告结果
- return AiReportConvert.INSTANCE.toAiReportResp(
- studyTime, readingTime, practiceCount, dailyStudyTime, homeworkCorrectRate, subjectRates
- );
+ return AiReportConvert.INSTANCE.toAiReportResp(studyTime, readingTime, practiceCount, encourage,
+ dailyStudyTime, homeworkCorrectRate, subjectRates);
}
+ /**
+ * 鼓励语句
+ *
+ * @param type 时间类型
+ * @param correctRate 当前正确率
+ * @return 鼓励语
+ */
+ private String generateEncouragement(ReportTypeEnum type, String correctRate, Integer userId) {
+ // 时间段
+ String timePeriod = type.getDesc();
+ // 当前正确率
+ BigDecimal currentRate = RateUtils.percentStrToRate(correctRate);
+
+ // 如果正确率为0%,说明没有做题,返回鼓励语
+ if (currentRate.compareTo(BigDecimal.ZERO) == 0) {
+ return String.format(NOT_STUDY, timePeriod);
+ }
+ // 获取历史正确率进行对比
+ Optional historyRateOpt = getHistoricalCorrectRate(userId, type);
+ // 如果没有历史学习记录,返回提示语
+ if (!historyRateOpt.isPresent()) {
+ return FIRST_STUDY;
+ }
+ // 获取提升百分比
+ BigDecimal historyRate = historyRateOpt.get();
+ BigDecimal improvementPercent = RateUtils.calculateImprovementPercent(currentRate, historyRate);
+ int improve = improvementPercent.intValue();
+ if (improve >= 20) {
+ return String.format(VERY_GREAT, timePeriod, improve);
+ } else if (improve >= 10) {
+ return String.format(NOT_BAD, timePeriod, improve);
+ } else if (improve >= 5) {
+ return String.format(COME_ON, timePeriod);
+ } else if (improve > -5) {
+ return String.format(CONTINUE_STUDYING, timePeriod);
+ } else {
+ return String.format(DON_LOSE_HEART, timePeriod);
+ }
+ }
+
+ /**
+ * 获取历史作业正确率
+ *
+ * @param userId 用户Id
+ * @param type 时间类型
+ * @return 历史作业正确率
+ */
+ private Optional getHistoricalCorrectRate(Integer userId, ReportTypeEnum type) {
+ // 获取当前查询时间段的开始时间
+ LocalDateTime currentTimeStart = DateUtils.getStartTime(type);
+
+ // 查询当前时间段之前的学习记录,按时间倒序排列
+ List previousLessons = lessonsService.lambdaQuery()
+ .eq(LessonsEntity::getUserId, userId)
+ .lt(LessonsEntity::getCreateTime, currentTimeStart)
+ .orderByDesc(LessonsEntity::getCreateTime)
+ .list();
+
+ List previousEvaluations = evaluationTasksService.lambdaQuery()
+ .eq(EvaluationTasksEntity::getUserId, userId)
+ .lt(EvaluationTasksEntity::getCreateTime, currentTimeStart)
+ .orderByDesc(EvaluationTasksEntity::getCreateTime)
+ .list();
+
+ // 合并并按时间倒序排序,找出最近一次学习会话的时间范围
+ List allCreateTimes = Stream.concat(
+ previousLessons.stream().map(LessonsEntity::getCreateTime),
+ previousEvaluations.stream().map(EvaluationTasksEntity::getCreateTime))
+ .sorted(Collections.reverseOrder()).collect(Collectors.toList());
+ if (allCreateTimes.isEmpty()) {
+ return Optional.empty();
+ }
+
+ // 获取最近一次学习的时间
+ LocalDateTime latestLearningTime = allCreateTimes.get(0);
+
+ // 根据报告类型确定历史时间段
+ LocalDateTime periodStart, periodEnd;
+
+ switch (type) {
+ case DAILY:
+ // 查找最近一次学习的那一天的所有学习记录
+ LocalDate learningDate = latestLearningTime.toLocalDate();
+ periodStart = learningDate.atStartOfDay();
+ periodEnd = learningDate.atTime(LocalTime.MAX);
+ break;
+ case WEEKLY:
+ // 查找最近一次学习的那周
+ LocalDate learningWeekStart = latestLearningTime.toLocalDate().with(DayOfWeek.MONDAY);
+ LocalDate learningWeekEnd = learningWeekStart.plusDays(6);
+ periodStart = learningWeekStart.atStartOfDay();
+ periodEnd = learningWeekEnd.atTime(LocalTime.MAX);
+ break;
+ case MONTHLY:
+ // 查找最近一次学习的那个月
+ YearMonth learningMonth = YearMonth.from(latestLearningTime);
+ periodStart = learningMonth.atDay(1).atStartOfDay();
+ periodEnd = learningMonth.atEndOfMonth().atTime(LocalTime.MAX);
+ break;
+ default:
+ return Optional.empty();
+ }
+
+ // 获取该历史时期的完整学习数据
+ List lessons = lessonsService.lambdaQuery()
+ .eq(LessonsEntity::getUserId, userId)
+ .ge(LessonsEntity::getCreateTime, periodStart)
+ .le(LessonsEntity::getCreateTime, periodEnd)
+ .list();
+
+ List evaluations = evaluationTasksService.lambdaQuery()
+ .eq(EvaluationTasksEntity::getUserId, userId)
+ .ge(EvaluationTasksEntity::getCreateTime, periodStart)
+ .le(EvaluationTasksEntity::getCreateTime, periodEnd)
+ .list();
+
+ int totalQ = getTotalQuestions(lessons) + getTotalQuestions(evaluations);
+ if (totalQ == 0) {
+ return Optional.empty();
+ }
+
+ BigDecimal totalCorrect = calculateTotalCorrect(lessons).add(calculateTotalCorrect(evaluations));
+ return Optional.of(totalCorrect.divide(BigDecimal.valueOf(totalQ), 2, RoundingMode.HALF_UP));
+ }
+
+
/**
* 获取指定用户的全部题目数据(包括测评和计划中的题目)。
*
@@ -535,6 +678,11 @@ public class ReportServiceImpl implements ReportService {
* @return 科目ID到名称的映射
*/
private Map getSubjectsByIds(Set ids) {
+ log.info("获取科目名称,ID集合: {}", ids);
+ if (ids == null || ids.isEmpty()) {
+ log.info("ID集合为空,返回空map");
+ return new HashMap<>();
+ }
Map map = new HashMap<>();
for (SubjectsEntity s : subjectsService.listByIds(new ArrayList<>(ids))) {
map.put(s.getId(), s.getSubject());
diff --git a/seer-teacher/seer-teacher-service/src/main/java/com/seer/teach/teacher/service/impl/WrongQuestionServiceImpl.java b/seer-teacher/seer-teacher-service/src/main/java/com/seer/teach/teacher/service/impl/WrongQuestionServiceImpl.java
index c57fe51..c6347ee 100644
--- a/seer-teacher/seer-teacher-service/src/main/java/com/seer/teach/teacher/service/impl/WrongQuestionServiceImpl.java
+++ b/seer-teacher/seer-teacher-service/src/main/java/com/seer/teach/teacher/service/impl/WrongQuestionServiceImpl.java
@@ -7,11 +7,15 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.databind.JsonNode;
import com.seer.teach.common.PageListBean;
-import com.seer.teach.common.enums.*;
+import com.seer.teach.common.enums.AiScenarioCodeEnum;
+import com.seer.teach.common.enums.BankQuestionStatusEnum;
+import com.seer.teach.common.enums.QuestionResultEnums;
+import com.seer.teach.common.enums.ResultCodeEnum;
import com.seer.teach.common.utils.AssertUtils;
import com.seer.teach.common.utils.PageConverterUtils;
import com.seer.teach.teacher.common.req.WrongQuestionBookReq;
import com.seer.teach.teacher.common.req.WrongQuestionPageListReq;
+import com.seer.teach.teacher.common.resp.SimilarQuestionResp;
import com.seer.teach.teacher.common.resp.WrongQuestionPageListResp;
import com.seer.teach.teacher.common.resp.WrongQuestionResp;
import com.seer.teach.teacher.convert.WrongQuestionBookConvert;
@@ -63,7 +67,7 @@ public class WrongQuestionServiceImpl implements WrongQuestionService {
* @return 返回封装了总错题数的响应对象
*/
@Override
- public WrongQuestionResp getWrongQuestion(WrongQuestionBookReq params) {
+ public WrongQuestionResp getWrongQuestionCount(WrongQuestionBookReq params) {
Integer userId = StpUtil.getLoginIdAsInt();
log.info("开始获取用户[{}]测评错题本数据", userId);
@@ -243,6 +247,40 @@ public class WrongQuestionServiceImpl implements WrongQuestionService {
}).collect(Collectors.toList());
}
+ /**
+ * 举一反三获取相似题目
+ *
+ * @param id 题目Id
+ * @return 相似题目列表
+ */
+
+ @Override
+ public List getSimilarQuestionsByQuestionId(Integer id) {
+ // 根据题目Id获取题目
+ BankQuestionsEntity questionBank = bankQuestionsService.getById(id);
+ AssertUtils.notNull(questionBank, ResultCodeEnum.QUESTION_NOT_EXIST);
+ // 获取知识点Id
+ Integer knowId = questionBank.getKnowId();
+ // 查询相同知识点的题目,排除原题目
+ List questionsList = bankQuestionsService.lambdaQuery()
+ .eq(BankQuestionsEntity::getKnowId, knowId)
+ .ne(BankQuestionsEntity::getId, id)
+ .list();
+ List similarQuestionList = WrongQuestionBookConvert.INSTANCE.toSimilarQuestionList(questionsList);
+ // 过滤掉questionHtml为空的题目
+ List result = similarQuestionList.stream()
+ .filter(question -> question.getQuestionHtml() != null && !question.getQuestionHtml().isEmpty())
+ .collect(Collectors.toList());
+ // 将相似题目打乱顺序排序
+ Collections.shuffle(result);
+ // 返回10道知识点相同的题目
+ return result.stream()
+ .limit(10)
+ // 重新编号(序号1-10)
+ .peek((question) -> question.setId(result.indexOf(question) + 1))
+ .collect(Collectors.toList());
+ }
+
/**
* 异步批量插入生成的相似题目到题库
*
diff --git a/seer-teacher/seer-teacher-service/src/main/java/com/seer/teach/teacher/util/RateUtils.java b/seer-teacher/seer-teacher-service/src/main/java/com/seer/teach/teacher/util/RateUtils.java
new file mode 100644
index 0000000..4b5da5a
--- /dev/null
+++ b/seer-teacher/seer-teacher-service/src/main/java/com/seer/teach/teacher/util/RateUtils.java
@@ -0,0 +1,47 @@
+package com.seer.teach.teacher.util;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+
+@Slf4j
+@Component
+public class RateUtils {
+
+ /**
+ * 百分比字符串转正确率("62%" -> 0.62)
+ *
+ * @param percent 百分比字符串
+ * @return 正确率
+ */
+ public static BigDecimal percentStrToRate(String percent) {
+ if (percent == null || percent.trim().isEmpty() || "0%".equals(percent)) {
+ return BigDecimal.ZERO;
+ }
+ return new BigDecimal(percent.replace("%", ""))
+ .divide(BigDecimal.valueOf(100), 4, RoundingMode.HALF_UP);
+ }
+
+ /**
+ * 计算提升百分比
+ *
+ * @param current 当前正确率
+ * @param history 历史正确率
+ * @return 提升百分比
+ */
+ public static BigDecimal calculateImprovementPercent(BigDecimal current, BigDecimal history) {
+ if (history.compareTo(BigDecimal.ZERO) <= 0) {
+ // 历史为 0,认为是首次学习
+ return BigDecimal.valueOf(100);
+ }
+ BigDecimal improve = current.subtract(history)
+ .divide(history, 2, RoundingMode.HALF_UP)
+ .multiply(BigDecimal.valueOf(100))
+ .setScale(0, RoundingMode.HALF_UP);
+ // 限制范围 [-100, 100]
+ return improve.max(BigDecimal.valueOf(-100))
+ .min(BigDecimal.valueOf(100));
+ }
+}
diff --git a/seer-teacher/seer-teacher-service/src/main/java/com/seer/teach/teacher/util/WrongQuestionUtils.java b/seer-teacher/seer-teacher-service/src/main/java/com/seer/teach/teacher/util/WrongQuestionUtils.java
index 80e865e..10878cc 100644
--- a/seer-teacher/seer-teacher-service/src/main/java/com/seer/teach/teacher/util/WrongQuestionUtils.java
+++ b/seer-teacher/seer-teacher-service/src/main/java/com/seer/teach/teacher/util/WrongQuestionUtils.java
@@ -188,11 +188,10 @@ public class WrongQuestionUtils {
String processed = originalHtml
.replaceAll("\\s*解析:", "
解析:")
.replaceAll("
\\s*答案:\\s*", "
【正确答案】 ");
- String redX = " ❌";
+// String redX = " ❌";
// 构建学生答案段
String studentSegment = "
【你的答案】 "
+ (safeStudentAnswer.isEmpty() ? "" : safeStudentAnswer)
- + redX
+ "
\n\n";
// 插入到正确答案之前
processed = processed.replaceFirst("【正确答案】", studentSegment + "
【正确答案】");