资讯动态

SpringBoot+Vue3+MyBatis前后端分离兼职平台实战解析

发布时间:2026/10/22 3:33:16 来源:尧图企业网站定制
大白话聊透“蜗牛兼职网”这套前后端分离系统到底该怎么理解、怎么落地很多同学看到“Java SpringBoot Vue3 MyBatis 前后端分离兼职平台”这类标题时第一反应是“又是一套课程设计/毕业设计”。但真正把这套系统亲手做一遍之后我的体会是它表面是一张成绩单底层却是一整套完整的Web全栈工程训练。你写下的每一行代码几乎都能在未来找工作时的技术面里原封不动地问出来。这篇文章我不打算贴一堆一次性代码然后让你照抄而是把我做“蜗牛兼职网”时的完整思路拆给你看这个项目到底解决了什么真实需求、数据库为什么这样设计、兼职发布和报名这两种核心流程在后端里是怎么一步步走的、MyBatis的动态SQL写分页和模糊查询时有哪些人容易踩的坑、Vue3前端和后台管理界面又该怎么跟后端平稳对接。所有内容都围绕“前后端分离 MySQL持久化”这一个核心展开。如果你正处于这几个阶段之一这篇文章对你尤其有价值一是需要用兼职平台当课程设计或毕业设计的在校生二是刚学完SpringBoot基础、想做点完整项目来打通“数据库→接口→页面”的初级开发者三是准备面试、需要快速复习RBAC权限模型和系统设计的求职者。咱们不聊虚的直接从需求出发把骨头啃下来。1. 先想清楚蜗牛兼职网到底是个什么“业务”做任何系统前先别急着建表。你得先能在一分钟内说清楚这个平台给谁用、解决什么痛点、核心流程是什么。说不清楚代码写出来也是一盘散沙。1.1 核心需求解析C2C兼职撮合平台蜗牛兼职网本质是一个C2C兼职信息撮合平台它连接两拨人学生/兼职者在平台上浏览兼职岗位按城市、岗位类型、薪资筛选看到合适的岗位后提交报名申请。商家/雇主发布兼职岗位如发单员、促销员、活动协助等审核报名者通过后完成人员确认后续还可以对合作进行评价。平台运营方则是管理员负责审核岗位是否合规、管理岗位分类和用户状态处置那些“挂羊头卖狗肉”的虚假兼职信息。所以它的核心业务闭环是雇主发单 → 管理员审单 → 学生搜单/报名 → 雇主确认人选 → 双方完成交易与评价。你在设计表结构时脑子里始终要有这个闭环所有表都服务于这条链路。1.2 为什么是SpringBoot Vue3 MyBatis这套组合这套技术栈不是唯一解但却是最适合作为“练手展示”的组合层选型理由后端框架SpringBoot 2.7简化配置企业里用得最多提供依赖注入、事务管理、拦截器等成熟机制部署也方便前端框架Vue3 Vite组合式API写起来清晰生态成熟和Element Plus搭配能快速搭建管理界面也是目前国内中小公司的主流选型之一持久层MyBatisSQL由自己掌控灵活处理多表联查和动态条件这种“条件复杂、字段多筛选”的业务场景比JPA更直观好用数据库MySQL 8.x提供事务、索引、全文检索等能力支撑一个中小企业级兼职平台绰绰有余有个观点我想多说一句很多同学纠结“MyBatis还是MyBatis-Plus”。我的建议是手写Mapper XML至少写一次。等你亲手写出动态SQL、亲手踩过一次分页的坑再切换到Plus的任何高级功能都是降维打击。这不是教学任务是被实际的工作需求倒逼出来的。1.3 前后端分离到底分工分在哪前后端分离的本质是前端只负责渲染和交互后端只负责数据与校验两者通过JSON接口通信。在蜗牛兼职网里我的划分方式是前端Vue项目负责页面路由、状态管理、组件渲染、携带Token发起请求、本地存储省份城市数据完全不碰数据库。后端SpringBoot项目负责用户认证、角色权限校验、业务校验、SQL执行、事务控制、统一异常处理把结果封装成统一格式的JSON返回。这样划分之后前后端可以并行开发我只需要定义好一套接口文档路径、参数、返回结构两边各自干活联调时基本不会打架。2. 系统架构设计一张图看懂所有模块的依赖关系按照规范的课程设计标准系统架构一般分三层表现层Vue页面、业务层ControllerServiceMapper、数据层MySQL数据库。但在动手之前我更建议你先画清楚功能模块图再画部署架构图最后再画表关系图。这个顺序能让你的思路一次比一次清晰。2.1 角色权限模型一个账号一套角色还是三张用户表角色权限是我在设计这个系统时最先拍板的模块因为它牵一发动全身。有同学喜欢把“学生表、雇主表、管理员表”各建一张然后账号体系完全分开。这么做在登录的时候会很麻烦一个手机号可能同时是学生又是雇主难道注册两遍后期做成多端统一登录时更痛苦。我采用的是统一的用户账户表tb_user 角色表 权限控制的RBAC简化模型tb_user存手机号、密码BCrypt加密、昵称、头像、用户状态、创建时间。tb_student关联用户ID存放学号、学校、专业等学生扩展信息。tb_employer关联用户ID存放公司名称、联系人、营业执照编号、公司简介。tb_admin关联系统后台管理员账号。登录时前端传一个role字段student/employer/admin后端在统一登录接口里根据手机号和角色字段去对应扩展表里查信息组合返回。这样既避免了重复注册又让角色扩展性极好——将来要加个“实习管家”角色只需要新增一张扩展表并调整登录解析即可。2.2 功能模块拆解从用户端到管理端从功能角度看蜗牛兼职网大致分为四大块用户模块注册/登录/退出、个人信息维护、密码修改、头像上传。岗位模块兼职分类管理、岗位发布、岗位审核管理端、岗位浏览与搜索关键词城市分类、岗位详情、热门推荐。报名模块学生提交报名申请、取消报名、雇主查看申请列表、通过/拒绝申请、双方消息通知。管理端用户管理禁用/启用、岗位审核通过/驳回、分类管理与数据统计。设计功能边界时我坚持一个原则学生端页面绝不出现雇主操作入口后端接口必须校验角色前端只是隐藏按钮但后端绝不能省略校验。后面的权限核心里我再详细讲为什么。2.3 部署形态单机也能跑完整的全链路前后端分离并不代表必须上微服务、上Docker。作为练手项目单机部署完全够后端打包成jar部署在一台Linux服务器上监听8080端口或配置Nginx反向代理到API端口。前端npm run build产出静态文件由Nginx直接托管并配置/api前缀的代理转发到后端。MySQL单独部署开发时直接用云数据库或本地库后端通过连接池访问。这样做的好处是开发环境一个npm run dev一个java -jar就能跑起来部署环境也只需要一台最便宜的云服务器。省下的时间精力全部花在打磨业务逻辑上。3. 数据库设计这六张表必须一次建模到位表结构和字段命名很重要。数据库一旦在开发中期大改意味着所有Mapper、Service、前端页面全都要跟着返工。我花了整整一个晚上把主体表结构定下来之后再也没大动过。3.1 主体表一览用户、角色、岗位、报名、收藏、消息核心表如下每张表的职责边界非常清晰表名说明核心字段tb_user统一账号表id, phone, password, nickname, avatar, role, status, create_timetb_student学生扩展信息id, user_id, school, major, introducetb_employer雇主扩展信息id, user_id, company_name, contact, company_desctb_job兼职岗位表id, employer_id, category_id, title, description, city, salary, salary_unit, head_count, status, create_time, update_timetb_application报名申请表id, job_id, student_id, status, remark, create_time, handle_timetb_favorite收藏表id, user_id, job_id, create_timetb_message站内消息表id, from_user_id, to_user_id, content, is_read, create_time每张表的主键我统一用bigint自增方便理解。将来要分库分表时再换雪花ID也不迟。创建时间create_time这类公共字段我统一用datetime类型。一个重要提醒岗位表上status的取值含义要在设计文档里写清楚0草稿、1待审核、2上架中、3已下架、4已驳回后端代码里要定义常量类绝对不能在Service里裸写数字。3.2 岗位表索引设计搜索性能的根基兼职平台最核心的查询是按城市、分类、关键词、薪资范围分页找岗位。所以索引设计直接影响体验。我建了这些索引idx_city_jobstatus(city_id, status)城市筛选状态过滤。idx_category_status(category_id, status)分类浏览。idx_multi_search(status, city_id, category_id, create_time)组合查询排序。标题字段title加普通索引配合模糊查询description不建索引避免索引膨胀。MySQL的最左前缀原则在这里体现得很明显查询条件里如果带了status(city_id, status)这个联合索引就能生效。如果用户没选城市只按关键词搜那索引用不上也不要紧——数据量级别下LIKE %关键词%扫全表完全能接受。真到百万级数据我们再考虑全文索引或接入Elasticsearch那是另一个话题。3.3 报名表要不要加唯一约束防重复报名的关键报名表这里我最想强调一个小细节tb_application表要加uk_user_job唯一约束student_id job_id。没有这个约束两个并发请求同时提交报名时代码里的if判断形同虚设。因为SELECT和INSERT之间存在时间差两条线程都可能查到“没有报名记录”然后同时插入成功。加了唯一约束数据库层面直接拦截第二条SpringBoot捕获DuplicateKeyException后给出友好提示“你已报名过该岗位”。这是典型的从“代码逻辑防重”升级为“数据库兜底防重”的方案也是面试官爱考的知识点之一。4. 兼职发布与审核后端核心业务逻辑详解现在进入最干货的部分具体功能是怎么一步步实现的。4.1 雇主发布兼职岗位从校验到入库的完整链路雇主登录后点击“发布岗位”前端会把岗位表单标题、分类、城市、薪资、单价单位、工作内容、人数、联系人POST到/api/employer/jobs。后端Controller收到请求后第一件事不是直接存库而是走如下步骤身份校验从JWT中解析userId去tb_employer查是否存在合法的雇主信息。如果一个人没有完善雇主资料就发岗位直接返回提示。字段校验标题非空、城市必选、薪资必须大于0、人数必须是正整数。用Spring Validation注解或手动校验均可。数据组装status设为1待审核employer_id从当前登录用户解析不能相信前端传的employerId。事务插入调用Mapper插入岗位表返回自增ID同时记录一条审核日志。返回结果封装统一Result对象返回code200前端收到后路由跳转到“我发布的岗位”页面。这里的关键思维是服务端永远默认前端传来的数据不可信。用户ID从Token里拿角色从Token里拿前端传过来的任何ID只能作为业务参数而不能作为身份凭证——这是安全意识和专业习惯的分水岭。4.2 MyBatis动态SQL写岗位分页搜索的实战写法搜索接口是我认为整个项目中最体现功力的一段代码。它要同时支持关键词标题模糊匹配、城市、分类、岗位类型、状态过滤、薪资排序、分页。我在JobMapper.xml里用where标签动态拼条件select idselectPageByCondition resultTypemap SELECT j.id, j.title, j.city, j.salary, j.salary_unit, e.company_name, c.name AS category_name FROM tb_job j LEFT JOIN tb_employer e ON j.employer_id e.id LEFT JOIN tb_category c ON j.category_id c.id where j.status 2 if testkeyword ! null and keyword ! AND (j.title LIKE CONCAT(%, #{keyword}, %) OR j.description LIKE CONCAT(%, #{keyword}, %)) /if if testcity ! null and city ! AND j.city #{city} /if if testcategoryId ! null AND j.category_id #{categoryId} /if if testsalaryMin ! null AND j.salary gt; #{salaryMin} /if /where ORDER BY choose when testsort salary_descj.salary DESC/when when testsort create_time_descj.create_time DESC/when otherwisej.create_time DESC/otherwise /choose LIMIT #{offset}, #{pageSize} /select代码里有个细节salary gt; #{salaryMin}中的必须用XML实体gt;否则XML解析直接报错。这是新手最容易卡壳的地方之一。另外CONCAT(%, #{keyword}, %)这种写法能避免SQL注入比拼字符串安全得多。分页我用的是LIMIT offset, pageSize因为MyBatis的分页插件PageHelper在复杂动态SQL下偶尔会出一些难以排查的COUNT问题。对于这种业务量级下单表分页手写LIMIT完全够用而且面试时你能讲清楚原理。4.3 管理员审核岗位状态机与操作留痕管理员在后台看到的是所有待审核岗位列表。审核通过就把status从1改成2驳回就改成4并在返回结果里带上驳回原因。这里每一步操作我都记录到tb_audit_log审核人、岗位ID、动作、时间、备注方便纠纷追溯。前端管理界面很简单一个表格 两个按钮通过/驳回驳回时弹出一个输入框填原因。但这块逻辑的核心不在界面而在状态流转是否合法——后端的修改必须校验当前状态是否允许执行该操作。比如一个已经上架2天的岗位管理员不能误操作成“审核中”。5. 学生报名流程和消息通知双向互动的机制设计5.1 学生报名兼职防重、防自单、防下架的三重校验报名接口是学生端的核心操作。它的逻辑可以概括为“三重校验 一个事务”。三重校验分别是岗位存在且可报名岗位状态必须是2上架中不能是自己发布的。尚未报名过查tb_application是否有同人同岗记录同时靠唯一约束兜底。名额未满通过岗位表的人数字段和已报名人数判断防止超额报名。通过校验后在事务中执行两步插入报名记录status0待确认并把岗位表的apply_count字段1。人数校验我会在事务前的SELECT和事务内的UPDATE之间加一行UPDATE ... WHERE apply_count head_count如果受影响行数为0就回滚。这样并发下也能保证不会超卖名额——这个写法借鉴了电商库存扣减的思路。5.2 报名状态与双向消息站内信如何落地报名状态我用枚举0待确认、1已通过、2已拒绝、3已取消。每次状态变更都触发一条站内消息收件人是学生或雇主。tb_message表结构很简单字段就是from_user_id、to_user_id、content、is_read。前端轮询接口/api/messages?unreadtrue每30秒查一次未读消息有新消息就弹一个红色角标。30秒轮询在原型阶段完全够用不需要上WebSocket但要清楚它的局限——实时性不强同时可能造成轻微资源浪费。这块在扩展方案里我给了升级路线。Vue3侧我封装了一个useMessageCount组合式函数用setInterval定时请求页面卸载时记得clearInterval避免组件销毁后还在发请求这是前端性能优化的基本修养。5.3 JWT认证与角色权限后端权限校验的“兜底原则”蜗牛兼职网选用了JWT做登录状态管理。用户登录成功后后端签发一个Token前端存到localStorage里每次请求在Authorization头带上Bearer ${token}。后端用一个拦截器统一解析Token并把userId角色写入Request属性。但我要强调的是JWT只解决身份认证不解决角色授权。也就是Token告诉后端“你是谁”但“你能干什么”必须由后端接口自己判断。比如删除岗位的接口DeleteMapping(/api/employer/jobs/{id}) public Result deleteJob(PathVariable Long id, RequestAttribute(currentUserId) Long userId) { // 先查岗位属于谁只有岗位的创建者或管理员才能删 Job job jobService.getById(id); if (job null) return Result.error(岗位不存在); if (!job.getEmployerId().equals(userId)) { return Result.error(无权操作该岗位); } jobService.logicDelete(id); return Result.ok(); }逻辑很简单查询岗位归属 - 比对当前登录用户 - 权限匹配才能删除。前端隐藏按钮、后端双重校验只剩最后一个兜底数据库层面尽量不加RESTRICT外键而是用逻辑外键代码保证这样删除记录时更灵活。这套“前端隐藏 后端校验 数据库约束”三层防护是你面试时讲权限控制能打动面试官的完整链路。6. Vue3前端与后端联调传统开发中最容易被低估的环节后端接口写完了前端页面如果接不通项目等于白做。这里我想复盘前端实现与联调时那些容易翻车的点。6.1 前端页面结构与关键交互学生端的典型页面有首页城市选择热门推荐、岗位列表页、岗位详情页、报名记录页、收藏列表页、个人中心。雇主端则是岗位发布页、我发布的岗位、报名管理、雇主资料维护。管理端是用户管理、岗位审核、分类管理、数据概览。首页无需登录即可访问但报名、发布、消息等功能必须登录。所以我在前端路由守卫里增加了“是否需要登录”的meta标记未登录访问受保护页面时自动跳转登录并携带回跳地址。前端请求库我用axios做统一封装service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }) service.interceptors.response.use( res res.data, err { if (err.response?.status 401) { localStorage.removeItem(token) router.push(/login) } return Promise.reject(err) } )这个封装的妙处在于所有页面代码都不需要关心Token拼接和登录失效跳转只负责处理业务数据。代码量一下少了好多。6.2 跨域与代理开发环境最让人血压升高的配置前后端分离开发时前端跑在5173端口后端跑在8080端口浏览器会直接拦截跨域请求。我的解决办法不是在后端粗暴地开cors()而是在前端vite.config.js里配置代理server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }这样前端请求/api/jobs时Vite开发服务器会把请求转发到http://localhost:8080/api/jobs既解决了跨域上线后Nginx做同样配置即可无缝衔接。这才是生产级的前后端分离联调方式。6.3 管理端与用户端共用一个后端路由级与按钮级的双重权限控制很多同学做管理端时会新建一套独立后端这是不必要的工程浪费。蜗牛兼职网里管理端和用户端共用同一套后端只是前端拆了两个入口。管理端路由在Vue Router里配置了meta: { admin: true }配合全局前置守卫校验当前用户角色是否为管理员。这属于路由级控制。而页面内那些“通过/驳回”按钮则在后端接口返回的用户权限基础上用v-if控制是否渲染属于按钮级控制。还是要强调所有控制最终都必须落到后端。你甚至可以故意在管理端页面调用普通用户接口然后观察后端返回的403以此验证权限拦截是否真的生效。7. 部署上线与常见坑位从“能跑”到“稳跑”的关键细节项目写完部署又是一关。我盘点一下最容易出问题的几个地方提前帮你避开。7.1 MySQL字符集与时区上线第一个凌晨的定时任务事故首次部署时我在MySQL建库时指定了utf8mb4字符集但忽略了一个要命的设置time_zone默认是系统时区。后端连接MySQL的jdbcUrl里如果没有加serverTimezoneAsia/Shanghai一旦出现CST与UTC时区不一致的情况所有create_time的写入和查询都会差8个小时。你会在“今日新增岗位数”统计时发现数据乱七八糟。两个对应解法spring: datasource: url: jdbc:mysql://localhost:3306/snail?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse同时建库时显式指定CREATE DATABASE snail DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;utf8mb4能存Emoji和生僻字比如岗位描述里带个表情符这点千万别省。7.2 文件上传头像和岗位附件的存储方案用户头像和岗位图片我最初存在本地磁盘结果部署后发现因为Nginx没有转发静态资源请求图片全裂了。后来统一改成前端上传图片到后端后端存到服务器上配置的/data/upload目录返回相对路径/upload/20240915/xxx.jpgNginx里配置location /upload/ { alias /data/upload/; }这样前后端不用再做一次下载代理性能也更好。如果要更省事儿可以直接用云存储对象存储上传时由后端拿到临时凭证前端直传URL直接入库。这套方案适合数据量大的场景但原型阶段用本地磁盘足够。7.3 状态更新与缓存热点岗位详情页要不要上Redis岗位详情页每次打开都会查一次数据库如果是首页热门推荐位流量一大数据库压力不小。我的处理是给category列表和首页热门岗位列表加了本地缓存用Spring的Cacheable(cacheNames hotJobs, key #cityId)配合Caffeine设置5分钟过期。在Caffeine里存一个MapInteger, ListJobVO过期自动刷新。详细做法启动时把热门岗位预加载到缓存每次查询先命中缓存缓存没有才走DB并回填。这样首页接口能做到几十毫秒内返回。至于Redis这个数据量级别先不加等真的有高并发需求再升级否则就是过度设计。8. 写在最后这套系统做完你带走的不只是源码项目做完证书拿到桌面上那一刻我感觉自己才算彻底理解了“前后端分离”这四个字的分量。最大的收获其实不是写代码的速度而是调试能力。发布岗位接口返回500时你能迅速判断是前端字段名传错还是后端SQL写错报名重复时你知道去看唯一索引而不是去怼前端;登录失效时你清楚是Token过期还是要重新签发。这些能力比任何一行华丽代码都值钱。如果要做扩展我会建议走这三条路一是把站内信的轮询改成WebSocket做实时消息推送二是给搜索加上全文索引或接入Elasticsearch解决关键词搜索慢的问题三是加一个简单的数据看板统计每日发布量、报名量、热门城市Top10用ECharts画出来。每一条路都能让你的项目在答辩或简历里多一个亮点。最后分享一个实用的小技巧开发阶段一定要把后端日志级别调到DEBUG前端浏览器控制台别关掉。当你看到MyBatis打印出的SQL和前端传过来的参数对不上时80%的问题都能在半小时内定位。不要一上来就怀疑是框架bug绝大多数时候bug就是你自己埋进去的。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