简介:基于Spring Boot、MySQL与Vue构建的在线问卷调查系统完整项目,面向Java毕业设计或课程设计人群,提供前后端分离的问卷设计、发布、统计与管理实现方案。系统包含管理员与用户双角色,覆盖登录注册、问卷与题目管理、题目统计、问卷调查记录、新闻资讯、轮播图设置等常见业务模块。压缩包共755个文件,以Java源码、Vue组件、JS脚本、CSS样式、XML配置、SQL脚本为主,另含图标、图片、字体及安装运行批处理脚本,整体大小约23.59MB,结构清晰便于导入学习和二次开发。目前已吸引87人学习下载。包内不仅附有完整项目源码与数据库脚本,还包含毕业论文文档,可配合启动脚本快速在IDEA或Eclipse中运行调试,适合需要从零搭建同类系统、补充毕设材料或研究Spring Boot+Vue整合开发的读者,同时便于结合真实业务场景理解问卷创建、数据统计与权限分配的完整流程。
1. 一个常见的 Java 毕设项目,为什么有人跑三天跑不起来,有人十分钟就打开了问卷页面
基于 springBoot+mysql+maven+vue 的在线问卷调查系统,是这几年 Java 方向毕业设计和初级工程师练手出现频率最高的一类项目。你拿到的所谓“含源码+数据库+毕业论文”的压缩包里,通常是一个 Spring Boot 后端、一个 Vue 前端、一份 SQL 脚本和一篇可以直接改改用的论文。但它绝不是解压即跑——真正卡住绝大多数人的,不是业务代码,而是环境变量、数据库初始化、前端代理、以及那两个端口之间的联调。这套项目最大的价值也正在于此:它麻雀虽小,却完整地覆盖了从数据库建模、后端接口、JWT 登录鉴权到 Vue 页面联调的全链路。一篇合格的实践笔记,应该能把这条链路在你自己的电脑上完整复现,并且让你知道每一步是在干什么、改了什么、挂了该查哪里。这篇文章就按这个标准来写。
2. 在线问卷系统的完整技术拆解:从业务流程到数据表
2.1 这类系统到底在管理哪些对象
在打开源码之前,先把业务模型想清楚。一个在线问卷调查系统,核心参与方无非三种:管理员、创建问卷的人、填写问卷的人。在很多毕设版本里,管理员和创建问卷的人会合并成同一个角色,普通用户也能登录,但只能填写被分配到的问卷。业务上最基础的一条主链路是:管理员登录后台,创建一份问卷,往里添加若干题目,每道题可能有多个选项,然后发布问卷;用户拿到链接或登录后进入填写页,逐题作答,提交答卷;管理员在后台查看统计数据或导出原始结果。
理解这条链路的关键在于:问卷和题目是嵌套关系,题目和选项也是嵌套关系,而答卷是另一种完全不同的数据结构。很多人在改这类项目时翻车,就是因为把“一道题的几个选项”和“一份问卷的所有问题”装进了同一张表,最后统计时只能写出又臭又长的手写 SQL。源头做对,后面才不折腾。
2.2 数据库设计核心表:问卷、题目、选项、答卷
大多数 Spring Boot 版本的问卷系统,数据库最少会包含六张表:用户表、角色表(或直接在用户表里加 role 字段)、问卷表、题目表、选项表、答卷表。问卷表里通常会有一个 status 字段标记草稿、发布中、已结束,比如 0 草稿 1 发布 2 结束。题目表通过 survey_id 关联问卷表,再用一个 type 字段区分单选、多选、文本题。选项表通过 question_id 关联题目。答卷表则比较特殊,一条记录对应一份完整答卷,核心字段是 survey_id、user_id,以及一个存答案内容的大字段,很多项目直接用 text 类型把整个 JSON 结构存进去。
用 JSON 存答案看起来偷懒,但对这类课程项目和毕设来说是最务实的选择。原因是问卷题目在发布之后通常不允许改动,原始作答数据很少需要按题目维度做复杂的 SQL 关联,导出 Excel 统计时在 Java 里解析 JSON 反而比拼十几层 JOIN 要快得多。如果你想在技术上表现得比普通毕设更完整,可以在答卷表旁边加一张 answer_detail 表做明细拆分,但这不改变整体架构。
2.3 前后端分离架构下,Spring Boot 和 Vue 各自负责什么
这个项目的后端是一个标准的 Spring Boot 单体应用,Maven 管理依赖,MySQL 存数据,使用 MyBatis 或 MyBatis-Plus 做持久层,Spring Security 或简单拦截器做登录校验,用 JWT 换 token。前端是一个 Vue 2 或 Vue 3 的单页应用,通过 axios 请求后端接口,用 vue-router 做页面跳转,配合 Element UI 或类似组件库快速搭界面。
前端页面通常包含登录页、问卷管理列表页、问卷编辑页(动态增删题目)、问卷填写页、统计结果页。后端接口则按模块拆分,典型的 controller 有:AuthController(登录注册)、SurveyController(问卷增删改查与发布)、QuestionController(题目维护)、AnswerController(提交答卷与查询统计)。在跑通整个项目之前,建议先画一张接口清单,前端页面和接口对应上,后面联调会省很多力气。接口清单不用复杂,一张纸或一个文档就够。
3. 把环境先立住:JDK、Maven、MySQL、Vue 的安装与启动
3.1 这些环境到底要装到什么版本,才能和项目匹配
大多数这类项目的后端代码基于 Java 8 语法编写,Spring Boot 版本在 2.2.x 到 2.7.x 之间,对应的 JDK 8 或 JDK 11 都足够。少数新版本的源码用到了 Java 17 特性,但毕业设计项目里非常少,选 JDK 8 或 JDK 11 反而最稳妥。Maven 用 3.6.x 或 3.8.x 都可以,重点不是版本,而是三个配置项:本地仓库路径、阿里云镜像、JDK 编译版本。MySQL 用 5.7 或 8.0 都行,但 SQL 脚本如果是 5.7 时代导出的,直接导入 8.0 大概率遇到字符集或认证插件的问题,这一点在避坑一章会展开说。Vue 项目如果是 Vue 2 + Element UI 的脚手架,Node 版本建议 14.x 或 16.x,太新的 Node 18/20 在 node-sass 上很容易栽跟头。
安装顺序建议是:先 JDK,再 Maven,再 MySQL,最后 Node。前三个直接关系到后端能不能启动,Node 关系到前端能不能跑起来。每一步装完都先做一个最小验证,不要等全部装完再排查,那样出了问题根本不知道是哪一层坏了。
3.2 Maven 必须改的三个地方:本地仓库、镜像、JDK 版本
Maven 装好之后,不要急着运行项目。先打开 conf/settings.xml,确认三件事:localRepository 是否指向你想要的位置;mirror 是否配置了阿里云镜像;profiles 里是否锁定了 JDK 版本。很多初学者的默认 settings.xml 从未动过,下载依赖要走 Maven 中央仓库,速度慢且容易超时,项目第一次 build 就半路失败,这个坑几乎人手一份。
下面是一份可以直接替换到 settings.xml 的镜像配置片段,复制到<mirrors>标签内即可:
<mirror> <id>aliyunmaven</id> <mirrorOf>*</mirrorOf> <name>阿里云公共仓库</name> <url>https://maven.aliyun.com/repository/public</url> </mirror>这里mirrorOf的值写*,意思是所有 Maven 依赖下载请求都走这个镜像,包括中央仓库、Jcenter 等外部仓库。阿里云公共仓库聚合了绝大部分 Java 依赖,对这套项目来说完全够用。配置完成后在命令行执行mvn -v确认 Maven 版本正常,再执行mvn help:system触发一次依赖下载,看到 BUILD SUCCESS 后,后端依赖下载环节就不会再来回折磨你了。
3.3 用命令逐层验证环境:JDK、MySQL、Maven 各一行
装完环境先别解压源码,按顺序跑下面这几组命令,任何一步报错都停下来先解决。JDK 验证看的是版本号,MySQL 验证看的是能否免密或密码登录,Maven 验证看的是配置是否生效。
java -version javac -version mvn -v mysql -u root -p node -v npm -vjavac -version很多人会漏掉,但其实比java -version更关键。Spring Boot 项目用 Maven 编译时依赖 javac,如果只装了 JRE 而没装 JDK,java 命令有输出,javac 直接提示无法找到,后端代码根本无法编译。MySQL 的登录验证要注意,5.7 默认 root 密码为空或通过临时密码登录,8.0 的 root 初始化则强制要求先设置一个密码,两种方式差别很大。Node 的版本输出只要确认不是自动跳到最新的 20.x 即可,Vue 2 项目在这个版本下大概率出问题。
3.4 导入数据库:SQL 脚本不是只能整包执行
拿到 SQL 脚本后,先不要急着一次性执行。先打开脚本看一眼它是否包含CREATE DATABASE语句。很多毕设项目的 SQL 里已经建好库,直接执行即可;但另一部分只包含CREATE TABLE,这时候你需要先手动建库再导入。推荐在命令行里先确认库是否存在,再选择库执行脚本。常见的做法是:
mysql -u root -p -e "CREATE DATABASE IF NOT EXISTS survey DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;" mysql -u root -p survey < /path/to/survey.sql第一行命令用-e参数直接执行建库语句,utf8mb4字符集是这类项目必须的,否则问卷题目里一旦出现中文或表情符号,存储会直接乱码。第二行把 SQL 脚本导入到指定数据库中。执行完成后,用SHOW TABLES;确认表已经建出来,再挑一张核心表执行DESC survey;检查字段结构是否正常。脚本执行报错最常见的两种原因是字符集不匹配和脚本文件里包含 MySQL 8.0 不认识的注释语法,前者用 utf8mb4 解决,后者需要在导入前把脚本里的旧的排序规则关键字删掉。
3.5 后端第一个启动命令:spring-boot:run 和它的日志判读
数据库准备完毕后,在项目根目录执行mvn spring-boot:run启动后端。第一次启动会下载大量依赖,日志滚动速度很快,看到BUILD SUCCESS不算启动成功,看到 Spring Boot 的 Banner 和Tomcat started on port(s): 8080才算真正起来了。如果启动失败,日志的末尾几行通常会直接告诉你原因:端口被占、数据库密码错误、表不存在、或者某个 Bean 注入失败。
启动成功后先验证一个公共接口,比如登录接口或验证码接口,用 curl 直接打一次请求,比打开浏览器更快定位问题。例如:
curl -X POST http://localhost:8080/api/auth/login -H "Content-Type: application/json" -d '{"username":"admin","password":"123456"}'如果返回一段包含 token 的 JSON 数据,说明后端和数据源已经打通。如果返回空白或 500,切换后端日志窗口看异常栈,重点看是 SQL 语法错误还是数据库连接失败,这是两个完全不同的排错方向。后端到这一步,先停下来,把前端搭起来再说。
4. 把前后端联调打通:跨域配置、接口对接与数据回显
4.1 前端 devServer 代理:为什么跨域问题会在本地开发时出现
前端项目启动后默认跑在 5173(Vue 3 + Vite)或 8081(Vue 2 + vue-cli 配置端口),后端跑在 8080,两个端口不同,浏览器就会触发同源策略,所有请求在 Network 面板里显示 CORS error。解决方式有两种:后端加@CrossOrigin或全局 CORS 配置,前端 devServer 配代理。实际项目里最常见的做法是两边都做,后端为上线留余地,前端为本地开发省心。
Vue 2 项目在项目根目录下新建vue.config.js,配置 devServer 代理把/api前缀的请求转发到后端 8080 端口,这是保持前后端分离结构最干净的方式,浏览器里所有请求看起来都是相对路径,不会出现 CORS。Vue 3 + Vite 项目则是在vite.config.js里配置 server.proxy,原理一样。两种配置细节不完全相同,下面以 Vue 2 为例,如果你的项目是 Vue 3,照着关键词找 Vite 的 proxy 配置即可。
// vue.config.js const path = require("path"); module.exports = { devServer: { port: 8081, proxy: { "/api": { target: "http://localhost:8080", changeOrigin: true, pathRewrite: { "^/api": "" } } } } };port是前端开发服务器的端口号,改成 8081 是为了避免和后端 8080 冲突。proxy对象里,/api是所有后端接口的前缀,target指向后端服务地址,changeOrigin: true会把请求头里的 Host 改写成后端域名,部分后端鉴权逻辑依赖这个值。pathRewrite的作用是重写路径——如果后端接口本来就是/api/auth/login,那么这里不需要 rewrite,保留原路径;如果后端接口是/auth/login,前端请求写/api/auth/login代理过去时就得把/api剥掉。这里非常容易踩坑,要先确认后端接口有没有/api前缀再决定怎么写。
4.2 后端全局 CORS 配置:一个类解决所有请求跨域
前端代理在本地开发时解决的是“浏览器到 devServer”这一段,但如果你直接用 IP 访问后端,比如上线部署时 nginx 配置没做好,跨域问题会再次出现。所以后端加一个全局 CORS 配置是更稳的做法。在 Spring Boot 项目里常见做法是写一个配置类实现WebMvcConfigurer,覆盖addCorsMappings方法,也可以用@CrossOrigin注解但每个 Controller 都要加,太繁琐。
下面这套配置是当前最常见的全局 CORS 方案,需要放在 Spring Boot 能扫描到的包下:
package com.example.survey.config; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); } }addMapping("/**")的含义是所有后端接口都允许跨域请求,allowedOriginPatterns("*")表示允许任意来源访问,注意这里不能用allowedOrigins("*")和allowCredentials(true)同时使用,这是 JDK 版本的已知限制,会导致启动报错。毕设项目里鉴权方式通常是前端本地存的 JWT token,所以allowCredentials保持true即可。maxAge(3600)是预检请求的缓存时间,单位秒,设置之后同一来源的浏览器在一小时内不会重复发起 OPTIONS 预检请求,能减少很多无效请求。
4.3 axios 请求封装:登录态从哪里来,接口报错怎么统一处理
后端接口联调时,最常遇到的问题不是接口本身出错,而是前端没有携带 token。常见做法是在 axios 的请求拦截器里取出 localStorage 或 sessionStorage 中保存的 token,放到请求头 Authorization 字段里。下面是一个典型的 axios 封装片段,很多项目里直接放在src/utils/request.js:
import axios from "axios"; const request = axios.create({ baseURL: process.env.VUE_APP_API_BASE || "/api", timeout: 10000 }); request.interceptors.request.use(config => { const token = localStorage.getItem("token"); if (token) { config.headers.Authorization = "Bearer " + token; } return config; }); request.interceptors.response.use( response => response.data, error => { if (error.response && error.response.status === 401) { localStorage.removeItem("token"); window.location.href = "/login"; } return Promise.reject(error); } ); export default request;baseURL是接口前缀,开发环境走/api会被 devServer 代理,生产环境可以换成完整后端地址,这也是为什么推荐用环境变量而不是写死。timeout设为 10 秒,问卷提交接口如果包含大量文本答案,响应时间会比较长,10 秒是一个折中值。请求拦截器里关键的一行是Authorization: "Bearer " + token,有些项目的后端拦截器只认token字段而不是Authorization,需要对照后端的 JWT 过滤器源代码确认。响应拦截器里的401处理是一个好习惯——token 过期后自动清空登录态并跳回登录页,否则用户会在问卷填写页面遇到各种莫名其妙的白屏或请求失败。
4.4 一个最小可用的登录联调流程:从点击按钮到页面跳转
环境都立住之后,完整走一遍登录联调是检验全链路最有效的方式。前端登录页表单提交时,调用request.post("/auth/login", { username, password }),后端返回 token 和用户信息,前端把 token 存入 localStorage,再通过 vue-router 跳转到问卷列表页。列表页挂载时请求request.get("/survey/list")拉取问卷数据,渲染成表格。
如果这一步成功,说明后端数据库连接正常、接口路由正确、前端代理生效、axios 封装没有缺失,后续所有功能都建立在这条链路上。如果列表页空白但登录成功,优先打开浏览器开发者工具,切到 Network 面板刷新页面,看/survey/list请求是否发出、状态码是多少、响应体是什么。响应体如果是{"code":200,"data":[]},那是真的没数据;如果看到 404,大概率是接口路径不匹配,这种问题十有八九是前端写错路径或者代理 rewrite 配置不对,最容易解决但最难定位。
5. 在线问卷调查系统联调避坑:六个常见的翻车点与排查办法
5.1 启动时报错 Port already in use:不是代码问题,是端口被占
现象:执行mvn spring-boot:run后,日志提示Web server failed to start. Port 8080 was already in use.,后端直接退出。原因:之前启动过另一个 Spring Boot 项目,或者某个进程占用了 8080 端口没有释放。解决:在命令行执行netstat -ano | findstr 8080查出占用端口的 PID,根据系统是 Windows 还是 Linux 杀掉对应进程后重新启动;或者直接修改后端配置文件的server.port为 8081,同时同步修改前端代理 target 端口。不要一遇到端口冲突就改端口,先确认是不是自己之前启动的服务没关干净,否则改一次端口,后续所有接口配置都要跟着动。
5.2 mysql 8.0 导入 5.7 的 SQL 脚本:字符集和认证插件不兼容
现象:SQL 脚本在 MySQL 5.7 上正常执行,导入 8.0 时报错Unknown collation: utf8mb4_0900_ai_ci或using password: NO之类的问题,部分表现在启动后端时提示数据库连接失败。原因:MySQL 8.0 的默认字符集排序规则变成 utf8mb4_0900_ai_ci,而 5.7 时代的脚本写死的是 utf8mb4_general_ci;同时 8.0 默认使用 caching_sha2_password 认证插件,老版本连接驱动可能不认识。解决:打开 SQL 脚本,用编辑器全局替换utf8mb4_0900_ai_ci为utf8mb4_general_ci;在 pom.xml 里把 mysql-connector-java 升级到 8.0.33 或对应版本,如果你用的是 Spring Boot 2.7.x 以上,还要留意依赖中自带驱动版本的兼容性。
5.3 前端安装依赖时 node-sass 构建失败:Node 版本不对
现象:在 Vue 2 项目根目录执行npm install后,终端输出大量node-gyp错误,提示gyp ERR! stack Error: Can't find Python executable或直接node-sass加载失败。原因:node-sass 是一个需要本地编译的原生模块,依赖 Python 和 C++ 编译工具链,Node 版本太新时 node-sass 的二进制预编译产物不存在,只能现场编译,而大部分人的电脑没有安装对应的构建工具链。解决:优先换 Node 版本,14.17.x 或 16.15.x 是 Vue 2 + node-sass 项目的稳定区间;如果项目已经切换到 sass(dart-sass),则基本没有这个问题。不要强行在 Node 20 上编译 node-sass,浪费时间且成功率很低。
5.4 前端页面能打开但接口全 404:代理路径和后端接口前缀对不上
现象:前端登录成功,跳转列表页后表格一直转圈,Network 面板里接口请求显示 404。原因:很典型的一种情况是前端 axios 的 baseURL 是/api,代理把/api重写成了空,而后端真实接口路径是/survey/list,最终请求变成了/survey/list路径错位;另一种情况是后端接口本身就没有/api前缀,但代理没做 pathRewrite,请求带着/api去打后端,后端自然找不到。解决:先在后端 Controller 里找到登录接口的真实路径,比如@PostMapping("/auth/login"),然后决定代理写法——后端接口带/api前缀,则前端 baseURL 写/api,代理直接透传不 rewrite;不带前缀,则前端写法不变,代理打开 pathRewrite。核心原则只有一条:代理之后的最终请求路径必须和后端 Controller 里的路径完全一致。
5.5 提交问卷时提示未登录,后端却没有任何日志:前端少发了 token
现象:问卷填写页提交答案时,前端弹出“请先登录”的提示,但打开 Network 面板能看到请求正常发出,后端控制台也没有异常日志。原因:axios 拦截器没有生效,或者 token 的 key 名不对。更隐蔽的情况是——你用的 axios 实例是在request.js里创建的,但页面里却直接从axios公共库发请求,绕过了拦截器。解决:打开页面源码确认所有 HTTP 请求都用封装后的request对象发出;再打开浏览器 Application/Local Storage 面板确认 token 的 key 名,和后端拦截器的读取字段名一致。很多项目后端拦截器读的是token头部,前端写的却是Authorization,两边对不上就出现这种诡异的“假未登录”现象。
5.6 后端能跑、前端能跳,问卷列表却返回空数据:数据库里根本没有前端要求的字段
现象:接口返回 200,响应数据格式正常,但列表渲染不出来,或出现了字段名全部是 undefined 的表格行。原因:后端实体类的字段命名是下划线风格如survey_id,前端模板里写的却是驼峰surveyId,两者对不上。注意,如果项目使用了 MyBatis-Plus,开启驼峰映射后通常能自动转换,但如果手写 SQL 的 resultMap 没配全,就会漏字段。解决:先看后端实体类用了什么注解,再对比数据库列名和前端取值字段,优先在后端实体上统一加@TableField("survey_id")注解,或在前端模板里改成对应字段名。这个坑不涉及复杂技术,但一旦碰上,查起来特别费眼神。
6. 项目跑通之后,如何用一份演示清单证明这套系统真的可用
项目能启动、能登录、能提问卷之后,不要急着去改代码加功能,先做一遍完整的回归演示,验证所有核心链路。一个合格的在线问卷调查系统,至少要能在五分钟内按顺序演示以下流程:管理员登录、创建一份包含单选题、多选题、文本题的问卷、把问卷发布、退出登录、用普通用户账号注册并登录、找到该问卷并完成填写、再切回管理员账号查看统计结果。每一步都要能用一两句话说清前端页面在哪里操作、后端接口是什么、数据库哪张表的数据发生了变化。如果有人问你“为什么不用原生 JavaScript 或 JSP 做,而要用前后端分离这么重的一套”,你的回答锚点应该是:Vue 负责问卷编辑时的动态增删题目体验,Spring Boot 负责把问卷、答卷、统计结果通过 REST 接口稳定地暴露给多端,MySQL 负责把结构化数据按关系模型存下来。这套技术组合解决的不仅是功能问题,更是后续扩展和维护的结构问题。
如果想要在答辩或面试时多一些增量亮点,可以在统计报表里增加一条导出链路:用 Apache POI 把问卷结果导出成 Word 文档,并把每道题的选择占比生成一个简单的柱状图图表画进去。POI 的 XWPFDocument 支持在文档中插入图片,而柱状图的生成可以借助 JFreeChart 渲染成 BufferedImage 后写入 Word。这个功能的好处是它同时覆盖了文件生成、图表渲染、数据聚合三个能力点,不需要额外引入复杂的报表组件,工作量控制在 200 行左右的代码就能完成。这也恰好回应了“Java POI word 能生成图表吗”这个问题的核心思路——POI 本身不画图表,但用 JFreeChart 先画出图片,再把图片嵌进 Word,是 Java 生态里最不折腾的常规做法。
而如果要继续深挖,剩下的路无非三条:把问卷系统的权限模型从简单的角色字段换成 Spring Security + 细粒度权限控制;把原本存 JSON 的答卷表拆成明细表,配合定时任务做统计聚合;或者把前后端分别打包后用 nginx 部署到服务器上,把本地能跑变成线上能访问。这三条路每一条都能让这套课程项目向生产级迈一小步,但做之前一定要评估自己的时间。我个人的习惯是,无论多小的项目,跑通后先写一份 README 放在项目根目录,记录三件事:环境版本、启动命令、演示账号。这件事能让你三个月后回来看这个项目时少花一个下午重新“考古”,也能让答辩老师或接手的人不用猜你的项目怎么跑。希望这篇笔记帮到你,把你手上那套源码从“能打开”变成“能讲清楚、扛得住追问”。
本文还有配套的精品资源,点击获取