SpringBoot+Vue3+MyBatis全栈实战:前后端分离兼职平台开发

发布时间:2026/10/21 14:12:48
SpringBoot+Vue3+MyBatis全栈实战:前后端分离兼职平台开发 1. 项目概述与整体设计思路1.1 我为什么选这套技术栈组合这个项目最初的想法很简单做一个连接企业兼职需求和在校生求职需求的平台。但在技术选型上我确实纠结了一段时间。市面上常见的组合有JSPServlet、SpringMVCFreeMarker、SpringBootThymeleaf还有纯前后端分离的SpringBootVue。最终我选了SpringBoot Vue3 MyBatis MySQL这套组合核心原因有三个。第一SpringBoot目前是Java后端的事实标准。它内置Tomcat自动配置机制能省掉大量XML配置项目启动就是一个main方法的事。相比传统SSH时代的繁琐配置SpringBoot让开发者能把精力集中在业务逻辑上这对做毕业设计或者个人项目来说尤其重要——你不需要浪费大量时间在环境搭建上。第二Vue3 Vite的组合是当前前端的主流方向。Vue3的组合式APIComposition API比Vue2的选项式API更灵活配合script setup语法写起来非常简洁。更重要的是Vue3对TypeScript的支持更友好虽然我这个项目用的是JavaScript但后续如果想迁移TS成本极低。第三MyBatis作为持久层框架最大的优势是SQL可控。相比于JPA/Hibernate的自动生成SQLMyBatis让你自己写SQL这对涉及多表联查、统计报表、复杂条件筛选的场景非常友好。尤其是我这种习惯用SQL思维解决问题的人写起来特别顺手。顺便说一句为什么不用SpringBoot自带的JPAJPA在简单CRUD场景下确实省事但一旦牵扯到多表关联、动态条件查询你得花大量时间去学它的规范比如Query、Specification、EntityGraph这些机制学习曲线反而比MyBatis陡。而且对于国内中大型互联网项目的技术栈偏向来——说MyBatis的普及率远高于JPA很多公司在招人时都明确要求熟练MyBatis。从就业角度看选MyBatis也更划算。1.2 系统角色与功能模块拆解蜗牛兼职网这个项目我把它定位成一个轻量级的垂直招聘平台。整个系统围绕三类角色来设计管理员、企业、求职者。这不是我拍脑袋定的而是基于兼职业务的核心链路推导出来的——有岗位需求方企业、有劳动力供给方求职者、还需要一个平台运营方管理员来保障信息质量和交易安全。三类角色的功能边界划分如下角色核心功能业务价值管理员用户管理、兼职信息审核、分类管理、数据统计保障平台内容合规性维护运营秩序企业发布兼职、管理岗位、筛选应聘者、确认录用快速触达目标人群完成招聘求职者浏览搜索兼职、投递简历、收藏岗位、查看录用结果安全高效地找到靠谱兼职这个功能划分并不是简单地大锅饭每个角色还有一层细分的权限控制逻辑。比如企业用户只能操作自己发布的岗位不能看到其他企业的数据管理员可以禁用违规账号但不能修改用户密码求职者投递简历后不能重复投递同一个岗位。这些细节虽然看起来不起眼但真正实现了才算是完整的业务系统这也是评判一个项目是否用心的关键标准。1.3 前后端分离架构的取舍既然做了前后端分离就得想清楚一个问题它为这个项目带来了什么我最直接的体感是开发效率的提升。前端项目用Vite启动热更新速度飞快改一行代码一秒内就能看到效果后端用SpringBoot的DevTools做热部署两个服务并行开发互不堵路。相比传统模板引擎那种改完页面还要重启服务的老模式前后端分离的开发体验完全是两个时代。但前后端分离也有代价。最直观的是联调成本上升——接口文档要是写不清楚前后端沟通效率会非常低。我的解决办法是引入Apifox做接口管理后端把接口定义清楚前端直接按文档调省去了大量这个字段叫什么的扯皮。另外一个问题是跨域这个我在后面的常见问题章节会详细讲。从部署角度看前后端分离意味着两个独立的应用后端的SpringBoot跑在8080端口前端的Nginx托管在80端口通过反向代理把/api路径转发到后端服务。这样的好处是前端可以做CDN加速后端可以独立做集群扩展但前提是你得维护两套部署流程。对于课程设计或个人项目来说这个部署复杂度是可控的而且能让你提前体验企业级的架构模式含金量明显高于单体JSP项目。2. 数据库设计与核心业务建模2.1 数据表结构与字段设计要点数据库设计是一个项目的根基表结构没设计好后面写代码处处是坑。我在设计蜗牛兼职网的数据库时遵循了几个原则字段不冗余、关联用逻辑外键、状态用枚举值、必要的冗余字段要留好。核心数据表一共设计了8张用户表sys_user字段名类型说明idbigint主键自增usernamevarchar(50)用户名唯一索引passwordvarchar(100)BCrypt加密后的密码nicknamevarchar(50)昵称phonevarchar(20)手机号emailvarchar(100)邮箱roletinyint角色1管理员 2企业 3求职者avatarvarchar(255)头像URLstatustinyint状态0禁用 1正常create_timedatetime创建时间update_timedatetime更新时间这里有个容易踩坑的地方密码字段的长度一定要给够。BCrypt加密后的字符串长度是60位如果你的密码字段只设了varchar(32)存储时会直接报错。这个问题在单独测试注册接口时很难发现因为很多开发者会用明文密码先测试等正式接入加密才发现数据库结构的坑。兼职信息表job_post字段名类型说明idbigint主键company_idbigint发布企业ID关联sys_usertitlevarchar(100)兼职标题category_idbigint分类IDdescriptiontext职位描述salaryvarchar(50)薪资待遇如200-300/天work_timevarchar(100)工作时间描述locationvarchar(255)工作地点headcountint招聘人数statustinyint状态0待审核 1已发布 2已下架 3已满员view_countint浏览次数create_timedatetime创建时间薪资字段我用的是varchar而不是int可能有人会觉得奇怪。其实这是故意的——兼职的薪资表达方式很多样可能是120/天、按单结算20元/单、面议等等用数字类型反而存不住这些非标格式。当然如果你要做基于薪资的筛选排序那就得另加一个数值字段鱼与熊掌不可兼得得看业务优先级。投递表job_application这个表是系统的核心关联表记录求职者投递兼职的行为。字段包括id、job_id关联兼职、user_id关联投递用户、resume_text简历内容、status状态0待处理 1已通过 2已拒绝、create_time。设计一个唯一索引uk_job_user(job_id, user_id)来防止重复投递这个索引非常重要它比在代码里做判断更可靠。数据库层面的唯一约束是最终的兜底保障哪怕你的代码逻辑有bug数据库也能挡住重复数据。其他辅助表还包括分类表job_category、收藏表job_favorite、管理员操作日志表sys_log。日志表是很多毕设项目容易忽略的但管理员审核兼职信息这种操作如果没有任何记录数据安全无从谈起。2.2 权限模型设计权限模型我采用了最简单但很实用的RBAC轻量方案用户表里直接存角色字段没有单独的权限表。为什么不做用户-角色-权限三层模型因为这三层RBAC模型更适合大型企业系统那里面的权限划分非常细比如运营A角色能审核但无法下架。但兼职网这个体量用三层模型只会增加代码复杂度并不会带来实际收益。我的做法是sys_user.role字段区分三类角色配合前端路由守卫和后端拦截器做双重校验。SpringBoot侧通过自定义拦截器解析JWT判断当前用户角色然后针对管理员接口做角色校验。比如只有role1的管理员才能调用/api/admin/job/review这个审核接口企业用户只能操作companyId等于自己的数据。2.3 核心业务流的数据库支撑兼职平台的主线业务流有两条企业发布兼职并招聘、求职者浏览岗位并投递。这两条链路对数据库的依赖体现在事务处理和状态流转上。以投递流程为例求职者点击投递简历后端要做三件事——检查该岗位是否存在且状态为已发布、检查该求职者是否重复投递、插入一条投递记录。这三步操作必须在同一个事务中执行否则可能出现审核通过了但投递记录没查到这类脏数据问题。再比如管理员审核企业发布的兼职审核通过后岗位状态从0变为1这个操作要用乐观锁控制。我在job_post表加了一个version字段更新时检查version是否匹配避免两个管理员同时审核同一岗位导致状态覆盖问题。虽然在小项目里这种极端情况不太可能发生但养成写并发安全代码的习惯总是好的。3. 后端实现SpringBoot MyBatis 落地细节3.1 工程结构与分层规范后端工程的包结构我采用的是业界最常见的四层架构com.snailwork ├── controller // 控制层接收请求校验参数 ├── service // 业务层核心业务逻辑 ├── mapper // 持久层MyBatis的Mapper接口 ├── entity // 实体类对应数据库表 ├── dto // 数据传输对象接收前端参数 ├── vo // 视图对象返回给前端的数据 ├── config // 配置类拦截器、跨域、事务等 ├── util // 工具类JWT、结果封装等 └── exception // 自定义异常与全局处理器分层设计最核心的价值是隔离变化。Controller层只负责参数接收和结果返回不知道SQL和业务逻辑Service层处理业务规则不关心HTTP细节Mapper层只做数据访问。这样的结果是我改SQL不需要碰Controller改前端接口参数不需要动Service。项目上线运行至今这个分层结构帮我减少了很多不必要的修改返工。DTO和VO这两个概念值得细说。很多初学者会把实体类直接返回给前端这是个坏习惯。因为实体类可能包含密码、手机号等敏感信息直接序列化返回等于数据泄露。我的做法是系统内部用Entity对外交互用VO。比如用户信息我需要返回给前端的是id、昵称、头像、角色这些展示字段就用UserVO来封装密码字段根本不进入这个对象。3.2 认证与鉴权JWT无状态方案登录认证我选了JWTJSON Web Token方案而不是传统的Session。核心考虑是前后端分离架构下后端接口需要同时服务Web前端和未来的移动端Session没法做到跨端共享登录态而JWT天然无状态客户端拿到Token后每次请求带上即可后端不需要存储任何会话数据。JWT的处理逻辑分成三块签发Token用户登录成功后把userId、role、过期时间封装进Token用HMAC256算法签名。设置过期时间为7天记得刷新机制不然用户7天后就得被迫重新登录。拦截器校验写一个JwtInterceptor实现HandlerInterceptor接口在preHandle方法中解析请求头中的Authorization字段取出Token并验证签名和过期时间然后把解析出的用户信息放到ThreadLocal中供后续业务使用。角色鉴权在拦截器中根据接口路径前缀做角色校验。比如/api/admin/**开头的路径必须满足role1/api/company/**开头的必须是企业用户。写一个简单的路径匹配规则即可不需要引入Shiro或Spring Security这种重量级安全框架。写这段代码时我踩过一个坑JWT的setExpiration方法的参数是毫秒数很多新手会把它当成秒来用导致Token瞬间过期。正确的写法是new Date(System.currentTimeMillis() EXPIRATION * 1000)这里的EXPIRATION是以秒为单位的常量乘以1000换算成毫秒。3.3 MyBatis开发的核心技巧MyBatis这层是整个项目的关键。我在实际编码中总结了几条实用经验。第一条Mapper接口和XML文件要放在同一个包路径下。不是业务包路径而是MyBatis扫描的路径。我在application.yml中配置mybatis.mapper-locations: classpath:mapper/*.xmlXML文件放在resources/mapper目录下这样Mapper接口和XML能正确绑定。很多人写了一大堆代码最后启动报错Invalid bound statement八成就是这个路径没配对。第二条动态SQL用where和if标签。以兼职列表的筛选查询为例前端传分类、关键词、工作地点、薪资范围等参数每个参数都可能为空。如果用where标签配合ifMyBatis会自动生成合理的WHERE子句不需要自己拼SQL或用字符串Buffer拼接既安全又优雅。动态SQL这一块是MyBatis的精华所在值得花时间把where、set、foreach、choose几组标签吃透。第三条大于号小于号要转义这在XML中是个大坑。比如你要查询发布时间在三天前的公告写![CDATA[ create_time DATE_SUB(NOW(), INTERVAL 3 DAY) ]]或者把写成lt;。否则XML解析会直接报错而且报错信息晦涩难懂第一次遇到的人通常要排查很久。第四条结果映射使用驼峰自动转换。在配置文件中开启map-underscore-to-camel-case: true数据库字段create_time就能自动映射到Java属性createTime省去大量resultMap的书写。只有碰到特殊联表查询时才需要手写resultMap比如查询兼职列表时联表查出公司名称。4. 前端实现Vue3 组合式API 开发实践4.1 前端工程化搭建前端我用的构建工具是Vite而不是Webpack。Vite基于ESModule的开发服务器冷启动速度极快项目稍微大一点优势就明显了。初始化命令是npm create vitelatest snail-web -- --template vue一个干净的Vue3项目就出来了。UI组件库选了Element Plus很多人纠结选Ant Design Vue还是Element Plus。我的建议是如果是纯个人项目两选你喜欢的一个就行如果是面向校园作业展示Element Plus在毕业设计中的普及度更高、使用案例更多遇到问题更容易搜到答案。Element Plus配合Vue3的生态很完善中后台场景基本开箱即用。工程化的要求上我在前端配置了ESLint和Prettier代码风格统一工具链的配置本身也是前端能力的体现。4.2 状态管理与路由守卫Vue3的状态管理我用了Pinia它是Vuex的官方替代品API更简洁去掉了mutations概念直接改state即可。对中小型项目来说Pinia的写法明显更清爽。Pinia在这个项目里承担两个职责。第一是管理登录用户信息登录成功后把用户信息写入Pinia的store同时持久化到localStorage刷新页面后从localStorage恢复登录态。第二是管理全局数据分类列表、系统配置等需要在多个页面共用的数据只在首次进入时请求一次后续从store中读取。路由守卫是权限控制的前端保障。我定义了固定的路由表在router.beforeEach中做三个判断页面是否需要登录meta.requiresAuth、当前用户角色是否匹配meta.roles、Token是否存在且未过期。如果用户访问管理员页面但身份是求职者直接重定向到403页面而不是等后端拒绝后才报错。4.3 接口封装与前后端联调前端的API层我用Axios做统一封装。核心思路是创建一个axios实例配置baseURL为/api通过Vite的代理把请求转发到后端8080端口。拦截器做了两件事请求拦截器在header中自动添加Authorization: Bearer ${token}响应拦截器统一处理错误码比如401跳转登录页、500弹出错误提示。这样的封装能让自己在组件里只关心业务数据。以获取兼职列表为例前端组件里只需要写const res await getJobList({ page: 1, size: 10, categoryId: 2 })不需要在每个请求里手动处理loading状态、错误提示、鉴权头这都属于接口封装层的重复劳动。写到后期页面之间逻辑差异纯粹体现在业务data的组装上开发效率大幅提升。前后端联调阶段我用Apifox统一管理接口后端每写一个接口就同步到Apifox前端按照文档联调。把接口文档做好前后端并行开发的效率才能真正发挥出来。5. 常见问题与排查实录5.1 跨域问题与解决方案前后端分离项目最常见的问题就是跨域。前端跑在5173端口后端跑在8080端口浏览器发请求时会因为同源策略被拦截。直接表现是前端控制台报错Access to XMLHttpRequest at http://localhost:8080/api/... from origin http://localhost:5173 has been blocked by CORS policy。我的解决方案分两层开发环境用Vite的proxy代理在vite.config.js中配置server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }这样前端请求/api开头的接口Vite开发服务器会把请求转发到后端浏览器认为同源跨域问题消失。生产环境则用Nginx做反向代理配置路径也是一样的思路。另外后端也要开启跨域配置SpringBoot中通过WebMvcConfigurer的addCorsMappings方法配置允许的源、请求方法、请求头。我建议两边都配好这样无论怎么部署都不会出问题。5.2 时间日期与JSON序列化坑前后端联调时日期格式是高频坑。后端返回的LocalDateTime默认序列化成2024-05-20T10:30:00这种带T的ISO格式前端展示时分隔符看着奇怪而前端提交给后端的日期可能是2024-05-20 10:30:00直接导致解析失败。我的解法是在后端统一配置Jackson的日期格式spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8这解决了大部分场景。但要注意这个配置只对java.util.Date生效对LocalDateTime还需要额外配置。我干脆在实体类的日期字段上统一加JsonFormat(pattern yyyy-MM-dd HH:mm:ss, timezone GMT8)注解彻底解决问题。5.3 分页查询与统计问题MyBatis分页最常见的做法是PageHelper插件。但我在这个项目里没用它因为PageHelper是物理分页的一种实现在复杂SQL下有SQL解析风险而且它对嵌套结果映射支持不友好。我更倾向于手写分页通过LIMIT #{offset}, #{size}配合count查询。不过在分页统计上我踩了个坑兼职列表需要同时统计每个岗位的投递人数。如果是在分页前用子查询把投递数带出来数据量大时会有明显的性能问题。我用的是关联子查询缓存优化核心查询先在主表范围约束好再用LEFT JOIN关联投递统计子查询SELECT j.*, (SELECT COUNT(*) FROM job_application a WHERE a.job_id j.id) AS apply_count FROM job_post j WHERE j.status 1 ORDER BY j.create_time DESC LIMIT #{offset}, #{size}这个方案在数据量不超过万级时性能没问题如果后续数据量上涨再考虑把统计数冗余到一个单独的字段或者用Redis缓存解决。5.4 权限控制绕过的风险点做权限控制时最需要警惕的就是越权漏洞。我遇到过一个问题用户A投递简历时篡改请求体里的jobId参数把jobId改成用户B发布的兼职岗位ID就实现了越权投递。这在数据层面不会造成致命影响但它说明了一个通用风险前端传进来的ID必须经过归属校验不能直接信任。解决方法是在后端Service层做数据归属判断。比如企业删除兼职时不仅校验兼职ID是否存在还必须校验job_post.company_id等于当前登录用户的ID否则拒绝操作。JWT里的userId是唯一的信任来源前端传的所有参数都要基于这个userId做校验。权限控制这些细致的点面试时如果被问到项目亮点是很加分的——因为它体现的是安全意识和防御性编程思维这恰恰是很多只有CRUD经验的人缺乏的。6. 部署与上线经验6.1 后端打包与环境配置SpringBoot的部署就是个打包过程。我在pom.xml中配置了Maven的打包插件执行mvn clean package -DskipTests构建出可执行的Jar包。因为内置了Tomcat直接java -jar就能启动。生产环境的配置文件我单独做了application-prod.yml数据库连接、Redis地址、JWT密钥等参数都通过环境变量注入避免把密钥写死在配置文件里。启动命令是java -jar snail-backend.jar --spring.profiles.activeprod6.2 前端构建与反向代理前端打包用npm run build生成dist目录下的静态文件。我把这些文件放到Nginx的html/snail目录然后在Nginx配置中设置反向代理server { listen 80; server_name your-domain.com; root /usr/share/nginx/html/snail; index index.html; location /api/ { proxy_pass http://127.0.0.1:8080/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location / { try_files $uri $uri/ /index.html; } }try_files $uri $uri/ /index.html这行很重要它解决了Vue Router的history模式在刷新页面时404的问题。因为Vue Router的history模式是纯前端的路由直接刷新/job/detail/3这个路径时Nginx找不到对应的物理文件需要回退到index.html让前端路由接管。6.3 日志与监控日志这块我在项目里用了Logback按天生成日志文件保留30天。发现线上问题时直接查看指定日期的日志即可定位。另外我加了一个简单的接口耗时统计拦截器用Around切面统计每个接口的响应时间超过500ms的接口自动打印警告日志。这个简单的工具帮我在开发阶段就发现了好几个慢查询问题算是性价比极高的投资。写在最后的一些体会这个项目从最初构思到完整上线前前后后花了两周多时间。回头复盘我最深的感受是技术其实是最简单的那部分。真正需要花心思的是业务逻辑的完整性——什么是合理的兼职审核状态机、怎么设计数据类型才能存住各种薪资表达方式、怎么在重复投递时给出精准的错误提示。这些业务细节才是让一个系统从能跑到好用的关键差距。如果你也想做一个类似的全栈项目我的建议是先画清楚角色的业务流程图把每个角色的操作路径和状态流转列出来再去写代码。数据库设计和接口设计这两件事占整个项目40%的工作量千万别等代码写了一堆才回头改表结构那时候的迁移成本会让人崩溃。还有一点项目完成后一定要写一份简单清晰的README把技术栈、部署方式、默认账号密码都写清楚。一方面是方便别人快速跑起来另一方面也是整理自己思路的过程。代码写完只是起点能把自己的思路清晰表达出来本身就是软件工程师的重要能力。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询