ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

学科竞赛管理系统毕设源码拆解:Django+Vue+MySQL实战要点

学科竞赛管理系统毕设源码拆解:Django+Vue+MySQL实战要点 简介面向高校计算机专业毕业设计场景这套学科竞赛管理系统以Python、Django、Vue和MySQL为技术栈交付完整源码、数据库、毕业论文及视频演示。系统围绕管理员与学生两类角色划分功能管理员登录后可处理学生、教师、赛项信息、赛项报名、奖项统计及系统管理学生注册后可查看首页赛项资讯、新闻公告并对感兴趣的赛项在线报名整体贴合高校竞赛管理业务流程运行稳定、界面友好。资源包共744个文件大小为42.82MB主要包含py后端代码、vue前端组件、js交互脚本、css样式、svg图标、sql数据库脚本、mp4演示视频并附带bat批处理脚本方便一键安装与启动项目。目前已有411人学习使用。通过该项目可快速掌握前后端分离开发、数据库设计与权限管理思路论文和录屏能辅助理解开发流程、撰写毕业论文及准备答辩适合毕业设计参考、功能扩展或二次开发。1. 学科竞赛管理系统Python Django Vue MySQL 的毕设源码拿到的第一件事是什么拿到“学科竞赛管理系统”毕业设计题目的同学最常问的一句话就是这套 Python Django Vue MySQL 的源码到底能不能跑通我拆过这套资源的完整目录里面是 Django 后端、Vue 前端、MySQL 初始化脚本、毕业论文和视频演示功能覆盖竞赛发布、学生报名、评委打分、成绩公示的完整管理闭环。它适合三类人还没定题目的准毕业生、拿到题目但不会装配的动手派、想借一套管理系统快速改造成作品的初级开发者。这次我不讲空道理直接把源码拆开说清楚每层怎么协作、环境怎么搭、业务代码怎么改、以及最容易栽跟头的五个地方。2. 技术栈与架构拆解Django 后端、Vue 前端、MySQL 数据库各管哪一块2.1 为什么这个组合适合作毕业设计作为常年帮人拆毕设源码的人我拿到这套学科竞赛管理系统时的第一反应是看它是不是“前后端分离”。多数情况下评审老师看重的不是功能多炫而是你能不能把项目分成清晰的层次。这套系统用 Django 做后端服务、Vue 做前端页面、MySQL 做数据存储属于典型的分离式结构答辩时一句话就能讲明白。选择这个组合不是因为它热门而是因为每一层都有现成的轮子Django 自带 ORM、Admin 后台、用户认证和表单校验学科竞赛里的用户角色、竞赛发布、报名审核都属于常规管理功能Vue 的组件化写法适合做竞赛列表、报名表单、成绩展示这类交互页面MySQL 的 SQL 脚本可以被轻松导入导出换电脑复现也不容易丢数据。对毕设而言这种“各管一段、边界清楚”的架构比把逻辑全堆在一个文件里要稳妥得多。2.2 Django 后端目录结构与数据建模这套资源解压后的目录我不逐层细列常见做法是把功能拆成独立应用结构大致是这样manage.py requirements.txt db.sqlite3 或 init.sql contest/ settings.py urls.py apps/ users/ # 用户、角色、登录注册 contests/ # 竞赛发布、报名管理 scores/ # 评委打分、成绩查询把业务拆进独立 app 而不是全部堆在 models.py 里是 Django 项目能长期维护的基础。用 django-admin startapp 创建的每个应用都要在 INSTALLED_APPS 里注册漏注册是导入模型时最常见的翻车点。我一般会先打开 settings.py 确认这三个应用都在列表里再往下看数据模型。竞赛表的核心模型长这样from django.db import models class Contest(models.Model): title models.CharField(max_length128, verbose_name竞赛名称) category models.CharField(max_length32, verbose_name竞赛类别) organizer models.CharField(max_length64, blankTrue, verbose_name主办方) start_time models.DateTimeField(verbose_name报名开始时间) end_time models.DateTimeField(verbose_name报名截止时间) max_teams models.IntegerField(default50, verbose_name最大参赛队伍数) status models.CharField( max_length16, defaultpending, choices[(pending, 待审核), (open, 报名中), (closed, 已结束)], verbose_name状态 ) def __str__(self): return f{self.title}-{self.status}这段代码里字段类型直接决定数据库列的类型DateTimeField 存日期时间IntegerField 存整数CharField 必须指定 max_length否则迁移时报错。choices 参数让 Admin 后台的下拉框直接显示中文界面层不用再做一次翻译这是毕设里展示细节很讨巧的地方。报名记录表是另一张关键表它同时关联竞赛和学生from django.conf import settings class Registration(models.Model): contest models.ForeignKey( Contest, on_deletemodels.CASCADE, related_nameregistrations ) student models.ForeignKey( settings.AUTH_USER_MODEL, on_deletemodels.CASCADE, related_nameenrolled_contests ) team_name models.CharField(max_length64, verbose_name团队名称) member_count models.IntegerField(default1, verbose_name成员数量) status models.CharField(max_length16, defaultsubmitted, verbose_name报名状态) created_at models.DateTimeField(auto_now_addTrue, verbose_name报名时间) class Meta: unique_together (contest, student)ForeignKey 后面的 on_deletemodels.CASCADE 表示竞赛被删除时它的报名记录也一并删除这是 Django 模型设计的常见约定。related_name 是反向查询的入口后面我用 contest.registrations.count() 统计报名人数就是靠它。unique_together 从数据库层面阻止同一个学生重复报名比在视图层写 if 判断更可靠这个设计值得写进论文。2.3 Vue 前端页面流转与接口对接前端部分我拿到手会先打开 src/router 目录看路由配置确认页面跳转关系import Vue from vue import Router from vue-router import ContestList from /views/ContestList.vue import ContestDetail from /views/ContestDetail.vue import Login from /views/Login.vue Vue.use(Router) export default new Router({ mode: history, routes: [ { path: /, name: ContestList, component: ContestList }, { path: /contest/:id, name: ContestDetail, component: ContestDetail, props: true }, { path: /login, name: Login, component: Login } ] })路由里的 :id 是动态参数访问 /contest/3 时 Vue 会把 3 传给 ContestDetail 组件组件里通过 this.$route.params.id 读取。props: true 是更规范的做法它把路由参数直接映射到组件 props页面里就不用到处解包。mode: history 会让 URL 去掉 #看着干净但部署后刷新会 404这个坑在第 5 章专门讲。接口对接通常用 axios 统一封装import axios from axios const api axios.create({ baseURL: process.env.VUE_APP_API_BASE || http://127.0.0.1:8000/api, timeout: 5000 }) export function fetchContests(params) { return api.get(/contests/, { params }) } export function submitRegistration(contestId, data) { return api.post(/contests/${contestId}/register/, data) }封装成独立函数的好处是页面里只关心业务函数不关心 URL 拼写后端接口路径调整时只改一个文件。timeout 是超时时间Django 开发服务偶尔启动慢5 秒是个折中值太短会频繁误报网络错误。2.4 MySQL 核心表结构与初始化数据数据库文件是这套资源里最值钱的部分之一。导入后常见的表包括用户表、竞赛表、报名表、成绩表关联关系如下表名核心字段关联关系users_userusername, password, role报名表的 student 外键指向它contests_contesttitle, category, status报名表与成绩表外键指向它contests_registrationcontest_id, student_id, status竞赛与学生之间的桥梁表scores_scorecontest_id, judge_id, score一个竞赛对应多条打分记录导入顺序要遵循先父表后子表先建用户和竞赛再导入报名与成绩否则外键约束会报错。用 Navicat for MySQL 或命令行导入都行注意 .sql 文件开头如果带了 CREATE DATABASE直接执行即可。到这里架构基本走通了下一章把环境从零搭起来。3. 环境搭建与项目启动从建虚拟环境到前后端联调跑通3.1 Python 环境准备与依赖安装拿到这套源码我先看 requirements.txt 再动手。里面的版本号我不照抄Django 3.x 和 4.x 在这套代码上都能跑Python 用 3.10 或 3.11 遇到问题的概率最低。网上搜 python 安装教程的人十有八九倒在了环境变量这一步装完记得勾选 Add Python to PATH。python -m venv venv # Windows 下激活 venv\Scripts\activate # macOS 或 Linux 下激活 source venv/bin/activatepython -m venv 的作用是在当前目录生成一个独立 Python 环境避免项目依赖装进系统全局导致版本冲突。激活虚拟环境后命令行提示符会多出 (venv) 前缀看到它再执行后面的安装命令。pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple用国内镜像是因为默认源在部分网络环境下很慢甚至超时。requirements.txt 里一般列了 Django、djangorestframework、django-cors-headers、mysqlclient 等。如果安装 mysqlclient 报错多半是缺编译环境解决方式放在第 5 章细讲。3.2 Django 配置与数据库迁移导入数据库前先确认 MySQL 服务已启动。很多人会遇到 mysql: command not found这通常是安装 MySQL 时没勾选 Add to PATH或者服务没启动。Windows 下先去服务管理器确认 MySQL 服务在运行再继续。mysql -u root -p -e CREATE DATABASE contest_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; mysql -u root -p contest_system contest_system.sql第一条命令创建数据库utf8mb4 是为了兼容中文和特殊字符如果用默认字符集后面写入中文会出现乱码。第二条命令把资源里的 SQL 脚本导入指定库脚本内含建表语句和管理员初始数据。接下来对齐 settings.py 里的数据库配置DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: contest_system, USER: root, PASSWORD: 你的数据库密码, HOST: 127.0.0.1, PORT: 3306, OPTIONS: {charset: utf8mb4}, } }ENGINE 用 django.db.backends.mysql 告诉 Django 走 MySQL 驱动NAME 要和上一步创建的库名完全一致HOST 写 127.0.0.1 是为了避免走 Unix socket。改完配置后执行迁移和启动python manage.py migrate python manage.py createsuperuser python manage.py runserver 0.0.0.0:8000migrate 把 Django 内置模型同步进数据库新库必须执行这一步。createsuperuser 创建后台管理员用户名密码记好后面登录 Admin 要用。runserver 后面的 0.0.0.0:8000 表示对所有网卡开放方便用手机或虚拟机访问只在自己电脑看的话写 127.0.0.1:8000 就够。3.3 Vue 前端依赖安装与启动前端目录一般叫 frontend 或 web进去先装依赖cd frontend npm installnode_modules 不会随源码打包所以这一步避不开。如果 npm install 卡在 node-sass 长期不动多半是 Node 版本和依赖不匹配切到 Node 14 或 16 基本能过。装完依赖启动开发服务器npm run serve启动后控制台会打印访问地址默认是 http://localhost:8080。建议在浏览器里装 Vue Devtools 插件调试时能直接看组件状态和数据流。VSCode 写 Vue 的话把 Python 环境配好因为很多 npm 脚本在 Windows 上依赖 Python 路径。3.4 前后端联调跨域与开发环境代理本地最省事的联调方式是给 Vue 开发服务器配代理在 vue.config.js 里写module.exports { devServer: { proxy: { /api: { target: http://127.0.0.1:8000, changeOrigin: true } } } }这个配置的意思是前端发出的所有以 /api 开头的请求都由 webpack-dev-server 转发到 8000 端口的 Django。前端请求地址写成 /api/contests/浏览器看到的是同源请求跨域问题自然消失。代理只在 npm run serve 的开发环境生效生产部署要用 Nginx 反向代理答辩时能说出这层区别是加分项。如果资源里的接口不带 /api 前缀也可以改用 Django 侧的 CORS 配置INSTALLED_APPS [ # ... corsheaders, ] MIDDLEWARE [ # ... corsheaders.middleware.CorsMiddleware, ] CORS_ALLOWED_ORIGINS [ http://localhost:8080, http://127.0.0.1:8080, ]corsheaders 是 django-cors-headers 库提供的中间件CORS_ALLOWED_ORIGINS 里明确列出允许访问的源。生产环境不要用 CORS_ALLOW_ALL_ORIGINS True那等于把接口暴露给所有网站答辩时这句很容易被追问。4. 核心业务模块实现竞赛发布、报名、评审与成绩公示的代码路径4.1 竞赛发布与管理接口Django 侧实现竞赛列表最直接的方式是函数视图配合 JsonResponse 返回 JSONfrom django.http import JsonResponse from .models import Contest def contest_list(request): status request.GET.get(status, open) contests Contest.objects.filter(statusstatus).order_by(-start_time) data [ { id: c.id, title: c.title, category: c.category, start_time: c.start_time.strftime(%Y-%m-%d %H:%M), end_time: c.end_time.strftime(%Y-%m-%d %H:%M), max_teams: c.max_teams, registered: c.registrations.count(), } for c in contests ] return JsonResponse({code: 0, data: data})filter(statusstatus) 是条件过滤order_by(-start_time) 按报名开始时间倒序。registered 通过反向关联 count 统计已报名人数直接展示在前端列表。数据量小的时候这么写没问题如果竞赛记录上千条count() 在循环里会产生 N1 查询改成 annotate 一次查完这个优化点可以在论文里展开写。4.2 报名流程防止重复报名与并发超报报名是整套系统的核心事务。常见的错误是只做前端判断后端不管你重复点了几次刷新页面还能再报一次。后端应该加事务from django.db import transaction from django.http import JsonResponse from .models import Contest, Registration transaction.atomic def apply_contest(request, contest_id): contest Contest.objects.select_for_update().get(idcontest_id) if Registration.objects.filter(contestcontest, studentrequest.user).exists(): return JsonResponse({code: 1, msg: 你已经报过名}) if contest.registrations.count() contest.max_teams: return JsonResponse({code: 1, msg: 报名人数已满}) Registration.objects.create( contestcontest, studentrequest.user, team_namerequest.POST.get(team_name, ), member_countint(request.POST.get(member_count, 1)), ) return JsonResponse({code: 0, msg: 报名成功})transaction.atomic 把整个操作包进数据库事务任何一步失败都会回滚不会出现“检查人数时没满、插入时超员”的中间状态。select_for_update() 对这条竞赛记录加行级锁两个学生在同一秒点击报名时后一个会等前一个提交完再判断。“并发超报”是实际系统里非常典型的问题能写出这个方案答辩时技术深度起码上一个台阶。4.3 评审打分与成绩公示评分功能要区分角色。通常的做法是评委登录后只能给分配给他的竞赛打分普通学生只能看已公布的成绩from django.http import JsonResponse def submit_score(request, contest_id): if not request.user.is_authenticated or not getattr(request.user, is_judge, False): return JsonResponse({code: 403, msg: 无权限}, status403) score request.POST.get(score) if score is None or not (0 float(score) 100): return JsonResponse({code: 1, msg: 分数必须在 0~100 之间}) # 更新或创建评委打分记录 return JsonResponse({code: 0, msg: 打分成功})is_judge 字段来自自定义用户表。用 getattr 取它是因为系统里某些历史用户对象可能没有该属性直接访问会抛 AttributeError。分数范围校验必须在后端做不能只靠前端输入框限制这是毕设里最容易漏的校验逻辑也是评委老师最常测的点。4.4 三种角色的权限边界学科竞赛系统里至少有三类人管理员、老师评委、学生。管理端直接复用 Django Admin 是最省事的方案把 Contest、Registration 注册进 admin 就能增删改查还自带搜索和筛选from django.contrib import admin from .models import Contest, Registration admin.register(Contest) class ContestAdmin(admin.ModelAdmin): list_display (title, category, status, start_time, end_time) list_filter (status, category) search_fields (title, organizer)list_display 控制后台列表展示哪些列list_filter 在右侧生成筛选面板search_fields 决定搜索框匹配哪些字段。毕设场景下这三个属性够用演示时能快速定位一条记录。学生端的操作边界靠接口层判断老师端靠 is_judge管理员端靠 is_staff把角色判断收口在视图入口处比到处散落 if 判断可维护得多。这套目录里如果还配了 Django REST Framework就用 permission_classes 来声明权限类效果等价但说法上更贴接口开发的习惯。5. 避坑与常见问题排查启动这套毕设时的五个典型故障毕设源码的坑分两类一类是环境坑一类是业务坑。环境坑的特点是报错堆栈五花八门业务坑则是程序跑起来但结果不对。这一章列的五个问题是我在 Windows 和 macOS 两种系统上复现时都踩过的按出现频率从高到低排序。5.1 MySQL 连接失败ERROR 2002 与 ERROR 1045现象执行 python manage.py migrate 时报 ERROR 2002 (HY000): Cant connect to local MySQL server through socket /tmp/mysql.sock或者报 ERROR 1045 (28000): Access denied for user。原因2002 说明 MySQL 服务没启动或者客户端把连接方式理解成了 Unix socket而实际 MySQL 跑在别的路径1045 是账号密码错误或者 root 用户用了 Django 驱动不支持的认证插件。解决Windows 上先去服务管理器确认 MySQL 服务已启动连接参数里把 HOST 显式写成 127.0.0.1避免走 socket密码错误就重置密码或者用 Navicat 图形化验证一遍再改 settings.py。换新电脑复现时可以把下面这几条按顺序排查sc query mysql # Windows 下查看 MySQL 服务状态 mysql -u root -p -h 127.0.0.1 # 手动验证连接是否通畅 python manage.py migrate # 通过后立即执行迁移5.2 安装依赖失败mysqlclient 编译报错现象pip install -r requirements.txt 时 mysqlclient 报 error: Microsoft Visual C 14.0 is required或者直接卡在 Building wheel for mysqlclient。原因mysqlclient 在 Windows 上需要编译原生扩展系统缺 MSVC 构建工具时必然失败。很多同学在这里反复找 python 安装教程其实跟 Python 版本无关是缺编译器。解决不要硬刚编译。下载对应 Python 版本的 mysqlclient 预编译 wheel 再 pip install或者换纯 Python 的 PyMySQL 驱动。换 PyMySQL 时在项目init.py 里加两行import pymysql pymysql.install_as_MySQLdb()这样 Django 底层还是用 MySQLdb 的调用方式实际驱动换成 PyMySQL这是 Windows 上复现这套毕设最快的后悔药。注意装完 PyMySQL 后要重启 runserver因为那个补丁代码只在进程启动时执行一次。5.3 Vue 前端能打开但接口全 404 或跨域报错现象页面样式和路由都正常但前端请求 /api/contests/ 返回 404或者浏览器控制台报 Access-Control-Allow-Origin 相关错误。原因没配代理时前端把请求直接发到了 8080 端口的 webpack 服务上而接口在 Django 里或者配了代理但接口路径前缀对不上Django 的 url 里写的是 /api/前端请求的是 /apis/。解决按第 3 章在 vue.config.js 里配置 proxy并确认 Django 的 urls.py 里确实有 /api/contests/ 这个路径。排查时先用浏览器直接访问后端接口能通再检查前端配置curl http://127.0.0.1:8000/api/contests/如果 curl 返回 JSON后端没问题问题一定在前端代理或路径前缀如果 curl 都 404老老实实回去看 urls.py。5.4 页面能登录但静态文件加载不出来现象Django Admin 后台打开只有文字没有样式图片和 logo 全是裂图。视频演示时这个问题最尴尬评委一眼就能看到。原因settings.py 里没配置 STATICFILES_DIRS或者 Vue 前端打包后的静态资源放在了 Django 管辖不到的位置。Django 默认只知道每个 app 下的 static 目录项目级的静态目录必须显式声明。解决在 settings.py 里做三处配置STATIC_URL /static/ STATICFILES_DIRS [BASE_DIR / static] STATIC_ROOT BASE_DIR / staticfiles开发环境有 django.contrib.staticfiles 自动服务配置完重启立即生效。生产部署前记得执行 python manage.py collectstatic把散落的静态文件收集到 STATIC_ROOT再交给 Nginx 托管。5.5 Vue history 模式刷新页面 404现象从首页点进竞赛详情没问题但直接刷新 /contest/3 这个地址时出现 404 或者空白页。原因history 模式下浏览器按真实路径向服务器要资源后端没有匹配 /contest/3 的路由就返回 404。这不是 Vue 代码问题是服务器不知道要回退到 index.html。解决最省事的是把路由 mode 改成 hashURL 会变成 /#/contest/3刷新不会 404缺点是 URL 带个 #。想保留 history在 vue.config.js 里加module.exports { devServer: { historyApiFallback: true } }生产环境则是 Nginx 的 try_files 指令把未知路径回退到 index.html在 Nginx 配置里写 try_files $uri $uri/ /index.html。我做演示时为了稳直接用 hash 模式答辩现场最怕的就是演示到一半刷出 404。6. 答辩前改造把通用源码变成你自己的毕设6.1 全局改名与去重拿到这套学科竞赛管理系统源码第一步不是急着加功能而是全局搜索替换项目名和页面标题。项目名、页面版权信息、论文里的项目名称、数据库库名保证代码、数据库、论文三处一致。我一般用编辑器的全局替换功能替换后重启前后端把登录、报名、打分完整走一遍确认没有残留旧项目名出现在页面或邮件模板里。这一步看起来琐碎却是查重和答辩演示时最容易扣分的隐性项。6.2 加一个低成本高辨识度的功能点如果时间充裕给系统加一个“导出报名名单 CSV”的功能性价比极高。代码量不大演示效果却很直观import csv from django.http import HttpResponse def export_registrations(request, contest_id): response HttpResponse(content_typetext/csv) response[Content-Disposition] attachment; filenamecontest_{}.csv.format(contest_id) writer csv.writer(response) writer.writerow([团队名称, 负责人, 成员数, 报名时间]) for reg in Registration.objects.filter(contest_idcontest_id): writer.writerow([reg.team_name, reg.student.username, reg.member_count, reg.created_at]) return response导出功能打通了“管理系统”和“办公场景”的闭环答辩时当场下载一个 CSV 文件比口述十个功能点都有效。注意加完新功能后要重新跑一遍原有流程确保没破坏报名和打分的逻辑。6.3 答辩验证清单验证项检查内容验收标准环境复现按第 3 章步骤在新机器上操作迁移、启动、登录全部通过数据一致性Admin 后台创建一条竞赛前端列表能立即看到权限边界学生账号访问管理接口返回 403 而非数据核心事务同一账号重复报名第二次被拦截并收到提示答辩演示前把这四项各走两遍。舞台上翻车的概率跟准备熟练度成反比尤其是环境复现和重复报名这两个点一个是稳定性一个是业务亮点两个都稳了答辩基本就稳了。从那以后我每拿到一套毕设源码都强制自己走一遍“备份数据库 → 重建环境 → 跑通全流程 → 再改代码”的顺序顺序错一次后面所有改造都建立在沙地上。这套学科竞赛管理系统从压缩包到能演示、能讲解整个过程就是按这个顺序来的。希望帮到你。本文还有配套的精品资源点击获取
返回列表