Browse Source

经典题库套卷生成

master
luoweijian 3 months ago
parent
commit
1b3aa8e986
  1. 42
      src/main/java/com/project/classicpaper/application/ClassicPaperApplicationService.java
  2. 157
      src/main/java/com/project/classicpaper/application/impl/ClassicPaperApplicationServiceImpl.java
  3. 57
      src/main/java/com/project/classicpaper/controller/ClassicPaperController.java
  4. 39
      src/main/java/com/project/classicpaper/domain/dto/ClassicPaperDTO.java
  5. 22
      src/main/java/com/project/classicpaper/domain/dto/ClassicPaperQuestionDTO.java
  6. 52
      src/main/java/com/project/classicpaper/domain/dto/GenerateClassicPaperRequestDTO.java
  7. 73
      src/main/java/com/project/classicpaper/domain/entity/ClassicPaperEntity.java
  8. 44
      src/main/java/com/project/classicpaper/domain/entity/ClassicPaperQuestionEntity.java
  9. 15
      src/main/java/com/project/classicpaper/domain/enums/ClassicPaperSourceTypeEnum.java
  10. 26
      src/main/java/com/project/classicpaper/domain/enums/ClassicPaperStatusEnum.java
  11. 7
      src/main/java/com/project/classicpaper/domain/service/ClassicPaperBaseService.java
  12. 7
      src/main/java/com/project/classicpaper/domain/service/ClassicPaperQuestionBaseService.java
  13. 25
      src/main/java/com/project/classicpaper/domain/service/ClassicPaperQuestionGenerator.java
  14. 15
      src/main/java/com/project/classicpaper/domain/service/GenerateClassicPaperDomainService.java
  15. 16
      src/main/java/com/project/classicpaper/domain/service/RegenerateQuestionDomainService.java
  16. 11
      src/main/java/com/project/classicpaper/domain/service/impl/ClassicPaperBaseServiceImpl.java
  17. 11
      src/main/java/com/project/classicpaper/domain/service/impl/ClassicPaperQuestionBaseServiceImpl.java
  18. 109
      src/main/java/com/project/classicpaper/domain/service/impl/FallbackClassicPaperQuestionGenerator.java
  19. 222
      src/main/java/com/project/classicpaper/domain/service/impl/GenerateClassicPaperDomainServiceImpl.java
  20. 124
      src/main/java/com/project/classicpaper/domain/service/impl/RegenerateQuestionDomainServiceImpl.java
  21. 9
      src/main/java/com/project/classicpaper/mapper/ClassicPaperMapper.java
  22. 9
      src/main/java/com/project/classicpaper/mapper/ClassicPaperQuestionMapper.java

42
src/main/java/com/project/classicpaper/application/ClassicPaperApplicationService.java

@ -0,0 +1,42 @@
package com.project.classicpaper.application;
import com.project.base.domain.result.PageResult;
import com.project.base.domain.result.Result;
import com.project.classicpaper.domain.dto.ClassicPaperDTO;
import com.project.classicpaper.domain.dto.ClassicPaperQuestionDTO;
import com.project.classicpaper.domain.dto.GenerateClassicPaperRequestDTO;
import java.util.List;
public interface ClassicPaperApplicationService {
/**
* 批量生成经典套题N个版本
*/
Result<List<ClassicPaperDTO>> generate(GenerateClassicPaperRequestDTO request) throws Exception;
/**
* 单题重抽
*/
Result<ClassicPaperQuestionDTO> regenerateQuestion(Long paperId, Long questionId) throws Exception;
/**
* 套卷列表
*/
Result<PageResult<ClassicPaperDTO>> list(Long subLineId, Long classicCategoryId, Integer status, Integer pageNum, Integer pageSize);
/**
* 套卷详情含题目列表
*/
Result<ClassicPaperDTO> getDetail(Long id);
/**
* 套题组详情查一组N个版本
*/
Result<List<ClassicPaperDTO>> getGroupDetail(Long paperGroupId);
/**
* 废弃套卷
*/
Result<String> delete(Long id) throws Exception;
}

157
src/main/java/com/project/classicpaper/application/impl/ClassicPaperApplicationServiceImpl.java

