refactor: use DTOs in QuestionController API responses
- Change getQuestion endpoint to return QuestionResponseDTO instead of Question entity - Change listQuestions endpoint to return Page<QuestionResponseDTO> instead of Page<Question> - Simplify createQuestion endpoint by using createQuestionWithChain method directly - Add chain-related DTOs for question processing pipeline Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import cn.meowrain.aioj.backend.question.dao.entity.Question;
|
||||
import cn.meowrain.aioj.backend.question.dto.req.QuestionCreateRequestDTO;
|
||||
import cn.meowrain.aioj.backend.question.dto.req.QuestionEditRequestDTO;
|
||||
import cn.meowrain.aioj.backend.question.dto.req.QuestionQueryRequestDTO;
|
||||
import cn.meowrain.aioj.backend.question.dto.resp.QuestionResponseDTO;
|
||||
import cn.meowrain.aioj.backend.question.service.QuestionService;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@@ -36,9 +37,7 @@ public class QuestionController {
|
||||
public Result<Long> createQuestion(
|
||||
@Parameter(description = "题目信息", required = true)
|
||||
@RequestBody @Valid QuestionCreateRequestDTO request) {
|
||||
Question question = new Question();
|
||||
BeanUtils.copyProperties(request, question);
|
||||
Long questionId = questionService.createQuestion(question);
|
||||
Long questionId = questionService.createQuestionWithChain(request);
|
||||
return Results.success(questionId);
|
||||
}
|
||||
|
||||
@@ -79,10 +78,10 @@ public class QuestionController {
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
@Operation(summary = "获取题目详情", description = "根据ID获取题目详情")
|
||||
public Result<Question> getQuestion(
|
||||
public Result<QuestionResponseDTO> getQuestion(
|
||||
@Parameter(description = "题目ID", required = true)
|
||||
@PathVariable("id") Long id) {
|
||||
Question question = questionService.getQuestionById(id);
|
||||
QuestionResponseDTO question = questionService.getQuestionById(id);
|
||||
return Results.success(question);
|
||||
}
|
||||
|
||||
@@ -92,9 +91,9 @@ public class QuestionController {
|
||||
*/
|
||||
@GetMapping
|
||||
@Operation(summary = "分页查询题目列表", description = "支持按标题、难度、标签等条件查询")
|
||||
public Result<Page<Question>> listQuestions(
|
||||
public Result<Page<QuestionResponseDTO>> listQuestions(
|
||||
@Parameter(description = "查询条件") QuestionQueryRequestDTO request) {
|
||||
Page<Question> page = questionService.listQuestions(request);
|
||||
Page<QuestionResponseDTO> page = questionService.listQuestions(request);
|
||||
return Results.success(page);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package cn.meowrain.aioj.backend.question.dto.chains;
|
||||
|
||||
import cn.meowrain.aioj.backend.framework.core.designpattern.chains.AbstractChianHandler;
|
||||
import cn.meowrain.aioj.backend.framework.core.errorcode.ErrorCode;
|
||||
import cn.meowrain.aioj.backend.framework.core.exception.ClientException;
|
||||
import cn.meowrain.aioj.backend.question.common.enums.ChainMarkEnums;
|
||||
import cn.meowrain.aioj.backend.question.dto.req.QuestionCreateRequestDTO;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 题目内容校验责任链处理器
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class QuestionContentVerifyChain implements AbstractChianHandler<QuestionCreateRequestDTO> {
|
||||
|
||||
@Override
|
||||
public void handle(QuestionCreateRequestDTO requestParam) {
|
||||
String content = requestParam.getContent();
|
||||
|
||||
// 校验内容不为空
|
||||
if (StringUtils.isBlank(content)) {
|
||||
throw new ClientException("题目内容不能为空", ErrorCode.PARAMS_ERROR);
|
||||
}
|
||||
|
||||
// 校验内容长度(至少20个字符)
|
||||
if (content.length() < 20) {
|
||||
throw new ClientException("题目内容过短,至少需要20个字符", ErrorCode.PARAMS_ERROR);
|
||||
}
|
||||
|
||||
// 校验内容长度(最多10000个字符)
|
||||
if (content.length() > 10000) {
|
||||
throw new ClientException("题目内容过长,最多支持10000个字符", ErrorCode.PARAMS_ERROR);
|
||||
}
|
||||
|
||||
log.debug("题目内容校验通过");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String mark() {
|
||||
return ChainMarkEnums.QUESTION_CREATE_PARAM_VERIFY_CHAIN.getMark();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 20;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package cn.meowrain.aioj.backend.question.dto.chains;
|
||||
|
||||
import cn.meowrain.aioj.backend.framework.core.designpattern.chains.AbstractChianHandler;
|
||||
import cn.meowrain.aioj.backend.framework.core.errorcode.ErrorCode;
|
||||
import cn.meowrain.aioj.backend.framework.core.exception.ClientException;
|
||||
import cn.meowrain.aioj.backend.question.common.enums.ChainMarkEnums;
|
||||
import cn.meowrain.aioj.backend.question.dto.req.QuestionCreateRequestDTO;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 题目难度校验责任链处理器
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class QuestionDifficultyVerifyChain implements AbstractChianHandler<QuestionCreateRequestDTO> {
|
||||
|
||||
/**
|
||||
* 允许的难度等级
|
||||
*/
|
||||
private static final List<String> ALLOWED_DIFFICULTIES = Arrays.asList("easy", "medium", "hard");
|
||||
|
||||
@Override
|
||||
public void handle(QuestionCreateRequestDTO requestParam) {
|
||||
String difficulty = requestParam.getDifficulty();
|
||||
|
||||
// 校验难度不为空
|
||||
if (StringUtils.isBlank(difficulty)) {
|
||||
throw new ClientException("题目难度不能为空", ErrorCode.PARAMS_ERROR);
|
||||
}
|
||||
|
||||
// 校验难度是否为允许的值(不区分大小写)
|
||||
String normalizedDifficulty = difficulty.toLowerCase().trim();
|
||||
if (!ALLOWED_DIFFICULTIES.contains(normalizedDifficulty)) {
|
||||
throw new ClientException(
|
||||
String.format("题目难度必须是以下之一: %s", String.join(", ", ALLOWED_DIFFICULTIES)),
|
||||
ErrorCode.PARAMS_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
log.debug("题目难度校验通过: {}", normalizedDifficulty);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String mark() {
|
||||
return ChainMarkEnums.QUESTION_CREATE_PARAM_VERIFY_CHAIN.getMark();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 30;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package cn.meowrain.aioj.backend.question.dto.chains;
|
||||
|
||||
import cn.meowrain.aioj.backend.framework.core.designpattern.chains.AbstractChianHandler;
|
||||
import cn.meowrain.aioj.backend.framework.core.errorcode.ErrorCode;
|
||||
import cn.meowrain.aioj.backend.framework.core.exception.ClientException;
|
||||
import cn.meowrain.aioj.backend.question.common.enums.ChainMarkEnums;
|
||||
import cn.meowrain.aioj.backend.question.dto.req.JudgeConfig;
|
||||
import cn.meowrain.aioj.backend.question.dto.req.QuestionCreateRequestDTO;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 题目判题配置校验责任链处理器
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class QuestionJudgeConfigVerifyChain implements AbstractChianHandler<QuestionCreateRequestDTO> {
|
||||
|
||||
/**
|
||||
* 默认时间限制(毫秒)
|
||||
*/
|
||||
private static final Long DEFAULT_TIME_LIMIT = 3000L;
|
||||
|
||||
/**
|
||||
* 最大时间限制(毫秒)- 10秒
|
||||
*/
|
||||
private static final Long MAX_TIME_LIMIT = 10000L;
|
||||
|
||||
/**
|
||||
* 最小时间限制(毫秒)
|
||||
*/
|
||||
private static final Long MIN_TIME_LIMIT = 100L;
|
||||
|
||||
/**
|
||||
* 默认内存限制(MB)
|
||||
*/
|
||||
private static final Long DEFAULT_MEMORY_LIMIT = 256L;
|
||||
|
||||
/**
|
||||
* 最大内存限制(MB)- 1GB
|
||||
*/
|
||||
private static final Long MAX_MEMORY_LIMIT = 1024L;
|
||||
|
||||
/**
|
||||
* 最小内存限制(MB)
|
||||
*/
|
||||
private static final Long MIN_MEMORY_LIMIT = 16L;
|
||||
|
||||
@Override
|
||||
public void handle(QuestionCreateRequestDTO requestParam) {
|
||||
JudgeConfig judgeConfig = requestParam.getJudgeConfig();
|
||||
|
||||
// 判题配置可以为空,创建时使用默认值
|
||||
if (judgeConfig == null) {
|
||||
log.debug("判题配置为空,将使用默认值");
|
||||
return;
|
||||
}
|
||||
|
||||
// 校验时间限制
|
||||
Long timeLimit = judgeConfig.getTimeLimit();
|
||||
if (timeLimit != null) {
|
||||
if (timeLimit < MIN_TIME_LIMIT) {
|
||||
throw new ClientException(
|
||||
String.format("时间限制不能小于 %d 毫秒", MIN_TIME_LIMIT),
|
||||
ErrorCode.PARAMS_ERROR
|
||||
);
|
||||
}
|
||||
if (timeLimit > MAX_TIME_LIMIT) {
|
||||
throw new ClientException(
|
||||
String.format("时间限制不能大于 %d 毫秒", MAX_TIME_LIMIT),
|
||||
ErrorCode.PARAMS_ERROR
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 校验内存限制
|
||||
Long memoryLimit = judgeConfig.getMemoryLimit();
|
||||
if (memoryLimit != null) {
|
||||
if (memoryLimit < MIN_MEMORY_LIMIT) {
|
||||
throw new ClientException(
|
||||
String.format("内存限制不能小于 %d MB", MIN_MEMORY_LIMIT),
|
||||
ErrorCode.PARAMS_ERROR
|
||||
);
|
||||
}
|
||||
if (memoryLimit > MAX_MEMORY_LIMIT) {
|
||||
throw new ClientException(
|
||||
String.format("内存限制不能大于 %d MB", MAX_MEMORY_LIMIT),
|
||||
ErrorCode.PARAMS_ERROR
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("判题配置校验通过: timeLimit={}ms, memoryLimit={}MB",
|
||||
timeLimit != null ? timeLimit : DEFAULT_TIME_LIMIT,
|
||||
memoryLimit != null ? memoryLimit : DEFAULT_MEMORY_LIMIT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String mark() {
|
||||
return ChainMarkEnums.QUESTION_CREATE_PARAM_VERIFY_CHAIN.getMark();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 40;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package cn.meowrain.aioj.backend.question.dto.chains;
|
||||
|
||||
import cn.meowrain.aioj.backend.framework.core.designpattern.chains.AbstractChianHandler;
|
||||
import cn.meowrain.aioj.backend.framework.core.errorcode.ErrorCode;
|
||||
import cn.meowrain.aioj.backend.framework.core.exception.ClientException;
|
||||
import cn.meowrain.aioj.backend.question.common.enums.ChainMarkEnums;
|
||||
import cn.meowrain.aioj.backend.question.dto.req.QuestionCreateRequestDTO;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 题目标签校验责任链处理器
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class QuestionTagsVerifyChain implements AbstractChianHandler<QuestionCreateRequestDTO> {
|
||||
|
||||
/**
|
||||
* 最大标签数量
|
||||
*/
|
||||
private static final int MAX_TAGS_COUNT = 10;
|
||||
|
||||
/**
|
||||
* 最小标签数量
|
||||
*/
|
||||
private static final int MIN_TAGS_COUNT = 1;
|
||||
|
||||
/**
|
||||
* 单个标签最大长度
|
||||
*/
|
||||
private static final int MAX_TAG_LENGTH = 20;
|
||||
|
||||
@Override
|
||||
public void handle(QuestionCreateRequestDTO requestParam) {
|
||||
List<String> tags = requestParam.getTags();
|
||||
|
||||
// 标签可以为空,但如果提供了则进行校验
|
||||
if (tags == null || tags.isEmpty()) {
|
||||
log.debug("题目标签为空,跳过校验");
|
||||
return;
|
||||
}
|
||||
|
||||
// 校验标签数量
|
||||
if (tags.size() > MAX_TAGS_COUNT) {
|
||||
throw new ClientException(
|
||||
String.format("标签数量不能超过 %d 个", MAX_TAGS_COUNT),
|
||||
ErrorCode.PARAMS_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
// 校验每个标签
|
||||
for (String tag : tags) {
|
||||
// 校验标签不为空
|
||||
if (StringUtils.isBlank(tag)) {
|
||||
throw new ClientException("标签不能为空", ErrorCode.PARAMS_ERROR);
|
||||
}
|
||||
|
||||
// 校验标签长度
|
||||
if (tag.trim().length() > MAX_TAG_LENGTH) {
|
||||
throw new ClientException(
|
||||
String.format("标签长度不能超过 %d 个字符", MAX_TAG_LENGTH),
|
||||
ErrorCode.PARAMS_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
// 校验标签不包含特殊字符(可以根据需求调整)
|
||||
if (tag.contains(",") || tag.contains(";") || tag.contains("|")) {
|
||||
throw new ClientException(
|
||||
String.format("标签 '%s' 包含非法字符", tag),
|
||||
ErrorCode.PARAMS_ERROR
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("题目标签校验通过,共 {} 个标签", tags.size());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String mark() {
|
||||
return ChainMarkEnums.QUESTION_CREATE_PARAM_VERIFY_CHAIN.getMark();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 50;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package cn.meowrain.aioj.backend.question.dto.chains;
|
||||
|
||||
import cn.meowrain.aioj.backend.framework.core.designpattern.chains.AbstractChianHandler;
|
||||
import cn.meowrain.aioj.backend.framework.core.errorcode.ErrorCode;
|
||||
import cn.meowrain.aioj.backend.framework.core.exception.ClientException;
|
||||
import cn.meowrain.aioj.backend.question.common.enums.ChainMarkEnums;
|
||||
import cn.meowrain.aioj.backend.question.dto.req.QuestionCreateRequestDTO;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 题目标题校验责任链处理器
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class QuestionTitleVerifyChain implements AbstractChianHandler<QuestionCreateRequestDTO> {
|
||||
|
||||
@Override
|
||||
public void handle(QuestionCreateRequestDTO requestParam) {
|
||||
String title = requestParam.getTitle();
|
||||
|
||||
// 校验标题不为空(虽然 @NotBlank 已经处理,但责任链可以提供更详细的业务校验)
|
||||
if (StringUtils.isBlank(title)) {
|
||||
throw new ClientException("题目标题不能为空", ErrorCode.PARAMS_ERROR);
|
||||
}
|
||||
|
||||
// 校验标题长度
|
||||
if (title.length() < 2) {
|
||||
throw new ClientException("题目标题长度不能少于2个字符", ErrorCode.PARAMS_ERROR);
|
||||
}
|
||||
|
||||
if (title.length() > 100) {
|
||||
throw new ClientException("题目标题长度不能超过100个字符", ErrorCode.PARAMS_ERROR);
|
||||
}
|
||||
|
||||
// 可以添加更多业务规则,比如不允许包含特殊字符等
|
||||
// if (title.contains("[deleted]")) {
|
||||
// throw new ClientException("题目标题包含非法字符", ErrorCode.PARAMS_ERROR);
|
||||
// }
|
||||
|
||||
log.debug("题目标题校验通过: {}", title);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String mark() {
|
||||
return ChainMarkEnums.QUESTION_CREATE_PARAM_VERIFY_CHAIN.getMark();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 10;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package cn.meowrain.aioj.backend.question.dto.chains.context;
|
||||
|
||||
import cn.meowrain.aioj.backend.framework.core.designpattern.chains.CommonChainContext;
|
||||
import cn.meowrain.aioj.backend.question.dto.req.QuestionCreateRequestDTO;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 题目创建参数校验责任链上下文
|
||||
*/
|
||||
@Component
|
||||
public class QuestionCreateRequestParamVerifyContext extends CommonChainContext<QuestionCreateRequestDTO> {
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user