From 4dd27e3dfb08abc88b58ae2429ff63213b5c8957 Mon Sep 17 00:00:00 2001 From: luoweijian <1329394916@qq.com> Date: Tue, 3 Feb 2026 15:56:24 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AD=94=E9=A2=98=E7=9B=B8=E5=85=B3=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 11 + .../base/domain/service/IBaseService.java | 40 +++ .../ding/controller/LoginController.java | 14 +- .../domain/service/AuthDomainService.java | 2 + .../service/impl/AuthDomainServiceImpl.java | 43 +++ .../java/com/project/ding/utils/JwtUtils.java | 2 +- .../com/project/ding/utils/SecurityUtils.java | 24 ++ .../ExamRecordApplicationService.java | 9 + .../ExamRecordApplicationServiceImpl.java | 19 ++ .../exam/controller/ExamRecordController.java | 21 ++ .../exam/domain/dto/ExamRecordDTO.java | 66 +++++ .../domain/entity/ExamRecordEntity.java | 23 +- .../service/AssemblePaperDomainService.java | 11 + .../service/BuildExamRecordDomainService.java | 7 + .../domain/service/ExamRecordBaseService.java | 7 + .../StepSaveExamRecordDomainService.java | 6 + .../service/SummitPaperDomainService.java | 4 + .../impl/AssemblePaperDomainServiceImpl.java | 245 ++++++++++++++++++ .../BuildExamRecordDomainServiceImpl.java | 34 +++ .../impl/ExamRecordBaseServiceImpl.java | 11 + .../StepSaveExamRecordDomainServiceImpl.java | 29 +++ .../project/exam/mapper/ExamRecordMapper.java | 22 ++ .../impl/SaveClusterDomainServiceImpl.java | 1 + .../question/domain/dto/QuestionDTO.java | 1 - .../domain/dto/TaskKnowledgePointDTO.java | 3 + .../domain/entity/QuestionAnswerEntity.java | 54 ---- .../domain/entity/QuestionEntity.java | 9 +- .../entity/TaskKnowledgePointEntity.java | 14 +- .../question/mapper/QuestionKpRelMapper.java | 16 ++ .../project/task/domain/dto/TaskUserDTO.java | 17 ++ .../task/domain/enums/QuestionTypeEnum.java | 6 +- .../impl/DeleteTaskDomainServiceImpl.java | 6 +- 32 files changed, 697 insertions(+), 80 deletions(-) create mode 100644 src/main/java/com/project/base/domain/service/IBaseService.java create mode 100644 src/main/java/com/project/ding/utils/SecurityUtils.java create mode 100644 src/main/java/com/project/exam/application/ExamRecordApplicationService.java create mode 100644 src/main/java/com/project/exam/application/impl/ExamRecordApplicationServiceImpl.java create mode 100644 src/main/java/com/project/exam/controller/ExamRecordController.java create mode 100644 src/main/java/com/project/exam/domain/dto/ExamRecordDTO.java rename src/main/java/com/project/{task => exam}/domain/entity/ExamRecordEntity.java (71%) create mode 100644 src/main/java/com/project/exam/domain/service/AssemblePaperDomainService.java create mode 100644 src/main/java/com/project/exam/domain/service/BuildExamRecordDomainService.java create mode 100644 src/main/java/com/project/exam/domain/service/ExamRecordBaseService.java create mode 100644 src/main/java/com/project/exam/domain/service/StepSaveExamRecordDomainService.java create mode 100644 src/main/java/com/project/exam/domain/service/SummitPaperDomainService.java create mode 100644 src/main/java/com/project/exam/domain/service/impl/AssemblePaperDomainServiceImpl.java create mode 100644 src/main/java/com/project/exam/domain/service/impl/BuildExamRecordDomainServiceImpl.java create mode 100644 src/main/java/com/project/exam/domain/service/impl/ExamRecordBaseServiceImpl.java create mode 100644 src/main/java/com/project/exam/domain/service/impl/StepSaveExamRecordDomainServiceImpl.java create mode 100644 src/main/java/com/project/exam/mapper/ExamRecordMapper.java delete mode 100644 src/main/java/com/project/question/domain/entity/QuestionAnswerEntity.java create mode 100644 src/main/java/com/project/task/domain/dto/TaskUserDTO.java diff --git a/pom.xml b/pom.xml index 37468fc..a5d0793 100644 --- a/pom.xml +++ b/pom.xml @@ -76,6 +76,17 @@ org.springframework.boot spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-data-redis + + + + + org.apache.commons + commons-pool2 + org.springframework.boot spring-boot-starter-aop diff --git a/src/main/java/com/project/base/domain/service/IBaseService.java b/src/main/java/com/project/base/domain/service/IBaseService.java new file mode 100644 index 0000000..930740d --- /dev/null +++ b/src/main/java/com/project/base/domain/service/IBaseService.java @@ -0,0 +1,40 @@ +package com.project.base.domain.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.project.base.domain.dto.BaseDTO; +import com.project.base.domain.entity.BaseEntity; +import java.util.function.Supplier; + +/** + * 增强版通用 Service 接口 + */ +public interface IBaseService extends IService { + + /** + * 通用保存 DTO 的方法:转换 -> 保存 -> 回传带ID的DTO + */ + default D saveDTO(D dto, Supplier entitySupplier, Supplier dtoSupplier) { + if (dto == null) return null; + + // 1. DTO 转 Entity + T entity = dto.toEntity(entitySupplier); + + // 2. 调用 MyBatis-Plus 的 save 方法 + // 由于 IBaseService 继承了 IService,所以这里可以直接调 this.save + this.save(entity); + + // 3. 将持久化后(带ID和自动填充字段)的 Entity 转回 DTO 并返回 + return entity.toDTO(dtoSupplier); + } + + /** + * 【扩展】通用更新 DTO 的方法 + */ + default boolean updateDTO(D dto, Supplier entitySupplier) { + if (dto == null) { + return false; + } + T entity = dto.toEntity(entitySupplier); + return this.updateById(entity); + } +} \ No newline at end of file diff --git a/src/main/java/com/project/ding/controller/LoginController.java b/src/main/java/com/project/ding/controller/LoginController.java index 746bd39..5cc0f17 100644 --- a/src/main/java/com/project/ding/controller/LoginController.java +++ b/src/main/java/com/project/ding/controller/LoginController.java @@ -4,10 +4,7 @@ import com.project.base.domain.result.Result; import com.project.ding.domain.dto.LoginDTO; import com.project.ding.domain.service.AuthDomainService; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/login") @@ -21,7 +18,14 @@ public class LoginController { */ @PostMapping("/ding") public Result login(@RequestParam("authCode") String authCode) { - // 逻辑全部下沉到 Service return Result.success(authDomainService.loginByDing(authCode)); } + + /** + * 钉钉免登 + */ + @GetMapping("/getToken") + public Result getToken(String userId) { + return Result.success(authDomainService.getToken(userId)); + } } \ No newline at end of file diff --git a/src/main/java/com/project/ding/domain/service/AuthDomainService.java b/src/main/java/com/project/ding/domain/service/AuthDomainService.java index 9b6fe60..26d0ef0 100644 --- a/src/main/java/com/project/ding/domain/service/AuthDomainService.java +++ b/src/main/java/com/project/ding/domain/service/AuthDomainService.java @@ -7,4 +7,6 @@ public interface AuthDomainService { * 钉钉免登逻辑 */ LoginDTO loginByDing(String authCode); + + LoginDTO getToken(String userId); } \ No newline at end of file diff --git a/src/main/java/com/project/ding/domain/service/impl/AuthDomainServiceImpl.java b/src/main/java/com/project/ding/domain/service/impl/AuthDomainServiceImpl.java index 11b5634..4ea3d80 100644 --- a/src/main/java/com/project/ding/domain/service/impl/AuthDomainServiceImpl.java +++ b/src/main/java/com/project/ding/domain/service/impl/AuthDomainServiceImpl.java @@ -1,10 +1,14 @@ package com.project.ding.domain.service.impl; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.project.base.domain.exception.BusinessErrorException; import com.project.ding.auth.DingTalkAuthenticationToken; import com.project.ding.domain.dto.LoginDTO; +import com.project.ding.domain.entity.AdminWhiteListEntity; import com.project.ding.domain.entity.UserEntity; import com.project.ding.domain.service.AuthDomainService; +import com.project.ding.mapper.AdminWhiteListMapper; +import com.project.ding.mapper.UserMapper; import com.project.ding.utils.JwtUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -14,7 +18,9 @@ import org.springframework.security.core.AuthenticationException; import org.springframework.stereotype.Service; import org.springframework.security.core.GrantedAuthority; +import java.util.ArrayList; import java.util.List; +import java.util.Objects; @Service @Slf4j @@ -26,6 +32,12 @@ public class AuthDomainServiceImpl implements AuthDomainService { @Autowired private JwtUtils jwtUtils; + @Autowired + private AdminWhiteListMapper adminWhiteListMapper; + + @Autowired + private UserMapper userMapper; + @Override public LoginDTO loginByDing(String authCode) { // 1. 构建未认证的内部 Token @@ -57,4 +69,35 @@ public class AuthDomainServiceImpl implements AuthDomainService { .roles(roles) .build(); } + + @Override + public LoginDTO getToken(String userId) { + // 1. 模拟从数据库获取用户信息(跳过钉钉 API 校验) + UserEntity user = userMapper.selectById(userId); + if (user == null) { + throw new BusinessErrorException("调试失败:本地数据库不存在该用户,请先执行同步"); + } + + // 2. 模拟权限分配逻辑(与 Provider 中的逻辑保持一致) + List roles = new ArrayList<>(); + roles.add("ROLE_CANDIDATE"); // 所有人都是考生 + + // 检查是否在管理员白名单 + AdminWhiteListEntity adminWhiteListEntity = adminWhiteListMapper.selectOne(new LambdaQueryWrapper() + .eq(AdminWhiteListEntity::getUserId, userId)); + if (Objects.nonNull(adminWhiteListEntity)) { + roles.add("ROLE_ADMIN"); + } + + // 3. 生成 JWT + String token = jwtUtils.createToken(user.getId(), roles); + + log.info(">>> [DEBUG] 为用户 {} 手动签发了调试 Token", userId); + + return LoginDTO.builder() + .token(token) + .name(user.getName()) + .roles(roles) + .build(); + } } \ No newline at end of file diff --git a/src/main/java/com/project/ding/utils/JwtUtils.java b/src/main/java/com/project/ding/utils/JwtUtils.java index d1757e3..c4a484d 100644 --- a/src/main/java/com/project/ding/utils/JwtUtils.java +++ b/src/main/java/com/project/ding/utils/JwtUtils.java @@ -21,7 +21,7 @@ public class JwtUtils { .claim("roles", roles) .setExpiration(new Date(System.currentTimeMillis() + EXPIRE)) .signWith(KEY).compact(); - } + } public Claims parseToken(String token) { return Jwts.parserBuilder().setSigningKey(KEY).build().parseClaimsJws(token).getBody(); diff --git a/src/main/java/com/project/ding/utils/SecurityUtils.java b/src/main/java/com/project/ding/utils/SecurityUtils.java new file mode 100644 index 0000000..885a9ee --- /dev/null +++ b/src/main/java/com/project/ding/utils/SecurityUtils.java @@ -0,0 +1,24 @@ +package com.project.ding.utils; + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; + +import java.util.Optional; + +public class SecurityUtils { + /** + * 获取当前登录用户的 ID + */ + public static String getUserId() { + return getAuthentication() + .map(auth -> (String) auth.getPrincipal()) + .orElse(null); + } + + /** + * 获取当前认证信息 + */ + private static Optional getAuthentication() { + return Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication()); + } +} diff --git a/src/main/java/com/project/exam/application/ExamRecordApplicationService.java b/src/main/java/com/project/exam/application/ExamRecordApplicationService.java new file mode 100644 index 0000000..3f1ec35 --- /dev/null +++ b/src/main/java/com/project/exam/application/ExamRecordApplicationService.java @@ -0,0 +1,9 @@ +package com.project.exam.application; + +import com.project.base.domain.result.Result; +import com.project.exam.domain.dto.ExamRecordDTO; + +public interface ExamRecordApplicationService { + + Result assemblePaper(Long taskId) throws Exception; +} diff --git a/src/main/java/com/project/exam/application/impl/ExamRecordApplicationServiceImpl.java b/src/main/java/com/project/exam/application/impl/ExamRecordApplicationServiceImpl.java new file mode 100644 index 0000000..63e77c5 --- /dev/null +++ b/src/main/java/com/project/exam/application/impl/ExamRecordApplicationServiceImpl.java @@ -0,0 +1,19 @@ +package com.project.exam.application.impl; + +import com.project.base.domain.result.Result; +import com.project.ding.utils.SecurityUtils; +import com.project.exam.application.ExamRecordApplicationService; +import com.project.exam.domain.dto.ExamRecordDTO; +import com.project.exam.domain.service.AssemblePaperDomainService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class ExamRecordApplicationServiceImpl implements ExamRecordApplicationService { + @Autowired + private AssemblePaperDomainService assemblePaperDomainService; + @Override + public Result assemblePaper(Long taskId) throws Exception { + return Result.success(assemblePaperDomainService.assemblePaper(taskId , SecurityUtils.getUserId())); + } +} diff --git a/src/main/java/com/project/exam/controller/ExamRecordController.java b/src/main/java/com/project/exam/controller/ExamRecordController.java new file mode 100644 index 0000000..84b1f02 --- /dev/null +++ b/src/main/java/com/project/exam/controller/ExamRecordController.java @@ -0,0 +1,21 @@ +package com.project.exam.controller; + + +import com.project.base.domain.result.Result; +import com.project.exam.application.ExamRecordApplicationService; +import com.project.exam.domain.dto.ExamRecordDTO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/examRecord") +public class ExamRecordController { + @Autowired + private ExamRecordApplicationService examRecordApplicationService; + @PostMapping("/assemblePaper") + public Result assemblePaper(Long taskId) throws Exception{ + return examRecordApplicationService.assemblePaper(taskId); + } +} diff --git a/src/main/java/com/project/exam/domain/dto/ExamRecordDTO.java b/src/main/java/com/project/exam/domain/dto/ExamRecordDTO.java new file mode 100644 index 0000000..5e1ecc5 --- /dev/null +++ b/src/main/java/com/project/exam/domain/dto/ExamRecordDTO.java @@ -0,0 +1,66 @@ +package com.project.exam.domain.dto; + +import cn.hutool.core.collection.CollUtil; +import com.project.base.domain.dto.BaseDTO; +import com.project.exam.domain.entity.ExamRecordEntity; +import com.project.task.domain.dto.TaskDTO; +import lombok.Data; +import org.springframework.beans.BeanUtils; + +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +@Data +public class ExamRecordDTO extends BaseDTO { + private Long id; + private Long taskUserId; + private Double score; + private Boolean pass; + private Date startTime; + private Date submitTime; + private List answerSnapshotDTOList; + + private Long taskId; + private TaskDTO taskDTO; + @Data + public static class QuestionSnapshotDTO { + private Long questionId; + // 题干原文 + private String questionContent; + // 题型 + private Integer type; + // 用户看的选项:{"A":"xxx", "B":"yyy"} + private Map options; + // 正确项 + private String rightAnswer; + // 用户选的 + private String userAnswer; + // 判定正确 + private Boolean isRight; + // AI解析 + private String analysis; + // 该题是否已发起申诉 + private Boolean hasAppealed; + private Double score; + } + + @Override + public T toEntity(Supplier supplier) { + T result = super.toEntity(supplier); + if (result instanceof ExamRecordEntity entity) { + // 处理内部列表快照的转换 + if (CollUtil.isNotEmpty(this.answerSnapshotDTOList)) { + List snapshotList = this.answerSnapshotDTOList.stream().map(dto -> { + ExamRecordEntity.QuestionSnapshot snapshot = new ExamRecordEntity.QuestionSnapshot(); + BeanUtils.copyProperties(dto, snapshot); + return snapshot; + }).toList(); + entity.setAnswerSnapshot(snapshotList); + } + entity.setId(this.getId()); + } + return result; + } +} diff --git a/src/main/java/com/project/task/domain/entity/ExamRecordEntity.java b/src/main/java/com/project/exam/domain/entity/ExamRecordEntity.java similarity index 71% rename from src/main/java/com/project/task/domain/entity/ExamRecordEntity.java rename to src/main/java/com/project/exam/domain/entity/ExamRecordEntity.java index d59f061..91cce56 100644 --- a/src/main/java/com/project/task/domain/entity/ExamRecordEntity.java +++ b/src/main/java/com/project/exam/domain/entity/ExamRecordEntity.java @@ -1,22 +1,26 @@ -package com.project.task.domain.entity; +package com.project.exam.domain.entity; +import cn.hutool.core.collection.CollUtil; 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.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; import com.project.base.domain.entity.BaseEntity; +import com.project.exam.domain.dto.ExamRecordDTO; import jakarta.persistence.*; import lombok.Data; import lombok.EqualsAndHashCode; import org.hibernate.annotations.Comment; import org.hibernate.annotations.JdbcTypeCode; import org.hibernate.type.SqlTypes; +import org.springframework.beans.BeanUtils; import java.util.Date; import java.util.List; import java.util.Map; +import java.util.function.Supplier; @Data @Table(name = "evaluator_exam_record") @@ -79,5 +83,22 @@ public class ExamRecordEntity extends BaseEntity { private Boolean hasAppealed = false; } + @Override + public T toDTO(Supplier supplier) { + T result = super.toDTO(supplier); + if (result instanceof ExamRecordDTO dto) { + // 处理内部快照列表从 Entity 到 DTO 的转换 + if (CollUtil.isNotEmpty(this.answerSnapshot)) { + List dtoList = this.answerSnapshot.stream().map(entitySnapshot -> { + ExamRecordDTO.QuestionSnapshotDTO snapshotDTO = new ExamRecordDTO.QuestionSnapshotDTO(); + BeanUtils.copyProperties(entitySnapshot, snapshotDTO); + return snapshotDTO; + }).toList(); + dto.setAnswerSnapshotDTOList(dtoList); + } + } + return result; + } + } diff --git a/src/main/java/com/project/exam/domain/service/AssemblePaperDomainService.java b/src/main/java/com/project/exam/domain/service/AssemblePaperDomainService.java new file mode 100644 index 0000000..21276e7 --- /dev/null +++ b/src/main/java/com/project/exam/domain/service/AssemblePaperDomainService.java @@ -0,0 +1,11 @@ +package com.project.exam.domain.service; + +import com.project.exam.domain.dto.ExamRecordDTO; + +public interface AssemblePaperDomainService { + + + ExamRecordDTO assemblePaper(Long taskId , String userId) throws Exception; + + +} diff --git a/src/main/java/com/project/exam/domain/service/BuildExamRecordDomainService.java b/src/main/java/com/project/exam/domain/service/BuildExamRecordDomainService.java new file mode 100644 index 0000000..0af62ed --- /dev/null +++ b/src/main/java/com/project/exam/domain/service/BuildExamRecordDomainService.java @@ -0,0 +1,7 @@ +package com.project.exam.domain.service; + +import com.project.exam.domain.dto.ExamRecordDTO; + +public interface BuildExamRecordDomainService { + ExamRecordDTO buildDTO(ExamRecordDTO dto) throws Exception; +} diff --git a/src/main/java/com/project/exam/domain/service/ExamRecordBaseService.java b/src/main/java/com/project/exam/domain/service/ExamRecordBaseService.java new file mode 100644 index 0000000..045b322 --- /dev/null +++ b/src/main/java/com/project/exam/domain/service/ExamRecordBaseService.java @@ -0,0 +1,7 @@ +package com.project.exam.domain.service; + +import com.project.base.domain.service.IBaseService; +import com.project.exam.domain.entity.ExamRecordEntity; + +public interface ExamRecordBaseService extends IBaseService { +} diff --git a/src/main/java/com/project/exam/domain/service/StepSaveExamRecordDomainService.java b/src/main/java/com/project/exam/domain/service/StepSaveExamRecordDomainService.java new file mode 100644 index 0000000..64dd765 --- /dev/null +++ b/src/main/java/com/project/exam/domain/service/StepSaveExamRecordDomainService.java @@ -0,0 +1,6 @@ +package com.project.exam.domain.service; + +public interface StepSaveExamRecordDomainService { + + void stepSave(Long recordId , int index , String answer) throws Exception; +} diff --git a/src/main/java/com/project/exam/domain/service/SummitPaperDomainService.java b/src/main/java/com/project/exam/domain/service/SummitPaperDomainService.java new file mode 100644 index 0000000..fb4bc40 --- /dev/null +++ b/src/main/java/com/project/exam/domain/service/SummitPaperDomainService.java @@ -0,0 +1,4 @@ +package com.project.exam.domain.service; + +public interface SummitPaperDomainService { +} diff --git a/src/main/java/com/project/exam/domain/service/impl/AssemblePaperDomainServiceImpl.java b/src/main/java/com/project/exam/domain/service/impl/AssemblePaperDomainServiceImpl.java new file mode 100644 index 0000000..20fda3e --- /dev/null +++ b/src/main/java/com/project/exam/domain/service/impl/AssemblePaperDomainServiceImpl.java @@ -0,0 +1,245 @@ +package com.project.exam.domain.service.impl; + +import cn.hutool.core.collection.CollUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.project.base.domain.exception.BusinessErrorException; +import com.project.exam.domain.service.AssemblePaperDomainService; +import com.project.exam.domain.service.BuildExamRecordDomainService; +import com.project.question.domain.dto.QuestionDTO; +import com.project.question.domain.dto.TaskKnowledgePointDTO; +import com.project.question.domain.entity.QuestionEntity; +import com.project.question.domain.entity.TaskKnowledgePointEntity; +import com.project.question.domain.enums.QuestionUseStatusEnum; +import com.project.question.domain.service.TaskKnowledgePointBaseService; +import com.project.question.mapper.QuestionKpRelMapper; +import com.project.question.mapper.QuestionMapper; +import com.project.exam.domain.dto.ExamRecordDTO; +import com.project.task.domain.dto.TaskDTO; +import com.project.task.domain.dto.TaskUserDTO; +import com.project.exam.domain.entity.ExamRecordEntity; +import com.project.task.domain.entity.TaskUserEntity; +import com.project.task.domain.enums.QuestionTypeEnum; +import com.project.task.domain.enums.TaskUserStatusEnum; +import com.project.exam.domain.service.ExamRecordBaseService; +import com.project.task.domain.service.TaskBaseService; +import com.project.task.domain.service.TaskUserBaseService; +import io.vavr.control.Try; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; +import java.util.stream.Collectors; + +@Service +@Slf4j +public class AssemblePaperDomainServiceImpl implements AssemblePaperDomainService { + // 簇锁(针对多选题) + private final ConcurrentHashMap clusterLocks = new ConcurrentHashMap<>(); + // 知识点锁(针对单选和判断) + private final ConcurrentHashMap kpLocks = new ConcurrentHashMap<>(); + + @Autowired + private TaskKnowledgePointBaseService taskKnowledgePointBaseService; + + @Autowired + private TaskBaseService taskBaseService; + + @Autowired + private TaskUserBaseService taskUserBaseService; + + @Autowired + private ExamRecordBaseService examRecordBaseService; + + @Autowired + private StringRedisTemplate redisTemplate; + + @Autowired + private BuildExamRecordDomainService buildExamRecordDomainService; + + @Autowired + private QuestionMapper questionMapper; + + @Autowired + private QuestionKpRelMapper questionKpRelMapper; + + private static final String EXAM_START_LOCK = "lock:exam:start:%s:%s"; + + + + @Override + @Transactional(rollbackFor = Exception.class) + public ExamRecordDTO assemblePaper(Long taskId, String userId) throws Exception { + // 校验是否已通过考核 + TaskUserDTO taskUserDTO = Try.of(() -> taskUserBaseService.getOne(new LambdaQueryWrapper() + .eq(TaskUserEntity::getTaskId, taskId).eq(TaskUserEntity::getUserId, userId)) + .toDTO(TaskUserDTO::new)).getOrNull(); + if (Objects.nonNull(taskUserDTO)) { + throw new BusinessErrorException("您无需参与本场考核"); + } + if (TaskUserStatusEnum.Pass.getValue().equals(taskUserDTO.getStatus())) { + throw new BusinessErrorException("您已通过考核,无需重复考试"); + } + // 防抖 + String lockKey = String.format(EXAM_START_LOCK , taskId , userId); + Boolean isLock = redisTemplate.opsForValue().setIfAbsent(lockKey, "processing", 30, TimeUnit.SECONDS); + if (Boolean.FALSE.equals(isLock)) { + log.warn(">>> [考试限制] 用户{} 尝试短时间内重复参加任务{}", userId, taskId); + throw new BusinessErrorException("请勿短时间内重复参加考试"); + } + + // 获取任务配置 + TaskDTO taskDTO = taskBaseService.getById(taskId).toDTO(TaskDTO::new); + int totalQuestionNum = taskDTO.getSingleChoiceNum() + taskDTO.getMultipleChoiceNum() + taskDTO.getTrueFalseNum(); + // 权重乱序种子选取 + List seedKpList = selectWeightedSeedKpList(taskDTO, totalQuestionNum); + // 本张试卷已覆盖的知识点ID集合 + Set coveredKpPool = new HashSet<>(); + // 最终选定题目 + List selectedQuestionList = new ArrayList<>(); + // 分配种子:前N3个多选,中间N1个单选,最后N2个判断 + List mcSeeds = seedKpList.subList(0, taskDTO.getMultipleChoiceNum()); + List scSeeds = seedKpList.subList(taskDTO.getMultipleChoiceNum() , taskDTO.getMultipleChoiceNum() + taskDTO.getSingleChoiceNum()); + List tfSeeds = seedKpList.subList(taskDTO.getMultipleChoiceNum() + taskDTO.getSingleChoiceNum() , totalQuestionNum); + // 选择单选题 + pickWithLock(scSeeds, QuestionTypeEnum.SINGLE_CHOICE , selectedQuestionList, coveredKpPool); + // 选择判断题 + pickWithLock(tfSeeds, QuestionTypeEnum.TRUE_FALSE , selectedQuestionList, coveredKpPool); + + pickMultipleQuestionsWithGreedy(mcSeeds , selectedQuestionList, coveredKpPool); + selectedQuestionList.sort(Comparator.comparing(QuestionDTO::getQuestionType)); + // 校验题数 + if (selectedQuestionList.size() < totalQuestionNum) { + throw new BusinessErrorException("当前题库可用题目不足,请联系管理员补货"); + } + // todo 异步起一个检测库存 + + // 持久化记录 + ExamRecordDTO recordDTO = new ExamRecordDTO(); + recordDTO.setTaskDTO(taskDTO); + recordDTO.setTaskUserId(taskUserDTO.getId()); + recordDTO.setStartTime(new Date()); + recordDTO.setAnswerSnapshotDTOList(buildPaperSnapshot(selectedQuestionList)); + ExamRecordDTO examRecordDTO = examRecordBaseService.saveDTO(recordDTO, ExamRecordEntity::new, ExamRecordDTO::new); + return buildExamRecordDomainService.buildDTO(examRecordDTO); + } + + private List buildPaperSnapshot(List questionDTOList) { + return questionDTOList.stream().map(questionDTO -> { + QuestionDTO.QuestionDetailDTO detail = questionDTO.getQuestionDetailDTO(); + ExamRecordDTO.QuestionSnapshotDTO snapshot = new ExamRecordDTO.QuestionSnapshotDTO(); + BeanUtils.copyProperties(detail , snapshot); + return snapshot; + }).toList(); + } + + /** + * 算权重,带权乱序抽取种子知识点 + * @param dto + * @param totalNum + * @return + */ + private List selectWeightedSeedKpList(TaskDTO dto , int totalNum) { + List kpList = taskKnowledgePointBaseService.lambdaQuery() + .eq(TaskKnowledgePointEntity::getTaskId, dto.getId()).list().stream() + .map(entity -> entity.toDTO(TaskKnowledgePointDTO::new)).toList(); + // 按簇分组 + Map> clusterGroup = kpList.stream() + .collect(Collectors.groupingBy(TaskKnowledgePointDTO::getClusterId)); + // 计算分层常量 C = 当前任务中最大簇的规模 + 1 + int maxClusterSize = clusterGroup.values().stream() + .mapToInt(List::size) + .max() + .orElse(0); + int c = maxClusterSize + 1; + // 簇内洗牌并计算 S + clusterGroup.forEach((clusterId, kpsInCluster) -> { + // 每次组卷都随机洗牌,保证 IDx 动态变化 + Collections.shuffle(kpsInCluster); + int w = kpsInCluster.size(); // 固定簇大小 + for (int i = 0; i < kpsInCluster.size(); i++) { + TaskKnowledgePointDTO kp = kpsInCluster.get(i); + // 序号从 1 开始 + int idx = i + 1; + // 计算动态分值:S = W + (IDx * C) + Integer dynamicS = w + (idx * c); + kp.setWeightScore(dynamicS); + } + }); + // 带权乱序(A-Res算法) + return kpList.stream() + .sorted(Comparator.comparingDouble(kp -> -Math.pow(ThreadLocalRandom.current().nextDouble(), 1.0 / kp.getWeightScore()))) + .limit(totalNum) + .toList(); + } + + /** + * 通用的普通题抽取逻辑(锁 KP 级别) + */ + private void pickWithLock(List seeds, QuestionTypeEnum type, + List result, Set coveredPool) { + for (TaskKnowledgePointDTO seed : seeds) { + // 锁住当前知识点,不阻塞其他知识点的考生 + ReentrantLock lock = kpLocks.computeIfAbsent(seed.getId(), k -> new ReentrantLock()); + lock.lock(); + try { + // 查可用的题 + List qIds = questionKpRelMapper.findAvailableIdsByKpAndType(seed.getId(), type.getValue()); + if (CollUtil.isNotEmpty(qIds)) { + // 更新数据库状态 + QuestionEntity questionEntity = questionMapper.selectById(qIds.stream().findAny().get()); + questionEntity.setUseStatus(QuestionUseStatusEnum.Used.getValue()); + questionMapper.updateById(questionEntity); + QuestionDTO questionDTO = questionEntity.toDTO(QuestionDTO::new); + result.add(questionDTO); + coveredPool.addAll(questionDTO.getKpIdList()); + } + } finally { + lock.unlock(); + } + } + } + + /** + * 抽取多选题:执行贪婪算法 + */ + private void pickMultipleQuestionsWithGreedy(List seedKpList, + List result, Set coveredKpPool) { + for (TaskKnowledgePointDTO seedKp : seedKpList) { + // 锁定当前种子所属的簇,保证抽题互斥 + ReentrantLock lock = clusterLocks.computeIfAbsent(seedKp.getClusterId(), k -> new ReentrantLock()); + lock.lock(); + try { + // 候选题目id + List candidateIds = questionKpRelMapper.findAvailableIdsByKpAndType(seedKp.getId(), QuestionTypeEnum.MULTIPLE_CHOICE.getValue()); + if (CollUtil.isEmpty(candidateIds)){ + continue; + } + + QuestionDTO bestQuestionDTO = questionMapper.selectBatchIds(candidateIds).stream() + .max(Comparator.comparingInt(q -> { + int kp1 = CollUtil.count(q.getKpIdList(), k -> !coveredKpPool.contains(k)); + int kp2 = q.getKpIdList().size() - kp1; + return kp1 - kp2; + })).map(entity -> entity.toDTO(QuestionDTO::new)) + .orElse(null); + // 选题成功 + if (Objects.nonNull(bestQuestionDTO)) { + // c. 占位:立即更新数据库 is_used 状态,实现全系统排他 + bestQuestionDTO.setUseStatus(QuestionUseStatusEnum.Used.getValue()); + questionMapper.updateById(bestQuestionDTO.toEntity(QuestionEntity::new)); + result.add(bestQuestionDTO); + coveredKpPool.addAll(bestQuestionDTO.getKpIdList()); // 更新已覆盖池 + } + } finally { + lock.unlock(); + } + } + } +} diff --git a/src/main/java/com/project/exam/domain/service/impl/BuildExamRecordDomainServiceImpl.java b/src/main/java/com/project/exam/domain/service/impl/BuildExamRecordDomainServiceImpl.java new file mode 100644 index 0000000..302f1e4 --- /dev/null +++ b/src/main/java/com/project/exam/domain/service/impl/BuildExamRecordDomainServiceImpl.java @@ -0,0 +1,34 @@ +package com.project.exam.domain.service.impl; + +import com.project.exam.domain.dto.ExamRecordDTO; +import com.project.exam.domain.service.BuildExamRecordDomainService; +import com.project.task.domain.enums.QuestionTypeEnum; +import com.project.task.domain.service.TaskBaseService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Objects; + + +@Service +public class BuildExamRecordDomainServiceImpl implements BuildExamRecordDomainService { + @Autowired + private TaskBaseService taskBaseService; + @Override + public ExamRecordDTO buildDTO(ExamRecordDTO dto) throws Exception { + // 补全题目分数 + if (Objects.nonNull(dto.getTaskDTO())) { + dto.getAnswerSnapshotDTOList().stream().map(answerSnapshotDTO -> { + if (QuestionTypeEnum.SINGLE_CHOICE.getValue().equals(answerSnapshotDTO.getType())) { + answerSnapshotDTO.setScore(dto.getTaskDTO().getSingleChoiceScore()); + } else if (QuestionTypeEnum.TRUE_FALSE.getValue().equals(answerSnapshotDTO.getType())) { + answerSnapshotDTO.setScore(dto.getTaskDTO().getTrueFalseScore()); + } else { + answerSnapshotDTO.setScore(dto.getTaskDTO().getMultipleChoiceScore()); + } + return answerSnapshotDTO; + }); + } + return dto; + } +} diff --git a/src/main/java/com/project/exam/domain/service/impl/ExamRecordBaseServiceImpl.java b/src/main/java/com/project/exam/domain/service/impl/ExamRecordBaseServiceImpl.java new file mode 100644 index 0000000..61328fa --- /dev/null +++ b/src/main/java/com/project/exam/domain/service/impl/ExamRecordBaseServiceImpl.java @@ -0,0 +1,11 @@ +package com.project.exam.domain.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.project.exam.domain.entity.ExamRecordEntity; +import com.project.exam.domain.service.ExamRecordBaseService; +import com.project.exam.mapper.ExamRecordMapper; +import org.springframework.stereotype.Service; + +@Service +public class ExamRecordBaseServiceImpl extends ServiceImpl implements ExamRecordBaseService { +} diff --git a/src/main/java/com/project/exam/domain/service/impl/StepSaveExamRecordDomainServiceImpl.java b/src/main/java/com/project/exam/domain/service/impl/StepSaveExamRecordDomainServiceImpl.java new file mode 100644 index 0000000..3394c40 --- /dev/null +++ b/src/main/java/com/project/exam/domain/service/impl/StepSaveExamRecordDomainServiceImpl.java @@ -0,0 +1,29 @@ +package com.project.exam.domain.service.impl; + +import cn.hutool.core.util.StrUtil; +import com.project.base.domain.exception.BusinessErrorException; +import com.project.exam.domain.service.StepSaveExamRecordDomainService; +import com.project.exam.mapper.ExamRecordMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class StepSaveExamRecordDomainServiceImpl implements StepSaveExamRecordDomainService { + @Autowired + private ExamRecordMapper examRecordMapper; + + @Override + public void stepSave(Long recordId, int index, String answer) throws Exception { + if (index < 1) { + throw new BusinessErrorException("非法题号"); + } + if (StrUtil.isNotBlank(answer)) { + throw new BusinessErrorException("答案不能为空"); + } + int rows = examRecordMapper.updateAnswerIncremental(recordId, index - 1, answer); + + if (rows == 0) { + throw new BusinessErrorException("保存进度失败,请确认考试是否有效"); + } + } +} diff --git a/src/main/java/com/project/exam/mapper/ExamRecordMapper.java b/src/main/java/com/project/exam/mapper/ExamRecordMapper.java new file mode 100644 index 0000000..ff1da9b --- /dev/null +++ b/src/main/java/com/project/exam/mapper/ExamRecordMapper.java @@ -0,0 +1,22 @@ +package com.project.exam.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.project.exam.domain.entity.ExamRecordEntity; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Update; +import org.springframework.data.repository.query.Param; + +@Mapper +public interface ExamRecordMapper extends BaseMapper { + /** + * 原子更新 JSON 数组中指定索引的用户答案 + * SQL 解释:$[index].userAnswer 对应 JSON 数组中第 index 个对象的字段 + */ + @Update("UPDATE evaluator_exam_record SET " + + "answer_snapshot = JSON_SET(paper_snapshot, '$[${idx}].userAnswer', #{answer}), " + + "update_time = NOW() " + + "WHERE id = #{recordId}") + int updateAnswerIncremental(@Param("recordId") Long recordId, + @Param("idx") int idx, + @Param("answer") String answer); +} diff --git a/src/main/java/com/project/interaction/domain/service/impl/SaveClusterDomainServiceImpl.java b/src/main/java/com/project/interaction/domain/service/impl/SaveClusterDomainServiceImpl.java index 2303d66..0d2f371 100644 --- a/src/main/java/com/project/interaction/domain/service/impl/SaveClusterDomainServiceImpl.java +++ b/src/main/java/com/project/interaction/domain/service/impl/SaveClusterDomainServiceImpl.java @@ -68,6 +68,7 @@ public class SaveClusterDomainServiceImpl implements SaveClusterDomainService { taskKp.setClusterId(clusterEntity.getId()); taskKp.setAtomId(atomKp.getId()); taskKp.setContent(atomKp.getContent()); + taskKp.setClusterSize(clusterEntity.getClusterSize()); return taskKp; }).toList(); // 批量插入 diff --git a/src/main/java/com/project/question/domain/dto/QuestionDTO.java b/src/main/java/com/project/question/domain/dto/QuestionDTO.java index 1d4579a..e661e5a 100644 --- a/src/main/java/com/project/question/domain/dto/QuestionDTO.java +++ b/src/main/java/com/project/question/domain/dto/QuestionDTO.java @@ -53,7 +53,6 @@ public class QuestionDTO extends BaseDTO { entity.setId(this.getId()); } - return result; } } diff --git a/src/main/java/com/project/question/domain/dto/TaskKnowledgePointDTO.java b/src/main/java/com/project/question/domain/dto/TaskKnowledgePointDTO.java index 5b078f7..dc4f144 100644 --- a/src/main/java/com/project/question/domain/dto/TaskKnowledgePointDTO.java +++ b/src/main/java/com/project/question/domain/dto/TaskKnowledgePointDTO.java @@ -10,6 +10,9 @@ public class TaskKnowledgePointDTO extends BaseDTO { private Long clusterId; private Long atomId; private String content; + private Integer clusterSize; + + private Integer weightScore; } diff --git a/src/main/java/com/project/question/domain/entity/QuestionAnswerEntity.java b/src/main/java/com/project/question/domain/entity/QuestionAnswerEntity.java deleted file mode 100644 index 2298ad3..0000000 --- a/src/main/java/com/project/question/domain/entity/QuestionAnswerEntity.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.project.question.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.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import jakarta.persistence.Table; -import lombok.Data; -import lombok.EqualsAndHashCode; -import org.hibernate.annotations.Comment; - -@Data -@Table(name = "evaluator_question_answer" ) -@Entity -@TableName(value = "evaluator_question_answer", autoResultMap = true) -@EqualsAndHashCode(callSuper = true) -public class QuestionAnswerEntity extends BaseEntity { - @TableId(type = IdType.ASSIGN_ID) - @Id - private Long id; - - @Column(name = "exam_record_id") - @TableField("exam_record_id") - @Comment("所属考试记录ID") - private Long examRecordId; - - @Column(name = "user_id") - @TableField("user_id") - @Comment("所属用户ID") - private Long userId; - - @Column(name = "question_id") - @TableField("question_id") - @Comment("问题ID") - private Long questionId; - - @Column(name = "user_answer" , columnDefinition="varchar(255) comment '用户回答答案'") - @TableField("user_answer") - private String userAnswer; - - @Column(name = "is_right") - @TableField("is_right") - @Comment("是否正确") - private Boolean isRight; - - @Column(name = "score") - @TableField("score") - @Comment("得分") - private Double score; -} diff --git a/src/main/java/com/project/question/domain/entity/QuestionEntity.java b/src/main/java/com/project/question/domain/entity/QuestionEntity.java index 2279ebd..7378b44 100644 --- a/src/main/java/com/project/question/domain/entity/QuestionEntity.java +++ b/src/main/java/com/project/question/domain/entity/QuestionEntity.java @@ -8,10 +8,7 @@ import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; import com.project.base.domain.entity.BaseEntity; import com.project.question.domain.dto.QuestionDTO; import com.project.question.domain.enums.QuestionUseStatusEnum; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import jakarta.persistence.Table; +import jakarta.persistence.*; import lombok.Data; import lombok.EqualsAndHashCode; import org.hibernate.annotations.Comment; @@ -26,7 +23,9 @@ import java.util.function.Supplier; @Data -@Table(name = "evaluator_question" ) +@Table(name = "evaluator_question" , indexes = { + @Index(name = "useStatus_questionType", columnList = "use_status, question_type") +}) @Entity @TableName(value = "evaluator_question", autoResultMap = true) @EqualsAndHashCode(callSuper = true) diff --git a/src/main/java/com/project/question/domain/entity/TaskKnowledgePointEntity.java b/src/main/java/com/project/question/domain/entity/TaskKnowledgePointEntity.java index be4d448..5ea4e03 100644 --- a/src/main/java/com/project/question/domain/entity/TaskKnowledgePointEntity.java +++ b/src/main/java/com/project/question/domain/entity/TaskKnowledgePointEntity.java @@ -5,16 +5,14 @@ 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.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import jakarta.persistence.Table; +import jakarta.persistence.*; import lombok.Data; import lombok.EqualsAndHashCode; import org.hibernate.annotations.Comment; @Data -@Table(name = "evaluator_task_knowledge_point" ) +@Table(name = "evaluator_task_knowledge_point" , + indexes = {@Index(name = "Idx_taskId", columnList = "task_id")}) @Entity @TableName(value = "evaluator_task_knowledge_point", autoResultMap = true) @EqualsAndHashCode(callSuper = true) @@ -30,13 +28,11 @@ public class TaskKnowledgePointEntity extends BaseEntity { @Column(name = "cluster_id") @TableField("cluster_id") - @Comment("所属簇ID(关联 evaluator_task_knowledge_cluster)") private Long clusterId; @Column(name = "atom_id") @TableField("atom_id") - @Comment("关联原始原子知识点ID(对应原始资料解析出的ID)") private Long atomId; @@ -44,5 +40,9 @@ public class TaskKnowledgePointEntity extends BaseEntity { @Comment("知识点原文文本") private String content; + @Column(name = "cluster_size") + @TableField("cluster_size") + @Comment("所在簇大小(W):所在簇包含的知识点个数") + private Integer clusterSize; } diff --git a/src/main/java/com/project/question/mapper/QuestionKpRelMapper.java b/src/main/java/com/project/question/mapper/QuestionKpRelMapper.java index 821b9f3..312d4a1 100644 --- a/src/main/java/com/project/question/mapper/QuestionKpRelMapper.java +++ b/src/main/java/com/project/question/mapper/QuestionKpRelMapper.java @@ -3,7 +3,23 @@ package com.project.question.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.project.question.domain.entity.QuestionKpRelEntity; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Select; +import org.springframework.data.repository.query.Param; + +import java.util.List; @Mapper public interface QuestionKpRelMapper extends BaseMapper { + /** + * 根据知识点ID和题型,查询该点下所有【未被使用】的题目ID + * 逻辑:关联题目主表,检查 use_status 是否为 0 (未用) + */ + @Select("SELECT r.question_id " + + "FROM evaluator_question_kp_rel r " + + "INNER JOIN evaluator_question q ON r.question_id = q.id " + + "WHERE r.kp_id = #{kpId} " + + " AND q.question_type = #{type} " + // 增加题型过滤 + " AND q.use_status = 0 " + + " AND q.deleted = 0") + List findAvailableIdsByKpAndType(@Param("kpId") Long kpId, @Param("type") Integer type); } diff --git a/src/main/java/com/project/task/domain/dto/TaskUserDTO.java b/src/main/java/com/project/task/domain/dto/TaskUserDTO.java new file mode 100644 index 0000000..ad301d4 --- /dev/null +++ b/src/main/java/com/project/task/domain/dto/TaskUserDTO.java @@ -0,0 +1,17 @@ +package com.project.task.domain.dto; + +import com.project.base.domain.dto.BaseDTO; +import com.project.task.domain.enums.TaskUserStatusEnum; +import lombok.Data; + +@Data +public class TaskUserDTO extends BaseDTO { + private Long id; + private Long taskId; + private String userId; + private Integer status; + private Long lastRecordId; + private Integer attemptNum = 0; + + +} diff --git a/src/main/java/com/project/task/domain/enums/QuestionTypeEnum.java b/src/main/java/com/project/task/domain/enums/QuestionTypeEnum.java index d179289..9f9bd90 100644 --- a/src/main/java/com/project/task/domain/enums/QuestionTypeEnum.java +++ b/src/main/java/com/project/task/domain/enums/QuestionTypeEnum.java @@ -8,9 +8,9 @@ import lombok.RequiredArgsConstructor; @Getter @RequiredArgsConstructor public enum QuestionTypeEnum implements HasValueEnum { - SINGLE_CHOICE(1, "单选题","single"), // 对应黄色背景 - MULTIPLE_CHOICE(2, "多选题","multiple"), // 对应黄色背景 - TRUE_FALSE(3, "判断题","judgment"); // 对应绿色背景 + SINGLE_CHOICE(1, "单选题","single"), + MULTIPLE_CHOICE(2, "多选题","multiple"), + TRUE_FALSE(3, "判断题","judgment"); private final Integer value; private final String description; diff --git a/src/main/java/com/project/task/domain/service/impl/DeleteTaskDomainServiceImpl.java b/src/main/java/com/project/task/domain/service/impl/DeleteTaskDomainServiceImpl.java index 7d2c41c..c14ca4c 100644 --- a/src/main/java/com/project/task/domain/service/impl/DeleteTaskDomainServiceImpl.java +++ b/src/main/java/com/project/task/domain/service/impl/DeleteTaskDomainServiceImpl.java @@ -25,10 +25,10 @@ public class DeleteTaskDomainServiceImpl implements DeleteTaskDomainService { throw new BusinessErrorException("id不能为空"); } List idList = Arrays.asList(ids.split(",")); - - long count = taskBaseService.count(new LambdaQueryWrapper() + long count = taskBaseService.lambdaQuery() .in(TaskEntity::getId, idList) - .ge(TaskEntity::getStartTime, DateUtil.beginOfDay(new Date()))); + .ge(TaskEntity::getStartTime, DateUtil.beginOfDay(new Date())) + .count(); if (count > 0) { throw new BusinessErrorException("已开考 、 已截止的考试任务不允许删除"); }