@ -0,0 +1,157 @@
package com.project.classicpaper.application.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.project.base.domain.exception.BusinessErrorException;
import com.project.base.domain.result.PageResult;
import com.project.base.domain.result.Result;
import com.project.base.domain.result.ResultCodeEnum;
import com.project.classicpaper.application.ClassicPaperApplicationService;
import com.project.classicpaper.domain.dto.ClassicPaperDTO;
import com.project.classicpaper.domain.dto.ClassicPaperQuestionDTO;
import com.project.classicpaper.domain.dto.GenerateClassicPaperRequestDTO;
import com.project.classicpaper.domain.entity.ClassicPaperEntity;
import com.project.classicpaper.domain.entity.ClassicPaperQuestionEntity;
import com.project.classicpaper.domain.enums.ClassicPaperStatusEnum;
import com.project.classicpaper.domain.service.*;
import com.project.question.domain.entity.QuestionEntity;
import com.project.question.domain.service.QuestionBaseService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class ClassicPaperApplicationServiceImpl implements ClassicPaperApplicationService {
@Autowired
private GenerateClassicPaperDomainService generateClassicPaperDomainService;
@Autowired
private RegenerateQuestionDomainService regenerateQuestionDomainService;
@Autowired
private ClassicPaperBaseService classicPaperBaseService;
@Autowired
private ClassicPaperQuestionBaseService classicPaperQuestionBaseService;
@Autowired
private QuestionBaseService questionBaseService;
@Override
public Result<List<ClassicPaperDTO>> generate(GenerateClassicPaperRequestDTO request) throws Exception {
return generateClassicPaperDomainService.generate(request);
}
@Override
public Result<ClassicPaperQuestionDTO> regenerateQuestion(Long paperId, Long questionId) throws Exception {
return regenerateQuestionDomainService.regenerate(paperId, questionId);
}
@Override
public Result<PageResult<ClassicPaperDTO>> list(Long subLineId, Long classicCategoryId,
Integer status, Integer pageNum, Integer pageSize) {
Page<ClassicPaperEntity> page = new Page<>(pageNum == null ? 1 : pageNum, pageSize == null ? 10 : pageSize);
LambdaQueryWrapper<ClassicPaperEntity> wrapper = new LambdaQueryWrapper<>();
if (subLineId != null) {
wrapper.eq(ClassicPaperEntity::getSubLineId, subLineId);
}
if (classicCategoryId != null) {
wrapper.eq(ClassicPaperEntity::getClassicCategoryId, classicCategoryId);
}
if (status != null) {
wrapper.eq(ClassicPaperEntity::getStatus, status);
}
wrapper.orderByDesc(ClassicPaperEntity::getCreateTime);
classicPaperBaseService.page(page, wrapper);
PageResult<ClassicPaperDTO> pageResult = new PageResult<>(page.convert(entity -> {
ClassicPaperDTO dto = entity.toDTO(ClassicPaperDTO::new);
dto.setStatusText(ClassicPaperStatusEnum.findByValue(entity.getStatus()));
return dto;
}));
return Result.success(pageResult);
}
@Override
public Result<ClassicPaperDTO> getDetail(Long id) {
ClassicPaperEntity paper = classicPaperBaseService.getById(id);
if (paper == null) {
return Result.fail(ResultCodeEnum.RESOURCE_NOT_EXIST, "套卷不存在");
}
ClassicPaperDTO dto = paper.toDTO(ClassicPaperDTO::new);
dto.setStatusText(ClassicPaperStatusEnum.findByValue(paper.getStatus()));
// 查询关联题目
List<ClassicPaperQuestionEntity> paperQuestions = classicPaperQuestionBaseService.list(
new LambdaQueryWrapper<ClassicPaperQuestionEntity>()
.eq(ClassicPaperQuestionEntity::getPaperId, id)
.orderByAsc(ClassicPaperQuestionEntity::getSortOrder)
);
if (!paperQuestions.isEmpty()) {
List<Long> questionIds = paperQuestions.stream()
.map(ClassicPaperQuestionEntity::getQuestionId).collect(Collectors.toList());
List<QuestionEntity> questions = questionBaseService.listByIds(questionIds);
List<ClassicPaperQuestionDTO> questionDTOs = new ArrayList<>();
for (ClassicPaperQuestionEntity pq : paperQuestions) {
ClassicPaperQuestionDTO qDto = new ClassicPaperQuestionDTO();
qDto.setId(pq.getId());
qDto.setPaperId(pq.getPaperId());
qDto.setQuestionId(pq.getQuestionId());
qDto.setSortOrder(pq.getSortOrder());
qDto.setQuestionType(pq.getQuestionType());
questions.stream()
.filter(q -> q.getId().equals(pq.getQuestionId()))
.findFirst()
.ifPresent(q -> qDto.setQuestionDetail(q.getQuestionDetail()));
questionDTOs.add(qDto);
}
dto.setQuestionList(questionDTOs);
}
return Result.success(dto);
}
@Override
public Result<List<ClassicPaperDTO>> getGroupDetail(Long paperGroupId) {
List<ClassicPaperEntity> papers = classicPaperBaseService.list(
new LambdaQueryWrapper<ClassicPaperEntity>()
.eq(ClassicPaperEntity::getPaperGroupId, paperGroupId)
.orderByAsc(ClassicPaperEntity::getSetIndex)
);
if (papers.isEmpty()) {
return Result.fail(ResultCodeEnum.RESOURCE_NOT_EXIST, "套题组不存在");
}
List<ClassicPaperDTO> dtoList = papers.stream()
.map(entity -> {
ClassicPaperDTO dto = entity.toDTO(ClassicPaperDTO::new);
dto.setStatusText(ClassicPaperStatusEnum.findByValue(entity.getStatus()));
return dto;
})
.collect(Collectors.toList());
return Result.success(dtoList);
}
@Override
public Result<String> delete(Long id) throws Exception {
ClassicPaperEntity paper = classicPaperBaseService.getById(id);
if (paper == null) {
throw new BusinessErrorException("套卷不存在");
}
if (ClassicPaperStatusEnum.CONFIRMED.getValue().equals(paper.getStatus())) {
throw new BusinessErrorException("已固化的套卷不能废弃");
}
paper.setStatus(ClassicPaperStatusEnum.DISCARDED.getValue());
classicPaperBaseService.updateById(paper);
return Result.success("废弃成功");
}
}

57
src/main/java/com/project/classicpaper/controller/ClassicPaperController.java

