十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Spring Boot + Vue实战:MBTI性格测试系统前后端分离与Docker部署

Spring Boot + Vue实战:MBTI性格测试系统前后端分离与Docker部署 简介该项目为基于Spring Boot实现的MBTI性格测试系统前端采用ThymeleafLayui后端整合Shiro安全登录框架覆盖用户注册登录、性格指标评测以及管理员端的试题管理、测试者管理、用户管理、角色菜单与部门管理、日志监控和大屏展示等模块适用于JavaWeb初学者、毕业设计以及需要快速搭建带权限管理的Spring Boot实战项目的开发者。资源包内含556个文件共12.26MB以Java源码与class文件为主同时配有HTML/CSS/JS页面、SQL数据库脚本、XML配置以及150个gif演示动图和png截图方便对照效果与排查流程。目前已有1274人学习下载。通过完整项目源码、数据库初始化脚本和图文演示可清晰掌握Shiro登录鉴权与权限控制、试题动态维护和MBTI结果自动匹配的实现思路后台管理中的大屏展示、日志监控等功能模块相互配合能帮助读者快速理解企业级管理系统的分层设计与前后端交互逻辑。 沉浸式做项目的感觉是最爽的尤其是一个技术栈完整、业务逻辑又不复杂、还能直接拿来用的系统。最近我在做一个基于 Spring Boot 的 MBTI 性格测试系统前后端彻底分离从数据库设计到接口开发再到前端联调完整跑通了一遍期间踩了不少坑也沉淀出不少经验。这篇文章就把整个项目的落地过程拆开讲清楚包括为什么这么设计、核心代码怎么写的、前后端怎么联调、最后怎么用 Docker Compose 一键部署以及我在实际开发中遇到的一些典型问题。不管你是准备拿它当毕设还是想练习前后端分离项目的完整流程这篇内容都值得收藏着慢慢对照做。1. 项目整体设计与技术选型1.1 先搞清楚这个系统到底要做什么MBTI 性格测试的核心逻辑并不复杂它通过一组选择题来测量人在四个维度上的偏好分别是精力来源维度 E外倾和 I内倾、信息获取维度 S感觉和 N直觉、决策方式维度 T思考和 F情感、生活方式维度 J判断和 P感知。每个维度两端各代表一种性格倾向四个维度的倾向组合起来就形成 16 种人格类型比如 INTJ、ENFP 这些。我做的这个系统业务上就三块用户注册登录、参与测试答题、查看测试结果。前端用户答完一组题后端接收答案后按维度统计得分算出四个字母的倾向最后输出类型描述和维度分布。整个链路非常清晰非常适合用来练习 Spring Boot 的项目组织能力也适合从零开始理解前后端分离项目的完整形态。1.2 为什么选择 Spring Boot Vue 这套组合选 Spring Boot 的原因非常直接它把配置简化到了一个极致。传统 SSM 项目里光 Spring 和 MyBatis 整合的 XML 配置就能写上一大堆而 Spring Boot 用自动配置机制把大部分样板配置都消化掉了。我只需要在pom.xml里引入spring-boot-starter-web、mybatis-plus-boot-starter、mysql-connector-java这几个核心依赖一个可运行的后端服务就起来了。尤其是对个人项目或者毕设场景时间有限Spring Boot 能帮你把精力集中在业务代码上而不是耗在环境搭建里。前端选 Vue配合 Element UI 组件库原因是这两个技术在国内前后端分离项目里的普及度太高了遇到问题随手一搜就能找到解决方案。Vue 的响应式数据绑定非常适合表单类的交互场景MBTI 测试页面本质上就是一个大型动态表单用 Vue 来做几乎不用手动操作 DOM体验很顺。这里特别说明一下我的分层思路后端只提供 RESTful API前端通过 axios 发请求拿数据渲染页面。这种模式的好处是前后端可以并行开发我在实际项目中先定好接口文档前端用 Mock 数据模拟联调后端起服务后切真实接口切换成本几乎为零。用不用若依这类现成框架我当时也纠结过但最终决定手写一套轻量的权限和用户体系这样代码的可读性反而更好面试时也能讲清楚每个模块的原理。2. 数据库设计与计分模型2.1 三张表搞定所有业务我最终设计了三个核心表用户表、题目表、测试记录表。用户表存基础信息题目表存 MBTI 试题内容和维度标识测试记录表存用户每次测试的答案序列和最终结果。题目表不需要拆分选项到独立表因为每道题只有两个选择分别对应维度的两极用两个字段直接存选项文本就行表结构简单且查询高效。用户表的创建语句大概是这样的CREATE TABLE user ( id bigint(20) NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL COMMENT 用户名, password varchar(100) NOT NULL COMMENT 密码BCrypt加密, nickname varchar(50) DEFAULT NULL COMMENT 昵称, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;题目表需要注意把 dimension 字段设计成可枚举的我用的是字符串类型存 EI、SN、TF、JP 这样的维度标识选项 A 代表维度左端倾向选项 B 代表维度右端倾向。测试记录表会 JSON 格式存储用户的答案数组比如[{questionId:1,option:A},{questionId:2,option:B}]同时冗余存储测试结果类型这样查询历史记录时不用重新计算得分。2.2 MBTI 计分逻辑核心算法其实不复杂MBTI 的计分逻辑是整个系统的灵魂也是最容易出错的地方。我当时设计的规则是每个维度 7 道题共 28 道题。用户选择选项 A 则左端得分加 1选择选项 B 则右端得分加 1。全部答完后逐一比较每个维度左右两端的得分哪边得分高就取哪边对应的字母。以 EI 维度举例如果 E外倾得了 5 分I内倾得了 2 分那么这个维度结果就是 E。如果两端得分一样比如 3 比 3这种情况需要特殊处理可以在建表时给每个维度设定一个默认偏向或者在前端提示用户选择“更接近哪一端”来打破平局。我在实践中推荐后一种方案让用户再选一次不仅体验更好结果也更准确。得分比较逻辑用代码写起来非常简洁public String calculateResult(ListAnswerDTO answers) { int e 0, i 0, s 0, n 0, t 0, f 0, j 0, p 0; for (AnswerDTO answer : answers) { Question question questionMapper.selectById(answer.getQuestionId()); if (A.equals(answer.getOption())) { switch (question.getDimension()) { case EI: e; break; case SN: s; break; case TF: t; break; case JP: j; break; } } else { switch (question.getDimension()) { case EI: i; break; case SN: n; break; case TF: f; break; case JP: p; break; } } } StringBuilder type new StringBuilder(); type.append(e i ? E : I); type.append(s n ? S : N); type.append(t f ? T : F); type.append(j p ? J : P); return type.toString(); }这段代码看起来简单但有个性能上的小问题循环内逐条查询题目表28 道题就是 28 次数据库查询。实际开发中我优化成了先把 28 道题一次性查出来放到 Map 里内存中匹配维度接口耗时从几十毫秒降到了个位数毫秒。这个优化点虽然小但面试的时候讲出来非常加分。3. Spring Boot 后端核心实现3.1 搭建项目与统一响应结构后端我用的 Java 8 Spring Boot 2.7.x MyBatis-PlusJava 8 是目前大多数公司生产环境的稳定版本Spring Boot 2.7 也是目前兼容性最好的桥梁版本。创建项目我直接用的 IDEA 里的 Spring Initializr注意网络超时的问题如果卡住就换阿里云的初始化地址很快就能拉起来。接口开发前我先定义了一个统一的响应体类ResultT包含 code、message、data 三个字段。这么做能极大简化前端处理逻辑前端axios拦截器只需要判断 code 是否等于 200就能统一处理成功和异常情况。代码是这样的Data public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(success); result.setData(data); return result; } public static T ResultT error(String message) { ResultT result new Result(); result.setCode(500); result.setMessage(message); return result; } }3.2 用户登录与 JWT 鉴权用户模块我用了 JWT 做无状态鉴权这也是前后端分离项目最主流的方案。登录成功后后端签发一个 token 返回给前端前端存在 localStorage 里之后每次请求都在请求头带上Authorization: Bearer token后端通过拦截器解析 token 辨别用户身份。我在项目中用的是 jjwt 库核心逻辑就是一个拦截器实现HandlerInterceptor接口在preHandle方法里解析 token如果校验失败直接返回 401 状态码。需要特别注意的是密码加密问题千万不能用明文存数据库我用的是 Spring Security 自带的 BCryptPasswordEncoder 做单向加密即使数据库泄露也无法反推出用户密码。这里有一个新手常犯的错引入 spring-boot-starter-security 后所有的接口默认会被拦截掉需要在配置类里放开登录、注册接口的匿名访问权限我当时在这个问题上卡了大半天。3.3 题目与测试接口的幂等性设计测试提交接口POST /api/test/submit我额外做了幂等处理。什么是幂等就是用户因网络问题重复提交同一份答案时系统不会生成多条测试记录。实现方式也很简单前端生成一个请求唯一标识requestId传到后端后端在插入测试记录前先检查这个 requestId 是否已经存在如果存在直接返回原结果。题目列表接口我加了一层 Redis 缓存因为题目内容基本不变没必要每次都查数据库。第一次请求时从数据库加载并写入缓存设置过期时间 30 分钟后续请求直接从缓存取。这一套组合拳下来接口响应速度有了质的提升也让我在实际项目中积累了对缓存策略的理解。4. 前端实现与前后端联调4.1 Vue 项目搭建与页面设计前端我用的 Vue 2 Element UI用 Vue CLI 创建项目。页面一共五个登录页、注册页、测试页、结果页、历史记录页。这里有一点实践经验分享——页面不要贪多把核心路径走通比堆砌页面数量重要得多。测试页是核心交互页面我设计成了一次展示一道题底部放“上一题”和“下一题”按钮顶部用进度条提示当前进度。这样设计的好处是用户每次只需要做一次二选一的判断交互负担小测试体验会明显好于把所有题目堆在一页里。所有答案存在 Vue data 里的一个对象中最后一题时调用提交接口。4.2 axios 拦截器与跨域处理前端请求后端跨域问题是绕不开的坎。我在 Spring Boot 后端写了一个全局跨域配置类允许所有来源访问允许所有请求头和方法。开发环境下还可以用 Vue CLI 的 devServer 代理把/api前缀的请求代理到http://localhost:8080这样前端代码里的请求地址可以写成相对路径部署时不用改代码。axios 拦截器我在实际项目中配置了两层请求拦截器负责从 localStorage 取出 token 并塞进请求头响应拦截器统一处理错误码如果收到 401 说明 token 过期自动跳转到登录页并弹出提示。这个设计能避免每个接口单独写错误处理代码清爽很多。4.3 前后端联调的效率问题联调阶段最容易出的问题是接口字段名对不上。我的做法是在开始写代码之前先定义一个简单的 API 文档用尽量具体的字段名然后用 Apifox 生成 Mock 数据给前端并行开发。前端按 Mock 数据写完页面后后端接口完成后只需切换 baseURL 就能无缝对接整个过程非常顺利。还有一个经验联调的时候一定要看 Network 面板里的实际请求和响应而不是凭直觉猜测。很多前后端 bug 的根源在于前端传参格式和后端接收格式不一致比如前端传了 JSON 字符串而后端需要对象用 Network 面板一瞬间就能定位问题。5. API 接口设计与错误处理体系5.1 六个核心接口一览整个后端我一共设计了六个接口覆盖了系统的全部业务能力。分别是注册接口、登录接口、获取题目列表接口、提交测试接口、获取最新测试结果接口、获取历史测试记录接口。前后端分离项目的接口设计关键在于语义清晰、职责单一每个接口只做一件事。接口设计好之后我用一个表格做了自测清单接口路径请求方式鉴权功能说明/api/user/registerPOST否用户注册/api/user/loginPOST否用户登录返回 token/api/question/listGET是获取全部测试题目/api/test/submitPOST是提交答案并返回测试结果/api/result/latestGET是获取用户最近一次测试结果/api/result/historyGET是获取用户历史测试记录5.2 全局异常处理Spring Boot 里接口如果抛异常默认返回的是一段难看的错误堆栈前端拿不到友好提示。我写了一个全局异常处理器用RestControllerAdvice注解捕获业务异常、参数校验异常和兜底异常三类分别返回不同的提示信息。这里要说一个细节不要把所有异常都暴露给前端尤其是数据库连接失败这类敏感信息一旦返回到浏览器就有一定的安全隐患日志里记录详细原因接口只返回“系统繁忙请稍后重试”即可。参数校验我也统一做了。前端传进来的答案列表可能是空的用户名可能为空密码可能太短这些都在后端用 Spring Validation 的Validated注解和自定义校验逻辑提前拦截避免脏数据落到数据库。后端校验是最后一道防线绝不能依赖前端的校验。6. Docker Compose 部署与运维实践6.1 容器化部署方案项目开发完成后部署我选择了 Docker Compose。原因是项目包含两个服务——后端应用和 MySQL 数据库如果用传统的部署方式得分别装 JDK、配环境变量、装 MySQL、建库建表一套流程下来至少折腾一两个小时。Docker Compose 用一个docker-compose.yml文件把服务编排好一条命令docker-compose up -d就全部启动非常省事。我的 docker-compose.yml 核心内容长这样version: 3.8 services: mysql: image: mysql:8.0 container_name: mbti-mysql environment: MYSQL_ROOT_PASSWORD: root123456 MYSQL_DATABASE: mbti_db ports: - 3306:3306 volumes: - ./sql:/docker-entrypoint-initdb.d - mysql_data:/var/lib/mysql healthcheck: test: [CMD, mysqladmin, ping, -h, localhost] interval: 10s retries: 5 backend: build: ./backend container_name: mbti-backend depends_on: mysql: condition: service_healthy ports: - 8080:8080 environment: SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/mbti_db?useSSLfalseserverTimezoneAsia/Shanghai SPRING_DATASOURCE_USERNAME: root SPRING_DATASOURCE_PASSWORD: root123456 frontend: build: ./frontend container_name: mbti-frontend depends_on: - backend ports: - 80:80 volumes: mysql_data:MySQL 容器启动时自动执行挂载目录下的 SQL 脚本完成建库建表后端 Dockerfile 用的是多阶段构建先 Maven 打包再拷贝 jar 文件到 JRE 镜像里运行这样最终镜像体积能控制在 200MB 左右。前端 Dockerfile 用 Nginx 托管静态文件同时配置了一个反向代理把/api开头的请求转发给后端服务这样前端部署后不需要额外配置接口地址就能直接工作。6.2 服务器部署踩坑记录我实际部署到云服务器时踩了三个坑分享出来大家部署时可以少走弯路。第一个坑是 yml 配置文件里的数据库地址写的是 localhost导致容器内后端连不上数据库——在 Docker 网络环境下服务之间通信要写服务名而不是 localhost改成jdbc:mysql://mysql:3306/mbti_db后问题解决。第二个坑是 Nginx 配置的 proxy_pass 末尾没加/导致部分 API 请求 404。这个问题的本质是反向代理路径拼接规则proxy_pass http://backend:8080/和proxy_pass http://backend:8080在路径拼接上行为完全不同前者会丢掉匹配到的前缀后者会原样转发完整路径。第三个坑比较隐蔽是 MySQL 8.0 默认的认证插件是 caching_sha2_password而某些旧版本的 JDBC 驱动不支持。换上最新版的mysql-connector-java后连接就正常了。如果不想升级驱动也可以在创建 MySQL 用户时指定mysql_native_password认证方式两个方案都能解决。6.3 环境变量与配置管理部署环境和生产环境配置不一样比如数据库密码、端口这些不能写死在代码里。我用的是 Spring Boot 的application.yml配合环境变量的方式yml 文件里写占位符实际值从 Docker 容器的 environment 注入。这样代码可以在多个环境之间无缝切换不用改一行代码。application.yml的核心配置长这样spring: datasource: url: ${SPRING_DATASOURCE_URL:jdbc:mysql://localhost:3306/mbti_db} username: ${SPRING_DATASOURCE_USERNAME:root} password: ${SPRING_DATASOURCE_PASSWORD:root} redis: host: ${SPRING_REDIS_HOST:localhost} port: 6379冒号后面的值是默认值本地开发时不用设置任何环境变量就能直接跑起来部署到服务器时通过 docker-compose 注入真实值开发环境和生产环境的行为完全一致。这个习惯建议每个人都有就算不是容器化部署对将来上 CI/CD 也大有帮助。7. 常见问题与排查技巧实录7.1 Spring Boot 启动失败处理实际开发中最容易遇到的启动失败场景主要有三个端口被占用、数据库连不上、依赖冲突。端口被占用时日志会明确提示Port 8080 was already in use解决方式是lsof -i:8080找出占用进程并kill掉或者直接改项目的 server.port。数据库连不上的 Log 通常会有一条Cannot create PoolableConnectionFactory的堆栈排查思路是先确认 MySQL 服务有没有启动再确认账号密码对不对最后确认数据库地址是否可达。如果用的是云数据库还要检查安全组是否放行了对应端口这个我印象里不少人容易忽略。依赖冲突的问题典型表现是启动时报noclassdeffounderror或者NoSuchMethodError排查方式是在 IDEA 里用 Maven Helper 插件查看依赖树找到冲突的 jar 包并在 pom.xml 里用exclusions排除掉不需要的传递依赖。7.2 前后端联调的经典 bug前后端分离项目里我最常遇到的三类 bug 是后端返回的字段名是下划线格式而前端用的是驼峰、Integer 类型字段返回给前端变成了字符串或者相反、日期格式前后端不一致。第一个问题可以在 Spring Boot 配置里开启map-underscore-to-camel-case自动映射第二个问题要检查 JSON 序列化配置第三个问题在 Date 字段上加JsonFormat注解统一格式。联调时还有一个非常实用的排查技巧先打开浏览器开发者工具看请求有没有发出、请求头对不对、响应状态码是多少再去看后端日志。这个两步排查法能快速缩小问题范围避免在前端代码和后端代码之间反复横跳。7.3 测试逻辑边界情况MBTI 计分逻辑的边界情况我梳理出来主要有四种全部选 A、全部选 B、每个维度都打平、用户未完成所有题目就提交。前两种情况因为得分极端结果非常明确一般不会出错每个维度打平的情况我用的是打破平局的补充选择逻辑未完成就提交的情况我在后端做了总题数校验数量不够直接返回参数错误提示。这四种边界情况测试用例必须写全它们覆盖了计分逻辑的所有分支路径不只是为了功能正确更重要的是这个测试用例本来就是面试时展示代码质量的最好素材。我在项目中用 JUnit 5 写了这几个场景的单测整个计分核心逻辑测试覆盖率达到了 90% 以上。8. 项目复盘与优化方向整个项目从零到一我自己最满意的是技术栈选型没有走偏整体保持在 Java 开发者的主流知识范围之内不会为了炫技引入一些冷门框架导致自己给自己挖坑。MBTI 测试系统的业务特点决定了它很适合作为 Spring Boot 入门到进阶的实战项目业务逻辑简单清晰但覆盖了 CRUD、鉴权、缓存、多表关联、接口设计、容器化部署等真实项目必备的环节一套流程走下来知识体系会非常完整。做完之后我还列了几个后续优化方向写在最后供大家参考一是引入 Redis 记录用户答题过程断点续答避免用户中途退出后需要重新开始二是加入结果对比功能两个用户之间对比性格类型增加社交趣味性三是用 ECharts 可视化展示 16 种人格类型的分布比例让结果页更有层次。这些优化点不看代码复杂程度单是思路本身就能让项目在面试介绍时更有亮点。我个人在实际操作中的体会是做这类偏业务型的小项目真正拉开差距的不是用什么框架、写得多花哨而是对业务细节的理解和工程质量意识的呈现。比如答题接口的幂等处理、密码加密存储、统一异常响应、环境隔离配置这些看似琐碎的细节才是评判一个人是否有实际项目经验的关键指标。如果你正在准备面试或者毕设把这些点一个个落实到位能讲出来的东西比单纯跑通项目多得多。本文还有配套的精品资源点击获取
返回列表