feat(plan): 支持学案生成状态轮询与进度显示

- 新增接口检查学案是否正在生成,防止重复生成任务
- 使用 Redis 缓存标识学案生成状态,设置 12 分钟过期时间
- 生成学案时记录状态至 Redis,生成完成后自动清除
- Vue 学案列表新增学案生成进度条显示与已生成标签
- 新增组件事件监听生成成功,触发轮询检测学案状态
- 轮询间隔 10 秒,动态更新学案生成进度,最高至 95%
- 路由离开与组件卸载时停止所有轮询,防止内存泄漏
- 优化学案生成逻辑,新增小测试卷自动关联及数据入库
- 更新配置文件模板路径,提高文档管理一致性
This commit is contained in:
lbw
2025-12-31 15:41:53 +08:00
parent 504dd8d964
commit 868e0bb7bd
10 changed files with 167 additions and 16 deletions

View File

@@ -1,10 +1,17 @@
package com.yinlihupo.enlish.service.constant;
public interface LessonPlanConstant {
public class LessonPlanConstant {
String TITLE = "Title";
String PASSAGE = "ThePassage";
String QUIZ = "Quiz";
String ANSWER_KEY_EXPLANATION = "AnswerKey&Explanation";
String FULL_TRANSLATION = "FullTranslation";
public static final String TITLE = "Title";
public static final String PASSAGE = "ThePassage";
public static final String QUIZ = "Quiz";
public static final String ANSWER_KEY_EXPLANATION = "AnswerKey&Explanation";
public static final String FULL_TRANSLATION = "FullTranslation";
// 正在生成学案标识
public static final String GENERATING_PLAN = "GeneratingPlan";
public static String buildGeneratePlanContent(Integer studentId) {
return GENERATING_PLAN + ":" + studentId;
}
}

View File

@@ -1,5 +1,6 @@
package com.yinlihupo.enlish.service.controller;
import com.yinlihupo.enlish.service.constant.LessonPlanConstant;
import com.yinlihupo.enlish.service.domain.dataobject.LessonPlansDO;
import com.yinlihupo.enlish.service.domain.dataobject.VocabularyBankDO;
import com.yinlihupo.enlish.service.model.vo.plan.*;
@@ -13,6 +14,7 @@ import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -30,6 +32,9 @@ public class LessonPlanController {
@Resource
private LessonPlansService lessonPlanService;
@Resource
private RedisTemplate<String, Object> redisTemplate;
@Resource(name = "taskExecutor")
private Executor taskExecutor;
@Resource
@@ -47,6 +52,9 @@ public class LessonPlanController {
Integer unitId = addLessonPlanReqVO.getUnitId();
Integer wordSize = addLessonPlanReqVO.getWordSize();
try {
if (redisTemplate.opsForValue().get(LessonPlanConstant.buildGeneratePlanContent(studentId)) != null) {
throw new RuntimeException("学案正常生成中");
}
taskExecutor.execute(() -> lessonPlanService.generateLessonPlans(studentId, unitId, wordSize));
return Response.success("生成学案成功,请等待 10 分钟");
} catch (Exception e) {
@@ -96,4 +104,17 @@ public class LessonPlanController {
public void findPlanWordVoiceTTS(@RequestBody FindWordTTSVoiceReqVO findWordVoiceReqVO, HttpServletResponse response) {
ttsUtil.generateWordVoice(findWordVoiceReqVO.getText(), response);
}
@PostMapping("check")
@ApiOperationLog(description = "检测学案是否在生成")
public Response<String> checkLessonPlan(@RequestBody FindIsGeneratePlanReqVO findIsGeneratePlanReqVO) {
Integer studentId = findIsGeneratePlanReqVO.getStudentId();
String key = LessonPlanConstant.buildGeneratePlanContent(studentId);
if (redisTemplate.opsForValue().get(key) != null) {
return Response.fail();
}
return Response.success("学案生成完成");
}
}

View File

@@ -0,0 +1,15 @@
package com.yinlihupo.enlish.service.model.vo.plan;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
@Data
@Builder
public class FindIsGeneratePlanReqVO {
private Integer studentId;
}

View File

@@ -12,12 +12,14 @@ import com.yinlihupo.framework.common.util.JsonUtils;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
@Service
@Slf4j
@@ -45,10 +47,17 @@ public class LessonPlansServiceImpl implements LessonPlansService {
private StudentDOMapper studentDOMapper;
@Resource
private PlanExamDOMapper planExamDOMapper;
@Resource
private RedisTemplate<String, Object> redisTemplate;
@Override
public void generateLessonPlans(Integer studentId, Integer unitId, Integer wordSize) {
String key = LessonPlanConstant.buildGeneratePlanContent(studentId);
redisTemplate.opsForValue().set(key, studentId);
redisTemplate.expire( key, 12, TimeUnit.MINUTES);
log.info("开始生成计划");
List<VocabularyBankDO> vocabularyBankDOS = vocabularyBankDOMapper.selectVocabularyBankDOAllByUnitId(unitId);
UnitDO unitDO = unitDOMapper.selectByPrimaryKey(unitId);
GradeUnitDO gradeUnitDO = gradeUnitDOMapper.selectByUnitId(unitId);
@@ -146,7 +155,7 @@ public class LessonPlansServiceImpl implements LessonPlansService {
Map<String, Object> map = generateWeekendPlans(checkList, i + 6, gradeDO, unitDO, studentId);
LessonPlansDO lessonPlansDO = LessonPlansDO.builder()
.title(map.get("title").toString())
.title(map.get("examStr").toString() + "复习")
.gradeId(gradeDO.getId().toString())
.unitId(unitDO.getId())
.createdAt(LocalDateTime.now())
@@ -154,6 +163,13 @@ public class LessonPlansServiceImpl implements LessonPlansService {
.build();
lessonPlansDOMapper.insert(lessonPlansDO);
Integer examId = (Integer) map.get("examId");
PlanExamDO planExamDO = PlanExamDO.builder()
.planId(lessonPlansDO.getId())
.examId(examId)
.build();
planExamDOMapper.insert(planExamDO);
StudentLessonPlansDO studentLessonPlansDO = StudentLessonPlansDO.builder()
.studentId(studentId)
.planId(lessonPlansDO.getId())
@@ -177,13 +193,32 @@ public class LessonPlansServiceImpl implements LessonPlansService {
}
private Map<String, Object> generateWeekendPlans(List<VocabularyBankDO> checkList,
private Map<String, Object> generateWeekendPlans(List<VocabularyBankDO> words,
int day,
GradeDO gradeDO, UnitDO unitDO, Integer studentId) throws IOException {
Map<String, Object> data = new HashMap<>();
data.put("title", "" + day + "" + "复习" + gradeDO.getTitle() + unitDO.getTitle() + studentId);
data.put("checkList", checkList);
words.forEach(word -> word.setDefinition(word.getDefinition().length() > 5 ? word.getDefinition().substring(0, 5) : word.getDefinition()));
List<Integer> wordIds = words.stream().map(VocabularyBankDO::getId).toList();
StudentDO studentDO = studentDOMapper.selectStudentById(studentId);
String ExamTitle = gradeDO.getTitle() + unitDO.getTitle() + "教案小测第" + ExamWordsConstant.day2Chinese(day) + "" + studentDO.getName();
ExamWordsDO examWordsDO = ExamWordsDO.builder()
.gradeId(gradeDO.getId())
.level(1)
.wordIds(wordIds)
.type(ExamWordsConstant.EXAM_TYPE_TEST)
.title(ExamTitle)
.createdAt(LocalDateTime.now())
.build();
examWordsDOMapper.insert(examWordsDO);
studentExamWordsDOMapper.insertStudentsExam(studentId, examWordsDO.getId());
data.put("examId", examWordsDO.getId());
data.put("studentId", studentId);
data.put("studentStr", studentDO.getName());
data.put("examStr", ExamTitle);
data.put("words", words);
// LoopRowTableRenderPolicy policy = new LoopRowTableRenderPolicy();
// Configure config = Configure.builder()
// .bind("checkList", policy)

View File

@@ -36,7 +36,7 @@ templates:
data: C:\project\tess
plan:
weekday: C:\project\java\enlish_edu\enlish\enlish-service\src\main\resources\templates\tem_study_plan_v5.docx
weekend: C:\project\java\enlish_edu\enlish\enlish-service\src\main\resources\templates\study_plan_review_v1.docx
weekend: C:\project\java\enlish_edu\enlish\enlish-service\src\main\resources\templates\study_plan_review_v2.docx
plan_day: 7
tmp:
png: C:\project\java\enlish_edu\enlish\enlish-service\src\main\resources\tmp\png\