@ -0,0 +1,57 @@
package com.project.classicpaper.controller;
import com.project.base.domain.result.PageResult;
import com.project.base.domain.result.Result;
import com.project.classicpaper.application.ClassicPaperApplicationService;
import com.project.classicpaper.domain.dto.ClassicPaperDTO;
import com.project.classicpaper.domain.dto.ClassicPaperQuestionDTO;
import com.project.classicpaper.domain.dto.GenerateClassicPaperRequestDTO;
import com.project.operation.annotation.OperationLog;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@Slf4j
@RequestMapping("/api/admin/classicPaper")
public class ClassicPaperController {
@Autowired
private ClassicPaperApplicationService classicPaperApplicationService;
@PostMapping("/generate")
@OperationLog(module = "经典套题")
public Result<List<ClassicPaperDTO>> generate(@RequestBody GenerateClassicPaperRequestDTO request) throws Exception {
return classicPaperApplicationService.generate(request);
}
@PostMapping("/regenerateQuestion")
@OperationLog(module = "经典套题")
public Result<ClassicPaperQuestionDTO> regenerateQuestion(Long paperId, Long questionId) throws Exception {
return classicPaperApplicationService.regenerateQuestion(paperId, questionId);
}
@GetMapping("/list")
public Result<PageResult<ClassicPaperDTO>> list(Long subLineId, Long classicCategoryId,
Integer status, Integer pageNum, Integer pageSize) {
return classicPaperApplicationService.list(subLineId, classicCategoryId, status, pageNum, pageSize);
}
@GetMapping("/detail")
public Result<ClassicPaperDTO> detail(Long id) {
return classicPaperApplicationService.getDetail(id);
}
@GetMapping("/groupDetail")
public Result<List<ClassicPaperDTO>> groupDetail(Long paperGroupId) {
return classicPaperApplicationService.getGroupDetail(paperGroupId);
}
@PostMapping("/delete")
@OperationLog(module = "经典套题")
public Result<String> delete(Long id) throws Exception {
return classicPaperApplicationService.delete(id);
}
}

39
src/main/java/com/project/classicpaper/domain/dto/ClassicPaperDTO.java

@ -0,0 +1,39 @@
package com.project.classicpaper.domain.dto;
import com.project.base.domain.dto.BaseDTO;
import lombok.Data;
import java.util.List;
@Data
public class ClassicPaperDTO extends BaseDTO {
private Long id;
private String name;
private String description;
private Integer sourceType;
private Long subLineId;
private String subLineName;
private Long classicCategoryId;
private String classicCategoryName;
private Long informationId;
private Integer status;
private String statusText;
private Integer questionCount;
private Long paperGroupId;
private Integer setIndex;
private List<ClassicPaperQuestionDTO> questionList;
}

22
src/main/java/com/project/classicpaper/domain/dto/ClassicPaperQuestionDTO.java

@ -0,0 +1,22 @@
package com.project.classicpaper.domain.dto;
import com.project.question.domain.entity.QuestionEntity;
import lombok.Data;
@Data
public class ClassicPaperQuestionDTO {
private Long id;
private Long paperId;
private Long questionId;
private Integer sortOrder;
private Integer questionType;
/**
* 题目详情 QuestionEntity 关联查询
*/
private QuestionEntity.QuestionDetail questionDetail;
}

52
src/main/java/com/project/classicpaper/domain/dto/GenerateClassicPaperRequestDTO.java

@ -0,0 +1,52 @@
package com.project.classicpaper.domain.dto;
import lombok.Data;
@Data
public class GenerateClassicPaperRequestDTO {
/**
* 子产品线ID必填
*/
private Long subLineId;
/**
* 经典分类ID必填
*/
private Long classicCategoryId;
/**
* 生成版本数 N1~5必填
*/
private Integer setCount;
/**
* 套卷名称前缀必填
*/
private String namePrefix;
/**
* 套卷描述可选
*/
private String description;
/**
* 单选题数量
*/
private Integer singleChoiceNum;
/**
* 多选题数量
*/
private Integer multipleChoiceNum;
/**
* 判断题数量
*/
private Integer trueFalseNum;
/**
* 简答题数量
*/
private Integer shortAnswerNum;
}

73
src/main/java/com/project/classicpaper/domain/entity/ClassicPaperEntity.java

@ -0,0 +1,73 @@
package com.project.classicpaper.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.project.base.domain.entity.BaseEntity;
import jakarta.persistence.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.Comment;
@Data
@Table(name = "evaluator_classic_paper",
indexes = {@Index(name = "Idx_sub_line_id", columnList = "sub_line_id"),
@Index(name = "Idx_status", columnList = "status"),
@Index(name = "Idx_paper_group_id", columnList = "paper_group_id")})
@Entity
@TableName(value = "evaluator_classic_paper")
@EqualsAndHashCode(callSuper = true)
public class ClassicPaperEntity extends BaseEntity {
@TableId(value = "id", type = IdType.ASSIGN_ID)
@Id
private Long id;
@Column(name = "name", columnDefinition = "varchar(200) comment '套卷名称'")
@Comment("套卷名称")
private String name;
@Column(name = "description", columnDefinition = "TEXT comment '套卷描述'")
@Comment("套卷描述")
private String description;
@Column(name = "source_type")
@TableField("source_type")
@Comment("来源:0-Word导入,1-AI生成")
private Integer sourceType;
@Column(name = "sub_line_id")
@TableField("sub_line_id")
@Comment("来源子产品线ID")
private Long subLineId;
@Column(name = "information_id")
@TableField("information_id")
@Comment("关联资料ID")
private Long informationId;
@Column(name = "status")
@TableField("status")
@Comment("状态:0-生成中,1-草稿,2-已固化,3-已废弃")
private Integer status;
@Column(name = "question_count")
@TableField("question_count")
@Comment("题目总数")
private Integer questionCount;
@Column(name = "paper_group_id")
@TableField("paper_group_id")
@Comment("套题组ID,N个版本共享")
private Long paperGroupId;
@Column(name = "set_index")
@TableField("set_index")
@Comment("组内序号(1~N)")
private Integer setIndex;
@Column(name = "classic_category_id")
@TableField("classic_category_id")
@Comment("经典分类ID")
private Long classicCategoryId;
}

