# serve_template 在线评测系统(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 | ## 已实现功能 - **用户认证**:注册、登录,密码 BCrypt 加密,登录后签发 JWT(有效期 24 小时) - **接口鉴权**:基于角色的方法级鉴权(`user` / `coach` / `admin`),未认证、无权限统一 JSON 返回 - **用户管理**:分页查询、详情查询、新增、编辑、批量删除,以及状态 / 头像 / 密码更新 - **统一响应**:`Response` 通用响应体 + 全局异常处理(参数校验、业务异常) - **请求日志**:基于 `CommonsRequestLoggingFilter` 记录请求入参 - **接口文档**:Swagger UI 在线调试 - **本地代码沙箱**:基于本地 JVM 进程的判题执行(当前支持 Java),覆盖编译、逐用例执行、超时控制、异步 IO、输出归一化判定与并发限流 ## 项目结构 ``` 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 ``` ## 快速开始 ### 1. 环境要求 - JDK 17+ - PostgreSQL(本地或远程均可) ### 2. 初始化数据库 ```bash psql -U -f sql/postgersql.sql ``` 脚本会创建 `oj` 数据库、`dev` schema,以及用户、题目、评测用例、提交记录、分类、比赛等表(含注释与索引)。 ### 3. 配置环境变量 `application.yaml` 中数据库连接通过环境变量注入: | 环境变量 | 说明 | 默认值 | | --- | --- | --- | | `databaseHost` | 数据库地址 | - | | `databaseUsername` | 数据库用户名 | - | | `databasePassword` | 数据库密码 | - | ### 4. 启动 ```bash ./gradlew bootRun ``` 服务默认运行在 `http://localhost:8080`。 ## 接口文档 启动后访问 Swagger UI: ``` http://localhost:8080/swagger-ui/index.html ``` > `auth/login`、`auth/register` 及 Swagger 相关路径无需认证,其余接口需在请求头携带 `Authorization: Bearer `。 ## 数据库设计 表均位于 `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` | ```sql -- 触发器函数(定义一次,所有表共用) 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`,插入与更新均由数据库自动处理。 ### E-R 图 ```mermaid 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`)。 ### 执行流程 1. 将提交代码写入临时目录 `code//Main.java` 2. 调用 `javac -encoding utf8 Main.java` 编译,编译超时(`COMPILE_TIMEOUT_MS`,默认 10s)判 `SE` 3. 逐用例执行 `java -Xmxk -Dfile.encoding=utf8 Main`: - stdout / stderr 异步读取、stdin 异步写入(`CompletableFuture` + 守护线程池),主线程不阻塞 - `process.waitFor(maxTime, ms)` 超时则 `destroyForcibly()` 并判 `TLE`,防止死循环耗尽资源 4. `finally` 中清理临时目录,所有返回路径均保证删除 5. 统计结果:`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` 参数传给子 JVM - `CodeContext` 缺省值:`maxTime = 1000ms`、`maxMemory = 256MB(262144 KB)` ### 注意事项 - `javac` / `java` 路径取 `JAVA_HOME`,未设置时回退到当前运行时 `java.home`;需指向 **JDK(非 JRE)** - 本次提交尚未接入业务接口(`JudgeController` / `SubmissionService`),沙箱为独立可调用的能力,测试床见 `JVMNativeCodeSandBoxTest` ### 测试 ```bash JAVA_HOME=/path/to/jdk17 ./gradlew test --tests "space.anyi.codesandbox.JVMNativeCodeSandBoxTest" ``` 覆盖:AC/WA/TLE/RE、多行输出与行尾空白归一化、死循环超时、10 并发、自定义内存上限(`-Xmxk` 格式)。 ## 主要依赖 | 依赖 | 用途 | | --- | --- | | 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 | 健康检查与监控(端点全量暴露) |