feat: 实现题目服务基础架构
- 创建题目服务模块 aioj-backend-question-service - 实现 Question、TestCase、QuestionSubmit 实体类 - 实现 RESTful 风格的 Controller 接口 - 添加完善的 Swagger 注解和校验 - 配置 Nacos 服务发现和 Redis 缓存 - 实现分页查询和条件过滤功能 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
87
.idea/mybatisx/templates.xml
generated
Normal file
87
.idea/mybatisx/templates.xml
generated
Normal file
@@ -0,0 +1,87 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="TemplatesSettings">
|
||||
<option name="templateConfigs">
|
||||
<TemplateContext>
|
||||
<option name="generateConfig">
|
||||
<GenerateConfig>
|
||||
<option name="annotationType" value="MYBATIS_PLUS3" />
|
||||
<option name="basePackage" value="generator" />
|
||||
<option name="basePath" value="src/main/java" />
|
||||
<option name="classNameStrategy" value="camel" />
|
||||
<option name="encoding" value="UTF-8" />
|
||||
<option name="extraClassSuffix" value="" />
|
||||
<option name="ignoreFieldPrefix" value="" />
|
||||
<option name="ignoreFieldSuffix" value="" />
|
||||
<option name="ignoreTablePrefix" value="" />
|
||||
<option name="ignoreTableSuffix" value="" />
|
||||
<option name="moduleName" value="ai-oj" />
|
||||
<option name="modulePath" value="$PROJECT_DIR$" />
|
||||
<option name="moduleUIInfoList">
|
||||
<list>
|
||||
<ModuleInfoGo>
|
||||
<option name="basePath" value="${domain.basePath}" />
|
||||
<option name="configFileName" value="serviceImpl.ftl" />
|
||||
<option name="configName" value="serviceImpl" />
|
||||
<option name="encoding" value="${domain.encoding}" />
|
||||
<option name="fileName" value="${domain.fileName}ServiceImpl" />
|
||||
<option name="fileNameWithSuffix" value="${domain.fileName}ServiceImpl.java" />
|
||||
<option name="modulePath" value="$PROJECT_DIR$" />
|
||||
<option name="packageName" value="${domain.basePackage}.service.impl" />
|
||||
</ModuleInfoGo>
|
||||
<ModuleInfoGo>
|
||||
<option name="basePath" value="${domain.basePath}" />
|
||||
<option name="configFileName" value="mapperInterface.ftl" />
|
||||
<option name="configName" value="mapperInterface" />
|
||||
<option name="encoding" value="${domain.encoding}" />
|
||||
<option name="fileName" value="${domain.fileName}Mapper" />
|
||||
<option name="fileNameWithSuffix" value="${domain.fileName}Mapper.java" />
|
||||
<option name="modulePath" value="$PROJECT_DIR$" />
|
||||
<option name="packageName" value="${domain.basePackage}.mapper" />
|
||||
</ModuleInfoGo>
|
||||
<ModuleInfoGo>
|
||||
<option name="basePath" value="${domain.basePath}" />
|
||||
<option name="configFileName" value="serviceInterface.ftl" />
|
||||
<option name="configName" value="serviceInterface" />
|
||||
<option name="encoding" value="${domain.encoding}" />
|
||||
<option name="fileName" value="${domain.fileName}Service" />
|
||||
<option name="fileNameWithSuffix" value="${domain.fileName}Service.java" />
|
||||
<option name="modulePath" value="$PROJECT_DIR$" />
|
||||
<option name="packageName" value="${domain.basePackage}.service" />
|
||||
</ModuleInfoGo>
|
||||
<ModuleInfoGo>
|
||||
<option name="basePath" value="src/main/resources" />
|
||||
<option name="configFileName" value="mapperXml.ftl" />
|
||||
<option name="configName" value="mapperXml" />
|
||||
<option name="encoding" value="${domain.encoding}" />
|
||||
<option name="fileName" value="${domain.fileName}Mapper" />
|
||||
<option name="fileNameWithSuffix" value="${domain.fileName}Mapper.xml" />
|
||||
<option name="modulePath" value="$PROJECT_DIR$" />
|
||||
<option name="packageName" value="${domain.basePackage}.mapper" />
|
||||
</ModuleInfoGo>
|
||||
</list>
|
||||
</option>
|
||||
<option name="needsComment" value="true" />
|
||||
<option name="needsModel" value="true" />
|
||||
<option name="relativePackage" value="domain" />
|
||||
<option name="superClass" value="" />
|
||||
<option name="tableUIInfoList">
|
||||
<list>
|
||||
<TableUIInfo>
|
||||
<option name="className" value="QuestionSubmit" />
|
||||
<option name="tableName" value="question_submit" />
|
||||
</TableUIInfo>
|
||||
</list>
|
||||
</option>
|
||||
<option name="templatesName" value="mybatis-plus3" />
|
||||
<option name="useActualColumns" value="true" />
|
||||
<option name="useLombokPlugin" value="true" />
|
||||
</GenerateConfig>
|
||||
</option>
|
||||
<option name="moduleName" value="ai-oj" />
|
||||
<option name="projectPath" value="$PROJECT_DIR$" />
|
||||
<option name="templateName" value="mybatis-plus3" />
|
||||
</TemplateContext>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>cn.meowrain.aioj</groupId>
|
||||
<artifactId>ai-oj</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
<artifactId>aioj-backend-question-service</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<description>AIOJ 题目服务</description>
|
||||
<licenses>
|
||||
<license>
|
||||
<name>Apache License, Version 2.0</name>
|
||||
<url>https://www.apache.org/licenses/LICENSE-2.0</url>
|
||||
</license>
|
||||
</licenses>
|
||||
</project>
|
||||
@@ -14,5 +14,74 @@
|
||||
<packaging>jar</packaging>
|
||||
<description>AIOJ 题目服务</description>
|
||||
|
||||
<!-- TODO: 添加依赖 -->
|
||||
|
||||
<dependencies>
|
||||
<!-- ==================== API文档 ==================== -->
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- ==================== 内部模块 ==================== -->
|
||||
<dependency>
|
||||
<groupId>cn.meowrain.aioj</groupId>
|
||||
<artifactId>aioj-backend-common-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.meowrain.aioj</groupId>
|
||||
<artifactId>aioj-backend-common-log</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.meowrain.aioj</groupId>
|
||||
<artifactId>aioj-backend-common-mybatis</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.meowrain.aioj</groupId>
|
||||
<artifactId>aioj-backend-common-security</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- ==================== Web ==================== -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- ==================== Redis & 缓存 ==================== -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.session</groupId>
|
||||
<artifactId>spring-session-data-redis</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-cache</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- ==================== 数据库 ==================== -->
|
||||
<dependency>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- ==================== Spring Cloud 服务发现 ==================== -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- ==================== 测试 ==================== -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,15 @@
|
||||
package cn.meowrain.aioj.backend.question;
|
||||
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
@MapperScan("cn.meowrain.aioj.backend.question.dao.mapper")
|
||||
public class QuestionServiceApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(QuestionServiceApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package cn.meowrain.aioj.backend.question.common.constants;
|
||||
|
||||
/**
|
||||
* 题目服务 Redis Key 常量
|
||||
*/
|
||||
public class RedisKeyConstants {
|
||||
|
||||
/**
|
||||
* 题目缓存 Key 前缀
|
||||
*/
|
||||
public static final String QUESTION_CACHE_KEY_PREFIX = "question:";
|
||||
|
||||
/**
|
||||
* 题目详情缓存 Key
|
||||
*/
|
||||
public static final String QUESTION_DETAIL_KEY = QUESTION_CACHE_KEY_PREFIX + "detail:";
|
||||
|
||||
/**
|
||||
* 题目列表缓存 Key
|
||||
*/
|
||||
public static final String QUESTION_LIST_KEY = QUESTION_CACHE_KEY_PREFIX + "list";
|
||||
|
||||
/**
|
||||
* 测试用例缓存 Key 前缀
|
||||
*/
|
||||
public static final String TEST_CASE_CACHE_KEY_PREFIX = "test_case:";
|
||||
|
||||
/**
|
||||
* 题目提交缓存 Key 前缀
|
||||
*/
|
||||
public static final String QUESTION_SUBMIT_CACHE_KEY_PREFIX = "question_submit:";
|
||||
|
||||
private RedisKeyConstants() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package cn.meowrain.aioj.backend.question.common.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 题目服务责任链标识枚举
|
||||
*/
|
||||
@Getter
|
||||
public enum ChainMarkEnums {
|
||||
|
||||
/**
|
||||
* 题目创建参数校验
|
||||
*/
|
||||
QUESTION_CREATE_PARAM_VERIFY_CHAIN("question_create_param_verify_chain", "题目创建参数校验责任链"),
|
||||
|
||||
/**
|
||||
* 题目更新参数校验
|
||||
*/
|
||||
QUESTION_UPDATE_PARAM_VERIFY_CHAIN("question_update_param_verify_chain", "题目更新参数校验责任链"),
|
||||
|
||||
/**
|
||||
* 测试用例创建参数校验
|
||||
*/
|
||||
TEST_CASE_CREATE_PARAM_VERIFY_CHAIN("test_case_create_param_verify_chain", "测试用例创建参数校验责任链"),
|
||||
|
||||
;
|
||||
|
||||
/**
|
||||
* 标识
|
||||
*/
|
||||
private final String mark;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private final String desc;
|
||||
|
||||
ChainMarkEnums(String mark, String desc) {
|
||||
this.mark = mark;
|
||||
this.desc = desc;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package cn.meowrain.aioj.backend.question.common.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 编程语言枚举
|
||||
*/
|
||||
@Getter
|
||||
public enum LanguageEnum {
|
||||
|
||||
/**
|
||||
* Java
|
||||
*/
|
||||
JAVA("java", "Java"),
|
||||
|
||||
/**
|
||||
* C++
|
||||
*/
|
||||
CPP("cpp", "C++"),
|
||||
|
||||
/**
|
||||
* Python
|
||||
*/
|
||||
PYTHON("python", "Python"),
|
||||
|
||||
/**
|
||||
* Go
|
||||
*/
|
||||
GO("go", "Go"),
|
||||
|
||||
/**
|
||||
* JavaScript
|
||||
*/
|
||||
JAVASCRIPT("javascript", "JavaScript"),
|
||||
|
||||
/**
|
||||
* C
|
||||
*/
|
||||
C("c", "C"),
|
||||
|
||||
/**
|
||||
* C#
|
||||
*/
|
||||
CSHARP("csharp", "C#"),
|
||||
|
||||
/**
|
||||
* Rust
|
||||
*/
|
||||
RUST("rust", "Rust"),
|
||||
|
||||
/**
|
||||
* PHP
|
||||
*/
|
||||
PHP("php", "PHP"),
|
||||
|
||||
/**
|
||||
* Swift
|
||||
*/
|
||||
SWIFT("swift", "Swift"),
|
||||
|
||||
/**
|
||||
* Kotlin
|
||||
*/
|
||||
KOTLIN("kotlin", "Kotlin"),
|
||||
|
||||
/**
|
||||
* TypeScript
|
||||
*/
|
||||
TYPESCRIPT("typescript", "TypeScript"),
|
||||
|
||||
/**
|
||||
* Ruby
|
||||
*/
|
||||
RUBY("ruby", "Ruby"),
|
||||
|
||||
/**
|
||||
* Shell
|
||||
*/
|
||||
SHELL("shell", "Shell"),
|
||||
|
||||
;
|
||||
|
||||
/**
|
||||
* 语言值
|
||||
*/
|
||||
private final String value;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private final String desc;
|
||||
|
||||
LanguageEnum(String value, String desc) {
|
||||
this.value = value;
|
||||
this.desc = desc;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package cn.meowrain.aioj.backend.question.common.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 题目难度枚举
|
||||
*/
|
||||
@Getter
|
||||
public enum QuestionDifficultyEnum {
|
||||
|
||||
/**
|
||||
* 简单
|
||||
*/
|
||||
EASY("easy", "简单"),
|
||||
|
||||
/**
|
||||
* 中等
|
||||
*/
|
||||
MEDIUM("medium", "中等"),
|
||||
|
||||
/**
|
||||
* 困难
|
||||
*/
|
||||
HARD("hard", "困难"),
|
||||
|
||||
;
|
||||
|
||||
/**
|
||||
* 难度值
|
||||
*/
|
||||
private final String value;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private final String desc;
|
||||
|
||||
QuestionDifficultyEnum(String value, String desc) {
|
||||
this.value = value;
|
||||
this.desc = desc;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package cn.meowrain.aioj.backend.question.common.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 题目提交状态枚举
|
||||
*/
|
||||
@Getter
|
||||
public enum QuestionSubmitStatusEnum {
|
||||
|
||||
/**
|
||||
* 待判题
|
||||
*/
|
||||
WAITING(0, "待判题"),
|
||||
|
||||
/**
|
||||
* 判题中
|
||||
*/
|
||||
JUDGING(1, "判题中"),
|
||||
|
||||
/**
|
||||
* 成功
|
||||
*/
|
||||
SUCCESS(2, "成功"),
|
||||
|
||||
/**
|
||||
* 失败
|
||||
*/
|
||||
FAILED(3, "失败"),
|
||||
|
||||
;
|
||||
|
||||
/**
|
||||
* 状态值
|
||||
*/
|
||||
private final Integer value;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private final String desc;
|
||||
|
||||
QuestionSubmitStatusEnum(Integer value, String desc) {
|
||||
this.value = value;
|
||||
this.desc = desc;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package cn.meowrain.aioj.backend.question.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 框架配置类
|
||||
*/
|
||||
@Configuration
|
||||
public class FrameworkConfiguration {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package cn.meowrain.aioj.backend.question.config;
|
||||
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.info.Contact;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.ApplicationArguments;
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Swagger 配置
|
||||
*/
|
||||
@Configuration
|
||||
@Slf4j
|
||||
public class SwaggerConfiguration implements ApplicationRunner {
|
||||
|
||||
@Value("${server.port:8080}")
|
||||
private String serverPort;
|
||||
|
||||
@Value("${server.servlet.context-path:}")
|
||||
private String contextPath;
|
||||
|
||||
@Bean
|
||||
public OpenAPI customOpenAPI() {
|
||||
return new OpenAPI()
|
||||
.info(new Info()
|
||||
.title("AIOJ 题目服务 API 文档")
|
||||
.version("2.0")
|
||||
.description("提供题目管理、测试用例管理等功能")
|
||||
.contact(new Contact()
|
||||
.name("AIOJ Team")
|
||||
.email("contact@aioj.com")));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(ApplicationArguments args) throws Exception {
|
||||
log.info("✨API Document: http://127.0.0.1:{}{}/doc.html", serverPort, contextPath);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package cn.meowrain.aioj.backend.question.controller;
|
||||
|
||||
import cn.meowrain.aioj.backend.framework.core.web.Result;
|
||||
import cn.meowrain.aioj.backend.framework.core.web.Results;
|
||||
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.service.QuestionService;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 题目管理控制器 - RESTful API
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/v1/questions")
|
||||
@Tag(name = "题目管理", description = "题目增删改查等接口")
|
||||
public class QuestionController {
|
||||
|
||||
private final QuestionService questionService;
|
||||
|
||||
/**
|
||||
* 创建题目
|
||||
* POST /v1/questions
|
||||
*/
|
||||
@PostMapping
|
||||
@Operation(summary = "创建题目", description = "创建新的编程题目")
|
||||
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);
|
||||
return Results.success(questionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新题目
|
||||
* PUT /v1/questions/{id}
|
||||
*/
|
||||
@PutMapping("/{id}")
|
||||
@Operation(summary = "更新题目", description = "根据ID更新题目信息")
|
||||
public Result<Void> updateQuestion(
|
||||
@Parameter(description = "题目ID", required = true)
|
||||
@PathVariable("id") Long id,
|
||||
@Parameter(description = "题目信息", required = true)
|
||||
@RequestBody @Valid QuestionEditRequestDTO request) {
|
||||
Question question = new Question();
|
||||
BeanUtils.copyProperties(request, question);
|
||||
question.setId(id);
|
||||
questionService.updateQuestion(question);
|
||||
return Results.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除题目
|
||||
* DELETE /v1/questions/{id}
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
@Operation(summary = "删除题目", description = "根据ID删除题目")
|
||||
public Result<Void> deleteQuestion(
|
||||
@Parameter(description = "题目ID", required = true)
|
||||
@PathVariable("id") Long id) {
|
||||
questionService.deleteQuestion(id);
|
||||
return Results.success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取题目详情
|
||||
* GET /v1/questions/{id}
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
@Operation(summary = "获取题目详情", description = "根据ID获取题目详情")
|
||||
public Result<Question> getQuestion(
|
||||
@Parameter(description = "题目ID", required = true)
|
||||
@PathVariable("id") Long id) {
|
||||
Question question = questionService.getQuestionById(id);
|
||||
return Results.success(question);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询题目列表
|
||||
* GET /v1/questions
|
||||
*/
|
||||
@GetMapping
|
||||
@Operation(summary = "分页查询题目列表", description = "支持按标题、难度、标签等条件查询")
|
||||
public Result<Page<Question>> listQuestions(
|
||||
@Parameter(description = "查询条件") QuestionQueryRequestDTO request) {
|
||||
Page<Question> page = questionService.listQuestions(request);
|
||||
return Results.success(page);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package cn.meowrain.aioj.backend.question.controller;
|
||||
|
||||
import cn.meowrain.aioj.backend.framework.core.web.Result;
|
||||
import cn.meowrain.aioj.backend.framework.core.web.Results;
|
||||
import cn.meowrain.aioj.backend.question.dao.entity.QuestionSubmit;
|
||||
import cn.meowrain.aioj.backend.question.dto.req.QuestionSubmitRequestDTO;
|
||||
import cn.meowrain.aioj.backend.question.service.QuestionSubmitService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 题目提交管理控制器 - RESTful API
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/v1/question-submits")
|
||||
@Tag(name = "题目提交管理", description = "题目提交相关接口")
|
||||
public class QuestionSubmitController {
|
||||
|
||||
private final QuestionSubmitService questionSubmitService;
|
||||
|
||||
/**
|
||||
* 提交代码
|
||||
* POST /v1/question-submits
|
||||
*/
|
||||
@PostMapping
|
||||
@Operation(summary = "提交代码", description = "用户提交代码答案")
|
||||
public Result<Long> submitQuestion(
|
||||
@Parameter(description = "提交信息", required = true)
|
||||
@RequestBody @Valid QuestionSubmitRequestDTO request) {
|
||||
QuestionSubmit questionSubmit = new QuestionSubmit();
|
||||
BeanUtils.copyProperties(request, questionSubmit);
|
||||
// 设置初始状态为待判题
|
||||
questionSubmit.setStatus(0);
|
||||
Long submitId = questionSubmitService.createSubmit(questionSubmit);
|
||||
return Results.success(submitId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取提交详情
|
||||
* GET /v1/question-submits/{id}
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
@Operation(summary = "获取提交详情", description = "根据ID获取提交详情")
|
||||
public Result<QuestionSubmit> getSubmit(
|
||||
@Parameter(description = "提交ID", required = true)
|
||||
@PathVariable("id") Long id) {
|
||||
QuestionSubmit submit = questionSubmitService.getSubmitById(id);
|
||||
return Results.success(submit);
|
||||
}
|
||||
|
||||
/**
|
||||
* 内部接口:更新提交状态
|
||||
* PATCH /v1/question-submits/{id}/status
|
||||
*/
|
||||
@PatchMapping("/{id}/status")
|
||||
@Operation(summary = "更新提交状态", description = "判题服务更新提交状态(内部接口)")
|
||||
public Result<Void> updateSubmitStatus(
|
||||
@Parameter(description = "提交ID", required = true)
|
||||
@PathVariable("id") Long id,
|
||||
@Parameter(description = "状态信息", required = true)
|
||||
@RequestBody QuestionSubmit questionSubmit) {
|
||||
questionSubmit.setId(id);
|
||||
questionSubmitService.updateSubmitStatus(questionSubmit);
|
||||
return Results.success();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package cn.meowrain.aioj.backend.question.dao.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 题目实体类
|
||||
*/
|
||||
@Data
|
||||
@TableName(value = "question")
|
||||
@Accessors(chain = true)
|
||||
public class Question implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 题目ID
|
||||
*/
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 题目标题
|
||||
*/
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 题目描述
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 难度等级:easy/medium/hard
|
||||
*/
|
||||
private String difficulty;
|
||||
|
||||
/**
|
||||
* 标签(JSON格式)
|
||||
*/
|
||||
private String tags;
|
||||
|
||||
/**
|
||||
* 题目答案
|
||||
*/
|
||||
private String answer;
|
||||
|
||||
/**
|
||||
* 通过数
|
||||
*/
|
||||
private Integer acceptedCount;
|
||||
|
||||
/**
|
||||
* 提交数
|
||||
*/
|
||||
private Integer submitCount;
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 判题用例
|
||||
*/
|
||||
private String judgeCase;
|
||||
|
||||
/**
|
||||
* 判题配置(JSON格式)
|
||||
*/
|
||||
private String judgeConfig;
|
||||
|
||||
/**
|
||||
* 点赞数
|
||||
*/
|
||||
private Integer thumbCount;
|
||||
|
||||
/**
|
||||
* 收藏数
|
||||
*/
|
||||
private Integer favourCount;
|
||||
|
||||
/**
|
||||
* 创建者ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private Date updateTime;
|
||||
|
||||
/**
|
||||
* 是否删除
|
||||
*/
|
||||
@TableLogic
|
||||
private Integer isDelete;
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package cn.meowrain.aioj.backend.question.dao.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 题目提交实体类
|
||||
*/
|
||||
@Data
|
||||
@TableName(value = "question_submit")
|
||||
@Accessors(chain = true)
|
||||
public class QuestionSubmit implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* id
|
||||
*/
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 编程语言
|
||||
*/
|
||||
private String language;
|
||||
|
||||
/**
|
||||
* 用户代码
|
||||
*/
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 判题信息(json 对象)
|
||||
*/
|
||||
private String judgeInfo;
|
||||
|
||||
/**
|
||||
* 判题状态(0 - 待判题、1 - 判题中、2 - 成功、3 - 失败)
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 题目 id
|
||||
*/
|
||||
private Long questionId;
|
||||
|
||||
/**
|
||||
* 创建用户 id
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private Date updateTime;
|
||||
|
||||
/**
|
||||
* 是否删除
|
||||
*/
|
||||
private Integer isDelete;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package cn.meowrain.aioj.backend.question.dao.mapper;
|
||||
|
||||
import cn.meowrain.aioj.backend.question.dao.entity.Question;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
|
||||
/**
|
||||
* 题目 Mapper
|
||||
*/
|
||||
public interface QuestionMapper extends BaseMapper<Question> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package cn.meowrain.aioj.backend.question.dao.mapper;
|
||||
|
||||
import cn.meowrain.aioj.backend.question.dao.entity.QuestionSubmit;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
|
||||
/**
|
||||
* 题目提交 Mapper
|
||||
*/
|
||||
public interface QuestionSubmitMapper extends BaseMapper<QuestionSubmit> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package cn.meowrain.aioj.backend.question.dto.req;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 题目用例
|
||||
*/
|
||||
@Schema(description = "题目用例")
|
||||
@Data
|
||||
public class JudgeCase {
|
||||
// ---- 输入用例 ------
|
||||
/**
|
||||
* 输入用例
|
||||
*/
|
||||
@Schema(description = "输入用例")
|
||||
private String input;
|
||||
|
||||
/**
|
||||
* 输出用例
|
||||
*/
|
||||
@Schema(description = "输出用例")
|
||||
private String output;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package cn.meowrain.aioj.backend.question.dto.req;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 题目配置
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "题目配置")
|
||||
public class JudgeConfig {
|
||||
|
||||
|
||||
/**
|
||||
* 时间限制(ms)
|
||||
*/
|
||||
@Schema(description = "时间限制(ms)")
|
||||
private Long timeLimit;
|
||||
|
||||
|
||||
/**
|
||||
* 内存限制(KB)
|
||||
*/
|
||||
@Schema(description = "内存限制(KB)")
|
||||
private Long memoryLimit;
|
||||
/**
|
||||
* 堆栈限制(KB)
|
||||
*/
|
||||
@Schema(description = "堆栈限制(KB)")
|
||||
private String stackLimit;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package cn.meowrain.aioj.backend.question.dto.req;
|
||||
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 判题信息
|
||||
*/
|
||||
@Schema(description = "判题信息")
|
||||
@Data
|
||||
public class JudgeInfo {
|
||||
|
||||
/**
|
||||
* 程序耗费内存
|
||||
*/
|
||||
@Schema(description = "程序耗费内存")
|
||||
private int memory;
|
||||
|
||||
/**
|
||||
* 程序执行时间
|
||||
*/
|
||||
@Schema(description = "程序执行时间")
|
||||
private int time;
|
||||
|
||||
/**
|
||||
* 程序执行信息
|
||||
*/
|
||||
@Schema(description = "程序执行信息")
|
||||
private String message;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package cn.meowrain.aioj.backend.question.dto.req;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 创建题目请求 DTO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "创建题目请求")
|
||||
public class QuestionCreateRequestDTO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "题目标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "两数之和")
|
||||
@NotBlank(message = "题目标题不能为空")
|
||||
private String title;
|
||||
|
||||
@Schema(description = "题目描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "给定一个整数数组 nums 和一个整数目标值 target...")
|
||||
@NotBlank(message = "题目描述不能为空")
|
||||
private String content;
|
||||
|
||||
@Schema(description = "难度等级", requiredMode = Schema.RequiredMode.REQUIRED, example = "easy", allowableValues = {"easy", "medium", "hard"})
|
||||
@NotBlank(message = "难度等级不能为空")
|
||||
private String difficulty;
|
||||
|
||||
@Schema(description = "标签列表", example = "[\"数组\", \"哈希表\"]")
|
||||
private List<String> tags;
|
||||
|
||||
@Schema(description = "题目答案", example = "class Solution {\n public int[] twoSum(int[] nums, int target) {\n ...\n }\n}")
|
||||
private String answer;
|
||||
|
||||
@Schema(description = "判题用例(JSON格式)", example = "[{\"input\":\"[2,7,11,15]\\n9\",\"output\":\"[0,1]\"}]")
|
||||
private List<JudgeCase> judgeCase;
|
||||
|
||||
@Schema(description = "判题配置(JSON格式)", example = "{\"timeLimit\":3000,\"memoryLimit\":256}")
|
||||
private JudgeConfig judgeConfig;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package cn.meowrain.aioj.backend.question.dto.req;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 编辑题目请求 DTO(用户使用)
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "编辑题目请求-用户用")
|
||||
public class QuestionEditRequestDTO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "题目标题", example = "两数之和")
|
||||
private String title;
|
||||
|
||||
@Schema(description = "题目描述", example = "给定一个整数数组 nums 和一个整数目标值 target...")
|
||||
private String content;
|
||||
|
||||
@Schema(description = "难度等级", example = "easy", allowableValues = {"easy", "medium", "hard"})
|
||||
private String difficulty;
|
||||
|
||||
@Schema(description = "标签列表", example = "[\"数组\", \"哈希表\"]")
|
||||
private List<String> tags;
|
||||
|
||||
@Schema(description = "题目答案", example = "class Solution {\n public int[] twoSum(int[] nums, int target) {\n ...\n }\n}")
|
||||
private String answer;
|
||||
|
||||
@Schema(description = "判题用例(JSON格式)", example = "[{\"input\":\"[2,7,11,15]\\n9\",\"output\":\"[0,1]\"}]")
|
||||
private List<JudgeCase> judgeCase;
|
||||
|
||||
@Schema(description = "判题配置(JSON格式)", example = "{\"timeLimit\":3000,\"memoryLimit\":256}")
|
||||
private JudgeConfig judgeConfig;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package cn.meowrain.aioj.backend.question.dto.req;
|
||||
|
||||
import cn.meowrain.aioj.backend.question.dao.entity.Question;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 题目查询请求 DTO
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "题目查询请求")
|
||||
public class QuestionQueryRequestDTO extends Page<Question> implements java.io.Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "题目ID", example = "1")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "题目标题(模糊查询)", example = "两数之和")
|
||||
private String title;
|
||||
|
||||
@Schema(description = "题目描述(模糊查询)", example = "给定一个整数数组")
|
||||
private String content;
|
||||
|
||||
@Schema(description = "难度等级", example = "easy", allowableValues = {"easy", "medium", "hard"})
|
||||
private String difficulty;
|
||||
|
||||
@Schema(description = "标签列表", example = "[\"数组\", \"哈希表\"]")
|
||||
private List<String> tags;
|
||||
|
||||
@Schema(description = "题目答案", example = "class Solution {...}")
|
||||
private String answer;
|
||||
|
||||
@Schema(description = "创建者ID", example = "1")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "排序字段", example = "createTime")
|
||||
private String sortField;
|
||||
|
||||
@Schema(description = "排序方向", example = "desc", allowableValues = {"asc", "desc"})
|
||||
private String sortOrder;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package cn.meowrain.aioj.backend.question.dto.req;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 题目提交请求 DTO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "题目提交请求")
|
||||
public class QuestionSubmitRequestDTO {
|
||||
|
||||
@Schema(description = "题目ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "题目ID不能为空")
|
||||
private Long questionId;
|
||||
|
||||
@Schema(description = "编程语言", requiredMode = Schema.RequiredMode.REQUIRED, example = "java", allowableValues = {"java", "cpp", "python", "go", "javascript", "c", "csharp", "rust", "php", "swift", "kotlin", "typescript", "ruby", "shell"})
|
||||
@NotBlank(message = "编程语言不能为空")
|
||||
private String language;
|
||||
|
||||
@Schema(description = "用户代码", requiredMode = Schema.RequiredMode.REQUIRED, example = "class Solution {\n public int[] twoSum(int[] nums, int target) {\n ...\n }\n}")
|
||||
@NotBlank(message = "代码不能为空")
|
||||
private String code;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package cn.meowrain.aioj.backend.question.dto.req;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 更新题目请求 DTO(管理员使用)
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "更新题目请求-管理员用-可以指定更多字段")
|
||||
public class QuestionUpdateRequestDTO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "题目ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "题目ID不能为空")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "题目标题", example = "两数之和")
|
||||
private String title;
|
||||
|
||||
@Schema(description = "题目描述", example = "给定一个整数数组 nums 和一个整数目标值 target...")
|
||||
private String content;
|
||||
|
||||
@Schema(description = "难度等级", example = "easy", allowableValues = {"easy", "medium", "hard"})
|
||||
private String difficulty;
|
||||
|
||||
@Schema(description = "标签列表", example = "[\"数组\", \"哈希表\"]")
|
||||
private List<String> tags;
|
||||
|
||||
@Schema(description = "题目答案", example = "class Solution {\n public int[] twoSum(int[] nums, int target) {\n ...\n }\n}")
|
||||
private String answer;
|
||||
|
||||
|
||||
@Schema(description = "判题用例(JSON格式)", example = "[{\"input\":\"[2,7,11,15]\\n9\",\"output\":\"[0,1]\"}]")
|
||||
private List<JudgeCase> judgeCase;
|
||||
|
||||
@Schema(description = "判题配置(JSON格式)", example = "{\"timeLimit\":3000,\"memoryLimit\":256}")
|
||||
private JudgeConfig judgeConfig;
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package cn.meowrain.aioj.backend.question.dto.resp;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 题目响应 DTO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "题目响应")
|
||||
public class QuestionResponseDTO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package cn.meowrain.aioj.backend.question.dto.resp;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 题目提交响应 DTO
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "题目提交响应")
|
||||
public class QuestionSubmitResponseDTO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* 题目服务模块
|
||||
*
|
||||
* <p>提供编程题目管理、测试用例管理等功能
|
||||
*/
|
||||
package cn.meowrain.aioj.backend.question;
|
||||
@@ -0,0 +1,54 @@
|
||||
package cn.meowrain.aioj.backend.question.service;
|
||||
|
||||
import cn.meowrain.aioj.backend.question.dao.entity.Question;
|
||||
import cn.meowrain.aioj.backend.question.dto.req.QuestionQueryRequestDTO;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
|
||||
/**
|
||||
* 题目服务接口
|
||||
*/
|
||||
public interface QuestionService extends IService<Question> {
|
||||
|
||||
/**
|
||||
* 创建题目
|
||||
* @param question 题目信息
|
||||
* @return 题目ID
|
||||
*/
|
||||
Long createQuestion(Question question);
|
||||
|
||||
/**
|
||||
* 更新题目
|
||||
* @param question 题目信息
|
||||
* @return 是否成功
|
||||
*/
|
||||
Boolean updateQuestion(Question question);
|
||||
|
||||
/**
|
||||
* 删除题目
|
||||
* @param questionId 题目ID
|
||||
* @return 是否成功
|
||||
*/
|
||||
Boolean deleteQuestion(Long questionId);
|
||||
|
||||
/**
|
||||
* 根据ID获取题目详情
|
||||
* @param questionId 题目ID
|
||||
* @return 题目详情
|
||||
*/
|
||||
Question getQuestionById(Long questionId);
|
||||
|
||||
/**
|
||||
* 根据ID获取题目详情(内部接口)
|
||||
* @param questionId 题目ID
|
||||
* @return 题目详情
|
||||
*/
|
||||
Question getQuestionByIdInner(Long questionId);
|
||||
|
||||
/**
|
||||
* 分页查询题目列表
|
||||
* @param request 查询条件
|
||||
* @return 题目分页列表
|
||||
*/
|
||||
Page<Question> listQuestions(QuestionQueryRequestDTO request);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package cn.meowrain.aioj.backend.question.service;
|
||||
|
||||
import cn.meowrain.aioj.backend.question.dao.entity.QuestionSubmit;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
|
||||
/**
|
||||
* 题目提交服务接口
|
||||
*/
|
||||
public interface QuestionSubmitService extends IService<QuestionSubmit> {
|
||||
|
||||
/**
|
||||
* 创建提交记录
|
||||
* @param questionSubmit 提交信息
|
||||
* @return 提交ID
|
||||
*/
|
||||
Long createSubmit(QuestionSubmit questionSubmit);
|
||||
|
||||
/**
|
||||
* 更新提交状态
|
||||
* @param questionSubmit 提交信息
|
||||
* @return 是否成功
|
||||
*/
|
||||
Boolean updateSubmitStatus(QuestionSubmit questionSubmit);
|
||||
|
||||
/**
|
||||
* 根据ID获取提交记录
|
||||
* @param submitId 提交ID
|
||||
* @return 提交记录
|
||||
*/
|
||||
QuestionSubmit getSubmitById(Long submitId);
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package cn.meowrain.aioj.backend.question.service.impl;
|
||||
|
||||
import cn.meowrain.aioj.backend.question.dao.entity.Question;
|
||||
import cn.meowrain.aioj.backend.question.dao.mapper.QuestionMapper;
|
||||
import cn.meowrain.aioj.backend.question.dto.req.QuestionQueryRequestDTO;
|
||||
import cn.meowrain.aioj.backend.question.service.QuestionService;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 题目服务实现
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class QuestionServiceImpl extends ServiceImpl<QuestionMapper, Question> implements QuestionService {
|
||||
|
||||
@Override
|
||||
public Long createQuestion(Question question) {
|
||||
this.save(question);
|
||||
return question.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean updateQuestion(Question question) {
|
||||
return this.updateById(question);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean deleteQuestion(Long questionId) {
|
||||
return this.removeById(questionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Question getQuestionById(Long questionId) {
|
||||
return this.getById(questionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Question getQuestionByIdInner(Long questionId) {
|
||||
return this.getById(questionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<Question> listQuestions(QuestionQueryRequestDTO request) {
|
||||
LambdaQueryWrapper<Question> wrapper = new LambdaQueryWrapper<>();
|
||||
|
||||
// ID 精确查询
|
||||
if (request.getId() != null) {
|
||||
wrapper.eq(Question::getId, request.getId());
|
||||
}
|
||||
|
||||
// 标题模糊查询
|
||||
if (StringUtils.isNotBlank(request.getTitle())) {
|
||||
wrapper.like(Question::getTitle, request.getTitle());
|
||||
}
|
||||
|
||||
// 难度精确查询
|
||||
if (StringUtils.isNotBlank(request.getDifficulty())) {
|
||||
wrapper.eq(Question::getDifficulty, request.getDifficulty());
|
||||
}
|
||||
|
||||
// 创建者 ID 查询
|
||||
if (request.getUserId() != null) {
|
||||
wrapper.eq(Question::getUserId, request.getUserId());
|
||||
}
|
||||
|
||||
// 排序
|
||||
if (StringUtils.isNotBlank(request.getSortField())) {
|
||||
if ("asc".equalsIgnoreCase(request.getSortOrder())) {
|
||||
wrapper.orderByAsc(Question::getCreateTime);
|
||||
} else {
|
||||
wrapper.orderByDesc(Question::getCreateTime);
|
||||
}
|
||||
} else {
|
||||
wrapper.orderByDesc(Question::getCreateTime);
|
||||
}
|
||||
|
||||
// 直接使用 request 作为分页对象
|
||||
return this.page(request, wrapper);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package cn.meowrain.aioj.backend.question.service.impl;
|
||||
|
||||
import cn.meowrain.aioj.backend.question.dao.entity.QuestionSubmit;
|
||||
import cn.meowrain.aioj.backend.question.dao.mapper.QuestionSubmitMapper;
|
||||
import cn.meowrain.aioj.backend.question.service.QuestionSubmitService;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 题目提交服务实现
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class QuestionSubmitServiceImpl extends ServiceImpl<QuestionSubmitMapper, QuestionSubmit> implements QuestionSubmitService {
|
||||
|
||||
@Override
|
||||
public Long createSubmit(QuestionSubmit questionSubmit) {
|
||||
this.save(questionSubmit);
|
||||
return questionSubmit.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean updateSubmitStatus(QuestionSubmit questionSubmit) {
|
||||
return this.updateById(questionSubmit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuestionSubmit getSubmitById(Long submitId) {
|
||||
return this.getById(submitId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
spring:
|
||||
datasource:
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://10.0.0.10/aioj_dev
|
||||
username: root
|
||||
password: root
|
||||
data:
|
||||
redis:
|
||||
host: 10.0.0.10
|
||||
port: 6379
|
||||
password: 123456
|
||||
cloud:
|
||||
nacos:
|
||||
discovery:
|
||||
enabled: true
|
||||
register-enabled: true
|
||||
server-addr: 10.0.0.10:8848
|
||||
username: nacos
|
||||
password: nacos
|
||||
@@ -0,0 +1,11 @@
|
||||
spring:
|
||||
datasource:
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://10.0.0.10/aioj_prod
|
||||
username: root
|
||||
password: root
|
||||
data:
|
||||
redis:
|
||||
host: 10.0.0.10
|
||||
port: 6379
|
||||
password: 123456
|
||||
@@ -0,0 +1,11 @@
|
||||
spring:
|
||||
datasource:
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://10.0.0.10/aioj_test
|
||||
username: root
|
||||
password: 123456
|
||||
data:
|
||||
redis:
|
||||
host: 10.0.0.10
|
||||
port: 6379
|
||||
password: 123456
|
||||
@@ -0,0 +1,49 @@
|
||||
spring:
|
||||
application:
|
||||
name: aioj-question-service
|
||||
profiles:
|
||||
active: @env@
|
||||
server:
|
||||
port: 18083
|
||||
servlet:
|
||||
context-path: /api
|
||||
error:
|
||||
include-stacktrace: never
|
||||
springdoc:
|
||||
api-docs:
|
||||
enabled: true
|
||||
path: /v3/api-docs
|
||||
default-flat-param-object: true
|
||||
swagger-ui:
|
||||
path: /swagger-ui.html
|
||||
tags-sorter: alpha
|
||||
operations-sorter: alpha
|
||||
group-configs:
|
||||
- group: 'default'
|
||||
paths-to-match: '/api/**'
|
||||
packages-to-scan: cn.meowrain.aioj.backend.questionservice.controller
|
||||
knife4j:
|
||||
basic:
|
||||
enable: true
|
||||
setting:
|
||||
language: zh_cn
|
||||
mybatis-plus:
|
||||
configuration:
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
map-underscore-to-camel-case: true
|
||||
mapper-locations: classpath*:/mapper/**/*.xml
|
||||
|
||||
# JWT 配置(必须与 auth-service 保持一致)
|
||||
jwt:
|
||||
enabled: true
|
||||
secret: "12345678901234567890123456789012" # 至少32字节
|
||||
access-expire: 900000 # 15分钟
|
||||
refresh-expire: 604800000 # 7天
|
||||
|
||||
aioj:
|
||||
log:
|
||||
enabled: true
|
||||
max-length: 20000
|
||||
logging:
|
||||
file:
|
||||
path: ./logs/${spring.application.name}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration scan="true" scanPeriod="10 seconds" debug="false">
|
||||
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
|
||||
<springProperty scope="context" name="APP_NAME" source="spring.application.name"/>
|
||||
|
||||
<timestamp key="time-month" datePattern="yyyy-MM"/>
|
||||
<timestamp key="time-month-day" datePattern="yyyy-MM-dd"/>
|
||||
<property name="LOG_FILE_PATH" value="${LOG_PATH:-./logs/${APP_NAME}}"/>
|
||||
|
||||
<property name="FILE_LOG_PATTERN"
|
||||
value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} -[%X{traceId:-}] %msg%n"/>
|
||||
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${LOG_FILE_PATH}/${time-month}/${time-month-day}/info.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<fileNamePattern>${LOG_FILE_PATH}/${time-month}/${time-month-day}/info.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
|
||||
<maxFileSize>100MB</maxFileSize>
|
||||
<maxHistory>31</maxHistory>
|
||||
<totalSizeCap>100GB</totalSizeCap>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>${FILE_LOG_PATTERN}</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||
<level>INFO</level>
|
||||
</filter>
|
||||
</appender>
|
||||
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<file>${LOG_FILE_PATH}/${time-month}/${time-month-day}/error.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<fileNamePattern>${LOG_FILE_PATH}/${time-month}/${time-month-day}/error.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
|
||||
<maxFileSize>100MB</maxFileSize>
|
||||
<maxHistory>31</maxHistory>
|
||||
<totalSizeCap>100GB</totalSizeCap>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<pattern>${FILE_LOG_PATTERN}</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>ERROR</level>
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
</appender>
|
||||
|
||||
<appender name="ASYNC_INFO"
|
||||
class="ch.qos.logback.classic.AsyncAppender">
|
||||
<discardingThreshold>0</discardingThreshold>
|
||||
<queueSize>512</queueSize>
|
||||
<appender-ref ref="INFO_FILE"/>
|
||||
</appender>
|
||||
<appender name="ASYNC_ERROR" class="ch.qos.logback.classic.AsyncAppender">
|
||||
<discardingThreshold>0</discardingThreshold>
|
||||
<queueSize>512</queueSize>
|
||||
<appender-ref ref="ERROR_FILE"/>
|
||||
</appender>
|
||||
|
||||
<springProfile name="dev">
|
||||
<root level="INFO">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
<appender-ref ref="ASYNC_ERROR"/>
|
||||
<appender-ref ref="ASYNC_INFO"/>
|
||||
</root>
|
||||
</springProfile>
|
||||
|
||||
<springProfile name="prod">
|
||||
<root level="INFO">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
<appender-ref ref="ASYNC_ERROR"/>
|
||||
<appender-ref ref="ASYNC_INFO"/>
|
||||
</root>
|
||||
</springProfile>
|
||||
</configuration>
|
||||
Reference in New Issue
Block a user