44
src/main/java/com/project/classicpaper/domain/entity/ClassicPaperQuestionEntity.java

@ -0,0 +1,44 @@
package com.project.classicpaper.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.project.base.domain.entity.BaseEntity;
import jakarta.persistence.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.Comment;
@Data
@Table(name = "evaluator_classic_paper_question",
indexes = {@Index(name = "Idx_paper_id", columnList = "paper_id"),
@Index(name = "Idx_question_id", columnList = "question_id")})
@Entity
@TableName(value = "evaluator_classic_paper_question")
@EqualsAndHashCode(callSuper = true)
public class ClassicPaperQuestionEntity extends BaseEntity {
@TableId(value = "id", type = IdType.ASSIGN_ID)
@Id
private Long id;
@Column(name = "paper_id")
@TableField("paper_id")
@Comment("套卷ID")
private Long paperId;
@Column(name = "question_id")
@TableField("question_id")
@Comment("题目ID")
private Long questionId;
@Column(name = "sort_order")
@TableField("sort_order")
@Comment("排序序号")
private Integer sortOrder;
@Column(name = "question_type")
@TableField("question_type")
@Comment("题型")
private Integer questionType;
}

15
src/main/java/com/project/classicpaper/domain/enums/ClassicPaperSourceTypeEnum.java

@ -0,0 +1,15 @@
package com.project.classicpaper.domain.enums;
import com.project.base.domain.enums.HasValueEnum;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Getter
public enum ClassicPaperSourceTypeEnum implements HasValueEnum<Integer> {
WORD_IMPORT(0, "Word导入"),
AI_GENERATED(1, "AI生成");
private final Integer value;
private final String desc;
}

26
src/main/java/com/project/classicpaper/domain/enums/ClassicPaperStatusEnum.java

@ -0,0 +1,26 @@
package com.project.classicpaper.domain.enums;
import com.project.base.domain.enums.HasValueEnum;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Getter
public enum ClassicPaperStatusEnum implements HasValueEnum<Integer> {
GENERATING(0, "生成中"),
DRAFT(1, "草稿"),
CONFIRMED(2, "已固化"),
DISCARDED(3, "已废弃");
private final Integer value;
private final String desc;
public static String findByValue(Integer value) {
for (ClassicPaperStatusEnum e : values()) {
if (e.value.equals(value)) {
return e.desc;
}
}
return "";
}
}

7
src/main/java/com/project/classicpaper/domain/service/ClassicPaperBaseService.java

@ -0,0 +1,7 @@
package com.project.classicpaper.domain.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.project.classicpaper.domain.entity.ClassicPaperEntity;
public interface ClassicPaperBaseService extends IService<ClassicPaperEntity> {
}

7
src/main/java/com/project/classicpaper/domain/service/ClassicPaperQuestionBaseService.java

@ -0,0 +1,7 @@
package com.project.classicpaper.domain.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.project.classicpaper.domain.entity.ClassicPaperQuestionEntity;
public interface ClassicPaperQuestionBaseService extends IService<ClassicPaperQuestionEntity> {
}

25
src/main/java/com/project/classicpaper/domain/service/ClassicPaperQuestionGenerator.java

@ -0,0 +1,25 @@
package com.project.classicpaper.domain.service;
import com.project.information.domain.entity.KnowledgePointEntity;
import com.project.question.domain.entity.QuestionEntity;
import com.project.task.domain.enums.QuestionTypeEnum;
import java.util.List;
/**
* 经典套卷题目生成器接口
* 当前使用降级实现后续替换为算法服务新接口实现
*/
public interface ClassicPaperQuestionGenerator {
/**
* 为经典套卷生成题目
*
* @param knowledgePoints 知识点列表素材
* @param questionType 题型
* @param count 生成数量
* @return 生成的题目实体列表已保存到数据库
*/
List<QuestionEntity> generate(List<KnowledgePointEntity> knowledgePoints,
QuestionTypeEnum questionType, int count) throws Exception;
}

15
src/main/java/com/project/classicpaper/domain/service/GenerateClassicPaperDomainService.java

@ -0,0 +1,15 @@
package com.project.classicpaper.domain.service;
import com.project.base.domain.result.Result;
import com.project.classicpaper.domain.dto.ClassicPaperDTO;
import com.project.classicpaper.domain.dto.GenerateClassicPaperRequestDTO;
import java.util.List;
public interface GenerateClassicPaperDomainService {
/**
* 批量生成经典套题N个版本
*/
Result<List<ClassicPaperDTO>> generate(GenerateClassicPaperRequestDTO request) throws Exception;
}

