|
|
|
@ -6,11 +6,9 @@ import com.project.base.domain.result.Result; |
|
|
|
import com.project.classicpaper.domain.dto.ClassicPaperQuestionDTO; |
|
|
|
import com.project.classicpaper.domain.entity.ClassicPaperEntity; |
|
|
|
import com.project.classicpaper.domain.entity.ClassicPaperQuestionEntity; |
|
|
|
import com.project.classicpaper.domain.entity.RegenerateQuestionTaskEntity; |
|
|
|
import com.project.classicpaper.domain.enums.ClassicPaperStatusEnum; |
|
|
|
import com.project.classicpaper.domain.service.ClassicPaperBaseService; |
|
|
|
import com.project.classicpaper.domain.service.ClassicPaperQuestionBaseService; |
|
|
|
import com.project.classicpaper.domain.service.ClassicPaperQuestionGenerator; |
|
|
|
import com.project.classicpaper.domain.service.RegenerateQuestionDomainService; |
|
|
|
import com.project.classicpaper.domain.service.*; |
|
|
|
import com.project.information.domain.entity.KnowledgePointEntity; |
|
|
|
import com.project.information.domain.service.KnowledgePointBaseService; |
|
|
|
import com.project.question.domain.entity.QuestionEntity; |
|
|
|
@ -20,17 +18,28 @@ import com.project.question.domain.service.QuestionKpRelBaseService; |
|
|
|
import com.project.task.domain.enums.QuestionTypeEnum; |
|
|
|
import lombok.extern.slf4j.Slf4j; |
|
|
|
import org.springframework.beans.factory.annotation.Autowired; |
|
|
|
import org.springframework.beans.factory.annotation.Qualifier; |
|
|
|
import org.springframework.beans.factory.annotation.Value; |
|
|
|
import org.springframework.stereotype.Service; |
|
|
|
import org.springframework.transaction.annotation.Transactional; |
|
|
|
|
|
|
|
import java.util.Collections; |
|
|
|
import java.util.List; |
|
|
|
import java.util.Map; |
|
|
|
import java.util.UUID; |
|
|
|
import java.util.concurrent.CompletableFuture; |
|
|
|
import java.util.concurrent.Executor; |
|
|
|
import java.util.concurrent.ThreadLocalRandom; |
|
|
|
import java.util.stream.Collectors; |
|
|
|
|
|
|
|
@Service |
|
|
|
@Slf4j |
|
|
|
public class RegenerateQuestionDomainServiceImpl implements RegenerateQuestionDomainService { |
|
|
|
|
|
|
|
@Autowired |
|
|
|
@Qualifier("regenerateExecutor") |
|
|
|
private Executor regenerateExecutor; |
|
|
|
|
|
|
|
@Autowired |
|
|
|
private ClassicPaperBaseService classicPaperBaseService; |
|
|
|
|
|
|
|
@ -49,17 +58,23 @@ public class RegenerateQuestionDomainServiceImpl implements RegenerateQuestionDo |
|
|
|
@Autowired |
|
|
|
private ClassicPaperQuestionGenerator classicPaperQuestionGenerator; |
|
|
|
|
|
|
|
@Autowired |
|
|
|
private RegenerateQuestionTaskBaseService regenerateQuestionTaskBaseService; |
|
|
|
|
|
|
|
@Value("${classicpaper.mock.regenerate-delay-ms:4000}") |
|
|
|
private long mockDelayMin; |
|
|
|
|
|
|
|
@Value("${classicpaper.mock.regenerate-delay-max-ms:5000}") |
|
|
|
private long mockDelayMax; |
|
|
|
|
|
|
|
@Override |
|
|
|
@Transactional(rollbackFor = Exception.class) |
|
|
|
public Result<ClassicPaperQuestionDTO> regenerate(Long paperId, Long paperQuestionId) throws Exception { |
|
|
|
// 1. 校验套卷存在且为草稿状态
|
|
|
|
// 1. 校验套卷存在
|
|
|
|
ClassicPaperEntity paper = classicPaperBaseService.getById(paperId); |
|
|
|
if (paper == null) { |
|
|
|
throw new BusinessErrorException("套卷不存在"); |
|
|
|
} |
|
|
|
if (!ClassicPaperStatusEnum.DRAFT.getValue().equals(paper.getStatus())) { |
|
|
|
throw new BusinessErrorException("只有草稿状态的套卷才能重抽题目"); |
|
|
|
} |
|
|
|
|
|
|
|
// 2. 直接用 paperQuestionId 查关联记录
|
|
|
|
ClassicPaperQuestionEntity paperQuestion = classicPaperQuestionBaseService.getById(paperQuestionId); |
|
|
|
@ -84,7 +99,7 @@ public class RegenerateQuestionDomainServiceImpl implements RegenerateQuestionDo |
|
|
|
.eq(QuestionKpRelEntity::getQuestionId, questionId) |
|
|
|
); |
|
|
|
if (kpRels.isEmpty()) { |
|
|
|
throw new BusinessErrorException("原题目没有关联知识点,无法重抽"); |
|
|
|
throw new BusinessErrorException("原题目没有关联知识点,无法重新生题"); |
|
|
|
} |
|
|
|
List<Long> kpIds = kpRels.stream() |
|
|
|
.map(QuestionKpRelEntity::getKpId).collect(Collectors.toList()); |
|
|
|
@ -95,7 +110,10 @@ public class RegenerateQuestionDomainServiceImpl implements RegenerateQuestionDo |
|
|
|
throw new BusinessErrorException("关联知识点数据不存在"); |
|
|
|
} |
|
|
|
|
|
|
|
// 6. 调用生成器生成新题
|
|
|
|
// 6. Mock 延迟:模拟算法服务返回耗时
|
|
|
|
mockSleep(); |
|
|
|
|
|
|
|
// 7. 调用生成器生成新题
|
|
|
|
QuestionTypeEnum questionType = QuestionTypeEnum.findByValue(originalQuestion.getQuestionType()); |
|
|
|
List<QuestionEntity> newQuestions = classicPaperQuestionGenerator.generate( |
|
|
|
knowledgePoints, questionType, 1); |
|
|
|
@ -104,13 +122,13 @@ public class RegenerateQuestionDomainServiceImpl implements RegenerateQuestionDo |
|
|
|
} |
|
|
|
QuestionEntity newQuestion = newQuestions.get(0); |
|
|
|
|
|
|
|
// 7. 更新关联关系,指向新题
|
|
|
|
// 8. 更新关联关系,指向新题
|
|
|
|
paperQuestion.setQuestionId(newQuestion.getId()); |
|
|
|
classicPaperQuestionBaseService.updateById(paperQuestion); |
|
|
|
|
|
|
|
log.info(">>> [经典套题-重抽] 套卷{} 关联记录{} 原题{} -> 新题{}", paperId, paperQuestionId, questionId, newQuestion.getId()); |
|
|
|
log.info(">>> [经典套题-重新生题] 套卷{} 关联记录{} 原题{} -> 新题{}", paperId, paperQuestionId, questionId, newQuestion.getId()); |
|
|
|
|
|
|
|
// 8. 构建返回
|
|
|
|
// 9. 构建返回
|
|
|
|
ClassicPaperQuestionDTO dto = new ClassicPaperQuestionDTO(); |
|
|
|
dto.setId(paperQuestion.getId()); |
|
|
|
dto.setPaperId(paperId); |
|
|
|
@ -122,4 +140,126 @@ public class RegenerateQuestionDomainServiceImpl implements RegenerateQuestionDo |
|
|
|
} |
|
|
|
return Result.success(dto); |
|
|
|
} |
|
|
|
|
|
|
|
@Override |
|
|
|
public void regenerateAsync(Long paperQuestionId) throws Exception { |
|
|
|
// 1. 前置校验(快速失败,不占用异步线程)
|
|
|
|
ClassicPaperQuestionEntity paperQuestion = classicPaperQuestionBaseService.getById(paperQuestionId); |
|
|
|
if (paperQuestion == null) { |
|
|
|
throw new BusinessErrorException("该题目不存在"); |
|
|
|
} |
|
|
|
Long paperId = paperQuestion.getPaperId(); |
|
|
|
ClassicPaperEntity paper = classicPaperBaseService.getById(paperId); |
|
|
|
if (paper == null) { |
|
|
|
throw new BusinessErrorException("套卷不存在"); |
|
|
|
} |
|
|
|
|
|
|
|
// 2. 检查是否已有进行中的任务
|
|
|
|
RegenerateQuestionTaskEntity existingTask = regenerateQuestionTaskBaseService.getOne( |
|
|
|
new LambdaQueryWrapper<RegenerateQuestionTaskEntity>() |
|
|
|
.eq(RegenerateQuestionTaskEntity::getPaperQuestionId, paperQuestionId) |
|
|
|
.eq(RegenerateQuestionTaskEntity::getStatus, 0)); |
|
|
|
if (existingTask != null) { |
|
|
|
throw new BusinessErrorException("该题目正在重新生题中,请稍后再试"); |
|
|
|
} |
|
|
|
|
|
|
|
// 3. 创建任务记录
|
|
|
|
String taskId = UUID.randomUUID().toString().replace("-", ""); |
|
|
|
RegenerateQuestionTaskEntity taskEntity = new RegenerateQuestionTaskEntity(); |
|
|
|
taskEntity.setTaskId(taskId); |
|
|
|
taskEntity.setPaperId(paperId); |
|
|
|
taskEntity.setPaperQuestionId(paperQuestionId); |
|
|
|
taskEntity.setStatus(0); // 进行中
|
|
|
|
regenerateQuestionTaskBaseService.save(taskEntity); |
|
|
|
|
|
|
|
// 4. 异步执行重新生题逻辑
|
|
|
|
CompletableFuture.runAsync(() -> doRegenerateAsync(taskId, paperQuestionId), regenerateExecutor); |
|
|
|
|
|
|
|
log.info(">>> [经典套题-重新生题-异步] 任务已提交, taskId={}, paperId={}, paperQuestionId={}", taskId, paperId, paperQuestionId); |
|
|
|
} |
|
|
|
|
|
|
|
private void doRegenerateAsync(String taskId, Long paperQuestionId) { |
|
|
|
try { |
|
|
|
// 1. 查询关联记录和原题
|
|
|
|
ClassicPaperQuestionEntity paperQuestion = classicPaperQuestionBaseService.getById(paperQuestionId); |
|
|
|
Long questionId = paperQuestion.getQuestionId(); |
|
|
|
QuestionEntity originalQuestion = questionBaseService.getById(questionId); |
|
|
|
|
|
|
|
// 2. 查询知识点
|
|
|
|
List<QuestionKpRelEntity> kpRels = questionKpRelBaseService.list( |
|
|
|
new LambdaQueryWrapper<QuestionKpRelEntity>() |
|
|
|
.eq(QuestionKpRelEntity::getQuestionId, questionId)); |
|
|
|
List<Long> kpIds = kpRels.stream() |
|
|
|
.map(QuestionKpRelEntity::getKpId).collect(Collectors.toList()); |
|
|
|
List<KnowledgePointEntity> knowledgePoints = knowledgePointBaseService.listByIds(kpIds); |
|
|
|
|
|
|
|
// 3. Mock 延迟:模拟算法服务返回耗时
|
|
|
|
mockSleep(); |
|
|
|
|
|
|
|
// 4. 调用生成器生成新题
|
|
|
|
QuestionTypeEnum questionType = QuestionTypeEnum.findByValue(originalQuestion.getQuestionType()); |
|
|
|
List<QuestionEntity> newQuestions = classicPaperQuestionGenerator.generate( |
|
|
|
knowledgePoints, questionType, 1); |
|
|
|
if (newQuestions.isEmpty()) { |
|
|
|
throw new BusinessErrorException("生成新题失败"); |
|
|
|
} |
|
|
|
QuestionEntity newQuestion = newQuestions.get(0); |
|
|
|
|
|
|
|
// 5. 更新关联关系
|
|
|
|
paperQuestion.setQuestionId(newQuestion.getId()); |
|
|
|
classicPaperQuestionBaseService.updateById(paperQuestion); |
|
|
|
|
|
|
|
// 6. 更新任务状态为成功
|
|
|
|
RegenerateQuestionTaskEntity taskEntity = regenerateQuestionTaskBaseService.getOne( |
|
|
|
new LambdaQueryWrapper<RegenerateQuestionTaskEntity>() |
|
|
|
.eq(RegenerateQuestionTaskEntity::getTaskId, taskId)); |
|
|
|
taskEntity.setStatus(1); |
|
|
|
taskEntity.setNewQuestionId(newQuestion.getId()); |
|
|
|
regenerateQuestionTaskBaseService.updateById(taskEntity); |
|
|
|
|
|
|
|
log.info(">>> [经典套题-重新生题-异步] 任务成功, taskId={}, 原题{} -> 新题{}", taskId, questionId, newQuestion.getId()); |
|
|
|
} catch (Exception e) { |
|
|
|
log.error(">>> [经典套题-重新生题-异步] 任务失败, taskId={}", taskId, e); |
|
|
|
// 更新任务状态为失败
|
|
|
|
RegenerateQuestionTaskEntity taskEntity = regenerateQuestionTaskBaseService.getOne( |
|
|
|
new LambdaQueryWrapper<RegenerateQuestionTaskEntity>() |
|
|
|
.eq(RegenerateQuestionTaskEntity::getTaskId, taskId)); |
|
|
|
if (taskEntity != null) { |
|
|
|
taskEntity.setStatus(2); |
|
|
|
taskEntity.setErrorMessage(e.getMessage()); |
|
|
|
regenerateQuestionTaskBaseService.updateById(taskEntity); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@Override |
|
|
|
public Map<String, Object> getTaskStatus(Long paperQuestionId) { |
|
|
|
RegenerateQuestionTaskEntity taskEntity = regenerateQuestionTaskBaseService.getOne( |
|
|
|
new LambdaQueryWrapper<RegenerateQuestionTaskEntity>() |
|
|
|
.eq(RegenerateQuestionTaskEntity::getPaperQuestionId, paperQuestionId) |
|
|
|
.orderByDesc(RegenerateQuestionTaskEntity::getCreateTime) |
|
|
|
.last("LIMIT 1")); |
|
|
|
if (taskEntity == null) { |
|
|
|
return null; |
|
|
|
} |
|
|
|
Map<String, Object> result = new java.util.HashMap<>(); |
|
|
|
result.put("status", taskEntity.getStatus()); |
|
|
|
result.put("newQuestionId", taskEntity.getNewQuestionId()); |
|
|
|
result.put("errorMessage", taskEntity.getErrorMessage()); |
|
|
|
return result; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Mock 延迟:模拟算法服务返回耗时(4~5秒随机) |
|
|
|
*/ |
|
|
|
private void mockSleep() { |
|
|
|
long delay = ThreadLocalRandom.current().nextLong(mockDelayMin, mockDelayMax + 1); |
|
|
|
log.info(">>> [经典套题-重新生题] Mock 延迟 {}ms", delay); |
|
|
|
try { |
|
|
|
Thread.sleep(delay); |
|
|
|
} catch (InterruptedException e) { |
|
|
|
Thread.currentThread().interrupt(); |
|
|
|
log.warn(">>> [经典套题-重新生题] Mock 延迟被中断"); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|