|
|
vor 1 Monat | |
|---|---|---|
| gradle | vor 7 Monaten | |
| sql | vor 1 Monat | |
| src | vor 1 Monat | |
| .gitattributes | vor 7 Monaten | |
| .gitignore | vor 7 Monaten | |
| AGENTS.md | vor 1 Monat | |
| README.md | vor 1 Monat | |
| build.gradle.kts | vor 2 Monaten | |
| gradlew | vor 1 Monat | |
| gradlew.bat | vor 7 Monaten | |
| settings.gradle.kts | vor 7 Monaten |
在线评测系统(OJ)后端服务模板,基于 Spring Boot 构建,提供统一的认证鉴权、用户管理与通用响应封装,可直接作为后续功能(题目、评测、比赛等)的开发底座。
| 分类 | 选型 | 版本 |
|---|---|---|
| 语言 | Java | 17 |
| 框架 | Spring Boot | 3.5.13 |
| 构建工具 | Gradle (Wrapper) | 8.14.4 |
| 安全 | Spring Security + JWT | - |
| ORM | MyBatis-Plus | 3.5.15 |
| 数据库 | PostgreSQL + Druid 连接池 | - |
| 接口文档 | springdoc-openapi (Swagger UI) | 2.8.16 |
user / coach / admin),未认证、无权限统一 JSON 返回Response<T> 通用响应体 + 全局异常处理(参数校验、业务异常)CommonsRequestLoggingFilter 记录请求入参serve_template
├── sql/postgersql.sql # 数据库初始化脚本(表结构、索引与触发器)
├── src/main/java/space/anyi
│ ├── serve # 业务工程包
│ │ ├── ServeApplication.java # 启动类
│ │ ├── config # SecurityConfig / WebConfig(CORS/日志) / OpenApiConfig
│ │ ├── controller # AuthController(登录注册) / UserController(用户管理)
│ │ ├── entity # Response(响应体) / PageVo(分页) / auth / user(DTO、VO、实体)
│ │ ├── filter # JwtAuthenticationFilter(JWT 解析过滤器)
│ │ ├── handler # GlobalExceptionHandler + security(JWT 工具、异常入口)
│ │ ├── mapper # MyBatis-Plus Mapper
│ │ └── service # UserService 接口 + impl 实现
│ └── codesandbox # 本地JVM代码沙箱(判题执行)
└── src/main/resources
├── application.yaml # 应用配置
└── mapper/UserMapper.xml # Mapper XML
psql -U <postgres用户> -f sql/postgersql.sql
脚本会创建 oj 数据库、dev schema,以及用户、题目、评测用例、提交记录、分类、比赛等表(含注释与索引)。
application.yaml 中数据库连接通过环境变量注入:
| 环境变量 | 说明 | 默认值 |
|---|---|---|
databaseHost |
数据库地址 | - |
databaseUsername |
数据库用户名 | - |
databasePassword |
数据库密码 | - |
./gradlew bootRun
服务默认运行在 http://localhost:8080。
启动后访问 Swagger UI:
http://localhost:8080/swagger-ui/index.html
auth/login、auth/register及 Swagger 相关路径无需认证,其余接口需在请求头携带Authorization: Bearer <token>。
表均位于 dev schema 下:
user:用户(账号、密码、用户名、角色、头像、启用状态、逻辑删除标记)problem:题目(标题、描述、时间/内存限制、提交与通过次数、公开状态、难度)problem_case:题目样例(展示给用户)problem_test:评测用例(后台判题,可隐藏)submission:提交记录(代码、语言、判题状态、耗时/内存、得分、用例统计)category / problem_category_rel:题目类别及多对多关联contest / contest_problem / contest_registration:比赛、比赛题目、比赛报名各表之间使用逻辑外键(仅记录 ID),便于水平扩展。
created_at 由数据库列默认值 DEFAULT NOW() 在插入时自动填充。updated_at 由 BEFORE UPDATE 触发器自动刷新。触发器函数 set_updated_at() 将每行更新前的 updated_at 置为当前时间,各表对应触发器如下(均在 sql/postgersql.sql 中定义):| 表 | 触发器 |
|---|---|
user |
trg_user_updated_at |
problem |
trg_problem_updated_at |
problem_case |
trg_problem_case_updated_at |
problem_test |
trg_problem_test_updated_at |
submission |
trg_submission_updated_at |
category |
trg_category_updated_at |
problem_category_rel |
trg_problem_category_rel_updated_at |
contest |
trg_contest_updated_at |
contest_problem |
trg_contest_problem_updated_at |
contest_registration |
trg_contest_registration_updated_at |
-- 触发器函数(定义一次,所有表共用)
CREATE OR REPLACE FUNCTION set_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- 每个表单独建触发器,例如:
CREATE TRIGGER trg_user_updated_at
BEFORE UPDATE ON dev.user
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
应用层(MyBatis-Plus 实体)无需维护 created_at / updated_at,插入与更新均由数据库自动处理。
erDiagram
user ||--o{ problem : "创建(created_by)"
user ||--o{ contest : "创建(created_by)"
user ||--o{ submission : "提交(user_id)"
user ||--o{ contest_registration : "报名(user_id)"
problem ||--o{ problem_case : "样例(problem_id)"
problem ||--o{ problem_test : "评测用例(problem_id)"
problem ||--o{ submission : "提交记录(problem_id)"
problem ||--o{ problem_category_rel : "关联(problem_id)"
category ||--o{ problem_category_rel : "关联(category_id)"
contest ||--o{ submission : "包含(contest_id)"
contest ||--o{ contest_problem : "题目(contest_id)"
problem ||--o{ contest_problem : "被纳入(problem_id)"
contest ||--o{ contest_registration : "报名(contest_id)"
基于本地 JVM 进程实现的判题沙箱(src/main/java/space/anyi/codesandbox/),当前仅支持 Java 语言。通过子进程编译并运行提交代码,与宿主服务隔离。
| 类 | 说明 |
|---|---|
CodeSandBox |
判题沙箱接口,定义 process(CodeContext) |
JVMNativeCodeSandBox |
基于本地 JVM 子进程的实现 |
CodeContext |
判题上下文(record):id、code、language、maxTime、maxMemory、testCases |
TestCase |
测试用例(record):id、input、output |
CodeResponse |
判题结果(record):id、status、usedTime、usedMemory、testCaseResponses、message |
TestCaseResponse |
单用例结果(record):id、status、usedTime、usedMemory |
状态值复用 Submission 实体的常量(STATUS_AC / STATUS_WA / STATUS_TLE / STATUS_MLE / STATUS_RE / STATUS_CE / STATUS_SE)。
code/<uuid>/Main.javajavac -encoding utf8 Main.java 编译,编译超时(COMPILE_TIMEOUT_MS,默认 10s)判 SEjava -Xmx<maxMemory>k -Dfile.encoding=utf8 Main:
CompletableFuture + 守护线程池),主线程不阻塞process.waitFor(maxTime, ms) 超时则 destroyForcibly() 并判 TLE,防止死循环耗尽资源finally 中清理临时目录,所有返回路径均保证删除usedTime 取各用例最大值,usedMemory 取最小值(实际内存统计尚未实现,标注为 todo)outputMatches):忽略每行行尾空白(空格 / \t / \r)与末尾空行,行首与中间空白敏感MAX_OUTPUT_LENGTH(1MB 字符),防止恶意输出耗尽内存Semaphore 限制最大并发判题数(MAX_CONCURRENT,默认 10)tryAcquire(5s) 超时,返回 SE「系统繁忙」,不排队阻塞线程maxTime:毫秒,作为 waitFor 的进程存活上限maxMemory:KB(与 submission.memory_used_kb 单位一致),作为 -Xmx 参数传给子 JVMCodeContext 缺省值:maxTime = 1000ms、maxMemory = 256MB(262144 KB)javac / java 路径取 JAVA_HOME,未设置时回退到当前运行时 java.home;需指向 JDK(非 JRE)JudgeController / SubmissionService),沙箱为独立可调用的能力,测试床见 JVMNativeCodeSandBoxTestJAVA_HOME=/path/to/jdk17 ./gradlew test --tests "space.anyi.codesandbox.JVMNativeCodeSandBoxTest"
覆盖:AC/WA/TLE/RE、多行输出与行尾空白归一化、死循环超时、10 并发、自定义内存上限(-Xmx<kb>k 格式)。
| 依赖 | 用途 |
|---|---|
| spring-boot-starter-web | Web MVC |
| spring-boot-starter-security | 认证与鉴权 |
| spring-boot-starter-validation | 参数校验 |
| mybatis-plus-spring-boot3-starter | ORM |
| druid-spring-boot-3-starter | 数据库连接池 |
| postgresql | PostgreSQL 驱动 |
| jjwt | JWT 生成与解析 |
| springdoc-openapi-starter-webmvc-ui | Swagger 接口文档 |
| spring-boot-starter-actuator | 健康检查与监控(端点全量暴露) |