feat:编写AI报告的接口、添加鼓励语句、修改举一反三从题库中获取相似题目的接口

This commit is contained in:
嘉多宝宝 2025-12-25 18:07:53 +08:00
parent 068f16ffaa
commit 3e863506aa
16 changed files with 378 additions and 49 deletions

View File

@ -25,5 +25,9 @@
<artifactId>common-validation</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -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;
}
}

View File

@ -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 + "分钟";
}
}
}

View File

@ -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;
}

View File

@ -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<SubjectCorrectRateResp> subjectCorrectRate;
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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<WrongQuestionResp> 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<List<String>> generateSimilarQuestions(@RequestBody @Validated IdEntity params) {
List<String> questions = wrongQuestionService.generateSimilarQuestions(params.getId(),
AiScenarioCodeEnum.LEARN_BY_ANALOGY_GENERATE_QUESTIONS, 3);
return ResultBean.success(questions);
@Operation(summary = "举一反三获取相似题目")
public ResultBean<List<SimilarQuestionResp>> getSimilarQuestions(@RequestBody @Validated IdEntity params) {
List<SimilarQuestionResp> result = wrongQuestionService.getSimilarQuestionsByQuestionId(params.getId());
return ResultBean.success(result);
}

View File

@ -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:

View File

@ -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<SubjectCorrectRateResp> subjectCorrectRate);
AiReportResp toAiReportResp(String studyTime, String readingTime, String practiceCount, String encourage,
String dailyStudyTime, String homeworkCorrectRate, List<SubjectCorrectRateResp> subjectCorrectRate);
}

View File

@ -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<SimilarQuestionResp> toSimilarQuestionList(List<BankQuestionsEntity> bankQuestionsEntity);
}

View File

@ -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<String> generateSimilarQuestions(Integer id, AiScenarioCodeEnum scenarioCode, Integer count);
/**
* 举一反三获取相似题目
* @param id 题目Id
* @return 相似题目
*/
List<SimilarQuestionResp> getSimilarQuestionsByQuestionId(Integer id);
}

View File

@ -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;
/**
* <p>
@ -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<BigDecimal> 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<BigDecimal> getHistoricalCorrectRate(Integer userId, ReportTypeEnum type) {
// 获取当前查询时间段的开始时间
LocalDateTime currentTimeStart = DateUtils.getStartTime(type);
// 查询当前时间段之前的学习记录按时间倒序排列
List<LessonsEntity> previousLessons = lessonsService.lambdaQuery()
.eq(LessonsEntity::getUserId, userId)
.lt(LessonsEntity::getCreateTime, currentTimeStart)
.orderByDesc(LessonsEntity::getCreateTime)
.list();
List<EvaluationTasksEntity> previousEvaluations = evaluationTasksService.lambdaQuery()
.eq(EvaluationTasksEntity::getUserId, userId)
.lt(EvaluationTasksEntity::getCreateTime, currentTimeStart)
.orderByDesc(EvaluationTasksEntity::getCreateTime)
.list();
// 合并并按时间倒序排序找出最近一次学习会话的时间范围
List<LocalDateTime> 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<LessonsEntity> lessons = lessonsService.lambdaQuery()
.eq(LessonsEntity::getUserId, userId)
.ge(LessonsEntity::getCreateTime, periodStart)
.le(LessonsEntity::getCreateTime, periodEnd)
.list();
List<EvaluationTasksEntity> 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<Integer, String> getSubjectsByIds(Set<Integer> ids) {
log.info("获取科目名称ID集合: {}", ids);
if (ids == null || ids.isEmpty()) {
log.info("ID集合为空返回空map");
return new HashMap<>();
}
Map<Integer, String> map = new HashMap<>();
for (SubjectsEntity s : subjectsService.listByIds(new ArrayList<>(ids))) {
map.put(s.getId(), s.getSubject());

View File

@ -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<SimilarQuestionResp> getSimilarQuestionsByQuestionId(Integer id) {
// 根据题目Id获取题目
BankQuestionsEntity questionBank = bankQuestionsService.getById(id);
AssertUtils.notNull(questionBank, ResultCodeEnum.QUESTION_NOT_EXIST);
// 获取知识点Id
Integer knowId = questionBank.getKnowId();
// 查询相同知识点的题目排除原题目
List<BankQuestionsEntity> questionsList = bankQuestionsService.lambdaQuery()
.eq(BankQuestionsEntity::getKnowId, knowId)
.ne(BankQuestionsEntity::getId, id)
.list();
List<SimilarQuestionResp> similarQuestionList = WrongQuestionBookConvert.INSTANCE.toSimilarQuestionList(questionsList);
// 过滤掉questionHtml为空的题目
List<SimilarQuestionResp> 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());
}
/**
* 异步批量插入生成的相似题目到题库
*

View File

@ -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));
}
}

View File

@ -188,11 +188,10 @@ public class WrongQuestionUtils {
String processed = originalHtml
.replaceAll("<!--\\s*题目解析\\s*-->\\s*<p><b>解析:</b>", "<p>解析:")
.replaceAll("<p>\\s*<b>答案:</b>\\s*", "<p>【正确答案】 ");
String redX = "";
// String redX = "";
// 构建学生答案段
String studentSegment = "<p>【你的答案】 "
+ (safeStudentAnswer.isEmpty() ? "" : safeStudentAnswer)
+ redX
+ "</p>\n\n";
// 插入到正确答案之前
processed = processed.replaceFirst("<p>【正确答案】", studentSegment + "<p>【正确答案】");