diff --git a/seer-common/common-enums/pom.xml b/seer-common/common-enums/pom.xml index d86f219..9003c9d 100644 --- a/seer-common/common-enums/pom.xml +++ b/seer-common/common-enums/pom.xml @@ -25,5 +25,9 @@ common-validation ${project.version} + + com.fasterxml.jackson.core + jackson-annotations + \ No newline at end of file diff --git a/seer-common/common-enums/src/main/java/com/seer/teach/common/enums/teacher/ReportTypeEnum.java b/seer-common/common-enums/src/main/java/com/seer/teach/common/enums/teacher/ReportTypeEnum.java new file mode 100644 index 0000000..9ad5b97 --- /dev/null +++ b/seer-common/common-enums/src/main/java/com/seer/teach/common/enums/teacher/ReportTypeEnum.java @@ -0,0 +1,36 @@ +package com.seer.teach.common.enums.teacher; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum ReportTypeEnum { + + DAILY(0, "今天"), + WEEKLY(1, "本周"), + MONTHLY(2, "本月"); + + private final Integer type; + + private final String desc; + + @JsonCreator + public static ReportTypeEnum fromType(Integer type) { + for (ReportTypeEnum e : values()) { + if (e.type.equals(type)) { + return e; + } + } + throw new IllegalArgumentException("非法报告类型type:" + type); + } + + @JsonValue + public Integer getType() { + return type; + } +} + + diff --git a/seer-common/common-utils/src/main/java/com/seer/teach/common/utils/DateUtils.java b/seer-common/common-utils/src/main/java/com/seer/teach/common/utils/DateUtils.java index 70ba2fb..ad97fe7 100644 --- a/seer-common/common-utils/src/main/java/com/seer/teach/common/utils/DateUtils.java +++ b/seer-common/common-utils/src/main/java/com/seer/teach/common/utils/DateUtils.java @@ -1,5 +1,7 @@ package com.seer.teach.common.utils; +import com.seer.teach.common.enums.teacher.ReportTypeEnum; + import java.time.DayOfWeek; import java.time.LocalDate; import java.time.LocalDateTime; @@ -8,7 +10,7 @@ import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; public class DateUtils { - + /** * 将符合RFC3339标准格式的字符串转换为LocalDateTime对象 * 格式:yyyy-MM-DDTHH:mm:ss+TIMEZONE @@ -23,12 +25,13 @@ public class DateUtils { } // 使用ISO_OFFSET_DATE_TIME解析带时区的日期时间字符串 OffsetDateTime offsetDateTime = OffsetDateTime.parse(dateTimeStr, DateTimeFormatter.ISO_OFFSET_DATE_TIME); - + return offsetDateTime.toLocalDateTime(); } /** * 根据创建时间计算距离当前时间相差多少天 + * * @param createTime 创建时间(LocalDateTime) * @return 相差的天数,例如:5 */ @@ -43,46 +46,54 @@ public class DateUtils { /** - * Ai报告获取开始时间 - * @param type 1-本周,2-本天 - * @return 开始时间 + * 获取开始时间 + * + * @param type 类型 + * @return LocalDateTime对象 */ - public static LocalDateTime getStartTime(Integer type) { - if (type == 1) { - // 本天 + public static LocalDateTime getStartTime(ReportTypeEnum type) { + if (type.getType().equals(0)) { + // 今天 return LocalDate.now().atStartOfDay(); - } else if(type == 0){ + } else if (type.getType().equals(1)) { // 本周 return LocalDate.now().with(DayOfWeek.MONDAY).atStartOfDay(); + } else if (type.getType().equals(2)) { + // 本月 + return LocalDate.now().withDayOfMonth(1).atStartOfDay(); } else { - throw new IllegalArgumentException("type只能为0或1"); + throw new IllegalArgumentException("type只能为0、1或2"); } } /** * 将秒数转换为小时或者天 + * * @param seconds 秒数 - * @param type 1-本周,2-本天 + * @param type 0-今天,1-本周,2-本月 * @return 时间字符串 */ - public static String formatSecondsToHourMinute(Integer seconds, Integer type) { - if (type == 1) { - // 本天转换为小时 - int hours = seconds / 3600; - return hours + "小时"; - } else if (type == 0){ - // 本周转换为天 + public static String formatSecondsToHourMinute(Integer seconds, ReportTypeEnum type) { + if (type.getType().equals(0)) { + // 今天转换为小时和分钟 + return formatSecondsToHourAndMinute(seconds); + } else if (type.getType().equals(1) || type.getType().equals(2)) { + // 本周和本月转换为天 int days = seconds / 86400; return days + "天"; } else { - throw new IllegalArgumentException("type只能为0或1"); + throw new IllegalArgumentException("type只能为0、1或2"); } } public static String formatSecondsToHourAndMinute(Integer seconds) { int hours = seconds / 3600; int minutes = (seconds % 3600) / 60; - return hours + "小时" + minutes + "分"; + if (hours > 0) { + return hours + "小时" + minutes + "分钟"; + } else { + return minutes + "分钟"; + } } } \ No newline at end of file diff --git a/seer-teacher/seer-teacher-common/src/main/java/com/seer/teach/teacher/common/req/AiReportReq.java b/seer-teacher/seer-teacher-common/src/main/java/com/seer/teach/teacher/common/req/AiReportReq.java index 3aa210b..3e7cdfb 100644 --- a/seer-teacher/seer-teacher-common/src/main/java/com/seer/teach/teacher/common/req/AiReportReq.java +++ b/seer-teacher/seer-teacher-common/src/main/java/com/seer/teach/teacher/common/req/AiReportReq.java @@ -1,13 +1,13 @@ package com.seer.teach.teacher.common.req; -import io.swagger.v3.oas.annotations.media.Schema; +import com.seer.teach.common.enums.teacher.ReportTypeEnum; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.ToString; -import jakarta.validation.constraints.NotNull; +import javax.validation.constraints.NotNull; @Data @AllArgsConstructor @@ -16,7 +16,7 @@ import jakarta.validation.constraints.NotNull; @Schema(description = "Ai报告请求实体类") public class AiReportReq { - @Schema(description = "报告类型(0-本周,1-本天)") + @Schema(description ="报告类型(0-今天,1-本周,2-本月)") @NotNull(message = "报告类型不能为空") - private Integer type; + private ReportTypeEnum type; } diff --git a/seer-teacher/seer-teacher-common/src/main/java/com/seer/teach/teacher/common/resp/AiReportResp.java b/seer-teacher/seer-teacher-common/src/main/java/com/seer/teach/teacher/common/resp/AiReportResp.java index fcfe7d7..4cd7659 100644 --- a/seer-teacher/seer-teacher-common/src/main/java/com/seer/teach/teacher/common/resp/AiReportResp.java +++ b/seer-teacher/seer-teacher-common/src/main/java/com/seer/teach/teacher/common/resp/AiReportResp.java @@ -1,7 +1,6 @@ package com.seer.teach.teacher.common.resp; -import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Data; @@ -32,6 +31,9 @@ public class AiReportResp { @Schema(description = "作业正确率") private String homeworkCorrectRate; + @Schema(description = "鼓励语句") + private String encourage; + @Schema(description = "科目正确率") private List subjectCorrectRate; } diff --git a/seer-teacher/seer-teacher-common/src/main/java/com/seer/teach/teacher/common/resp/SimilarQuestionResp.java b/seer-teacher/seer-teacher-common/src/main/java/com/seer/teach/teacher/common/resp/SimilarQuestionResp.java new file mode 100644 index 0000000..b1c6a5f --- /dev/null +++ b/seer-teacher/seer-teacher-common/src/main/java/com/seer/teach/teacher/common/resp/SimilarQuestionResp.java @@ -0,0 +1,24 @@ +package com.seer.teach.teacher.common.resp; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@ToString +@Schema(description = "举一反三类似题目返回实体类") +public class SimilarQuestionResp { + + @Schema(description = "题目Id") + private Integer id; + + @Schema(description = "做题富文本数据") + private String questionHtml; + + @Schema(description = "题型(1单选选择题 2多选选择题 4判断题 6填空题)") + private Integer type; +} diff --git a/seer-teacher/seer-teacher-controller/src/main/java/com/seer/teach/teacher/controller/app/ReportController.java b/seer-teacher/seer-teacher-controller/src/main/java/com/seer/teach/teacher/controller/app/ReportController.java index 6f4f4fb..d498db7 100644 --- a/seer-teacher/seer-teacher-controller/src/main/java/com/seer/teach/teacher/controller/app/ReportController.java +++ b/seer-teacher/seer-teacher-controller/src/main/java/com/seer/teach/teacher/controller/app/ReportController.java @@ -7,8 +7,8 @@ import com.seer.teach.common.annotation.LogPrint; import com.seer.teach.teacher.common.req.AiReportReq; import com.seer.teach.teacher.common.resp.AiReportResp; import com.seer.teach.teacher.service.ReportService; -import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PostMapping; diff --git a/seer-teacher/seer-teacher-controller/src/main/java/com/seer/teach/teacher/controller/app/WrongQuestionController.java b/seer-teacher/seer-teacher-controller/src/main/java/com/seer/teach/teacher/controller/app/WrongQuestionController.java index 3a83dce..20e7cac 100644 --- a/seer-teacher/seer-teacher-controller/src/main/java/com/seer/teach/teacher/controller/app/WrongQuestionController.java +++ b/seer-teacher/seer-teacher-controller/src/main/java/com/seer/teach/teacher/controller/app/WrongQuestionController.java @@ -5,19 +5,22 @@ import com.seer.teach.common.ResultBean; import com.seer.teach.common.annotation.DecryptionAnnotation; import com.seer.teach.common.annotation.EncryptionAnnotation; import com.seer.teach.common.annotation.LogPrint; -import com.seer.teach.common.enums.AiScenarioCodeEnum; 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.module.entity.IdEntity; import com.seer.teach.teacher.service.WrongQuestionService; -import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; import java.util.List; @@ -37,7 +40,7 @@ public class WrongQuestionController { @LogPrint @Operation(summary = "获取各科目的错题数量") public ResultBean getWrongQuestionBook(@RequestBody @Validated WrongQuestionBookReq params) { - WrongQuestionResp wrongQuestion = wrongQuestionService.getWrongQuestion(params); + WrongQuestionResp wrongQuestion = wrongQuestionService.getWrongQuestionCount(params); return ResultBean.success(wrongQuestion); } @@ -57,13 +60,12 @@ public class WrongQuestionController { return ResultBean.success(); } - @PostMapping("/generateQuestionsByQuestionId") + @PostMapping("/getSimilarQuestions") @LogPrint - @Operation(summary = "举一反三") - public ResultBean> generateSimilarQuestions(@RequestBody @Validated IdEntity params) { - List questions = wrongQuestionService.generateSimilarQuestions(params.getId(), - AiScenarioCodeEnum.LEARN_BY_ANALOGY_GENERATE_QUESTIONS, 3); - return ResultBean.success(questions); + @Operation(summary = "举一反三获取相似题目") + public ResultBean> getSimilarQuestions(@RequestBody @Validated IdEntity params) { + List result = wrongQuestionService.getSimilarQuestionsByQuestionId(params.getId()); + return ResultBean.success(result); } diff --git a/seer-teacher/seer-teacher-service-bootstrap/src/main/resources/application.yml b/seer-teacher/seer-teacher-service-bootstrap/src/main/resources/application.yml index 5200c02..bce3d8d 100644 --- a/seer-teacher/seer-teacher-service-bootstrap/src/main/resources/application.yml +++ b/seer-teacher/seer-teacher-service-bootstrap/src/main/resources/application.yml @@ -21,6 +21,7 @@ spring: import: - optional:nacos:${spring.application.name}-${spring.profiles.active}.yaml - optional:nacos:shared-database.yaml + - optional:nacos:shared-minio.yaml - optional:nacos:shared-redis.yaml cloud: nacos: diff --git a/seer-teacher/seer-teacher-service/src/main/java/com/seer/teach/teacher/convert/AiReportConvert.java b/seer-teacher/seer-teacher-service/src/main/java/com/seer/teach/teacher/convert/AiReportConvert.java index 50f589a..1347d9d 100644 --- a/seer-teacher/seer-teacher-service/src/main/java/com/seer/teach/teacher/convert/AiReportConvert.java +++ b/seer-teacher/seer-teacher-service/src/main/java/com/seer/teach/teacher/convert/AiReportConvert.java @@ -12,5 +12,6 @@ public interface AiReportConvert { AiReportConvert INSTANCE = Mappers.getMapper(AiReportConvert.class); - AiReportResp toAiReportResp(String studyTime, String readingTime, String practiceCount, String dailyStudyTime, String homeworkCorrectRate, List subjectCorrectRate); + AiReportResp toAiReportResp(String studyTime, String readingTime, String practiceCount, String encourage, + String dailyStudyTime, String homeworkCorrectRate, List subjectCorrectRate); } diff --git a/seer-teacher/seer-teacher-service/src/main/java/com/seer/teach/teacher/convert/WrongQuestionBookConvert.java b/seer-teacher/seer-teacher-service/src/main/java/com/seer/teach/teacher/convert/WrongQuestionBookConvert.java index 1742140..4f83445 100644 --- a/seer-teacher/seer-teacher-service/src/main/java/com/seer/teach/teacher/convert/WrongQuestionBookConvert.java +++ b/seer-teacher/seer-teacher-service/src/main/java/com/seer/teach/teacher/convert/WrongQuestionBookConvert.java @@ -1,10 +1,14 @@ package com.seer.teach.teacher.convert; +import com.seer.teach.teacher.common.resp.SimilarQuestionResp; import com.seer.teach.teacher.common.resp.WrongQuestionResp; +import com.seer.teach.teacher.module.entity.BankQuestionsEntity; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.factory.Mappers; +import java.util.List; + @Mapper public interface WrongQuestionBookConvert { @@ -13,4 +17,8 @@ public interface WrongQuestionBookConvert { @Mapping(source = "totalWrongCount",target = "count") WrongQuestionResp toWrongQuestion(String totalWrongCount); + + SimilarQuestionResp toSimilarQuestion(BankQuestionsEntity bankQuestionsEntity); + + List toSimilarQuestionList(List bankQuestionsEntity); } diff --git a/seer-teacher/seer-teacher-service/src/main/java/com/seer/teach/teacher/service/WrongQuestionService.java b/seer-teacher/seer-teacher-service/src/main/java/com/seer/teach/teacher/service/WrongQuestionService.java index f480a99..6f7140c 100644 --- a/seer-teacher/seer-teacher-service/src/main/java/com/seer/teach/teacher/service/WrongQuestionService.java +++ b/seer-teacher/seer-teacher-service/src/main/java/com/seer/teach/teacher/service/WrongQuestionService.java @@ -4,6 +4,7 @@ import com.seer.teach.common.PageListBean; import com.seer.teach.common.enums.AiScenarioCodeEnum; 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.module.entity.IdEntity; @@ -24,7 +25,7 @@ public interface WrongQuestionService { * @param params 请求参数 * @return 错题本 */ - WrongQuestionResp getWrongQuestion(WrongQuestionBookReq params); + WrongQuestionResp getWrongQuestionCount(WrongQuestionBookReq params); /** * 获取错题列表 @@ -51,4 +52,11 @@ public interface WrongQuestionService { * @return 举一反三 */ List generateSimilarQuestions(Integer id, AiScenarioCodeEnum scenarioCode, Integer count); + + /** + * 举一反三获取相似题目 + * @param id 题目Id + * @return 相似题目 + */ + List 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 + "

【正确答案】");