1. 这个兼职平台系统到底解决什么问题
做 JavaWeb 开发这几年,有一类项目几乎每隔一段时间就会在技术群里被重新问起,就是兼职平台、二手交易、校园服务这类信息撮合系统。而 Spring Boot + SSM + Thymeleaf + MySQL 这个技术组合,又恰好是绝大多数初学者和应届生最熟悉的一套路线,所以“基于 JavaWeb 和 MySQL 的 Spring Boot 兼职平台系统”才会成为 Java 方向课程设计、毕业设计的常青选题。
先把这个系统是什么说清楚。它本质上是一个连接“兼职发布者”和“兼职求职者”的信息平台,核心流程包括:企业或个人发布兼职信息,学生或社会求职者浏览、报名、收藏,管理员负责审核和对整体数据进行管理。整个项目采用 Java + Spring Boot 作为后端框架,SSM 指 Spring + SpringMVC + MyBatis 这套经典分层架构,前端页面使用 HTML + Thymeleaf 服务端模板渲染,局部交互通过 AJAX 完成,项目构建和依赖管理交给 Maven,数据存储落在 MySQL 上。
这个项目能解决的问题很明确:传统兼职信息往往散落在微信群、QQ 群、贴吧和各类中介手里,信息真假难辨,报名流程不透明,结算无保障。通过平台化方式,至少可以把发布、审核、报名、确认、评价这几个环节标准化,让兼职信息有一个相对可信的流转载体。对于开发者来说,这个项目带有完整的用户体系、业务流转和后台管理,是练习 Web 全流程开发非常合适的载体,既不像商城那样庞大复杂,又比单纯的学生管理系统更有业务感。
这套内容适合谁看?一个是正在做课程设计或毕业设计的在校生,可以拿它作为建模参考;一个是刚学完 Spring Boot 基础、想找一个完整项目练手的初级开发者,可以照着复现整个开发过程;还有一个是培训讲师或技术博主,需要一套结构清晰、可拆解讲授的案例项目。无论你属于哪一类,这篇文章会把功能设计、技术选型、数据库建模、核心实现和踩坑记录都铺开来讲,尽量做到可以直接照做。
2. 技术选型:为什么是 Spring Boot + SSM + Thymeleaf + Maven + AJAX + MySQL
2.1 Spring Boot 与 SSM 的关系,先理清楚
很多初学者看到“Spring Boot + SSM”会疑惑,SSM 不是 Spring + SpringMVC + MyBatis 吗,Spring Boot 用启动器方式管理依赖和自动配置,这两者不冲突吗?实际上完全不冲突。
Spring Boot 并没有取代 Spring,它只是简化了 Spring 应用的搭建和配置过程。你引入了 spring-boot-starter-web,底层依然走的是 SpringMVC 那套请求分发机制;你引入 mybatis-spring-boot-starter,连接的还是 MyBatis 这个 ORM 框架。所以这个项目标题里的 SSM,准确理解应该是:在 Spring Boot 的自动配置框架下,使用 Spring 管理业务对象、SpringMVC 处理 HTTP 请求、MyBatis 操作 MySQL 数据库,也就是“Spring Boot 整合 SSM 三层架构”。
选择这种方式而不是直接用传统 SSM 的 XML 配置时代,最直接的原因是开发效率。传统 SSM 项目建一个空壳要写一堆 web.xml、spring-context.xml、springmvc.xml,还要处理 jar 包冲突,光环境搭建就能劝退一批新手。Spring Boot 把这一切收敛为几个 starter 依赖和一份 application.yml,原来大半天的初始化工作压缩到十几分钟,而且是约定大于配置,项目结构一目了然。
不过这里有个实践经验想提醒你:Spring Boot 集成 SSM 之后,写代码的思路其实和传统三层没本质区别,Controller 层接收请求、Service 层处理业务逻辑、Mapper 层操作数据库这条链路不会变。所以如果你是拿这个项目练手,不要只顾着“会跑”,要把三层职责边界吃透,面试时被问到“一次请求怎么从浏览器走到数据库再回来”才能讲得明白。
2.2 Thymeleaf 模板引擎的价值
现在越来越多的新项目选择前后端分离,Vue/React + Spring Boot 接口,为什么这个项目还要用 Thymeleaf 做服务端渲染?要理解这个选择,得回到项目定位和开发者的学习目标上。
Thymeleaf 是服务端模板引擎,它最大的特点是 HTML 文件可以直接用浏览器打开预览,因为模板属性都是在标准 HTML 标签属性上扩展的,不像 JSP 那样需要容器解析才能看到效果。开发调试时,前端页面和后端模板可以相对独立,联调成本低。对于课程设计和毕设场景,这非常实用,因为大部分同学没有专业前端分工,一个人要做完所有事,用 Thymeleaf 在 Java 代码里通过 Model 把数据塞给页面,比从头搭一套 Vue 脚手架再写接口联调要快得多。
Thymeleaf 配合 AJAX 使用才是这套方案的完整形态。页面加载时用服务端渲染把骨架和数据首屏输出,保证直出速度和 SEO 友好度;局部交互,比如搜索联想、报名操作、收藏切换、状态更新这些,用 jQuery 的 AJAX 请求异步处理,返回 JSON 数据,再局部更新 DOM。两者结合,既保留了传统 Web 开发的可维护性,又兼顾了用户体验,不会出现每次点击都整页刷新那种割裂感。
2.3 Maven 的作用不只是一个构建工具
Maven 在标题里单独列出来,说明这个项目对构建管理是认真对待的。很多新手会觉得 Maven 无非就是 pom.xml 里声明几个依赖,然后点一下刷新,没有什么技术含量,这个理解有点浅。
Maven 在这个项目中承担三件事:依赖管理、标准化构建流程、环境一致性保证。依赖管理体现在你只需要声明 spring-boot-starter-web、mybatis-spring-boot-starter、mysql-connector-java,Maven 会自动把传递性依赖拉齐,比如 spring-web、spring-webmvc、jackson 这些都会被一并处理,不需要手工下载 jar 包再 Build Path。标准化构建体现在生命周期上,mvn clean package 能完成编译、测试、打包一整条流水线,毕业设计通常要求交付可运行 jar 包,一条命令就能出来。环境一致性则是 Maven 最容易被忽视的价值,只要有 pom.xml,换一台电脑也能拉到相同版本的依赖,不存在“我这儿能跑你那儿跑不了”的版本错乱问题。
实际使用中建议把 Maven 仓库镜像换成阿里云公共仓库,原理是让依赖下载请求走国内节点,速度提升非常明显。如果不换,第一次创建 Spring Boot 项目拉几百 MB 依赖时的等待感会相当煎熬。
2.4 MySQL 作为存储层的取舍
MySQL 在这个项目里是唯一的数据持久层,承担用户信息、兼职信息、报名记录、收藏记录、审核日志等数据的存储任务。选它而不选 Oracle 或者 PostgreSQL,很大程度是生态熟悉度决定的。
针对兼职平台这个业务场景,MySQL 的 InnoDB 引擎提供了事务支持,报名操作和状态更新可以放进事务里保证一致性;同时市面上大量公开资料和现成 SQL 考题都基于 MySQL,学生和初级开发者遇到问题更容易查到答案。更现实的原因在于,Spring Boot 的自动化配置对 MySQL 支持非常成熟,只需要配置 jdbc-url、username、password,MyBatis 就能顺利连上,换数据库反而可能要调整方言和驱动配置。
建表方面有一点想特别提醒:兼职平台涉及金额结算场景时,金额字段一定不要用 float 或 double,要使用 decimal,比如 decimal(10,2),否则浮点运算会产生精度缺失。这个知识点也是 Java 基础面试里“BigDecimal 为什么不能用浮点直接算”的经典场景。
2.5 AJAX 在整体交互中的定位
AJAX(Asynchronous JavaScript And XML)本质上不是什么高大上的技术,就是浏览器通过 XMLHttpRequest 对象与服务器进行异步数据交换。在这套技术栈里,通常直接用 jQuery 封装的 $.ajax 或 $.post,逐步替换成原生 fetch 也可以,但为了代码可读性,项目里用 jQuery 更省事。
这个系统的核心异步场景包括:兼职列表的分页切换、按条件筛选兼职、用户报名操作、收藏状态切换、管理员审核操作,以及用户名的唯一性校验等等。拿“报名”来举例,用户点击“我要报名”按钮时,页面不应该整页刷新,而是通过 AJAX 把兼职 ID 发给后端,后端校验报名条件、插入报名记录后返回一个 JSON 结果,页面再根据结果弹出提示并更新按钮状态。整个过程用户无感知刷新,交互反馈即时。
需要注意 AJAX 请求的 Content-Type 和后端接收数据的匹配问题。如果前端用 $.post 发的是 form 格式数据,后端用普通 POJO 参数接收即可;如果前端用 application/json 发送 JSON,那后端必须用 @RequestBody 才能正确绑定。这个错配是联调时最高频的报错来源之一,后面会专门展开。
3. 系统设计:从需求分析到数据库建模
3.1 角色与权限
兼职平台系统至少要有三种角色:学生(求职者)、企业(兼职发布者)、管理员(运营维护者)。按需求复杂程度,还可以扩展一个中介角色,但作为基础版,三种角色足够覆盖主业流程,也是答辩时最好讲清楚的部分。
三种角色在系统中的权限边界要划分明确,否则容易出现越权操作。学生的核心操作是浏览兼职、搜索筛选、报名兼职、收藏兼职、查看自己报名记录和结算状态;企业的核心操作是发布兼职、维护自己发布的兼职信息、查看报名者列表、确认兼职完成并结算;管理员有最高权限,能审核兼职信息、禁用违规账号、下架违规兼职、查看平台整体统计报表。
权限控制在实现上通常有两种做法。一种是简单的拦截器方案:基于 URL 前缀区分角色,比如 /student/** 只有学生角色可访问,/company/** 只有企业可访问,/admin/** 需要管理员账号,配合 Session 中保存的角色标识进行校验。另一种是引入 Spring Security 或 Shiro 做精细化权限管理。对于课程设计级别,拦截器方案足够用,而且代码量少、好解释;但如果项目说明里写了“权限管理”作为亮点,可以引入 Spring Security,用注解方式控制接口权限。我建议根据答辩要求酌情选择,功能不是越复杂越好,讲得清楚才是关键。
3.2 数据库表结构设计
数据库是这类系统的信息地基。先看最核心的几张表,我以实际可落地的 SQL 结构为例:
第一张用户表t_user,字段包括:id(主键自增)、username(用户名,唯一)、password(密码,需要加密存储)、real_name(真实姓名)、phone(手机号)、email(邮箱,可选)、user_type(角色类型,学生/企业/管理员)、avatar(头像路径)、status(账号状态,正常/禁用)、create_time(注册时间)。这里 user_type 字段是权限判断的重要依据,设计时可以用 int 或 varchar 存储角色标识,比如 1 表示学生,2 表示企业,3 表示管理员。
第二张兼职信息表t_job_info,字段包括:id、job_title(兼职标题)、job_desc(兼职详细描述)、job_type(兼职类型,如促销/家教/客服/跑腿等)、pay_amount(薪酬,decimal(10,2))、pay_unit(薪酬单位,按小时/按天/按月)、address(工作地点)、work_time(工作时间段)、need_num(招聘人数)、apply_num(已报名人数)、publisher_id(发布者 ID,关联用户表)、status(审核状态,待审核/已通过/已驳回/已下架)、create_time、update_time。这个表是整个系统的业务核心,查询频率最高,建表时在 job_type、status、publisher_id 上建立索引可以显著提升筛选效率。
第三张报名表t_apply_record,字段包括:id、job_id(兼职 ID)、user_id(报名用户 ID)、apply_time(报名时间)、status(报名状态,待确认/已录用/已拒绝/已完成)、remark(备注,可选)。报名表是学生和企业之间的桥梁,一个兼职下面可以有多条报名记录,一个用户也可以报名多个兼职,所以这里本质是多对多关系,通过中间表形式承载双方关联。
第四张收藏表t_favorite,字段包括:id、user_id、job_id、create_time。收藏功能看起来不起眼,但它涉及唯一索引的设计问题:同一个用户对同一个兼职只能收藏一次,所以建表时要加 UNIQUE KEY(user_id, job_id),后端插入前可以先查重,也可以依赖数据库约束防止重复数据。
除了上面四张,还可以按需求扩展公告表t_notice、兼职类型字典表t_job_type、操作日志表t_log等。扩展范围取决于你要不要把这个项目继续往深做,用于“创新点”展示。
3.3 数据库的三种关联关系
我把这个系统的具体建表 SQL 简化到可以跑通的程度:
用户表:
CREATE TABLE `t_user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL COMMENT '用户名', `password` varchar(100) NOT NULL COMMENT '密码(加密存储)', `real_name` varchar(50) DEFAULT NULL COMMENT '真实姓名', `phone` varchar(20) DEFAULT NULL COMMENT '手机号', `user_type` tinyint(4) NOT NULL DEFAULT '1' COMMENT '1学生 2企业 3管理员', `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '账号状态 1正常 0禁用', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;兼职信息表:
CREATE TABLE `t_job_info` ( `id` int(11) NOT NULL AUTO_INCREMENT, `job_title` varchar(100) NOT NULL COMMENT '兼职标题', `job_desc` text COMMENT '兼职描述', `job_type` varchar(30) DEFAULT NULL COMMENT '兼职类型', `pay_amount` decimal(10,2) NOT NULL COMMENT '薪酬', `pay_unit` varchar(10) DEFAULT '天' COMMENT '薪酬单位', `address` varchar(255) DEFAULT NULL COMMENT '工作地点', `work_time` varchar(100) DEFAULT NULL COMMENT '工作时间', `need_num` int(11) NOT NULL DEFAULT '1' COMMENT '招聘人数', `apply_num` int(11) NOT NULL DEFAULT '0' COMMENT '已报名人数', `publisher_id` int(11) NOT NULL COMMENT '发布者ID', `status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '0待审核 1已通过 2已驳回 3已下架', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_publisher` (`publisher_id`), KEY `idx_type` (`job_type`), KEY `idx_status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;报名记录表:
CREATE TABLE `t_apply_record` ( `id` int(11) NOT NULL AUTO_INCREMENT, `job_id` int(11) NOT NULL, `user_id` int(11) NOT NULL, `status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '0待确认 1已录用 2已拒绝 3已完成', `remark` varchar(255) DEFAULT NULL, `apply_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_job` (`job_id`), KEY `idx_user` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;这里有三点设计细节值得讲。第一,金额字段用 decimal(10,2),保证结算精度,避免浮点误差;第二,status 字段用 tinyint 存状态码,而不是直接存中文,原因一是存储节省空间,二是业务状态可能调整,中文文案更适合放在前端展示层映射;第三,所有表都带 create_time,并按需增加 update_time,这两个字段对排查数据问题和做统计报表极其重要,建表时不要省。
外键在课设项目中用不用?我的建议是逻辑外键为主,物理外键可加可不加。主表之间通过业务逻辑校验关联完整性,比如删除用户前先查有没有该用户的报名记录,这比数据库物理外键更灵活,而且面试时解释“为什么不用物理外键”会成为一个加分回答,因为可以提高写入性能并避免级联删除带来的运维成本。
4. 核心功能模块的落地实现
4.1 项目初始化与配置
整个项目推荐用 Spring Initializr 创建初始骨架,也可以直接在 IDEA 里新建 Spring Boot 项目,这里的关键在于 pom.xml 要引入哪几个依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.3.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>这里 mybatis-spring-boot-starter 的版本需要注意,2.x 对应 Spring Boot 2.x,Spring Boot 3.x 对应 mybatis-spring-boot-starter 3.x。如果你用的 Spring Boot 版本是 3.x,还把 2.3.1 的 MyBatis starter 塞进去,启动时会直接报版本兼容错误。这一点在下一章排查实录里会再强调。
application.yml 的核心配置:
server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/parttime_job?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false username: root password: 123456 thymeleaf: cache: false mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.parttime.entity configuration: map-underscore-to-camel-case: trueurl 里的 serverTimezone=Asia/Shanghai 是我强烈建议保留的参数,尤其是本机 MySQL 安装时选择 UTC 时区,不设置的话时间字段会出现 8 小时偏移。useSSL=false 也有必要,避免新版 MySQL 连 JDBC 频繁报 SSL 连接错误。Thymeleaf 的 cache 设为 false,这样开发时改模板保存后浏览器刷新就能看到效果,不用反复重启应用。
4.2 用户注册登录模块
注册登录是整个系统安全性的第一道门,也是课设答辩时老师一定会问的部分。设计上有几个关键决策。
密码不能明文存储,这是底线。这里选用 BCrypt 加密,Spring Boot 的 spring-security-crypto 包里直接可以引入 BCryptPasswordEncoder,或者引入一个小依赖。用它的好处是每次加密结果带随机盐,即使两个用户设置相同密码,密文也不一样,这样能抵御彩虹表攻击。对比密码时调用 encoder.matches(rawPassword, encodedPassword) 即可,不需要自己写加盐逻辑。
注册功能实现时,表单提交到 Controller,先校验用户名是否被占用(AJAX 可以提前做单独校验),再校验两次密码一致性,最后把密码加密后插入数据库。登录功能通过 Session 保存用户关键信息和角色标识,后续所有业务方法里通过拦截器从 Session 取当前登录用户。
用户名唯一性校验的 AJAX 代码如下:
$.post("/user/checkUsername", { username: $("#username").val() }, function(result) { if (result.code === 200) { $("#usernameMsg").text("用户名可使用").css("color", "green"); } else { $("#usernameMsg").text(result.msg).css("color", "red"); } }, "json");这里 Controller 的 response body 用一个统一 Result 对象包裹返回,包含 code、msg、data 三个字段,前端拿到后统一解析。这样保持风格一致,后续所有接口返回都可以复用。我看到很多新手项目每一步按自己心情返回不同类型,前端解析时写一堆分支,后期维护非常难受,建议从一开始就统一接口规范。
4.3 兼职发布与列表查询
兼职发布是企业的核心操作。表单包含标题、类型、描述、薪酬、单位、地点、时间、人数等字段。Controller 端最重要的一点是发布时把当前登录的企业的 id 填充到 publisherId,而不是信任前端传过来。
发布成功进入待审核状态,前端提示用户“平台会在XX小时内完成审核”。这里不需要在生产环境真做定时任务,但要有一个状态字段支持审核流程,这是系统闭环的关键。
兼职列表是学生端的首页核心,除了简单的全量列表,至少要支持三类筛选:按兼职类型筛选、按关键词搜索、按薪酬区间排序,再加上分页。分页如果自己手写,就是 LIMIT offset, pageSize 加一条 COUNT 查询;更省事的是引入 PageHelper 插件,拦截 SQL 自动生成 COUNT 和分页语句,十分方便。手写分页虽然代码多一点,但对理解 SQL 执行过程有好处,建议课设阶段先手写一遍,再去用 PageHelper 体会封装的价值。
Controller 层典型实现:
@GetMapping("/job/list") public String listJob(@RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "6") Integer pageSize, @RequestParam(required = false) String keyword, @RequestParam(required = false) String jobType, Model model) { PageInfo<JobInfoVO> page = jobService.queryJobList(pageNum, pageSize, keyword, jobType); model.addAttribute("page", page); return "job/list"; }后端返回 Model 交给 Thymeleaf 渲染,页面里用 th:each 循环输出兼职卡片,点击某个兼职查看详情后进入详情页。这里返回类型是 String 对应模板名,Spring Boot 传统服务端渲染标准写法。
4.4 报名、收藏与状态流转
学生浏览到合适的兼职后,点击报名按钮,前端通过 AJAX 提交:
$.post("/apply/doApply", { jobId: jobId }, function(result) { if (result.code === 200) { alert("报名成功,等待商家确认"); $("#applyBtn").text("已报名").prop("disabled", true); } else { alert(result.msg); } }, "json");后端报名逻辑要处理三个边界情况。第一,兼职必须处于“已通过”状态,待审核和已下架的都不能报名;第二,报名人数不应超过招聘人数,超过后返回“已招满”;第三,同一个学生不能重复报名同一兼职,需要先查报名记录是否存在。那就对应用户存在重复报名的情况,务必要后台校验而不是只靠前端按钮禁用兜底。
收藏功能与报名在数据上不同,它没有名额限制和业务状态,可以无限收藏,因此跟报名采用独立表。
这里还涉及到一致性处理的问题。报名成功后,需要执行两步数据库操作:插入报名记录 + 更新兼职表的 apply_num 加 1。这两步必须放在同一个事务里,如果用 MyBatis 的 Mapper 直接写两条 insert/update,不加 @Transactional,一旦第二步失败就会出现报名记录有了但名额没扣的脏数据。这是很多刚接触事务控制的开发者会踩到的问题。
@Transactional(rollbackFor = Exception.class) public int doApply(ApplyRecord record) { // 1. 校验兼职状态与名额 JobInfo job = jobMapper.selectById(record.getJobId()); if (job == null || job.getStatus() != 1) { throw new BusinessException("兼职不存在或不可报名"); } if (job.getApplyNum() >= job.getNeedNum()) { throw new BusinessException("该兼职已招满"); } if (applyMapper.countByJobIdAndUserId(record.getJobId(), record.getUserId()) > 0) { throw new BusinessException("您已报名过该兼职"); } // 2. 插入报名记录 + 更新报名人数 applyMapper.insert(record); jobMapper.increaseApplyNum(record.getJobId()); return 1; }这段代码里的事务粒度、异常回滚和业务校验顺序,都是实际项目中值得反复推敲的细节,也是面试聊项目时最容易展开的部分。
4.5 管理员后台与审核闭环
管理员账号登录后,进入后台管理页面。管理功能分三块:兼职审核、用户管理、数据统计,其中兼职审核是最核心的。
审核逻辑实现为:管理员看到待审核兼职列表,点击“通过”会把 status 从 0 改成 1,点击“驳回”则把 status 改成 2,同时可以填写驳回原因,通过消息或站内信告知发布者。审核操作本身就是一条 UPDATE SQL,关键在操作权限控制,确保只有 admin 角色能访问审核接口,否则任何学生账号都能改状态,整个流程就崩了。
统计报表推荐用最简单的方式实现:几个 COUNT 聚合查询,按天统计新增兼职数量、报名数量,前端用 Thymeleaf 把数值输出到页面,或者使用 ECharts 引入 CDN 渲染图表。不需要引入重量级的大数据技术,控制好查询 SQL 和索引即可。
5. 实操记录:从零跑通整个系统
5.1 环境准备清单
我自己在复现这类项目时用的环境配置是:JDK 1.8 或 11、Maven 3.6.3、MySQL 5.7 或 8.0、IDEA 2022 以上版本。如果你的电脑上已经装了更高版本的 JDK 21,运行 Spring Boot 2.x 项目大概率会遇到兼容问题,此时建议安装多版本 JDK,或直接选择 Spring Boot 3.x 版本并配套 MyBatis starter 3.x。环境版本对上,项目就成功了一半,这一步千万不要跳过。
5.2 建库建表与初始化数据
创建数据库:
CREATE DATABASE parttime_job DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;然后用上一章给出的建表语句创建基础表,再插入一条管理员账号:
INSERT INTO t_user (username, password, real_name, user_type, status) VALUES ('admin', '$2a$10$CwTycUXWue0Thq9StjUM0uJ8FVwB9GqYvDH8jT3gZaQeGm0yYy8iC', '管理员', 3, 1);这里密码是 BCrypt 加密后的 admin123,你可以用自定义测试类生成密文,也可以在系统启动后打开注册页注册一个账号,再去数据库把 user_type 改成 3。第二种方式在课设调试阶段非常常用,改一个字段就能造出管理员,不用费劲做加密。
5.3 启动与自测清单
一切配置完成后,启动项目,按下面这个顺序自测一遍:
- 打开 http://localhost:8080/ 确认首页加载正常;
- 注册一个学生账号、一个企业账号,确认跳转和 Session 数据正确;
- 用企业账号发布一个兼职,确认插入成功且状态为待审核;
- 用管理员账号登录后台,审核通过该兼职;
- 退出管理员,用学生账号搜索并报名,确认名额扣减;
- 用企业账号在报名列表看到学生信息,完成录用或拒绝操作。
这六步走通,说明项目主链路已经完整无缺陷。强烈建议把这六步整理成自测文档放进项目根目录,交付时老师可以直接按步骤操作,印象分会明显提升。
6. 常见问题与排查实录
6.1 端口被占用,项目起不来
报错信息一般是“Port 8080 was already in use”。要么是系统里已经跑了别的服务,要么是上次项目的进程没停干净。Windows 下用netstat -ano | findstr 8080找到 PID,再在任务管理器里结束进程;或者更省事,把 application.yml 里server.port改成 8081。开发阶段我倾向于直接改为其他端口,不要浪费时间追查之前的进程是谁启动的。
6.2 MySQL 连接报 SSL 错误或时区错误
SSH 下连接 MySQL 时经常遇到The server time zone value '...' is unrecognized或 SSL 连接一类错误。解决方案就是在 JDBC url 上加两个参数:
url: jdbc:mysql://localhost:3306/parttime_job?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false这两个参数是整套配置里最容易漏的,一旦漏掉,新装 MySQL 的报错概率接近百分之百。还有 MySQL 8.0 的驱动类是 com.mysql.cj.jdbc.Driver,老版本是 com.mysql.jdbc.Driver,两者不一致也会导致加载失败,配置时注意匹配。
6.3 Spring Boot 版本与 MyBatis starter 版本不兼容
Spring Boot 3.x 发布之后,包名从 javax 改成了 jakarta,这导致老代码里很多 import javax.servlet 的东西全部编译失败。对应地,MyBatis 官方也发布了 mybatis-spring-boot-starter 3.x。如果你新建项目默认选了 Spring Boot 3.2,却复制了 2.x 教程里的 MyBatis starter 依赖,启动后就会出现NoSuchBeanDefinitionException或ClassNotFoundException。
遇到这种情况,我建议要么把 Spring Boot 降回 2.7.x,要么把 MyBatis starter 升级到 3.0.x,二者选一,不要混用。
6.4 Thymeleaf 模板解析报错
Thymeleaf 模板里最常见的报错是Exception processing template,一般原因是页面中某个表达式写错了,比如th:each="item : ${jobList}"的 jobList 在 Model 里不存在,或者对 null 对象调用了属性。
排查手段也很简单:启动时看控制台完整报错会定位到模板文件第几行哪个表达式;或者临时把 thymeleaf 的 cache 关掉,改完模板后刷新页面就能看到最新效果,不会因为缓存了旧模板产生误判。如果只是模板里个别数据为空,考虑用 th:if 做空值判断,避免页面直接 500。
6.5 AJAX 请求返回 415 或 400
状态码 415 表示请求的媒体类型与后端不匹配,400 表示参数绑定失败。很多同学前端用 $.ajax 默认发送 JSON,后端却用普通 POJO 接收,两边对不上。最通用的做法:要么前端全部用 form 格式发送,要么后端方法参数加 @RequestBody。我的习惯是简单操作一律用 form 格式,再用统一 Result 返回 JSON 字符串,避免引入额外的对象映射问题。
6.6 报名表重复数据与并发问题
并发场景是课设里常被忽略但真实存在的坑。两个用户同时报名同一个只剩一个名额的兼职,代码里先查后插的方式可能会造成超报。要根治,可以在报名表加唯一索引(job_id, user_id),或者把扣减名额的 UPDATE 写成条件更新:
UPDATE t_job_info SET apply_num = apply_num + 1 WHERE id = #{jobId} AND apply_num < need_num;然后检查受影响行数,为 0 说明名额已被抢完。这是实际的并发控制手段,写进答辩材料里会是亮点。
7. 项目扩展方向与个人心得
7.1 还能往哪些方向扩展
一个兼职平台系统,基础版本跑通之后,后续扩展空间其实很大。比如引入 Spring Security 实现更精细的权限控制,覆盖页面按钮级权限;引入 Redis 做兼职列表缓存和 Session 共享,提升并发能力;增加消息通知功能,兼职审核通过、报名结果确认时通过站内信提醒用户;增加支付结算模块,企业在线支付兼职薪酬,学生确认收款,形成商业闭环。
这些扩展建议不要全部做,否则项目规模失控同时给答辩埋雷。建议只选一个方向深入,用技术难度和业务价值来说服听众。比如“用 Redis 重构兼职列表接口,缓存热点数据,扛住高并发查询”是一个很好讲的方向,因为它技术点明确,能解释缓存穿透和缓存一致性,回答空间很大。
7.2 踩坑之后的真实体会
这个项目我自己完整做过不止一遍,最后分享几个最痛的体会。
第一,项目一开始就要想清楚状态流转,不要开发到一半才补状态字段。兼职从“待审核”到“已通过”到“已下架”,报名从“待确认”到“已录用”到“已完成”,这些状态连成一条业务线,页面展示和接口校验都依赖它。后期补状态设计会牵一发动全身,改一张表就要改七八个页面。
第二,Session 里的用户对象不要只存一个 id 或 username,尽量把真实姓名、角色、头像都放进去,页面导航栏需要展示昵称头像,权限判断需要角色,都要查一次数据库很不优雅。缓存进 Session 一次查库终身复用。
第三,开发阶段一定不要把 Thymeleaf 的 cache 打开,否则改完模板刷新没反应,你会误以为是代码问题,白白花几小时排查。很多初学者栽在这上面。
第三点其实已经说了三遍,但确实是我每个带的学生几乎都会踩的坑,值得反复提。第四,代码提交前要跑一遍主流程,不要只测自己改的那一两个接口。报名接口改了,可能把报名数更新逻辑牵动了,列表页的分页、详情页的报名按钮都要一起回归测试。
第五,也是最重要的一点,做这种全栈项目,本质上是在训练一个人从“能写代码”到“会做系统”的转变。代码只是最后呈现在编辑器里的产物,真正的功夫在需求想清楚、表结构设计好、状态流转定义明白。这些能力是面试官最在意的,也是这个项目除了“跑起来”之外最值得你收获的东西。