学科竞赛管理这件事,表面上是"发通知、收报名、交作品、打分数",真做起来却是一地鸡毛。我在开发这套基于Spring Boot的学科竞赛管理系统时,最深的感受是:业务本身不复杂,复杂的是把多角色、多流程、多状态的零散信息串成一条有序的链路。这篇文章就把这套系统的设计思路、数据库模型、核心代码、调试部署经验全部拆开讲一遍,覆盖从竞赛发布、在线报名、作品提交到专家评审、成绩公示的完整闭环。无论你是要做课程设计、毕业设计,还是想为公司或学校快速搭建一个竞赛管理平台,都能从里面拿到可以直接落地的方案和代码思路。
1. 学科竞赛管理的真实痛点:为什么需要一套专门系统
1.1 传统管理方式的信息孤岛问题
很多高校和机构在竞赛管理上长期依赖一套"原始组合拳":通知靠群聊和邮件,报名靠Excel表格来回传,作品统一交到网盘链接,评委打分是独立填表、再由一个人手工汇总。这套模式在参赛人数少的时候勉强能用,一旦竞赛类别超过十个、单场报名破百人,问题就会集中爆发。
最典型的是信息不同步。学生报名的Excel在A老师手里,作品提交记录在B老师手里,评委打分表在C老师手里,三方数据对不上时,你需要逐个核对"哪个学生报名了但没交作品"、"哪个评委还没提交评分"、"某场比赛的最终排名是怎么算出来的"。这种排查过程极其消耗时间,而且每届竞赛的流程稍有变化,上一届的表格模板就作废了,所有统计逻辑要重来一遍。
另一个隐性成本是数据不可沉淀。竞赛信息和获奖记录分散在个人电脑和聊天记录里,学校想做学科竞赛成果分析、学生综测加分审核或者竞赛育人成效汇报时,往往要临时向多个老师要数据,再手工整理成报表。没有一套统一的数据模型,这些历史数据就无法被有效利用。
1.2 系统要解决的四个核心问题
在设计这套系统之前,我明确列出了它必须要解决的四个问题。
第一,报名信息统一化。学生只要在线填写一次报名信息,后台自动关联学号、姓名、院系和联系方式,管理员不需要维护多份Excel。
第二,作品提交规范化。每场比赛单独设置提交起止时间、文件格式和大小上限,系统自动校验并记录提交人、提交时间和文件哈希,杜绝"我交了你没收到"的扯皮。
第三,评审流程透明化。多评委独立打分,系统自动汇总平均分和排名,学生可以在公示期内查看自己的成绩和作品状态,减少人工干预和争议。
第四,数据沉淀可复用。所有竞赛、报名、作品、成绩数据都进入数据库,后面接综测系统、做统计分析或者生成年度报告,只需要写几条SQL就能拿到汇总结果。
1.3 系统适用场景与目标用户
这套系统面向三类使用者:学生、教师(或竞赛管理员)、系统管理员。学生的核心诉求是快速找到感兴趣的竞赛并完成报名和作品提交;教师的核心诉求是发布竞赛、管理审核报名、组织评委评审并导出成绩;系统管理员的诉求是维护基础数据(院系、用户、竞赛类型)和系统参数。
从开发角度看,这个项目的复杂度也正好卡在一个甜区:它包含完整的用户认证、角色权限、CRUD、文件上传下载、状态流转、统计报表,技术点覆盖面广,但不涉及复杂的并发和分布式问题,非常适合作为Spring Boot的练手项目,也够得上一篇毕业论文的体量。
2. 技术选型与项目结构:为什么是Spring Boot而不是其他框架
2.1 技术栈全景
这套系统采用了目前Java web开发最主流的组合:
| 层次 | 技术选型 | 主要用途 |
|---|---|---|
| 后端框架 | Spring Boot 2.7.x | 提供IOC容器、自动配置、嵌入式Web容器 |
| 持久层 | MyBatis-Plus | 单表CRUD无需手写SQL,复杂查询用注解/XML |
| 数据库 | MySQL 5.7 / 8.0 | 存储用户、竞赛、报名、作品、评审等业务数据 |
| 权限安全 | Spring Security + JWT | 登录认证、接口访问控制、无状态会话 |
| 前端 | Vue 3 + Element Plus(或Thymeleaf) | 管理后台界面与用户端页面 |
| 构建工具 | Maven | 依赖管理与项目构建 |
| API调试 | Knife4j(Swagger增强) | 自动生成接口文档,联调效率倍增 |
需要特别说明一点:如果你不熟悉前后端分离,前端也可以直接用Thymeleaf模板引擎,在Spring Boot里写页面,工程结构更简单,适合课程设计。我这里采用的是前后端分离方案,后端只提供JSON接口,前端工程独立运行在Node环境里。
2.2 为什么是Spring Boot
回到一个经常被学生问到的问题:为什么不选SSH(Spring MVC + Struts + Hibernate)或者传统的SSM?原因其实很实在。
Struts和Hibernate技术已经明显过时,资料少、配置繁琐、性能和维护成本都不占优。而早期SSM虽然结构清晰,但需要写大量XML配置:数据源、事务、MyBatis配置、Spring配置、Web配置,五六个配置文件来回折腾,还没开始写业务代码就先被配置劝退。
Spring Boot最大的价值是"约定大于配置"。内嵌Tomcat,应用只需一个main方法就能启动;自动配置机制根据你引入的依赖自动装配Bean;配合Spring Boot Starter体系,引入依赖就能获得对应的能力。对于竞赛管理系统这种业务逻辑清晰、需要快速开发交付的项目,Spring Boot能大幅缩短搭建时间,把精力留给真正的业务代码。
另一个现实因素是就业和答辩的接受度。Spring Boot已成为国内中小型Java项目的标准形态,企业招聘要求里大量出现,毕业设计答辩时也更容易通过。后面接Flink、对接消息队列、做微服务,Spring Boot都是绕不开的基础层,所以用它不会走弯路。
2.3 目录结构设计的学问
项目拿到手,第一步不是急着写代码,而是把目录结构规划好。我这里采用经典的分层架构:
com.example.competition ├── Controller // 接收请求,参数校验,返回结果 ├── Service // 业务逻辑层,事务控制 │ └── impl ├── Mapper // 数据访问层,MyBatis-Plus接口 ├── Entity // 数据库实体类 ├── DTO // 前端传递的数据对象(如登录请求、报名请求) ├── VO // 后端返回给前端的视图对象(如排名信息) ├── Config // 配置类:安全配置、跨域配置、文件上传配置 ├── Common // 统一返回值、异常处理、常量、枚举 ├── Utils // 工具类:文件处理、JWT工具 └── CompetitionApplication.java // 启动类这个结构的好处是职责单一、依赖方向明确。Controller层只负责参数接收和结果封装,不写SQL;Service层处理业务规则,比如"报名前检查是否已报名""评审前检查是否处于评审期";Mapper层只做数据读写。很多初学者把业务逻辑写进Controller里,短期内代码能跑,但后面加需求时维护成本剧增——比如要给报名加人数上限,你需要在每个调用了报名接口的地方都改一遍。
另外建议把返回结果统一封装成Result<T>对象,包含状态码、消息和数据三个字段,这样前端处理接口返回值时逻辑可以统一。不要一会儿直接返回Map,一会儿返回实体类,接口风格混乱会让后续联调非常痛苦。
3. 核心功能模块拆解:从报名到评审的全流程闭环
3.1 三种角色,三套视图
系统按用户角色划分了三套功能视图,本质上是同一套后端接口在不同权限下的过滤结果。
管理员(admin)拥有全部权限,可以维护用户、竞赛类型、院系信息,审核异常报名,导出成绩报表。教师(teacher)负责发布竞赛、设置比赛时间节点、审核报名、分配评委、发布成绩。学生(student)登录后能看到所有可报名的竞赛,提交报名信息,上传作品,查看自己的评审状态和最终成绩。
权限控制在后端统一实现。登录成功后服务端生成JWT令牌,前端每次请求都带上令牌,后端通过Spring Security的@PreAuthorize("hasRole('ADMIN')")这类注解做接口级控制。注意不要只在菜单上做权限隐藏,接口必须同样校验,否则懂技术的学生直接构造请求URL就能越权访问管理接口。
3.2 竞赛管理模块:发布到结束的状态流转
一场竞赛的生命周期我把它设计为五个状态:草稿(DRAFT)、报名中(REGISTERING)、评审中(REVIEWING)、已结束(FINISHED)、已取消(CANCELED)。教师在后台创建竞赛时先保存为草稿,确认信息无误后发布进入报名中;报名截止时间到达后系统自动或手动将竞赛置为评审中;所有评委打完分并确认发布成绩后,竞赛进入已结束。
这个状态字段是实现整个系统流转的枢纽。所有关键操作的合法性都依赖状态判断:报名接口先检查状态是否为报名中;作品上传接口检查是否在作品提交时间段内;打分接口检查是否处于评审中。建议用枚举类维护状态值,而不是散落的魔法数字。
@Getter public enum CompetitionStatus { DRAFT(0, "草稿"), REGISTERING(1, "报名中"), REVIEWING(2, "评审中"), FINISHED(3, "已结束"), CANCELED(4, "已取消"); private final Integer code; private final String desc; CompetitionStatus(Integer code, String desc) { this.code = code; this.desc = desc; } }3.3 报名与审核:怎么避免重复报名和资格问题
报名是并发操作最集中的环节。学生点击报名时,后端不能只做简单的INSERT,得先处理三个问题。
第一,是否重复报名。竞赛报名表对"竞赛ID+学生ID"建唯一索引,同时Service层在插入前查询一次,双重保险。单纯靠代码判断有并发风险,两个请求同时进来可能都查不到记录然后都执行插入,唯一索引是最后的防线。
第二,是否还在报名时间内。系统判断当前时间是否在竞赛的报名开始和结束时间之间,不在区间内直接拒绝报名,并给前端返回明确提示。
第三,是否超过人数上限。竞赛表里维护一个max_people字段,报名时先执行一条SELECT COUNT(*),如果已达上限则报名失败。更严谨的做法是使用乐观锁或UPDATE ... SET registered_count = registered_count + 1 WHERE id = ? AND registered_count < max_people的原子自增方式,避免高并发下超卖。竞赛管理系统的并发量通常不高,但把这个设计讲出来,在答辩中会成为加分项。
报名审核方面,我保留了"教师审核报名资格"的能力。教师可以看到报名列表,对不符合条件的报名执行驳回操作,驳回时填写原因,学生端会收到审核状态变更提示。
3.4 作品提交:文件上传的校验与存储
作品提交是本系统最容易踩坑的模块,因为文件上传涉及IO操作、路径安全、大小限制、重名覆盖等多个问题。
我在作品上传接口里做了四件事:
- 校验文件格式。通过文件扩展名和MIME类型双重判断,竞赛可以自行配置允许的格式列表,比如论文类允许PDF、Word,设计类额外允许图片和压缩包。
- 校验文件大小。Spring Boot的
spring.servlet.multipart.max-file-size配置全局上限,同时针对不同竞赛类型做细分限制,防止有些人传几个G的视频把服务器塞满。 - 重命名存储文件。不使用用户上传的原始文件名,而是按"竞赛ID_学号_时间戳_随机数.扩展名"的规则重新生成文件名,避免两个学生上传同名文件互相覆盖,也避免文件名包含特殊字符引发安全问题。
- 记录文件元数据。文件本身存储到服务器指定目录,数据库表只存文件路径、大小、上传时间、哈希值。这样数据库体积不会快速膨胀,上传下载速度也有保障。
String ext = FilenameUtils.getExtension(file.getOriginalFilename()); String newFileName = competitionId + "_" + stuNo + "_" + System.currentTimeMillis() + "_" + RandomUtil.randomNumbers(4) + "." + ext; File target = new File(uploadDir + "/" + newFileName); file.transferTo(target);3.5 评审打分:多评委独立打分与自动汇总
评审阶段是最能检验系统设计水平的地方。一场竞赛通常有3-5名评委,每个评委面对几十份作品,打完分后系统要自动汇总排名。
我采用的方案是:系统为每份通过初审的作品生成评审记录,关联多条"评委打分子表",每个评委登录后只能看到分配给自己的评审列表并打分提交。为防止评委之间互相干扰,评委端不显示其他评委的打分情况。全部评委提交后,系统自动计算平均分和排名,教师确认无误后一键发布。发布后成绩公示列表对所有参赛学生可见,学生只能看到自己的分数、排名和奖项,不能看其他人的成绩。
打分项可以拆成多个维度,比如创新性40分、完成度30分、实用性30分,每个维度独立打分后加权求和。这种细分维度在设计类竞赛里尤其常见,数据库里的评审表就得多加几个字段。第4章会有详细的表结构说明。
4. 数据库设计:竞赛业务的数据模型怎么建
4.1 六张核心表,撑起整套业务
数据库是整个系统的地基,表字段的合理程度直接决定后面写业务代码的顺畅程度。这套系统最核心的六张表如下。
| 表名 | 用途 | 关键字段 |
|---|---|---|
| sys_user | 用户表(学生/教师/管理员) | id, username, password, real_name, role, department_id |
| competition | 竞赛表 | id, title, type_id, status, max_people, register_start_time, register_end_time, submit_start_time, submit_end_time |
| registration | 报名表 | id, competition_id, student_id, audit_status, create_time |
| work | 作品表 | id, competition_id, student_id, work_name, file_url, file_size, submit_time |
| review_item | 评审项配置表 | id, competition_id, item_name, max_score, weight |
| review_record | 评委打分表 | id, work_id, reviewer_id, item_id, score, comment, submit_time |
其中需要重点关注的是评审相关的两张表。评审项配置表把"一场竞赛有哪些打分维度、每个维度占多少分"做成动态配置,而不是写死在代码里,这样不同竞赛可以使用不同的评分标准,系统通用性更强。评委打分表的一条记录代表"某个评委对某份作品的某个维度打了一次分",同一个评委同一份作品有N条维度分记录,统计平均分时按维度权重汇总即可。
4.2 外键与索引设计
字段定好了,下一步是索引设计。很多初学者做数据库设计时不建索引,数据量小的时候没感觉,数据一多查询就明显变慢。
我的索引设计经验总结如下:
- 唯一索引:
registration表的uk_competition_student(competition_id, student_id),防止重复报名;sys_user表的uk_username(username),保证用户名唯一。 - 普通索引:
registration表的idx_competition_id和idx_student_id,用于快速查询某场竞赛的所有报名学生、某学生报名的所有竞赛;review_record表的idx_work_id和idx_reviewer_id,用于按作品汇总成绩和查看评委评分情况。 - 组合索引:
work表的idx_competition_submit(competition_id, submit_time),按竞赛和时间范围筛选作品记录。
外键我建议在逻辑层面维护,而不是在数据库物理层面加太多FOREIGN KEY约束。原因是真实的项目中,物理外键会带来删除数据时的连锁约束问题,很多团队为了灵活性会选择不加物理外键,依靠业务代码保证引用完整性。但在答辩时,你要能说清楚"为什么表与表之间有关联关系但在数据库里没建外键",否则老师会认为你设计不严谨。
4.3 设计上的三个教训
第一,不要过度冗余。我最初在competition表里冗余了一个register_count字段,用来快速展示报名人数。初衷是减少COUNT查询,但维护这个字段需要保证每次报名、退赛、审核驳回时都同步更新,稍有不慎就会数据不一致。最终我保留了它,但加了一道定时校验任务,每天自动对账一次。如果你的场景并发不高,直接用COUNT查询也完全没问题。
第二,状态字段用tinyint存整数,不要用字符串。字符串状态容易拼写错误,也无法用<、>做范围判断,比如查询"所有报名中和评审中的竞赛"用status IN (1,2)就非常方便。
第三,所有时间字段统一用datetime,不要存时间戳整数。虽然时间戳排序和比较方便,但直接看数据库里的数字很难快速判断业务时间,排查问题时极度痛苦。配合MyBatis-Plus的自动填充功能,创建时间和更新时间在插入和更新时自动赋值,不用每个Mapper手动写当前时间。
5. 关键实现细节:配置、权限、文件上传与状态流转
5.1 配置文件中的关键参数
application.yml里有几项配置直接影响系统稳定性,单独拿出来说明一下。
server: port: 8080 servlet: context-path: / spring: datasource: url: jdbc:mysql://localhost:3306/competition_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver servlet: multipart: max-file-size: 50MB max-request-size: 100MB mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0 jwt: secret: your-secret-key-please-change-in-production expire: 604800数据源URL里的characterEncoding=utf8和serverTimezone=Asia/Shanghai是很多初学者必踩的坑。不加编码配置会导致中文乱码,不加时区配置在MySQL 8.x下会报时区错误。
MyBatis-Plus的map-underscore-to-camel-case: true把数据库的user_name自动映射到实体的userName,避免手写大量@TableField映射。
逻辑删除字段是另一个建议:给所有业务表加一个deleted字段,用MyBatis-Plus的全局逻辑删除配置,删除操作变成更新操作,保留历史数据可追溯。这在论文的数据库设计部分也是一个亮点。
5.2 登录鉴权与密码加密
密码存储绝对不能明文。我用Spring Security自带的BCryptPasswordEncoder做密码哈希,每次校验时调用matches方法即可。BCrypt的特点是自动加盐、计算成本可调,即便数据库泄露,攻击者也很难从哈希反推出原始密码。
JWT的生成和校验逻辑封装在工具类里,登录成功后服务端返回一个有效期7天的token。前端把token存在本地,请求时放到请求头Authorization字段中。服务端通过一个拦截器或Spring Security过滤器链统一解析token并设置用户上下文。
这里有一个容易被忽略的细节:修改密码后要让旧token失效。最简单的方案是JWT里携带一个token_version字段,用户表里维护版本号,每次修改密码版本号加1,校验token时对比版本号。因为这是毕业设计或课程设计,复杂方案不上也可以,但把这个话题提出来,会让答辩老师觉得你考虑过安全问题。
5.3 文件上传的完整链路
文件上传除了第3.4节讲到的存储细节,还有一个容易忽略的点:下载时的鉴权。竞赛作品往往涉及隐私和防抄袭,不能允许任何人凭URL直接下载。我的方案是下载接口要求携带token,后端将文件以ResponseEntity<byte[]>或StreamingResponseBody形式返回,并设置Content-Disposition: attachment。
另一个点是上传目录的分离。配置一个upload.dir参数,生产环境下把上传目录放在应用外部磁盘,而不是放在jar包内部或项目源码目录里。这样系统升级重新打包时,历史文件不会丢失。如果你把文件放在src/main/resources下,打包后文件会被写进jar包,重启就可能覆盖丢失,这是真实项目里一个非常严重的隐患。
5.4 用状态机思路管理报名到成绩的流程
业务流转用一个简单的状态机常量类维护,比在每个Service里散落if (xxx) { }更清晰。状态流转规则如下:
| 当前状态 | 允许的操作 | 目标状态 |
|---|---|---|
| 草稿 | 发布 | 报名中 |
| 报名中 | 报名截止/手动关闭 | 评审中 |
| 评审中 | 全部评分完成并发布成绩 | 已结束 |
| 报名中/评审中 | 管理员取消 | 已取消 |
每个操作在Service层先查竞赛状态,再用Assert.state或自定义业务异常做校验,不满足条件就抛出带提示信息的异常,由全局异常处理器统一返回给前端。全局异常处理器的作用是兜底,让用户看到的错误信息既友好又不暴露SQL异常等敏感细节。我的实现是一个@RestControllerAdvice类,里面定义了handleBusinessException、handleValidationException和handleException三个方法,分别处理业务异常、参数校验异常和未知异常。
6. 本地调试与部署:从开发环境到跑通的完整链路
6.1 环境准备清单
项目要跑起来,先确认下面几项环境:
| 组件 | 推荐版本 | 说明 |
|---|---|---|
| JDK | 1.8 或 11 | Spring Boot 2.7支持Java 8,如果是Spring Boot 3.x则需要Java 17 |
| Maven | 3.6以上 | 建议配置国内镜像源,否则依赖下载慢到怀疑人生 |
| MySQL | 5.7或8.0 | 注意8.0的驱动类换成了com.mysql.cj.jdbc.Driver |
| IDEA | 2022+ | 社区版也可以,但Ultimate版对Spring Boot的调试支持更好 |
| Node.js | 14+ | 前端工程运行需要,Vue项目构建和依赖安装都靠它 |
环境准备阶段最大的坑是JDK版本和Spring Boot版本的匹配。如果你用Spring Boot 3.x,JDK必须是17及以上;如果你用JDK 8,最高能用Spring Boot 2.7.x。很多人在网上随便复制一套Spring Initializr生成的工程,本机是JDK 8却引入了Spring Boot 3.2,一启动就报错。我的建议是:课程设计直接用Spring Boot 2.7 + JDK 8的稳定组合,网上资料最多、问题最容易搜到。
6.2 数据库初始化的常见坑
数据库脚本的执行顺序很重要。正确的顺序是先创建数据库、再创建用户相关基础表、然后插入初始管理员账号。
MySQL 8.0下执行.sql脚本时最常遇到的两个问题:一是默认字符集不是utf8mb4,中文插入后变乱码。解决办法是建库时显式指定:
CREATE DATABASE competition_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;二是MySQL 8.0的认证插件改成了caching_sha2_password,一些老版本驱动连不上。如果你用的是5.x的驱动jar,需要执行ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的密码';换回旧认证方式,或者直接把驱动升级到8.x。
6.3 后端接口调试的常用方法
我不建议用浏览器地址栏输入URL的方式来调试POST接口,信息量太少了。实际开发中我主要用两个工具。
第一个是Knife4j。Spring Boot工程里引入knife4j-spring-boot-starter依赖,启动后访问/doc.html就能看到所有接口的在线文档,参数结构、返回示例一目了然,还支持直接在线调试。它比Swagger原生UI好看很多,而且中文界面更友好。
第二个是Postman。用它做接口调试更灵活,可以保存接口调用历史、设置环境变量、写自动化测试断言。调试文件上传接口时,Body选form-data,文件字段选File类型即可。调试JWT鉴权的接口时,在Authorization标签页直接选择Bearer Token并粘贴token值即可。
6.4 本地打包与服务器部署
后端打包前先确保application.yml里所用数据库地址、账号密码无误,然后执行Maven打包命令:
mvn clean package -DskipTests打包完成后,target目录下会生成一个xxx.jar文件。本地可以用java -jar xxx.jar直接启动验证。部署到Linux服务器时,我习惯写一个简单的启动脚本,内容大致如下:
#!/bin/bash APP_NAME=competition-system.jar nohup java -jar /opt/app/$APP_NAME \ --spring.profiles.active=prod \ --server.port=8080 \ > /opt/app/logs/console.log 2>&1 & echo "Application started, pid: $!"这个脚本把日志输出到文件而不是直接扔到终端窗口,关闭SSH连接后服务也不会停止。如果你没有生产服务器,用云服务器或者虚拟机都可以,注意在云服务商的安全组策略里放行8080端口,否则外部访问不到。
前端如果用的是Vue工程,构建命令是npm run build,产物在dist目录下。部署时可以用Nginx托管静态文件,同时配置反向代理把/api请求转发到后端的8080端口,这样前端和后端就统一到同一个域名和端口下,避免跨域问题。
7. 配套论文与答辩准备:1万字文档怎么写才有价值
7.1 论文结构的组织思路
这套系统配套的论文文档,除致谢和参考文献外,核心章节通常按以下逻辑组织:
- 绪论:写背景和意义,重点说明学科竞赛信息化管理的必要性,加上国内外研究现状和论文结构安排。
- 相关技术介绍:写Spring Boot、MyBatis-Plus、MySQL、Vue的核心特性,注意不要大段抄官方文档,要结合你项目中的应用场景来讲。
- 系统需求分析:先写可行性分析(技术、经济、操作层面),再画功能需求和非功能需求,配合用例图说明三种角色的用例。
- 系统设计:写总体架构图、功能模块划分、数据库E-R图和表结构设计。数据库部分建议把每张表的字段都列出来,并解释每个字段的用途。
- 系统实现:按模块逐一贴核心代码片段,配页面截图,说明关键逻辑实现。
- 系统测试:整理测试用例表,包含测试项、预期结果、实际结果和是否通过。功能测试之外,建议补充一段性能测试描述,说明系统在并发场景下的表现。
7.2 图表绘制的几个建议
论文中需要配图的地方很明确,下面几个图是必备的:
- 系统总体架构图:展示浏览器、前端、后端、数据库的分层架构,用Visio或Draw.io绘制。
- 功能结构图:把所有功能模块按树形结构画全,这是功能设计部分的基础。
- E-R图:画出实体之间的关系,至少包含用户、竞赛、报名、作品、评审记录六个实体。用"矩形表示实体、椭圆表示属性、菱形表示关系"的标准画法。
- 系统流程图:展示学生从注册到查看成绩的完整流程。流程图绘制时注意用标准流程图形状,不要自己发明符号。
需要特别提醒:不要从网上直接截图别人的图。答辩老师对论文里的图非常敏感,如果系里能查到你的图在其他论文里出现过,或者图片风格和正文排版明显不统一,很容易被质疑。用Draw.io自己画一遍,风格统一,内容贴合你的系统,反而更省心。
7.3 答辩时容易被问到的几个问题
答辩中最常见的问题基本集中在设计决策上,这里提前给大家准备好回答思路。
问题一:"为什么选择Spring Boot而不是Spring Cloud?"回答要点:系统规模较小、并发量适中,单体应用足以支撑,Spring Cloud引入注册中心、网关等组件会显著增加部署和运维成本,对当前项目属于过度设计。
问题二:"数据库为什么不用外键?"回答要点:物理外键会在插入、更新、删除时带来额外的约束检查开销,一旦出现误删还会连锁影响关联数据。为了系统扩展性和灵活度,采用逻辑外键配合业务代码保证数据一致性。
问题三:"并发报名时你怎么防超报?"回答要点:一是报名表唯一索引防止重复报名;二是采用原子UPDATE自增报名人数并判断是否达到上限;三是利用MySQL事务隔离级别保证数据一致性。
问题四:"如果评委人数很多,系统要怎么扩展?"回答要点:当前设计评审记录按作品和评委存储,横向扩展可以按竞赛拆分数据库表或引入消息队列异步处理,但当前规模下单体架构足够。
7.4 论文写作的避坑建议
写论文最容易出现的问题就是"需求分析写得像技术文档",大段描述"系统支持XXX功能",但缺少分析的过程。正确写法是:先描述问题场景,再说明为什么需要这个功能,最后给出解决思路。比如报名模块,应该先写"传统报名方式存在数据分散、无法实时统计的问题",再提出"设计在线报名功能,支持学生填写信息、教师审核、系统自动计数"。
另外一个重点是字数控制。1万字以上的论文中,实现部分的代码不要贴太多,每段代码后面都要有文字解释代码背后的思路。如果一段代码占半页却没有一句解释,答辩老师会认为你是凑代码量。数据库设计部分同样需要配合字段说明表,不要只丢一张建表SQL就完事。
最后提醒一点:所有图片、表名前要有编号和图题,正文中要通过"如图X所示""如表X所示"来引用,这是论文格式的基本要求,很多学生因为这个小细节被扣分。
我个人在实际开发这类管理系统时的体会是:业务逻辑其实不难,真正影响体验的往往是那些"看不见的细节",比如文件上传路径的规划、状态流转的严谨性、接口返回结构的统一性。这套学科竞赛管理系统做完之后,最大的收获不是实现了多少功能,而是建立了一种"先设计状态和数据结构、再写业务代码"的思维方式。后续你想把它扩展成支持多轮评审、对接综测加分系统、甚至接入数据分析展示竞赛成果趋势,都会顺畅很多。如果你正在做类似的项目,建议先把数据库表和状态流转画明白再动手写代码,能少走不少弯路。