简介:本资源是一个基于Django框架构建的轻量级在线编程竞赛平台源码包,面向Python后端开发者、Web全栈学习者及高校计算机专业学生,旨在提供可快速部署、二次开发的OJ(Online Judge)系统参考实现。压缩包共193个文件,以163个Python核心模块为主,涵盖用户、题目、竞赛、提交、API、缓存、管理命令等完整业务逻辑;辅以7个配置文件(如supervisord.conf、nginx.conf、Dockerfile等),支撑生产环境部署与服务编排;另有HTML模板、Markdown文档、证书密钥及统计脚本,体现前后端分离与安全实践。资源包仅282KB,结构紧凑、模块职责清晰,适合深入理解Django项目工程化组织方式、RESTful API设计及竞赛系统业务建模。目前已有31人学习下载,可直接运行调试,获取从用户注册到代码评测反馈的全流程实现细节与典型问题解决方案。
1. 这不是另一个“Django博客模板”,而是一个可上线的在线编程竞赛平台最小可行系统
你下载到的(源码)基于Django框架的在线编程竞赛平台.zip,本质是一套面向 OJ(Online Judge)场景的生产级 Django 应用骨架——它不包含前端美化组件或管理后台皮肤,但已内置题目管理、代码提交、沙箱判题、实时状态反馈、用户权限分级和竞赛时间控制等核心能力。与常见 Django 教程项目不同,它默认采用Celery + Redis异步处理判题任务,用Docker Compose封装 Python 沙箱环境,并通过supervisord.conf管理多进程服务生命周期。适合两类人:一是想快速部署校内编程赛、企业内部 Hackathon 的运维/教务人员;二是正在学习 Django 实战开发、需要理解「如何让 Web 框架真正调度底层执行资源」的 Python 工程师。它不依赖 Vue 或 React 前端,纯 Django 模板渲染即可运行,但预留了 REST API 接口,方便后续对接任意前端框架。压缩包解压后直接进入manage.py所在目录,就能启动一个具备真实判题能力的平台——不是静态页面,不是模拟响应,而是真调用gcc、python3、java编译并执行用户代码。
2. 从解压到可运行:Django 在线编程竞赛平台的本地初始化全流程
2.1 解压与目录结构识别:确认关键配置文件是否存在
该 ZIP 包解压后应呈现标准 Django 项目结构,重点验证以下文件是否完整(缺失任一将导致启动失败):
manage.py:Django 入口脚本requirements.txt:明确列出django==4.2.11,celery==5.3.6,redis==4.6.0,psutil==5.9.8,docker==6.1.3等核心依赖core/(或oj/):主应用目录,含settings.py,urls.py,asgi.pyjudge/:独立判题子应用,含tasks.py(Celery 任务)、sandbox.py(沙箱执行逻辑)deploy/目录:必须包含supervisord.conf,nginx.conf,docker-compose.yml,.env.example
提示:若解压后无
deploy/目录,说明该 ZIP 是开发版而非部署版,需手动补全配置。不要尝试用pip install -r requirements.txt后直接python manage.py runserver—— 判题服务、Redis 队列、Nginx 反向代理均未就绪,前端提交将卡在「等待评测中」。
2.1.1 验证 ZIP 完整性与编码兼容性
部分 Windows 用户解压时可能因中文路径或 UTF-8 编码问题导致manage.py报错SyntaxError: Non-UTF-8 code starting with '\xe5'。此时需用支持 UTF-8 的解压工具(如 7-Zip 或unzip -O UTF-8)重新解压:
# Linux/macOS 下强制指定编码解压(避免乱码) unzip -O UTF-8 "(源码)基于Django框架的在线编程竞赛平台.zip" # Windows 用户请使用 7-Zip,右键 → “7-Zip” → “解压到...”,勾选“使用 Unicode 文件名”解压后检查core/settings.py开头是否有# -*- coding: utf-8 -*-,并确认INSTALLED_APPS中包含'judge','accounts','contests'等竞赛专属应用。
2.2 环境准备:Python 版本、数据库与 Redis 的硬性要求
该平台对运行环境有明确约束,不满足将直接报错:
| 组件 | 最低版本 | 必须启用特性 | 验证命令 |
|---|---|---|---|
| Python | 3.10+ | asyncio,zoneinfo | python3 --version |
| PostgreSQL | 12+ | jsonb,pg_trgm扩展 | psql --version |
| Redis | 7.0+ | STREAMS,RDB/AOF 混合持久化 | redis-cli INFO server | grep redis_version |
| Docker | 24.0+ | --cap-add=SYS_PTRACE,--security-opt seccomp=unconfined | docker version --format '{{.Server.Version}}' |
注意:MySQL 不被支持。
settings.py中数据库配置强制使用django.db.backends.postgresql,且迁移脚本含RunSQL("CREATE EXTENSION IF NOT EXISTS pg_trgm;")。若强行改用 MySQL,题目模糊搜索和用户昵称相似度匹配功能将失效。
2.2.1 初始化 PostgreSQL 并创建扩展
-- 以 postgres 用户登录 sudo -u postgres psql -- 创建专用数据库与用户(密码建议 12 位以上) CREATE DATABASE oj_platform ENCODING 'UTF8' LC_COLLATE='en_US.UTF-8' LC_CTYPE='en_US.UTF-8'; CREATE USER oj_user WITH PASSWORD 'StrongPassw0rd!'; GRANT ALL PRIVILEGES ON DATABASE oj_platform TO oj_user; -- 连接新库并启用扩展 \c oj_platform CREATE EXTENSION IF NOT EXISTS pg_trgm; CREATE EXTENSION IF NOT EXISTS btree_gin;随后修改core/settings.py中DATABASES配置:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': 'oj_platform', 'USER': 'oj_user', 'PASSWORD': 'StrongPassw0rd!', 'HOST': 'localhost', 'PORT': '5432', } }2.3 依赖安装与 Django 迁移:跳过collectstatic的实操技巧
执行以下命令链完成基础部署(必须按顺序):
# 1. 创建虚拟环境(避免污染系统 Python) python3 -m venv venv source venv/bin/activate # Linux/macOS # venv\Scripts\activate # Windows # 2. 升级 pip 并安装依赖(注意:requirements.txt 中已锁定版本) pip install --upgrade pip pip install -r requirements.txt # 3. 执行数据库迁移(关键:必须先运行 migrate,再创建超级用户) python manage.py migrate # 4. 创建管理员账户(用于登录后台管理题目/用户/竞赛) python manage.py createsuperuser # 5. 【重要】跳过 collectstatic(本地开发无需前端构建) # 因平台默认使用 Django 模板,静态文件由 DEBUG=True 下的开发服务器自动提供 # 若误执行 collectstatic,反而会导致 Nginx 静态路由冲突2.3.1 验证 Django 启动与基础路由
运行开发服务器并测试核心接口:
python manage.py runserver 0.0.0.0:8000访问http://localhost:8000/admin/登录后台,确认能进入用户、题目、竞赛管理界面;
访问http://localhost:8000/api/v1/status/(若存在 API),返回{"status": "ok", "judge_queue": 0}表示判题服务待命;
访问http://localhost:8000/contests/应显示空竞赛列表页,无 500 错误。
提示:若出现
ModuleNotFoundError: No module named 'judge',检查INSTALLED_APPS是否漏写'judge.apps.JudgeConfig';若runserver报django.core.exceptions.ImproperlyConfigured: The SECRET_KEY setting must not be empty,需在settings.py中设置SECRET_KEY = 'your-32-char-secret-key-here'(生成方式:python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())")。
3. 判题服务与进程管理:supervisord.conf 与 nginx.conf 的精准配置
3.1 supervisord.conf:定义 Django、Celery、Redis 三进程协同模型
deploy/supervisord.conf是该平台稳定运行的核心配置文件,它声明了三个必须共存的服务进程:
| 进程名 | 启动命令 | 作用 | 关键参数说明 |
|---|---|---|---|
web | gunicorn core.wsgi:application --bind 127.0.0.1:8001 --workers 4 --timeout 120 | Django Web 服务(非开发模式) | --timeout 120防止大代码提交超时中断 |
celery-worker | celery -A core worker -l info -Q judge_tasks | 专用判题任务消费者 | -Q judge_tasks限定只消费判题队列,避免干扰其他任务 |
celery-beat | celery -A core beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler | 定时任务调度器(如竞赛自动结束、排名刷新) | --scheduler指向数据库驱动的调度器,确保多节点一致性 |
3.1.1 修改 supervisord.conf 适配本地环境
打开deploy/supervisord.conf,重点修改以下三处(路径需绝对):
[program:web] command=/path/to/your/venv/bin/gunicorn core.wsgi:application --bind 127.0.0.1:8001 --workers 4 --timeout 120 directory=/path/to/your/project/root user=www-data autostart=true autorestart=true redirect_stderr=true stdout_logfile=/var/log/oj/web.log [program:celery-worker] command=/path/to/your/venv/bin/celery -A core worker -l info -Q judge_tasks directory=/path/to/your/project/root user=www-data environment=DJANGO_SETTINGS_MODULE="core.settings" autostart=true autorestart=true redirect_stderr=true stdout_logfile=/var/log/oj/celery-worker.log [program:celery-beat] command=/path/to/your/venv/bin/celery -A core beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler directory=/path/to/your/project/root user=www-data environment=DJANGO_SETTINGS_MODULE="core.settings" autostart=true autorestart=true redirect_stderr=true stdout_logfile=/var/log/oj/celery-beat.log注意:
/path/to/your/...必须替换为实际路径;user=www-data在 CentOS/RHEL 系统需改为user=nginx;日志目录/var/log/oj/需提前创建并赋权:sudo mkdir -p /var/log/oj && sudo chown www-data:www-data /var/log/oj。
3.2 nginx.conf:反向代理与静态资源路由的零配置陷阱
deploy/nginx.conf的核心在于两点:将/api/路径代理至 Gunicorn,将/static/和/media/直接由 Nginx 提供。以下是精简后的关键段落:
upstream django_app { server 127.0.0.1:8001; } server { listen 80; server_name your-domain.com; # 静态资源:Nginx 直接服务,不经过 Django location /static/ { alias /path/to/your/project/staticfiles/; expires 1y; add_header Cache-Control "public, immutable"; } location /media/ { alias /path/to/your/project/media/; expires 1y; } # API 与动态请求:全部代理至 Gunicorn location / { proxy_pass http://django_app; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 120; proxy_send_timeout 120; } # 判题结果轮询接口(前端 JS 会高频请求) location /api/v1/submission/ { proxy_pass http://django_app; proxy_set_header Host $host; proxy_read_timeout 300; # 允许长轮询 } }3.2.1 验证 Nginx 配置并重载
# 测试配置语法 sudo nginx -t # 若提示 "nginx: the configuration file /etc/nginx/nginx.conf syntax is ok",则重载 sudo systemctl reload nginx # 查看 Nginx 错误日志定位问题 sudo tail -f /var/log/nginx/error.log提示:若访问域名出现
502 Bad Gateway,首先检查upstream django_app对应的127.0.0.1:8001是否被 Gunicorn 正确监听(sudo netstat -tuln \| grep :8001);若静态文件 404,确认alias路径末尾有/且STATIC_ROOT在settings.py中指向/path/to/your/project/staticfiles/(需先运行python manage.py collectstatic --noinput)。
3.3 启动 supervisord 并监控进程状态
# 安装 supervisord(Ubuntu/Debian) sudo apt-get install supervisor # 复制配置文件 sudo cp deploy/supervisord.conf /etc/supervisor/conf.d/oj.conf # 重载配置并启动所有进程 sudo supervisorctl reread sudo supervisorctl update sudo supervisorctl start all # 查看进程状态(应显示 RUNNING) sudo supervisorctl status输出应类似:
celery-beat RUNNING pid 12345, uptime 0:01:23 celery-worker RUNNING pid 12346, uptime 0:01:23 web RUNNING pid 12347, uptime 0:01:23若某进程显示FATAL,查看对应日志(如/var/log/oj/celery-worker.log),常见错误为ConnectionRefusedError: Error 111 connecting to localhost:6379(Redis 未启动)或django.core.exceptions.ImproperlyConfigured: Set the DATABASE_URL environment variable(.env文件未配置)。
4. 判题沙箱安全加固:Docker 容器隔离与资源限制实战
4.1 docker-compose.yml 中的沙箱容器定义解析
deploy/docker-compose.yml定义了一个专用判题容器judge-sandbox,其设计直指 OJ 安全核心:
version: '3.8' services: judge-sandbox: image: python:3.10-slim cap_add: - SYS_PTRACE - SYS_NICE security_opt: - seccomp:unconfined mem_limit: 256m mem_reservation: 128m pids_limit: 32 ulimits: cpu: 3 fsize: 10485760 # 10MB 文件大小限制 nproc: 32 volumes: - ./judge/sandbox:/app:ro - /tmp/oj-judge:/tmp:rw working_dir: /app command: ["tail", "-f", "/dev/null"]4.1.1 关键安全参数作用说明
| 参数 | 作用 | 为什么必须 |
|---|---|---|
cap_add: [SYS_PTRACE] | 允许ptrace()系统调用 | 判题程序需strace监控用户进程系统调用,防止fork bomb |
mem_limit: 256m | 内存硬上限 | 阻止malloc(10**9)导致宿主机 OOM |
pids_limit: 32 | 进程数上限 | 防止while true; do :; done &创建无限子进程 |
ulimits.fsize: 10485760 | 输出文件最大 10MB | 避免print('a'*10000000)填满磁盘 |
seccomp:unconfined | 禁用默认 seccomp 过滤 | 允许gcc、java等编译器调用非常规系统调用(如pivot_root) |
注意:
seccomp:unconfined是必要妥协,但通过cap_add和ulimits已实现足够纵深防御。切勿删除cap_add或ulimits单独保留seccomp,否则判题将失败。
4.2 沙箱执行流程:从用户提交到返回结果的 7 步链
当用户点击「提交」后,平台执行以下原子操作(全部在judge/tasks.py中定义):
- 接收请求:Django 视图接收
POST /api/v1/submission/,校验题目 ID、语言、代码长度(≤100KB) - 入队:将提交数据序列化为 JSON,推入 Redis 队列
judge_tasks - 消费:
celery-worker从队列取出任务,调用execute_submission() - 准备沙箱:
docker run --rm ... judge-sandbox启动临时容器,挂载用户代码与测试用例 - 编译与运行:容器内执行
gcc main.c -o main && ./main < input.txt > output.txt 2>&1 - 结果比对:读取
output.txt与标准答案answer.txt,逐行比较(忽略空格/换行) - 回写状态:更新数据库
Submission.status为Accepted/Wrong Answer/Time Limit Exceeded
4.2.1 手动触发一次沙箱测试验证
进入项目根目录,运行以下命令模拟一次 Python 题目提交:
# 创建测试代码文件 echo "print('Hello World')" > test_code.py # 启动沙箱容器(保持前台运行) docker run --rm -v $(pwd):/workspace -w /workspace python:3.10-slim python test_code.py # 应输出 Hello World,且容器立即退出(exit code 0) # 若输出 "Killed" 或超时,说明 ulimits 或 mem_limit 生效若此命令失败,检查 Docker 是否启用cgroup v2(cat /proc/1/cgroup显示0::/),旧版 cgroup v1 需在/etc/default/grub中添加systemd.unified_cgroup_hierarchy=0并sudo update-grub && reboot。
5. 生产部署避坑指南:宝塔面板适配、常见 500 错误定位与性能调优
5.1 宝塔面板部署 Django 的 3 个必改项
使用宝塔部署该平台时,不能直接用「Python 项目」插件一键部署,必须手动干预:
| 项目 | 宝塔默认值 | 必须修改为 | 原因 |
|---|---|---|---|
| Python 版本 | 3.8 | 3.10+ | zoneinfo时区支持是 Django 4.2+ 强依赖 |
| 运行目录 | /www/wwwroot/your-site | 项目根目录(含 manage.py) | 否则python manage.py找不到core.settings |
| 启动命令 | gunicorn project.wsgi:application | gunicorn core.wsgi:application --bind 127.0.0.1:8001 | 必须显式指定--bind地址端口,否则宝塔反向代理失败 |
5.1.1 宝塔下 supervisord 配置的特殊处理
宝塔自带 supervisord,但默认不读取/etc/supervisor/conf.d/。需:
- 进入宝塔 → 「软件商店」→ 搜索「Supervisor」→ 安装
- 在 Supervisor 管理界面 → 「配置文件」→ 将
deploy/supervisord.conf内容粘贴到编辑框 - 关键:在「进程管理」→ 「添加进程」中,为每个进程单独填写:
- 名称:
web - 启动命令:
/www/wwwroot/your-site/venv/bin/gunicorn core.wsgi:application --bind 127.0.0.1:8001 --workers 4 --timeout 120 - 运行目录:
/www/wwwroot/your-site - 用户:
www
- 名称:
提示:宝塔的 Nginx 反向代理规则需手动添加。进入网站 → 「配置文件」→ 在
location / {块内添加proxy_pass http://127.0.0.1:8001;,并删除原有proxy_pass行。
5.2 500 错误速查表:从日志定位根本原因
当用户提交后返回 500,按以下顺序排查:
| 日志位置 | 关键错误信息 | 解决方案 |
|---|---|---|
/var/log/supervisor/web.log | ImportError: cannot import name 'xxx' from 'judge.sandbox' | 检查judge/sandbox.py是否有语法错误,或PYTHONPATH未包含项目根目录 |
/var/log/oj/celery-worker.log | ConnectionError: Error 111 connecting to localhost:6379 | sudo systemctl start redis-server,并确认settings.py中CELERY_BROKER_URL = 'redis://127.0.0.1:6379/0' |
/var/log/nginx/error.log | connect() failed (111: Connection refused) while connecting to upstream | sudo supervisorctl status查看web进程是否 RUNNING,若为STARTING则等待 10 秒再试 |
/var/log/oj/web.log | django.db.utils.OperationalError: FATAL: database "oj_platform" does not exist | 重新执行psql创建数据库,或检查DATABASES配置中NAME是否拼写错误 |
5.2.1 数据库迁移失败的终极修复
若python manage.py migrate报django.db.migrations.exceptions.InconsistentMigrationHistory:
# 1. 查看当前迁移状态 python manage.py showmigrations # 2. 强制标记所有迁移为已执行(仅限全新数据库) python manage.py migrate --fake-initial # 3. 若仍失败,清空迁移记录表(危险!仅限开发环境) sudo -u postgres psql -c "TRUNCATE django_migrations;" oj_platform python manage.py migrate5.3 性能调优:单机支撑 500 并发提交的 4 个参数
针对高并发竞赛场景,调整以下参数:
| 组件 | 参数 | 推荐值 | 效果 |
|---|---|---|---|
| Gunicorn | --workers | $(nproc) * 2 + 1(如 8 核设为 17) | 提升 HTTP 请求吞吐量 |
| Celery | worker_concurrency | $(nproc) | 充分利用 CPU 核心处理判题 |
| PostgreSQL | shared_buffers | 256MB(内存 ≥4GB 时) | 加速查询缓存 |
| Redis | maxmemory | 512MB | 防止内存溢出导致队列丢失 |
修改后重启服务:
sudo supervisorctl restart all sudo systemctl restart postgresql sudo systemctl restart redis-server提示:
worker_concurrency在core/celery.py中设置:app.conf.worker_concurrency = 8。不要盲目增加,超过 CPU 核心数将引发上下文切换开销,反而降低吞吐。
6. 验证判题准确性:用 Python 脚本批量提交测试用例并比对结果
6.1 构建自动化验证脚本test_judge.py
在项目根目录创建test_judge.py,用于验证沙箱判题逻辑是否正确:
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import requests import time import json # 平台基础配置 BASE_URL = "http://localhost:8000" API_TOKEN = "your-admin-token" # 从 admin 后台获取 PROBLEM_ID = 1 # 替换为实际题目 ID def submit_and_check(code, language, expected_status): """提交代码并等待结果,返回实际状态""" payload = { "problem_id": PROBLEM_ID, "language": language, "code": code, "test_case": "1 2\n" # 输入样例 } headers = {"Authorization": f"Token {API_TOKEN}"} resp = requests.post(f"{BASE_URL}/api/v1/submission/", json=payload, headers=headers) assert resp.status_code == 201, f"Submit failed: {resp.text}" submission_id = resp.json()["id"] # 轮询结果(最多 30 秒) for _ in range(30): time.sleep(1) result = requests.get(f"{BASE_URL}/api/v1/submission/{submission_id}/", headers=headers) status = result.json()["status"] if status in ["Accepted", "Wrong Answer", "Time Limit Exceeded", "Runtime Error"]: return status raise TimeoutError("Submission timeout") if __name__ == "__main__": # 测试用例:C 语言求和 c_code = '#include <stdio.h>\nint main(){int a,b;scanf("%d%d",&a,&b);printf("%d\\n",a+b);return 0;}' assert submit_and_check(c_code, "c", "Accepted") == "Accepted" # 测试用例:Python 错误代码 py_code = 'print(1/0)' assert submit_and_check(py_code, "python3", "Runtime Error") == "Runtime Error" print("✅ All judge tests passed!")6.1.1 运行验证并解读输出
# 安装依赖 pip install requests # 运行测试 python test_judge.py若输出✅ All judge tests passed!,说明判题链路完全打通;若失败,根据assert报错定位具体环节(如submit_and_check返回None表示轮询超时,需检查celery-worker是否在运行)。
6.2 检查判题日志中的沙箱执行细节
判题成功后,/var/log/oj/celery-worker.log中会出现类似记录:
[INFO] judge.tasks.execute_submission: Submission #12345 started for problem 1 [DEBUG] judge.sandbox.run_in_sandbox: Running command ['gcc', 'main.c', '-o', 'main'] in container judge-sandbox [DEBUG] judge.sandbox.run_in_sandbox: Command exited with code 0 [DEBUG] judge.sandbox.run_in_sandbox: Running command ['./main'] with timeout 2s [INFO] judge.tasks.execute_submission: Submission #12345 finished with status Accepted注意:
timeout 2s是题目配置中的「时间限制」,由Problem.time_limit字段控制。若日志中出现Command timed out after 2 seconds,说明用户代码超时,而非沙箱故障。
6.3 真实竞赛场景下的资源占用监控
使用htop实时观察三类进程:
gunicorn: master:常驻 1 个主进程 + N 个工作进程(--workers数)celery: worker:每个工作进程占用约 80MB 内存,CPU 使用率随判题任务波动docker-containerd-shim:每个沙箱容器对应一个 shim 进程,内存峰值 ≤256MB
若celery-worker进程数持续为 0,检查 Redis 队列长度:redis-cli llen judge_tasks,非零值表示任务堆积,需增加worker_concurrency或优化判题逻辑。
本文还有配套的精品资源,点击获取