16
src/main/java/com/project/classicpaper/domain/service/RegenerateQuestionDomainService.java

@ -0,0 +1,16 @@
package com.project.classicpaper.domain.service;
import com.project.base.domain.result.Result;
import com.project.classicpaper.domain.dto.ClassicPaperQuestionDTO;
public interface RegenerateQuestionDomainService {
/**
* 单题重抽替换套卷中的某道题
*
* @param paperId 套卷ID
* @param questionId 原题目ID
* @return 新题目信息
*/
Result<ClassicPaperQuestionDTO> regenerate(Long paperId, Long questionId) throws Exception;
}

11
src/main/java/com/project/classicpaper/domain/service/impl/ClassicPaperBaseServiceImpl.java

@ -0,0 +1,11 @@
package com.project.classicpaper.domain.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.project.classicpaper.domain.entity.ClassicPaperEntity;
import com.project.classicpaper.domain.service.ClassicPaperBaseService;
import com.project.classicpaper.mapper.ClassicPaperMapper;
import org.springframework.stereotype.Service;
@Service
public class ClassicPaperBaseServiceImpl extends ServiceImpl<ClassicPaperMapper, ClassicPaperEntity> implements ClassicPaperBaseService {
}

11
src/main/java/com/project/classicpaper/domain/service/impl/ClassicPaperQuestionBaseServiceImpl.java

@ -0,0 +1,11 @@
package com.project.classicpaper.domain.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.project.classicpaper.domain.entity.ClassicPaperQuestionEntity;
import com.project.classicpaper.domain.service.ClassicPaperQuestionBaseService;
import com.project.classicpaper.mapper.ClassicPaperQuestionMapper;
import org.springframework.stereotype.Service;
@Service
public class ClassicPaperQuestionBaseServiceImpl extends ServiceImpl<ClassicPaperQuestionMapper, ClassicPaperQuestionEntity> implements ClassicPaperQuestionBaseService {
}

109
src/main/java/com/project/classicpaper/domain/service/impl/FallbackClassicPaperQuestionGenerator.java

@ -0,0 +1,109 @@
package com.project.classicpaper.domain.service.impl;
import cn.hutool.core.util.RandomUtil;
import com.project.base.domain.result.Result;
import com.project.information.domain.entity.KnowledgePointEntity;
import com.project.question.domain.dto.QuestionDTO;
import com.project.question.domain.entity.QuestionEntity;
import com.project.question.domain.service.SaveQuestionDomainService;
import com.project.task.domain.enums.QuestionTypeEnum;
import com.project.classicpaper.domain.service.ClassicPaperQuestionGenerator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.stream.Collectors;
/**
* 降级实现生成占位题目
* 当算法服务新接口未就绪时使用后续替换为真正的算法调用
*/
@Component
@Slf4j
public class FallbackClassicPaperQuestionGenerator implements ClassicPaperQuestionGenerator {
@Autowired
private SaveQuestionDomainService saveQuestionDomainService;
private final String[] optionListStr = {"A", "B", "C", "D"};
private final String[] trueFalseListStr = {"A", "B"};
private final List<Integer> list = Arrays.asList(0, 1, 2, 3);
@Override
public List<QuestionEntity> generate(List<KnowledgePointEntity> knowledgePoints,
QuestionTypeEnum questionType, int count) throws Exception {
log.info(">>> [经典套题-降级] 开始生成占位题目, 题型: {}, 数量: {}, 知识点数: {}",
questionType, count, knowledgePoints.size());
List<QuestionEntity> result = new ArrayList<>();
for (int i = 0; i < count; i++) {
KnowledgePointEntity kp = knowledgePoints.get(i % knowledgePoints.size());
QuestionDTO questionDTO = new QuestionDTO();
questionDTO.setKpIdList(Collections.singletonList(kp.getId()));
questionDTO.setQuestionType(questionType.getValue());
questionDTO.setSourceType(0);
QuestionDTO.QuestionDetailDTO detailDTO = new QuestionDTO.QuestionDetailDTO();
questionDTO.setQuestionDetailDTO(detailDTO);
detailDTO.setQuestionContent(String.format("【经典套题】%s - 第%d题(%s)",
kp.getParseName(), i + 1, questionType.getDescription()));
detailDTO.setType(questionType.getValue());
if (QuestionTypeEnum.SINGLE_CHOICE.equals(questionType)) {
int rightAnswerNo = RandomUtil.randomInt(0, 4);
TreeMap<String, String> optionList = new TreeMap<>();
for (int j = 0; j < optionListStr.length; j++) {
optionList.put(optionListStr[j], String.format("选项%s", optionListStr[j]));
if (j == rightAnswerNo) {
detailDTO.setRightAnswer(optionListStr[j]);
detailDTO.setAnalysis(String.format("正确答案是%s", optionListStr[j]));
}
}
detailDTO.setOptions(optionList);
} else if (QuestionTypeEnum.MULTIPLE_CHOICE.equals(questionType)) {
Collections.shuffle(list);
Set<Integer> resultIdx = list.subList(0, 2).stream().collect(Collectors.toSet());
TreeMap<String, String> optionList = new TreeMap<>();
List<String> rightAnswerList = new ArrayList<>();
for (int j = 0; j < optionListStr.length; j++) {
optionList.put(optionListStr[j], String.format("选项%s", optionListStr[j]));
if (resultIdx.contains(j)) {
rightAnswerList.add(optionListStr[j]);
}
}
detailDTO.setRightAnswer(String.join(",", rightAnswerList));
detailDTO.setAnalysis(String.format("正确答案是%s", detailDTO.getRightAnswer()));
detailDTO.setOptions(optionList);
} else if (QuestionTypeEnum.TRUE_FALSE.equals(questionType)) {
int rightAnswerNo = RandomUtil.randomInt(0, 2);
TreeMap<String, String> optionList = new TreeMap<>();
for (int j = 0; j < trueFalseListStr.length; j++) {
optionList.put(trueFalseListStr[j], j == 0 ? "对" : "错");
if (j == rightAnswerNo) {
detailDTO.setRightAnswer(trueFalseListStr[j]);
detailDTO.setAnalysis(String.format("正确答案是%s", trueFalseListStr[j]));
}
}
detailDTO.setOptions(optionList);
} else {
// 简答题
detailDTO.setRightAnswer("参考答案");
detailDTO.setAnalysis("解析");
}
Result<QuestionDTO> saveResult = saveQuestionDomainService.save(questionDTO);
QuestionDTO saved = saveResult.getData();
QuestionEntity savedEntity = new QuestionEntity();
savedEntity.setId(saved.getId());
savedEntity.setQuestionType(questionType.getValue());
savedEntity.setKpIdList(saved.getKpIdList());
result.add(savedEntity);
}
log.info(">>> [经典套题-降级] 生成完成, 共{}题", result.size());
return result;
}
}

