16 changed files with 622 additions and 134 deletions
@ -0,0 +1,15 @@ |
|||||
|
package com.project.classicpaper.application; |
||||
|
|
||||
|
import com.project.base.domain.result.Result; |
||||
|
import com.project.classicpaper.domain.dto.ClassicCategoryDTO; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
public interface ClassicCategoryApplicationService { |
||||
|
|
||||
|
Result<List<ClassicCategoryDTO>> treeList(); |
||||
|
|
||||
|
Result<ClassicCategoryDTO> save(ClassicCategoryDTO dto) throws Exception; |
||||
|
|
||||
|
Result<String> delete(Long id) throws Exception; |
||||
|
} |
||||
@ -0,0 +1,95 @@ |
|||||
|
package com.project.classicpaper.application.impl; |
||||
|
|
||||
|
import cn.hutool.core.util.StrUtil; |
||||
|
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.base.domain.utils.TreeUtils; |
||||
|
import com.project.classicpaper.application.ClassicCategoryApplicationService; |
||||
|
import com.project.classicpaper.domain.dto.ClassicCategoryDTO; |
||||
|
import com.project.classicpaper.domain.entity.ClassicCategoryEntity; |
||||
|
import com.project.classicpaper.domain.service.ClassicCategoryBaseService; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
|
||||
|
import java.util.List; |
||||
|
import java.util.Objects; |
||||
|
import java.util.stream.Collectors; |
||||
|
|
||||
|
@Service |
||||
|
public class ClassicCategoryApplicationServiceImpl implements ClassicCategoryApplicationService { |
||||
|
|
||||
|
@Autowired |
||||
|
private ClassicCategoryBaseService classicCategoryBaseService; |
||||
|
|
||||
|
@Override |
||||
|
public Result<List<ClassicCategoryDTO>> treeList() { |
||||
|
List<ClassicCategoryDTO> list = classicCategoryBaseService.list( |
||||
|
new LambdaQueryWrapper<ClassicCategoryEntity>().orderByAsc(ClassicCategoryEntity::getSort) |
||||
|
).stream().map(entity -> entity.toDTO(ClassicCategoryDTO::new)) |
||||
|
.collect(Collectors.toList()); |
||||
|
return Result.success(TreeUtils.buildLongTree(list, |
||||
|
ClassicCategoryDTO::getId, |
||||
|
ClassicCategoryDTO::getParentId, |
||||
|
ClassicCategoryDTO::setChildrenList)); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Result<ClassicCategoryDTO> save(ClassicCategoryDTO dto) throws Exception { |
||||
|
if (StrUtil.isBlank(dto.getName())) { |
||||
|
throw new BusinessErrorException("分类名称不能为空"); |
||||
|
} |
||||
|
if (StrUtil.length(dto.getName()) > 50) { |
||||
|
throw new BusinessErrorException("分类名称过长"); |
||||
|
} |
||||
|
// 新增时校验层级
|
||||
|
if (dto.getId() == null) { |
||||
|
if (dto.getLevel() == null || (dto.getLevel() != 1 && dto.getLevel() != 2)) { |
||||
|
throw new BusinessErrorException("层级参数错误"); |
||||
|
} |
||||
|
if (dto.getLevel() == 2) { |
||||
|
if (dto.getParentId() == null || dto.getParentId() == 0) { |
||||
|
throw new BusinessErrorException("二级分类必须选择父级"); |
||||
|
} |
||||
|
ClassicCategoryEntity parent = classicCategoryBaseService.getById(dto.getParentId()); |
||||
|
if (parent == null || !Objects.equals(parent.getLevel(), 1)) { |
||||
|
throw new BusinessErrorException("父级分类不存在或不是一级分类"); |
||||
|
} |
||||
|
} else { |
||||
|
dto.setParentId(0L); |
||||
|
} |
||||
|
} |
||||
|
// 名称去重(同父级下不可重名)
|
||||
|
LambdaQueryWrapper<ClassicCategoryEntity> duplicateQuery = new LambdaQueryWrapper<ClassicCategoryEntity>() |
||||
|
.eq(ClassicCategoryEntity::getName, dto.getName()) |
||||
|
.eq(ClassicCategoryEntity::getParentId, dto.getParentId()); |
||||
|
if (dto.getId() != null) { |
||||
|
duplicateQuery.ne(ClassicCategoryEntity::getId, dto.getId()); |
||||
|
} |
||||
|
if (classicCategoryBaseService.count(duplicateQuery) > 0) { |
||||
|
throw new BusinessErrorException("同级下已存在同名分类"); |
||||
|
} |
||||
|
|
||||
|
ClassicCategoryEntity entity = dto.toEntity(ClassicCategoryEntity::new); |
||||
|
classicCategoryBaseService.saveOrUpdate(entity); |
||||
|
return Result.success(entity.toDTO(ClassicCategoryDTO::new)); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Result<String> delete(Long id) throws Exception { |
||||
|
ClassicCategoryEntity category = classicCategoryBaseService.getById(id); |
||||
|
if (category == null) { |
||||
|
throw new BusinessErrorException("分类不存在"); |
||||
|
} |
||||
|
// 如果是一级分类,校验是否有子分类
|
||||
|
if (Objects.equals(category.getLevel(), 1)) { |
||||
|
long childCount = classicCategoryBaseService.count( |
||||
|
new LambdaQueryWrapper<ClassicCategoryEntity>().eq(ClassicCategoryEntity::getParentId, id)); |
||||
|
if (childCount > 0) { |
||||
|
throw new BusinessErrorException("该分类下存在子分类,无法删除"); |
||||
|
} |
||||
|
} |
||||
|
classicCategoryBaseService.removeById(id); |
||||
|
return Result.success("删除成功"); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,40 @@ |
|||||
|
package com.project.classicpaper.controller; |
||||
|
|
||||
|
import com.project.base.domain.result.Result; |
||||
|
import com.project.classicpaper.application.ClassicCategoryApplicationService; |
||||
|
import com.project.classicpaper.domain.dto.ClassicCategoryDTO; |
||||
|
import com.project.operation.annotation.OperationLog; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.web.bind.annotation.GetMapping; |
||||
|
import org.springframework.web.bind.annotation.PostMapping; |
||||
|
import org.springframework.web.bind.annotation.RequestMapping; |
||||
|
import org.springframework.web.bind.annotation.RestController; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
@RestController |
||||
|
@Slf4j |
||||
|
@RequestMapping("/api/admin/classicCategory") |
||||
|
public class ClassicCategoryController { |
||||
|
|
||||
|
@Autowired |
||||
|
private ClassicCategoryApplicationService classicCategoryApplicationService; |
||||
|
|
||||
|
@GetMapping("/treeList") |
||||
|
public Result<List<ClassicCategoryDTO>> treeList() { |
||||
|
return classicCategoryApplicationService.treeList(); |
||||
|
} |
||||
|
|
||||
|
@PostMapping("/save") |
||||
|
@OperationLog(module = "经典分类") |
||||
|
public Result<ClassicCategoryDTO> save(ClassicCategoryDTO dto) throws Exception { |
||||
|
return classicCategoryApplicationService.save(dto); |
||||
|
} |
||||
|
|
||||
|
@PostMapping("/delete") |
||||
|
@OperationLog(module = "经典分类") |
||||
|
public Result<String> delete(Long id) throws Exception { |
||||
|
return classicCategoryApplicationService.delete(id); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
package com.project.classicpaper.domain.dto; |
||||
|
|
||||
|
import com.project.base.domain.dto.BaseDTO; |
||||
|
import lombok.Data; |
||||
|
|
||||
|
import java.util.ArrayList; |
||||
|
import java.util.List; |
||||
|
|
||||
|
@Data |
||||
|
public class ClassicCategoryDTO extends BaseDTO { |
||||
|
private Long id; |
||||
|
|
||||
|
private String name; |
||||
|
|
||||
|
private Long parentId; |
||||
|
|
||||
|
private Integer level; |
||||
|
|
||||
|
private Integer sort = 0; |
||||
|
|
||||
|
private List<ClassicCategoryDTO> childrenList = new ArrayList<>(); |
||||
|
} |
||||
@ -0,0 +1,43 @@ |
|||||
|
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_category", |
||||
|
indexes = {@Index(name = "Idx_parent_id", columnList = "parent_id")}) |
||||
|
@Entity |
||||
|
@TableName(value = "evaluator_classic_category") |
||||
|
@EqualsAndHashCode(callSuper = true) |
||||
|
public class ClassicCategoryEntity 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 = "parent_id") |
||||
|
@TableField("parent_id") |
||||
|
@Comment("父级ID,一级分类为0") |
||||
|
private Long parentId; |
||||
|
|
||||
|
@Column(name = "level") |
||||
|
@TableField("level") |
||||
|
@Comment("层级:1-一级分类,2-二级分类") |
||||
|
private Integer level; |
||||
|
|
||||
|
@Column(name = "sort") |
||||
|
@TableField("sort") |
||||
|
@Comment("排序权重") |
||||
|
private Integer sort = 0; |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
package com.project.classicpaper.domain.service; |
||||
|
|
||||
|
import com.baomidou.mybatisplus.extension.service.IService; |
||||
|
import com.project.classicpaper.domain.entity.ClassicCategoryEntity; |
||||
|
|
||||
|
public interface ClassicCategoryBaseService extends IService<ClassicCategoryEntity> { |
||||
|
} |
||||
@ -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.ClassicCategoryEntity; |
||||
|
import com.project.classicpaper.domain.service.ClassicCategoryBaseService; |
||||
|
import com.project.classicpaper.mapper.ClassicCategoryMapper; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
|
||||
|
@Service |
||||
|
public class ClassicCategoryBaseServiceImpl extends ServiceImpl<ClassicCategoryMapper, ClassicCategoryEntity> implements ClassicCategoryBaseService { |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
package com.project.classicpaper.mapper; |
||||
|
|
||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
||||
|
import com.project.classicpaper.domain.entity.ClassicCategoryEntity; |
||||
|
import org.apache.ibatis.annotations.Mapper; |
||||
|
|
||||
|
@Mapper |
||||
|
public interface ClassicCategoryMapper extends BaseMapper<ClassicCategoryEntity> { |
||||
|
} |
||||
@ -0,0 +1,103 @@ |
|||||
|
package com.project.task.domain.service.strategy; |
||||
|
|
||||
|
import cn.hutool.core.collection.CollUtil; |
||||
|
import cn.hutool.core.date.DateUnit; |
||||
|
import cn.hutool.core.date.DateUtil; |
||||
|
import cn.hutool.core.lang.Validator; |
||||
|
import cn.hutool.core.util.StrUtil; |
||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
||||
|
import com.project.base.domain.exception.BusinessErrorException; |
||||
|
import com.project.task.domain.dto.TaskDTO; |
||||
|
import com.project.task.domain.entity.TaskEntity; |
||||
|
import com.project.task.domain.service.TaskBaseService; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
import java.util.Objects; |
||||
|
|
||||
|
/** |
||||
|
* 经典套题模式:校验 + 字段默认值 |
||||
|
* 赋分来自套卷本身,此处不做赋分计算 |
||||
|
*/ |
||||
|
@Component |
||||
|
public class ClassicTaskConfigStrategy implements TaskConfigStrategy { |
||||
|
|
||||
|
@Autowired |
||||
|
private TaskBaseService taskBaseService; |
||||
|
|
||||
|
@Override |
||||
|
public void validateAndConfigure(TaskDTO dto) throws Exception { |
||||
|
validate(dto); |
||||
|
fillDefaults(dto); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 经典模式校验:通用参数 + classicPaperId 必填 |
||||
|
*/ |
||||
|
private void validate(TaskDTO dto) throws Exception { |
||||
|
if (StrUtil.isBlank(dto.getName())) { |
||||
|
throw new BusinessErrorException("考试任务名称不能为空"); |
||||
|
} |
||||
|
if (dto.getId() == null) { |
||||
|
long count = taskBaseService.count(new LambdaQueryWrapper<TaskEntity>().eq(TaskEntity::getName, dto.getName())); |
||||
|
if (count > 0) { |
||||
|
throw new BusinessErrorException("考试任务名称已存在"); |
||||
|
} |
||||
|
} |
||||
|
if (StrUtil.length(dto.getName()) > 10) { |
||||
|
throw new BusinessErrorException("考试任务名称过长"); |
||||
|
} |
||||
|
if (Objects.isNull(dto.getStartTime())) { |
||||
|
throw new BusinessErrorException("开始时间不能为空"); |
||||
|
} |
||||
|
if (Objects.isNull(dto.getEndTime())) { |
||||
|
throw new BusinessErrorException("截止时间不能为空"); |
||||
|
} |
||||
|
if (DateUtil.between(dto.getStartTime(), dto.getEndTime(), DateUnit.DAY) < 0) { |
||||
|
throw new BusinessErrorException("截止时间不能早于开始时间"); |
||||
|
} |
||||
|
// 经典模式不关联产品线,subLineId 非必填
|
||||
|
if (Objects.isNull(dto.getDuration())) { |
||||
|
throw new BusinessErrorException("考试时长不能为空"); |
||||
|
} |
||||
|
if (Objects.isNull(dto.getRemindTimePoint())) { |
||||
|
throw new BusinessErrorException("剩余时间提示点不能为空"); |
||||
|
} |
||||
|
if (dto.getRemindTimePoint() > dto.getDuration()) { |
||||
|
throw new BusinessErrorException("剩余时间提示点不能大于考试时长"); |
||||
|
} |
||||
|
if (Objects.isNull(dto.getPassScore())) { |
||||
|
throw new BusinessErrorException("通过考试分数线不能为空"); |
||||
|
} |
||||
|
if (!Validator.isBetween(dto.getPassScore(), 1, 100)) { |
||||
|
throw new BusinessErrorException("通过考试分数线设置错误"); |
||||
|
} |
||||
|
if (StrUtil.isBlank(dto.getNote())) { |
||||
|
throw new BusinessErrorException("注意事项不能为空"); |
||||
|
} |
||||
|
if (CollUtil.isEmpty(dto.getParticipantUserIdList())) { |
||||
|
throw new BusinessErrorException("参与用户不能为空"); |
||||
|
} |
||||
|
// 经典模式特有校验
|
||||
|
if (Objects.isNull(dto.getClassicPaperId())) { |
||||
|
throw new BusinessErrorException("经典套卷不能为空"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 经典模式下题目数量和分数来自套卷,DTO 中可能为 null,赋默认值 0 避免 NPE |
||||
|
*/ |
||||
|
private void fillDefaults(TaskDTO dto) { |
||||
|
if (dto.getSingleChoiceNum() == null) dto.setSingleChoiceNum(0); |
||||
|
if (dto.getMultipleChoiceNum() == null) dto.setMultipleChoiceNum(0); |
||||
|
if (dto.getTrueFalseNum() == null) dto.setTrueFalseNum(0); |
||||
|
if (dto.getShortAnswerNum() == null) dto.setShortAnswerNum(0); |
||||
|
if (dto.getSingleChoiceScore() == null) dto.setSingleChoiceScore(0.0); |
||||
|
if (dto.getMultipleChoiceScore() == null) dto.setMultipleChoiceScore(0.0); |
||||
|
if (dto.getTrueFalseScore() == null) dto.setTrueFalseScore(0.0); |
||||
|
if (dto.getShortAnswerScore() == null) dto.setShortAnswerScore(0.0); |
||||
|
if (dto.getAccurateGraspNum() == null) dto.setAccurateGraspNum(0); |
||||
|
if (dto.getVagueGraspNum() == null) dto.setVagueGraspNum(0); |
||||
|
if (dto.getTotalScore() == null) dto.setTotalScore(0); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,168 @@ |
|||||
|
package com.project.task.domain.service.strategy; |
||||
|
|
||||
|
import cn.hutool.core.collection.CollUtil; |
||||
|
import cn.hutool.core.date.DateUnit; |
||||
|
import cn.hutool.core.date.DateUtil; |
||||
|
import cn.hutool.core.lang.Validator; |
||||
|
import cn.hutool.core.util.StrUtil; |
||||
|
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.information.domain.dto.KnowledgePointStatisticsDTO; |
||||
|
import com.project.information.domain.service.GetStatisticsKnowledgePointDomainService; |
||||
|
import com.project.task.config.ExamScoreRatioConfig; |
||||
|
import com.project.task.domain.dto.TaskDTO; |
||||
|
import com.project.task.domain.entity.TaskEntity; |
||||
|
import com.project.task.domain.service.TaskBaseService; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
import java.util.Objects; |
||||
|
|
||||
|
/** |
||||
|
* 生成式套题模式:校验 + 赋分 |
||||
|
* 校验逻辑从 SaveOrUpdateTaskDomainServiceImpl.checkDTO() 原样搬出,新增简答题赋分 |
||||
|
*/ |
||||
|
@Component |
||||
|
public class GenerativeTaskConfigStrategy implements TaskConfigStrategy { |
||||
|
|
||||
|
@Autowired |
||||
|
private ExamScoreRatioConfig examScoreRatioConfig; |
||||
|
|
||||
|
@Autowired |
||||
|
private TaskBaseService taskBaseService; |
||||
|
|
||||
|
@Autowired |
||||
|
private GetStatisticsKnowledgePointDomainService getStatisticsKnowledgePointDomainService; |
||||
|
|
||||
|
@Override |
||||
|
public void validateAndConfigure(TaskDTO dto) throws Exception { |
||||
|
validate(dto); |
||||
|
configureScore(dto); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 校验任务参数(原 SaveOrUpdateTaskDomainServiceImpl.checkDTO 逻辑) |
||||
|
*/ |
||||
|
private void validate(TaskDTO dto) throws Exception { |
||||
|
if (StrUtil.isBlank(dto.getName())) { |
||||
|
throw new BusinessErrorException("考试任务名称不能为空"); |
||||
|
} |
||||
|
if (dto.getId() == null) { |
||||
|
long count = taskBaseService.count(new LambdaQueryWrapper<TaskEntity>().eq(TaskEntity::getName, dto.getName())); |
||||
|
if (count > 0) { |
||||
|
throw new BusinessErrorException("考试任务名称已存在"); |
||||
|
} |
||||
|
} |
||||
|
if (StrUtil.length(dto.getName()) > 10) { |
||||
|
throw new BusinessErrorException("考试任务名称过长"); |
||||
|
} |
||||
|
if (Objects.isNull(dto.getStartTime())) { |
||||
|
throw new BusinessErrorException("开始时间不能为空"); |
||||
|
} |
||||
|
if (Objects.isNull(dto.getEndTime())) { |
||||
|
throw new BusinessErrorException("截止时间不能为空"); |
||||
|
} |
||||
|
if (DateUtil.between(dto.getStartTime(), dto.getEndTime(), DateUnit.DAY) < 0) { |
||||
|
throw new BusinessErrorException("截止时间不能早于开始时间"); |
||||
|
} |
||||
|
if (Objects.isNull(dto.getSubLineId())) { |
||||
|
throw new BusinessErrorException("关联产品线不能为空"); |
||||
|
} |
||||
|
if (Objects.isNull(dto.getDuration())) { |
||||
|
throw new BusinessErrorException("考试时长不能为空"); |
||||
|
} |
||||
|
if (Objects.isNull(dto.getRemindTimePoint())) { |
||||
|
throw new BusinessErrorException("剩余时间提示点不能为空"); |
||||
|
} |
||||
|
if (dto.getRemindTimePoint() > dto.getDuration()) { |
||||
|
throw new BusinessErrorException("剩余时间提示点不能大于考试时长"); |
||||
|
} |
||||
|
if (Objects.isNull(dto.getPassScore())) { |
||||
|
throw new BusinessErrorException("通过考试分数线不能为空"); |
||||
|
} |
||||
|
if (!Validator.isBetween(dto.getPassScore(), 1, 100)) { |
||||
|
throw new BusinessErrorException("通过考试分数线设置错误"); |
||||
|
} |
||||
|
if (StrUtil.isBlank(dto.getNote())) { |
||||
|
throw new BusinessErrorException("注意事项不能为空"); |
||||
|
} |
||||
|
if (Objects.isNull(dto.getSingleChoiceNum())) { |
||||
|
throw new BusinessErrorException("单选题数量不能为空"); |
||||
|
} |
||||
|
if (Objects.isNull(dto.getMultipleChoiceNum())) { |
||||
|
throw new BusinessErrorException("多选题数量不能为空"); |
||||
|
} |
||||
|
if (Objects.isNull(dto.getTrueFalseNum())) { |
||||
|
throw new BusinessErrorException("判断题数量不能为空"); |
||||
|
} |
||||
|
if (CollUtil.isEmpty(dto.getParticipantUserIdList())) { |
||||
|
throw new BusinessErrorException("参与用户不能为空"); |
||||
|
} |
||||
|
if (CollUtil.isEmpty(dto.getRelatedDocumentList())) { |
||||
|
throw new BusinessErrorException("关联文档列表不能为空"); |
||||
|
} |
||||
|
if (Objects.isNull(dto.getAccurateGraspNum())) { |
||||
|
throw new BusinessErrorException("精准掌握知识点数量不能为空"); |
||||
|
} |
||||
|
if (Objects.isNull(dto.getVagueGraspNum())) { |
||||
|
throw new BusinessErrorException("模糊掌握知识点数量不能为空"); |
||||
|
} |
||||
|
Result<KnowledgePointStatisticsDTO> statistics = getStatisticsKnowledgePointDomainService.getStatistics(dto.getSubLineId()); |
||||
|
if (!Objects.equals(statistics.getData().getAccurateGraspNum(), dto.getAccurateGraspNum()) || |
||||
|
!Objects.equals(statistics.getData().getVagueGraspNum(), dto.getVagueGraspNum())) { |
||||
|
throw new BusinessErrorException("知识点数有变化,请重新选择关联产品线"); |
||||
|
} |
||||
|
|
||||
|
int totalQuestions = dto.getSingleChoiceNum() + dto.getMultipleChoiceNum() + dto.getTrueFalseNum() |
||||
|
+ (dto.getShortAnswerNum() != null ? dto.getShortAnswerNum() : 0); |
||||
|
int totalKnowledgePoints = dto.getAccurateGraspNum() + dto.getVagueGraspNum(); |
||||
|
if (totalQuestions > totalKnowledgePoints) { |
||||
|
throw new BusinessErrorException("题目总数" + totalQuestions + ",不能超过知识点总数" + totalKnowledgePoints); |
||||
|
} |
||||
|
if (totalQuestions > 100) { |
||||
|
throw new BusinessErrorException("题目总数不能超过 100 道,当前为:" + totalQuestions); |
||||
|
} |
||||
|
if (totalQuestions < 1) { |
||||
|
throw new BusinessErrorException("题目总数不能少于 1 道"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 各题型赋分计算(原逻辑 + 简答题权重) |
||||
|
*/ |
||||
|
private void configureScore(TaskDTO dto) { |
||||
|
int shortAnswerNum = dto.getShortAnswerNum() != null ? dto.getShortAnswerNum() : 0; |
||||
|
|
||||
|
// 1. 计算总权重份额
|
||||
|
int totalUnit = dto.getSingleChoiceNum() * examScoreRatioConfig.getSingle() |
||||
|
+ dto.getMultipleChoiceNum() * examScoreRatioConfig.getMultiple() |
||||
|
+ dto.getTrueFalseNum() * examScoreRatioConfig.getTrueFalse() |
||||
|
+ shortAnswerNum * examScoreRatioConfig.getShortAnswer(); |
||||
|
double base = examScoreRatioConfig.getTotalScore() * 1.0 / totalUnit; |
||||
|
|
||||
|
// 2. 基础赋值:按比例初步分配并保留2位小数
|
||||
|
dto.setSingleChoiceScore(dto.getSingleChoiceNum() > 0 ? Math.round(base * examScoreRatioConfig.getSingle() * 100) / 100.0 : 0.0); |
||||
|
dto.setMultipleChoiceScore(dto.getMultipleChoiceNum() > 0 ? Math.round(base * examScoreRatioConfig.getMultiple() * 100) / 100.0 : 0.0); |
||||
|
dto.setTrueFalseScore(dto.getTrueFalseNum() > 0 ? Math.round(base * examScoreRatioConfig.getTrueFalse() * 100) / 100.0 : 0.0); |
||||
|
dto.setShortAnswerScore(shortAnswerNum > 0 ? Math.round(base * examScoreRatioConfig.getShortAnswer() * 100) / 100.0 : 0.0); |
||||
|
|
||||
|
// 3. 余数平差:从配置中读取总分进行逆向补齐,确保总分 100% 对应配置
|
||||
|
double configTotal = examScoreRatioConfig.getTotalScore(); |
||||
|
|
||||
|
if (dto.getTrueFalseNum() > 0) { |
||||
|
dto.setTrueFalseScore(Math.round((configTotal - dto.getSingleChoiceNum() * dto.getSingleChoiceScore() |
||||
|
- dto.getMultipleChoiceNum() * dto.getMultipleChoiceScore() |
||||
|
- shortAnswerNum * dto.getShortAnswerScore()) / dto.getTrueFalseNum() * 100) / 100.0); |
||||
|
} else if (shortAnswerNum > 0) { |
||||
|
dto.setShortAnswerScore(Math.round((configTotal - dto.getSingleChoiceNum() * dto.getSingleChoiceScore() |
||||
|
- dto.getMultipleChoiceNum() * dto.getMultipleChoiceScore()) / shortAnswerNum * 100) / 100.0); |
||||
|
} else if (dto.getMultipleChoiceNum() > 0) { |
||||
|
dto.setMultipleChoiceScore(Math.round((configTotal - dto.getSingleChoiceNum() * dto.getSingleChoiceScore()) / dto.getMultipleChoiceNum() * 100) / 100.0); |
||||
|
} else if (dto.getSingleChoiceNum() > 0) { |
||||
|
dto.setSingleChoiceScore(Math.round(configTotal / dto.getSingleChoiceNum() * 100) / 100.0); |
||||
|
} |
||||
|
|
||||
|
dto.setTotalScore((int) configTotal); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
package com.project.task.domain.service.strategy; |
||||
|
|
||||
|
import com.project.task.domain.dto.TaskDTO; |
||||
|
|
||||
|
/** |
||||
|
* 任务配置策略接口 |
||||
|
* 不同考试模式(生成式/经典套题)的校验与赋分逻辑分离 |
||||
|
*/ |
||||
|
public interface TaskConfigStrategy { |
||||
|
|
||||
|
/** |
||||
|
* 校验任务参数并完成赋分配置(校验不通过直接抛异常) |
||||
|
* |
||||
|
* @param dto 任务DTO |
||||
|
*/ |
||||
|
void validateAndConfigure(TaskDTO dto) throws Exception; |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
package com.project.task.domain.service.strategy; |
||||
|
|
||||
|
import com.project.task.domain.enums.ExamModeEnum; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
/** |
||||
|
* 任务配置策略工厂,按考试模式返回对应策略 |
||||
|
*/ |
||||
|
@Component |
||||
|
public class TaskConfigStrategyFactory { |
||||
|
|
||||
|
@Autowired |
||||
|
private GenerativeTaskConfigStrategy generativeTaskConfigStrategy; |
||||
|
|
||||
|
@Autowired |
||||
|
private ClassicTaskConfigStrategy classicTaskConfigStrategy; |
||||
|
|
||||
|
public TaskConfigStrategy getStrategy(Integer examMode) { |
||||
|
if (ExamModeEnum.CLASSIC.getValue().equals(examMode)) { |
||||
|
return classicTaskConfigStrategy; |
||||
|
} |
||||
|
return generativeTaskConfigStrategy; |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue