☰
基于SpringBoot的民运会赛务管理系统毕设实战与设计思路
2026/10/9 3:57:58 网站建设 项目流程

基于SpringBoot的民运会赛务管理系统,毕设这么做稳得一匹

每年毕业季都有大量Java方向的同学卡在毕设选题和落地这两道坎上,尤其是赛务管理、运动会报名这类偏业务型的系统,看着数据表没几张,真做起来却发现权限、赛程、成绩统计到处是坑。我这次把一个基于SpringBoot的民运会赛务管理系统完整走了一遍,从需求拆分、表结构设计、核心代码实现到调试运行,踩了不少坑也填了不少坑,趁热分享一下完整的落地思路,给正在做类似管理系统方向毕设的同学一个可以直接参考的路线。

这个系统本质上解决的是“一场综合体育赛事的组织者、参赛方、裁判三方之间的信息协作”问题。运动员报名、资格审查、项目分组、赛程编排、成绩录入、排名公示、公告通知,每一环都是独立的业务模块,但凑在一起就特别考察你对SpringBoot整体架构的理解。如果你正在纠结毕设选题,或者是想自学的同学,这篇内容对你应该挺有参考价值。

1. 项目整体设计与思路拆解

1.1 为什么选民运会赛务管理这个题目

毕设选题有个不成文的规矩:太简单的体现不了工作量,太复杂的自己hold不住。民运会赛务管理这个选题,恰好卡在一个很舒服的位置——业务场景完整、需求清晰、涉及角色多样,但单模块的复杂度又不会高到劝退你。

民运会和普通单项赛事管理有一个明显差异,就是参赛项目多、民族组别多、团体赛与个人赛并行,加上运动员资格审查(民族成分、组别划分)这种非通用赛事的特殊场景,比常见的运动会管理系统多出了不少可写的业务细节。这些细节恰恰是答辩时展示你“认真分析过需求”的支撑材料。

我见过不少同学选“学生管理系统”“图书管理系统”这类经典到不能再经典的题目,答辩时老师问一句“你的系统有什么业务难点”,直接卡住。而赛务管理系统你可以答出“多角色权限隔离、赛程冲突检测、团体成绩自动汇总、Excel导入运动员名单”这些具体的业务点,每一句都是真实际的工作量,老师一听就知道你不是糊弄的。

1.2 核心功能模块划分

整个系统我按使用者角色拆成了三个端:系统管理员端、裁判/工作人员端、运动员/代表团端。每个端的功能边界在需求分析阶段就要划清楚,不然后期改起来非常痛苦。

系统管理员端需要包含用户管理、代表团管理、比赛项目管理、赛程安排、场地分配、公告管理、系统参数配置这些基础功能。注意这里有个很容易忽略的点——管理员不是管“一切”的,赛程编排和成绩管理应该属于裁判或编排记录组,管理员主要负责基础数据的维护,这个边界上的划分在需求文档里写清楚,既可以展示你对RBAC权限模型的理解,也能避免所有功能都堆在管理员头上导致代码混乱。

裁判端的功能相对聚焦:比赛分组管理、成绩录入与修改、成绩审核、犯规/取消成绩处理。这里我建议把“成绩录入”和“成绩审核”拆开,录入的人不能直接审核自己录的成绩,这是实际赛事里防止误操作和舞弊的通用规则,虽然毕设系统里未必真的有多个裁判使用,但这个设计逻辑写进文档里是加分项。

代表团/运动员端主要是报名管理和成绩查询。报名分为个人项目和团体项目,团体项目要能选择参赛队伍成员,一个运动员可以报多个项目但不是无限报,要设置报项上限。成绩查询就是一个列表,运动员只看到自己的成绩和排名,按项目筛选、按赛事筛选。

1.3 技术选型背后的考量

技术栈上我选的是SpringBoot 2.7 + MyBatis-Plus 3.5 + MySQL 8.0 + Redis + JWT + Vue 3 + Element Plus,前后端分离的经典组合。这套组合在毕设场景下有几个明确的好处:

SpringBoot 2.7是目前教程资料最丰富的版本,网上遇到的坑几乎都能搜到解决方案,比直接用3.x踩新特性的坑稳妥得多。MyBatis-Plus帮我省掉了大量单表CRUD的重复代码,BaseMapper内置的方法直接覆盖80%的增删改查,自定义SQL只需要写多表关联和复杂统计。Redis用来存验证码和Token,正好可以解释清楚“分布式会话为什么用Redis而不用Session”。JWT做无状态认证,前端的登录状态管理逻辑也简单直观。

前端选Vue 3 + Element Plus是因为Element Plus的表格、表单、弹窗这些组件对后台管理系统的适配度高,做出来界面不至于太丑。很多同学的毕设项目其实并不需要写多复杂的前端交互,把管理端的表单和列表做好,就已经能支撑整个系统展示了。

有一点我在前期吃过亏,就是先写代码后画图。实际应该先把用例图、ER图、功能结构图画出来,哪怕是用手画都行,否则做到后期你发现自己加了一些字段和接口,需求文档里的ER图对不上,返工改文档比写代码还痛苦。

2. 核心业务建模与数据库设计

2.1 数据表设计与字段解析

数据库设计我这边总计规划了11张核心表,基本覆盖了系统的全部核心流程,建议在写这个项目时不要缩减核心表,表的数量和质量直接影响答辩时展示的“工作量”。

用户表的核心字段除了常规的username、password、phone之外,我额外加了user_type(角色类型)和status(账号状态)。角色类型用int存,0表示管理员、1表示裁判/工作人员、2表示代表团账号,这里不设计单独的角色表,是因为这个系统的角色是固定的三种,没有动态扩展需求。如果你把RBAC那套用户-角色-权限三张表强行套进来,复杂度翻倍但业务上用不到,反而显得设计过度,这个取舍需要想清楚。

代表团表要包含代表团名称、所属地区、领队姓名、领队电话、总人数等,参赛单位都希望在系统里看到自己队伍的完整性,字段这里要舍得给。

运动员表是核心中的核心,字段包括姓名、性别、民族、身份证号、组别(甲组/乙组)、代表团ID、参赛项目,以及一个status标记审核状态。民族这个字段在这个系统里不是简单的字符串,它和“资格审查”逻辑绑定,审核时要人工核对民族成分证件,建议状态机设计为0待审核、1审核通过、2审核驳回、3已报名未审核。

比赛项目表要区分项目类型是个人还是团体,个人项目有年龄组别限制,团体项目有人数限制。字段上我设计了项目名称、项目类型、组别、性别限制、报名人数上限、是否有预赛决赛之分。

赛程表字段是比赛日期、时间段、场地、项目ID、组别,这里有个关键点是“项目+组别+场地”要加唯一索引,防止同一个场地同一时间段被安排了多个不同项目。我一开始没加这个索引,到了测试阶段造数据的时候就出现了场地冲突的问题,后来才加上索引解决。

成绩表要区分预赛成绩和决赛成绩,我的做法是单独一个成绩表,带stage字段(0预赛、1决赛),成绩值和排名各一个字段,成绩的录入和审核状态也放在这个表里。小组排名、总排名这两类排名逻辑用SQL窗口函数算还是很方便的,这一块在实现环节细说。

公告表比较简单,标题、内容、发布人ID、发布状态、置顶标志、发布时间这些字段就够用。考虑到后期可能有附件需求,比如秩序册、竞赛规程这些PDF,所以我额外预留了一个attachment字段,虽然功能上我最后没有做完整的附件上传,但字段先留好,避免后面改表。

2.2 三种角色权限的设计思路

很多教程喜欢一上来就讲Spring Security + JWT怎么配置,但忽略了权限模型的设计。我这里用的是最常规的做法:Spring Security做认证,@PreAuthorize注解做接口权限控制,用户表里role字段决定能访问哪些接口。