222
src/main/java/com/project/classicpaper/domain/service/impl/GenerateClassicPaperDomainServiceImpl.java

@ -0,0 +1,222 @@
package com.project.classicpaper.domain.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.project.base.config.SnowflakeIdWorker;
import com.project.base.domain.exception.BusinessErrorException;
import com.project.base.domain.result.Result;
import com.project.classicpaper.domain.dto.ClassicPaperDTO;
import com.project.classicpaper.domain.dto.ClassicPaperQuestionDTO;
import com.project.classicpaper.domain.dto.GenerateClassicPaperRequestDTO;
import com.project.classicpaper.domain.entity.ClassicPaperEntity;
import com.project.classicpaper.domain.entity.ClassicPaperQuestionEntity;
import com.project.classicpaper.domain.enums.ClassicPaperSourceTypeEnum;
import com.project.classicpaper.domain.enums.ClassicPaperStatusEnum;
import com.project.classicpaper.domain.service.*;
import com.project.information.domain.entity.InformationEntity;
import com.project.information.domain.entity.KnowledgePointEntity;
import com.project.information.domain.enums.InformationParseStatusEnum;
import com.project.information.domain.service.InformationBaseService;
import com.project.information.domain.service.KnowledgePointBaseService;
import com.project.question.domain.entity.QuestionEntity;
import com.project.task.domain.enums.QuestionTypeEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.util.stream.Collectors;
@Service
@Slf4j
public class GenerateClassicPaperDomainServiceImpl implements GenerateClassicPaperDomainService {
@Autowired
private InformationBaseService informationBaseService;
@Autowired
private KnowledgePointBaseService knowledgePointBaseService;
@Autowired
private ClassicPaperBaseService classicPaperBaseService;
@Autowired
private ClassicPaperQuestionBaseService classicPaperQuestionBaseService;
@Autowired
private ClassicPaperQuestionGenerator classicPaperQuestionGenerator;
private final SnowflakeIdWorker snowflakeIdWorker = new SnowflakeIdWorker(0, 0);
@Override
@Transactional(rollbackFor = Exception.class)
public Result<List<ClassicPaperDTO>> generate(GenerateClassicPaperRequestDTO request) throws Exception {
// 1. 校验参数
validateRequest(request);
// 2. 查询子产品线下已解析成功的资料
List<InformationEntity> informations = informationBaseService.list(
new LambdaQueryWrapper<InformationEntity>()
.eq(InformationEntity::getSubLineId, request.getSubLineId())
.eq(InformationEntity::getParseStatus, InformationParseStatusEnum.Success.getValue())
);
if (CollUtil.isEmpty(informations)) {
throw new BusinessErrorException("该子产品线下没有已解析成功的资料");
}
// 3. 查询这些资料的所有知识点
List<Long> informationIds = informations.stream()
.map(InformationEntity::getId).collect(Collectors.toList());
List<KnowledgePointEntity> allKps = knowledgePointBaseService.list(
new LambdaQueryWrapper<KnowledgePointEntity>()
.in(KnowledgePointEntity::getInformationId, informationIds)
);
if (CollUtil.isEmpty(allKps)) {
throw new BusinessErrorException("该子产品线下没有知识点数据");
}
// 4. 计算总题目数并校验知识点数量
int totalPerSet = Optional.ofNullable(request.getSingleChoiceNum()).orElse(0)
+ Optional.ofNullable(request.getMultipleChoiceNum()).orElse(0)
+ Optional.ofNullable(request.getTrueFalseNum()).orElse(0)
+ Optional.ofNullable(request.getShortAnswerNum()).orElse(0);
if (totalPerSet <= 0) {
throw new BusinessErrorException("至少需要一种题型的数量大于0");
}
int setCount = request.getSetCount();
if (allKps.size() < totalPerSet) {
throw new BusinessErrorException("知识点数量(" + allKps.size() + ")不足以生成每套 " + totalPerSet + " 道题");
}
// 5. 创建 paperGroupId
Long paperGroupId = snowflakeIdWorker.nextId();
// 6. 构建题型配置列表
List<QuestionTypeConfig> typeConfigs = buildTypeConfigs(request);
// 7. 循环生成 N 个版本
List<ClassicPaperDTO> paperDTOs = new ArrayList<>();
for (int i = 1; i <= setCount; i++) {
ClassicPaperDTO paperDTO = generateOnePaper(paperGroupId, i, request, allKps, typeConfigs);
paperDTOs.add(paperDTO);
}
log.info(">>> [经典套题] 批量生成完成, paperGroupId={}, 版本数={}, 每套题数={}",
paperGroupId, setCount, totalPerSet);
return Result.success(paperDTOs);
}
private void validateRequest(GenerateClassicPaperRequestDTO request) {
if (request.getSubLineId() == null) {
throw new BusinessErrorException("子产品线ID不能为空");
}
if (request.getClassicCategoryId() == null) {
throw new BusinessErrorException("经典分类不能为空");
}
if (request.getSetCount() == null || request.getSetCount() < 1 || request.getSetCount() > 5) {
throw new BusinessErrorException("版本数必须在1~5之间");
}
if (StrUtil.isBlank(request.getNamePrefix())) {
throw new BusinessErrorException("套卷名称前缀不能为空");
}
}
private List<QuestionTypeConfig> buildTypeConfigs(GenerateClassicPaperRequestDTO request) {
List<QuestionTypeConfig> configs = new ArrayList<>();
if (Optional.ofNullable(request.getSingleChoiceNum()).orElse(0) > 0) {
configs.add(new QuestionTypeConfig(QuestionTypeEnum.SINGLE_CHOICE, request.getSingleChoiceNum()));
}
if (Optional.ofNullable(request.getMultipleChoiceNum()).orElse(0) > 0) {
configs.add(new QuestionTypeConfig(QuestionTypeEnum.MULTIPLE_CHOICE, request.getMultipleChoiceNum()));
}
if (Optional.ofNullable(request.getTrueFalseNum()).orElse(0) > 0) {
configs.add(new QuestionTypeConfig(QuestionTypeEnum.TRUE_FALSE, request.getTrueFalseNum()));
}
if (Optional.ofNullable(request.getShortAnswerNum()).orElse(0) > 0) {
configs.add(new QuestionTypeConfig(QuestionTypeEnum.SHORT_ANSWER, request.getShortAnswerNum()));
}
return configs;
}
/**
* 生成一个版本的套卷
*/
private ClassicPaperDTO generateOnePaper(Long paperGroupId, int setIndex,
GenerateClassicPaperRequestDTO request,
List<KnowledgePointEntity> allKps,
List<QuestionTypeConfig> typeConfigs) throws Exception {
// 创建套卷实体
ClassicPaperEntity paper = new ClassicPaperEntity();
paper.setName(request.getNamePrefix() + "-V" + setIndex);
paper.setDescription(request.getDescription());
paper.setSourceType(ClassicPaperSourceTypeEnum.AI_GENERATED.getValue());
paper.setSubLineId(request.getSubLineId());
paper.setClassicCategoryId(request.getClassicCategoryId());
paper.setStatus(ClassicPaperStatusEnum.DRAFT.getValue());
paper.setPaperGroupId(paperGroupId);
paper.setSetIndex(setIndex);
paper.setQuestionCount(0);
classicPaperBaseService.save(paper);
// 打乱知识点顺序,每个版本用不同的排列
List<KnowledgePointEntity> shuffledKps = new ArrayList<>(allKps);
Collections.shuffle(shuffledKps);
int totalQuestions = 0;
int sortOrder = 1;
List<ClassicPaperQuestionEntity> paperQuestionEntities = new ArrayList<>();
// 对每种题型生成题目
for (QuestionTypeConfig config : typeConfigs) {
// 从知识点池中采样(不同版本使用不同组合)
List<KnowledgePointEntity> sampledKps = sampleKps(shuffledKps, config.count);
List<QuestionEntity> questions = classicPaperQuestionGenerator.generate(
sampledKps, config.questionType, config.count);
for (QuestionEntity question : questions) {
ClassicPaperQuestionEntity pq = new ClassicPaperQuestionEntity();
pq.setPaperId(paper.getId());
pq.setQuestionId(question.getId());
pq.setSortOrder(sortOrder++);
pq.setQuestionType(config.questionType.getValue());
paperQuestionEntities.add(pq);
totalQuestions++;
}
}
// 批量保存关联关系
classicPaperQuestionBaseService.saveBatch(paperQuestionEntities);
// 更新题目总数
paper.setQuestionCount(totalQuestions);
classicPaperBaseService.updateById(paper);
// 构建返回 DTO
ClassicPaperDTO dto = paper.toDTO(ClassicPaperDTO::new);
dto.setStatusText(ClassicPaperStatusEnum.DRAFT.getDesc());
return dto;
}
/**
* 从知识点池中采样指定数量的知识点
*/
private List<KnowledgePointEntity> sampleKps(List<KnowledgePointEntity> pool, int count) {
if (pool.size() <= count) {
return new ArrayList<>(pool);
}
return new ArrayList<>(pool.subList(0, count));
}
private static class QuestionTypeConfig {
final QuestionTypeEnum questionType;
final int count;
QuestionTypeConfig(QuestionTypeEnum questionType, int count) {
this.questionType = questionType;
this.count = count;
}
}
}

