1. 项目背景与需求分析
"DHUOJ 基础 1 2 4"这个看似简单的标题,实际上隐藏着一个完整的在线判题系统(Online Judge)的基础架构设计。作为东华大学(DHU)计算机专业的学生项目,它涵盖了在线编程评测平台最核心的三个基础模块:用户管理(1)、题目管理(2)和评测机管理(4)。
在线判题系统是计算机教育中不可或缺的工具,它能够自动编译、运行用户提交的代码,并根据预设的测试用例验证程序的正确性。对于计算机专业的学生而言,理解OJ系统的底层原理不仅有助于更好地使用这类平台,更是提升系统设计能力的绝佳实践。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 系统架构设计
2.1 整体架构概述
一个基础的OJ系统通常采用三层架构:
- 前端展示层:负责用户交互界面
- 业务逻辑层:处理核心业务逻辑
- 数据存储层:管理各类数据持久化
在"DHUOJ 基础 1 2 4"项目中,我们重点关注的是业务逻辑层的三个核心模块:
- 用户管理模块(1):负责用户注册、登录、权限控制等
- 题目管理模块(2):负责题目的增删改查、测试用例管理等
- 评测机模块(4):负责代码的编译、运行和结果判定
2.2 技术选型考量
对于学生项目而言,技术选型需要平衡学习成本和实用性。以下是推荐的技术栈:
- 后端:Python Django/Flask 或 Java Spring Boot
- 前端:Vue.js/React + Element UI/Ant Design
- 数据库:MySQL/PostgreSQL
- 评测机:Docker容器隔离 + 多语言编译器支持
选择这些技术的主要原因是:
- 学习资源丰富,社区支持完善
- 开发效率高,适合快速原型开发
- 扩展性强,便于后续功能迭代
3. 用户管理模块实现
3.1 用户模型设计
用户是OJ系统中最基础的实体,其核心属性包括:
- 用户名(唯一标识)
- 密码(需加密存储)
- 邮箱(用于找回密码)
- 用户角色(普通用户/管理员)
- 注册时间
- 最后登录时间
在Django中,用户模型可以这样定义:
python复制from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
avatar = models.ImageField(upload_to='avatars/', null=True, blank=True)
school = models.CharField(max_length=100, blank=True)
solved_problems = models.ManyToManyField('Problem', through='UserProblemStatus')
def __str__(self):
return self.username
3.2 认证与授权
安全的认证系统是OJ的核心需求之一。我们需要实现:
- 密码加密存储(使用PBKDF2或bcrypt)
- Session/Cookie管理
- CSRF防护
- 权限控制(基于角色的访问控制)
一个简单的权限检查中间件示例:
python复制from django.http import HttpResponseForbidden
class AdminRequiredMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
if request.path.startswith('/admin/') and not request.user.is_staff:
return HttpResponseForbidden()
return self.get_response(request)
4. 题目管理模块实现
4.1 题目数据结构
一道编程题目通常包含以下信息:
- 题目ID(唯一标识)
- 标题
- 描述(Markdown格式)
- 输入输出说明
- 时间/内存限制
- 测试用例(输入/输出对)
- 难度等级
- 标签分类
数据库模型示例:
python复制class Problem(models.Model):
DIFFICULTY_CHOICES = [
('E', '简单'),
('M', '中等'),
('H', '困难'),
]
title = models.CharField(max_length=200)
description = models.TextField()
input_description = models.TextField()
output_description = models.TextField()
time_limit = models.IntegerField(help_text="ms") # 毫秒
memory_limit = models.IntegerField(help_text="MB") # 兆字节
difficulty = models.CharField(max_length=1, choices=DIFFICULTY_CHOICES)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return f"{self.title} ({self.get_difficulty_display()})"
4.2 测试用例管理
测试用例是评判用户代码正确性的关键,需要特别注意:
- 输入输出文件的存储(建议使用单独的文件系统或对象存储)
- 测试用例的保密性(防止用户直接获取答案)
- 多样化的测试场景覆盖
测试用例模型示例:
python复制class TestCase(models.Model):
problem = models.ForeignKey(Problem, on_delete=models.CASCADE)
input = models.TextField()
output = models.TextField()
is_sample = models.BooleanField(default=False)
order = models.IntegerField()
class Meta:
ordering = ['order']
def __str__(self):
return f"TestCase {self.order} for {self.problem.title}"
5. 评测机模块实现
5.1 评测流程设计
评测机是OJ系统中最复杂的部分,主要流程包括:
- 接收用户提交
- 准备评测环境(Docker容器)
- 编译代码(如果需要)
- 运行程序并注入测试用例
- 收集输出并与预期结果比对
- 生成评测结果
评测状态机示例:
python复制class JudgeStatus:
PENDING = 'PD'
COMPILING = 'CP'
RUNNING = 'RN'
ACCEPTED = 'AC'
WRONG_ANSWER = 'WA'
TIME_LIMIT_EXCEEDED = 'TLE'
MEMORY_LIMIT_EXCEEDED = 'MLE'
RUNTIME_ERROR = 'RE'
COMPILE_ERROR = 'CE'
SYSTEM_ERROR = 'SE'
CHOICES = [
(PENDING, '等待中'),
(COMPILING, '编译中'),
(RUNNING, '运行中'),
(ACCEPTED, '答案正确'),
(WRONG_ANSWER, '答案错误'),
(TIME_LIMIT_EXCEEDED, '时间超限'),
(MEMORY_LIMIT_EXCEEDED, '内存超限'),
(RUNTIME_ERROR, '运行时错误'),
(COMPILE_ERROR, '编译错误'),
(SYSTEM_ERROR, '系统错误'),
]
5.2 Docker安全隔离
使用Docker进行代码运行隔离时需要注意:
- 限制容器资源(CPU、内存、进程数等)
- 禁用危险系统调用
- 设置合理的超时时间
- 防止容器逃逸
一个基本的Docker运行配置示例:
python复制import docker
client = docker.from_env()
def run_in_docker(code, input_data, timeout=5):
container = client.containers.run(
'python:3.9-slim',
command=f'python -c "{code}"',
stdin_open=True,
mem_limit='128m',
pids_limit=50,
network_mode='none',
remove=True,
detach=True
)
try:
container.put_archive('/input.txt', input_data.encode())
result = container.wait(timeout=timeout)
output = container.logs(stdout=True, stderr=False)
errors = container.logs(stdout=False, stderr=True)
return {
'exit_code': result['StatusCode'],
'output': output.decode().strip(),
'error': errors.decode().strip()
}
except Exception as e:
return {'error': str(e)}
finally:
container.stop()
6. 系统集成与测试
6.1 API接口设计
RESTful API是前后端分离架构的关键。以下是核心API示例:
-
用户相关:
- POST /api/auth/register - 用户注册
- POST /api/auth/login - 用户登录
- GET /api/users/me - 获取当前用户信息
-
题目相关:
- GET /api/problems - 题目列表
- GET /api/problems/{id} - 题目详情
- POST /api/problems - 创建题目(管理员)
-
提交相关:
- POST /api/submissions - 提交代码
- GET /api/submissions/{id} - 获取提交结果
6.2 压力测试要点
在开发完成后,需要进行以下测试:
- 并发提交测试(模拟考试场景)
- 恶意代码注入测试
- 长时间运行稳定性测试
- 资源泄漏检查
使用Locust进行压力测试的示例:
python复制from locust import HttpUser, task, between
class OJUser(HttpUser):
wait_time = between(1, 5)
@task
def submit_code(self):
self.client.post("/api/submissions", json={
"problem_id": 1,
"language": "python",
"code": "print(sum(map(int, input().split())))"
})
@task(3)
def view_problem(self):
self.client.get("/api/problems/1")
7. 项目部署与维护
7.1 生产环境部署
对于实际使用的OJ系统,建议采用以下部署方案:
- Web服务器:Nginx + Gunicorn/Uvicorn
- 数据库:PostgreSQL with connection pooling
- 缓存:Redis for session storage
- 消息队列:RabbitMQ/Celery for judge tasks
- 监控:Prometheus + Grafana
Docker-compose示例配置:
yaml复制version: '3'
services:
web:
build: .
command: gunicorn dhuoj.wsgi:application --bind 0.0.0.0:8000
volumes:
- .:/code
ports:
- "8000:8000"
depends_on:
- redis
- db
db:
image: postgres:13
environment:
POSTGRES_PASSWORD: dhuoj123
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:6
judge:
build: .
command: celery -A dhuoj worker -l info -Q judge
volumes:
- .:/code
depends_on:
- redis
- db
volumes:
postgres_data:
7.2 常见问题排查
在实际运行中可能会遇到以下问题:
-
评测结果不一致:
- 检查测试用例的换行符是否统一
- 验证浮点数比较的精度设置
- 确认时间/内存限制的单位是否正确
-
性能瓶颈:
- 数据库查询优化(添加索引)
- 评测任务队列管理
- 缓存频繁访问的数据
-
安全问题:
- 定期更新依赖库
- 检查容器隔离配置
- 监控异常提交行为
在开发这个项目的过程中,我发现最容易被忽视的是评测机的资源限制设置。初期我们经常遇到因为内存限制设置不当导致的误判,后来通过添加详细的运行日志和资源监控,才逐步完善了评测的准确性。另一个经验是测试用例的设计 - 不仅要考虑正常的输入情况,还要特别注意边界条件和极端情况,这能大大提高题目质量。