具体实施上,管理员接口统一以/api/admin/**开头,裁判端以/api/judge/**开头,运动员/代表团端以/api/athlete/**开头,用三套Controller去组织。然后在SecurityConfig里配置路径规则,admin开头的接口需要管理员权限,judge开头的接口需要裁判权限,athlete开头的接口登录即可。这样设计的好处是权限控制逻辑非常直观,不需要去维护权限表,对于固定角色的毕设项目完全够用,而且答辩的时候你能很清晰地讲出“基于URL的权限隔离策略”这个设计。

有个细节是用户表的role字段和前端路由要联动。前端根据登录返回的role渲染不同的侧边栏菜单,这个我一开始没做好,后来通过动态路由解决了。Vue Router里用addRoute动态添加对应角色的路由表,刷新页面时要重新拉取一次登录信息和路由表,否则一刷新就跳回登录页,这个坑不少同学都会踩。

2.3 赛程编排的业务逻辑

赛程编排是这个系统业务上最值得展开的地方。普通运动会管理的赛程就是一个简单的时间表,但民运会涉及多个场馆、多个组别同时开赛,赛程冲突检测必须有。

我的做法是赛程表里维护开始时间和结束时间,新增赛程时先查同场地、同时间段的冲突记录。MySQL里判断两个时间段是否重叠的标准条件:new_start <= existing_end AND new_end >= existing_start。这个条件千万不能用=,否则边界情况会漏判,编码时很容易因为下意识忽略而搞乱赛程。

再一个容易忽略的逻辑是运动员兼项的赛程冲突检测。同一个运动员报了田赛和径赛,如果两个项目的比赛时间重叠了,系统应该要提醒。我的做法是在查询运动员报项时,把他报的所有项目关联赛程,然后做一次两两比较,有重叠就返回冲突信息。虽然是全量比对,但一个运动员最多报3个项目,3个项目两两比较也就3次循环,性能上完全没问题。这里必须预判逻辑合理性,不要为了性能上过度设计。

3. 关键功能模块的实现细节

3.1 运动员报名与资格审查流程

报名流程是我花时间最多的一条链路。办赛前期要做运动员资格审查,你要在需求里想清楚它的状态流转:代表团上传运动员信息后,初始状态是“待审核”,管理员逐条审核,通过后运动员才能报名具体项目。也就是说“建立运动员档案”和“报项目”是两步操作,很多人做这类系统容易把这两步合成一步,反而不符合实际赛事流程。

具体实现上,运动员档案表建好之后,通过一个status字段标识审核状态。前端界面上,代表团提交的运动员列表会出现在管理员的待办列表里,管理员点进详情能看基础信息、证件照上传情况,然后选择通过或驳回。驳回时要填写驳回原因,原因会推送给对应的代表团账号。这个推送功能我最初只打算做站内通知,但后来顺手把公告模块复用了,变了现逻辑也更完善,公告表里加一个notice_type字段(0全局公告、1定向通知),接收到定向通知时指定user_id即可。

报名项目这个接口要注意事务控制。一个运动员批量选择多个项目提交时,后端要循环插入报名记录,其中任何一条失败,整个报名操作应全部回滚。这个点用@Transactional注解解决,但有坑——Spring的自调用事务失效问题,Controller调Service,Service内部必须通过代理对象调用另一个事务方法,否则事务不生效。这类问题很容易在复杂的项目中踩坑,尤其当你在类内部直接调用时,乍一看没报错,但数据就到了不该到的状态。

3.2 赛事成绩录入与排名计算

排名计算是能体现编程功底的功能。民运会的排名规则并不复杂,个人径赛按成绩排序、田赛按成绩数值排序、团体项目按团队最终得分排序。难点在于分组排名和总排名同时存在。

我采用的是在成绩表中记录成绩值和组别,通过SQL窗口函数来计算排名:

SELECT athlete_id, score, RANK() OVER (PARTITION BY event_id, group_id ORDER BY score ASC) AS group_rank FROM result WHERE event_id = #{eventId}

需要注意,径赛成绩数值越小排名越靠前,田赛成绩很多是数值越大越远越高。用同一个SQL时得根据项目类型传入不同的排序方向,这个细节要控制好。这里因为项目类型不同导致排序方向不一样,所以建议在项目表里加一个sort_type字段,0表示时间型越小越好,1表示距离型越大越好,这样就不用硬编码判断了。

团体成绩这块我采用了“按团队汇总”的方式,而不是每个人单独录一份成绩。团体赛比如接力、团体赛跑,成绩表中加一个team_id字段,录入时绑定代表团ID,计算时按team_id聚合,再和同组别的其他团队比。个人赛则用athlete_id关联。一个成绩表同时支持个人赛和团体赛,这要归功于两个冗余字段,我用时觉得这个设计确实省事。

裁判录入成绩后,要经过审核才公布。审核状态我设计成0待审核、1已发布、2已驳回,只有已发布的成绩才会出现在查询和排名里。这个“状态驱动可见性”的思路直接套用到公告模块也成立,凡是内容展示类的数据,都建议加一个发布状态的字段来控制可见性。

3.3 公告发布与消息通知

公告这块功能虽然不复杂,但能体现完整度。我实现了系统公告的CRUD,置顶功能,以及定向通知推送。发布公告时可以选择发送范围,全站可见还是指定某个代表团。定向通知在公告表中通过target_user_id字段实现,为空就是全站可见,不为空就是定向推送给对应用户。

前端在NavBar做个通知铃铛组件,定时5分钟轮询一次新公告。这里没有用WebSocket的必要,轮询对这种低频通知完全够用,如果要展示“实时性”,写个定时任务轮询更简单,能大幅减少开发复杂度。定时轮询用setInterval实现就行,后端用一个查询接口返回当前用户最新N条公告,过滤条件就是发布状态为已发布、公告时间大于某个时间点或按置顶排序。

Redis在这里可以派上用场——记录每个用户上次拉取公告的时间,轮询时把时间戳传过去,后端只返回该时间之后的公告,避免每次全量拉取造成不必要的数据传输。这个优化在答辩时可以重点讲,属于“性能优化”的真实实践,一句话就能说明为什么缓存优于数据库直接查询。

4. 实操过程与核心环节实现

4.1 项目环境搭建与初始化

环境版本这块我建议固定一套,不要追求新版。我的环境是JDK 1.8、Maven 3.8、SpringBoot 2.7.18、MySQL 8.0.34、Redis 6.2(本地Windows跑一个Redis服务)。JDK 1.8在这个阶段最稳,SpringBoot 2.7对JDK 8的兼容性也最好。

创建项目使用Spring Initializr,选择Maven项目,Java版本选8,SpringBoot版本选2.7.18,依赖云选择Web、MyBatis、MySQL Driver、Redis、Validation、Lombok。Spring Initializr在线生成时没有MyBatis-Plus的选项,需要手动在pom.xml里加一行依赖。

<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.5</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.5</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency>

数据库建库用utf8mb4字符集,在建库SQL里加一行:

CREATE DATABASE civil_games DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

4.2 后端核心代码架构

我采用的包结构是按业务模块划分的,保证代码可读性,也方便答辩时展示你的代码组织能力。

com.example.civilgames ├── config // 配置类,SecurityConfig、RedisConfig、CorsConfig等 ├── controller // 控制层,按业务模块拆分 ├── service // 服务层,接口+实现类 ├── mapper // MyBatis-Plus的Mapper接口 ├── entity // 数据库实体类 ├── dto // 数据传输对象,接收前端请求参数 ├── vo // 视图对象,返回前端响应数据 ├── common // 通用结果包装类、分页对象、异常处理 └── utils // JWT工具类、日期工具类、Excel导入工具类

common包里放一个Result统一返回类和GlobalExceptionHandler全局异常处理器。统一返回结构在联调时能省很多事,前端只需要处理好一种返回格式,后端不管哪个接口出了问题,错误信息都以一致的结构返回。GlobalExceptionHandler用@RestControllerAdvice注解,业务异常用@ExceptionHandler捕获,参数校验异常也在这里统一处理。

JwtUtil负责生成和解析Token,把userId和role写入Token的claims中,过期时间设为7天。在拦截器里解析Token后把userId和role放入ThreadLocal,这样业务代码里能随时拿到当前登录人的信息。

public String generateToken(Long userId, Integer role) { return Jwts.builder() .setSubject(String.valueOf(userId)) .claim("role", role) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + 7 * 24 * 3600 * 1000L)) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); }

登录接口逻辑要覆盖返回前端导航菜单信息这一步。登录成功后除了返回Token,还要放在SecurityContextHolder里,这样Spring Security才能识别当前用户已认证。实际编码时,如果漏掉这一句就会出现“已登录但访问受控接口还是401”的诡异问题。但JWT模式下通常是不需要把SecurityContext塞进Session的,而通过注册自定义的OncePerRequestFilter来解析Token,再把Authentication对象放入SecurityContextHolder,这一步不能少。

4.3 前端核心页面与关键交互

Vue 3项目我用Vite搭建,方便快速开发。管理端的整体布局是左侧菜单栏、顶部导航栏、内容区三个区域,这个布局用Element Plus的Container组件完成。

登录页表单校验用el-form自带的rules属性,用户名密码非空校验、密码长度不超过20位、手机号格式校验这些基础校验规则在rules里配置好。后端也要做到一致性校验,双端校验体现出工程意识,避免只依赖前端校验带来的安全问题。

运动员报名页是这个系统交互最复杂的页面。前端要做一个动态表单:运动员先选择项目类型,然后根据项目类型动态加载下拉选项。个人项目直接选择项目名称并带出组别限制;团体项目要弹出一个子表单,勾选团员列表并限制最多选择人数。动态表单这个功能在前端代码里要单独写一个组件,不然页面会很难维护。

成绩录入页面我用的是el-table内联录入。裁判在表格里选择某个项目的参赛运动员列表,直接输入成绩数值,保存时批量提交。批量提交接口后端接收List ,用一个事务方法循环插入。score字段用v-model.number修饰符转成数字,不然输入的是字符串,后端Decimal字段接收时类型不匹配会报错。这里要注意,成绩学号类型相关的输入框建议都加上数字校验和范围校验。

5. 常见问题与排查技巧实录

5.1 数据库连接与驱动问题

这几乎是每个做SpringBoot + MySQL项目的同学都会碰到的头号问题。MySQL 8.0的驱动类名是com.mysql.cj.jdbc.Driver,不是旧版的com.mysql.jdbc.Driver。数据库URL要用如下格式:

jdbc:mysql://localhost:3306/civil_games?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true

serverTimezone参数不能少,否则会直接报时区错误。allowPublicKeyRetrieval=true是针对MySQL 8.0密码加密方式的兼容性配置,很多同学遇到“Public Key Retrieval is not allowed”异常,原因就是在连接串里缺这个参数。

如果本地数据库跑的是MySQL 5.7,驱动版本建议用5.1.49。如果你C盘MySQL是8.0但服务器部署的是5.7,这种环境不一致的情况会让编译通过但运行报错,排查起来很伤人。建议本地环境和测试环境尽量统一MySQL大版本。

5.2 接口明明登录了还是返回401

JWT登录失效问题,90%的情况出在Authorization请求头的格式上。HTTP标准要求Authorization头的格式是Bearer <token>,Bearer后面跟一个空格。我用拦截器解析Token时,如果直接把前端传来的完整Token拿去解析,Signature会不匹配报401。解决方案是统一剥掉Bearer前缀,无论前端传来的格式是否正确,后端都做防御性处理:

String token = authHeader.substring(7);

另一个常见问题是前端存储Token的key不一致。登录页存的key是token,请求拦截器里取的是accessToken,这就是你自己挖的坑了。建议统一用一个常量文件管理,比如const TOKEN_KEY = 'admin_token',所有地方都引用这个变量,不要裸写字符串。

5.3 跨域问题排查

前后端分离项目,跨域是逃不掉的问题。我的解决方案是在后端加一个CorsConfig配置类,针对所有请求放行特定来源:

@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("http://localhost:*") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); } }

这里有一个非常重要的坑:加了Spring Security之后,仅仅这样配置跨域还不够。因为OPTIONS预检请求默认会被Spring Security拦下来,跨域配置和Security配置之间优先级不对时,前端请求预检就返回403,实际接口根本到达不了。解决方法是在SecurityConfig中放行OPTIONS请求:

http.cors().and().csrf().disable() .authorizeRequests() .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()

同时CorsConfig里的allowedOriginPatterns要允许前端实际地址,如果你前端跑在5173端口而只放行了8080,同样跨域失败。

5.4 MyBatis-Plus字段自动填充踩坑

实体类里有create_time和update_time这两个字段,如果用MyBatis-Plus的自动填充功能,需要在实体字段上写注解:

@TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime;

然后还要配置一个MetaObjectHandler实现类才能自动生效。漏了后者,你会发现插入数据时这两个字段是null,时间列显示异常。如果不想用自动填充,也可以直接在数据库层用DEFAULT CURRENT_TIMESTAMP兜底,但这样Java代码里就不好读值,我建议还是实现MetaObjectHandler,代码简单清晰。

5.5 成绩排名并列问题

体育赛事里成绩并列的排名逻辑是有讲究的,如果两个人成绩相同,按田径规则应该是并列名次、跳过下一名。但SQL的RANK()和DENSE_RANK()是有区别的:RANK()当两个人并列第一时,下一个名次是第三名(跳跃了第二名);DENSE_RANK()则并列第一后下一个名次是第二名。具体用哪个要看赛事规则。常规赛事通常用RANK(),但最终项目说明里最好明确写清楚这个规则,否则答辩时被老师问一句“为什么不是DENSE_RANK”,你只能说清楚规则才显得专业。

因为在项目表里加了sort_type字段,所以计算排名的SQL要动态拼接排序方向。MyBatis的XML里可以用choose标签:

<choose> <when test="sortType == 0"> ORDER BY score ASC </when> <otherwise> ORDER BY score DESC </otherwise> </choose>

5.6 数据初始化与演示数据

毕设系统演示的时候数据量是一个容易被忽视但影响很大的点。空的运动员列表让界面看起来非常空,评阅老师打开系统发现什么数据都没有,观感上就输了。所以我写了一个数据初始化Runner,在应用启动时自动检查数据库是否已经有数据,如果没有则自动插入一批演示数据。

@Component public class DataInitializer implements CommandLineRunner { @Override public void run(String... args) { if (athleteMapper.selectCount(null) > 0) { return; } // 插入代表团数据 // 插入运动员数据 // 插入比赛项目数据 // 插入赛程数据 // 插入成绩数据 } }

演示数据的构造要逻辑合理,比如组别要覆盖甲组和乙组,项目要覆盖个人径赛、个人田赛、团体接力,成绩数据要先录一部分待审核和一部分已发布。这样打开系统,每个页面都有内容可以看,每个状态都有样例数据,评分体验直接拉高一个档次。

提示:插入演示数据时务必先清空相关表的自增主键,或者在插入时指定ID。避免因为外键关联导致ID错位后演示数据关联不上,这是插入测试数据最容易碰到的问题。

写在最后

整个系统从设计到写完,我大概花了两周左右的业余时间。说实话,难点从来不在某一段代码有多刁钻,而在于把十几个业务模块串起来的时候,你心里要有一条清晰的主线:身份认证怎么走、报名流程的状态怎么流转、成绩从录入到公示需要经过哪几步。想清楚这条主线,代码就是往里面填东西的事。

在这个过程中我深刻感受到一点:赛务管理这一类信息系统,业务逻辑是王道,技术是工具。技术选型再高大上,业务理解不到位,做出来的系统就是一堆没有灵魂的CRUD。反过来,把流程设计清楚了,SpringBoot这些工具用起来得心应手。

如果你也在做类似的毕设管理系统,或者打算拿这类题目去参加项目实践,最后的建议是:先画图再建表,先建表再写代码。不要一开始就闷头去写Controller,先把用例图画明白,把一张一张表的关系理清楚,后面的编码会顺畅很多。中途遇到问题了别慌,把报错信息完整复制到搜索框里查一下,几乎所有坑都有人踩过,你要做的是学会定位问题和验证修复方案,而这也是毕设真正要锻炼你的能力。

祝大家都能顺顺利利搞定自己的项目,答辩一遍过,毕业不留遗憾。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询