124
src/main/java/com/project/classicpaper/domain/service/impl/RegenerateQuestionDomainServiceImpl.java

@ -0,0 +1,124 @@
package com.project.classicpaper.domain.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.project.base.domain.exception.BusinessErrorException;
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.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.information.domain.entity.KnowledgePointEntity;
import com.project.information.domain.service.KnowledgePointBaseService;
import com.project.question.domain.entity.QuestionEntity;
import com.project.question.domain.entity.QuestionKpRelEntity;
import com.project.question.domain.service.QuestionBaseService;
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.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@Service
@Slf4j
public class RegenerateQuestionDomainServiceImpl implements RegenerateQuestionDomainService {
@Autowired
private ClassicPaperBaseService classicPaperBaseService;
@Autowired
private ClassicPaperQuestionBaseService classicPaperQuestionBaseService;
@Autowired
private QuestionBaseService questionBaseService;
@Autowired
private QuestionKpRelBaseService questionKpRelBaseService;
@Autowired
private KnowledgePointBaseService knowledgePointBaseService;
@Autowired
private ClassicPaperQuestionGenerator classicPaperQuestionGenerator;
@Override
@Transactional(rollbackFor = Exception.class)
public Result<ClassicPaperQuestionDTO> regenerate(Long paperId, Long questionId) throws Exception {
// 1. 校验套卷存在且为草稿状态
ClassicPaperEntity paper = classicPaperBaseService.getById(paperId);
if (paper == null) {
throw new BusinessErrorException("套卷不存在");
}
if (!ClassicPaperStatusEnum.DRAFT.getValue().equals(paper.getStatus())) {
throw new BusinessErrorException("只有草稿状态的套卷才能重抽题目");
}
// 2. 校验题目属于该套卷
ClassicPaperQuestionEntity paperQuestion = classicPaperQuestionBaseService.getOne(
new LambdaQueryWrapper<ClassicPaperQuestionEntity>()
.eq(ClassicPaperQuestionEntity::getPaperId, paperId)
.eq(ClassicPaperQuestionEntity::getQuestionId, questionId)
);
if (paperQuestion == null) {
throw new BusinessErrorException("该题目不属于此套卷");
}
// 3. 查询原题目信息
QuestionEntity originalQuestion = questionBaseService.getById(questionId);
if (originalQuestion == null) {
throw new BusinessErrorException("原题目不存在");
}
// 4. 查询原题目的知识点关联
List<QuestionKpRelEntity> kpRels = questionKpRelBaseService.list(
new LambdaQueryWrapper<QuestionKpRelEntity>()
.eq(QuestionKpRelEntity::getQuestionId, questionId)
);
if (kpRels.isEmpty()) {
throw new BusinessErrorException("原题目没有关联知识点,无法重抽");
}
List<Long> kpIds = kpRels.stream()
.map(QuestionKpRelEntity::getKpId).collect(Collectors.toList());
// 5. 查询知识点详情
List<KnowledgePointEntity> knowledgePoints = knowledgePointBaseService.listByIds(kpIds);
if (knowledgePoints.isEmpty()) {
throw new BusinessErrorException("关联知识点数据不存在");
}
// 6. 调用生成器生成新题
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);
// 7. 更新关联关系,指向新题
paperQuestion.setQuestionId(newQuestion.getId());
classicPaperQuestionBaseService.updateById(paperQuestion);
log.info(">>> [经典套题-重抽] 套卷{} 题目{} -> 新题目{}", paperId, questionId, newQuestion.getId());
// 8. 构建返回
ClassicPaperQuestionDTO dto = new ClassicPaperQuestionDTO();
dto.setId(paperQuestion.getId());
dto.setPaperId(paperId);
dto.setQuestionId(newQuestion.getId());
dto.setSortOrder(paperQuestion.getSortOrder());
dto.setQuestionType(paperQuestion.getQuestionType());
if (newQuestion.getQuestionDetail() != null) {
dto.setQuestionDetail(newQuestion.getQuestionDetail());
}
return Result.success(dto);
}
}

9
src/main/java/com/project/classicpaper/mapper/ClassicPaperMapper.java

@ -0,0 +1,9 @@
package com.project.classicpaper.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.project.classicpaper.domain.entity.ClassicPaperEntity;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface ClassicPaperMapper extends BaseMapper<ClassicPaperEntity> {
}

9
src/main/java/com/project/classicpaper/mapper/ClassicPaperQuestionMapper.java

@ -0,0 +1,9 @@
package com.project.classicpaper.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.project.classicpaper.domain.entity.ClassicPaperQuestionEntity;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface ClassicPaperQuestionMapper extends BaseMapper<ClassicPaperQuestionEntity> {
}
Loading…
Cancel
Save