简介:这是一套基于Spring Boot的在线问卷调查系统完整源码,项目名为questionnaire-master,面向具备Java与Spring基础、希望以实战方式掌握Web全栈开发的学习者与开发者。系统覆盖用户登录注册与权限控制、问卷模板创建编辑、问卷发布与社交网络分享、结果统计与图表展示、个人中心等核心模块,并涉及Spring Security、Spring Data JPA、Spring MVC、RESTful API设计及MySQL数据库表结构设计等知识点,可作为课程设计、毕业设计或技术练手的参考案例。资源包共294个文件,以100个java源码为主体,辅以26个js、26个html、8个css等前端页面与脚本,另有4个sql建表脚本、6个properties配置、dockerfile与yml部署文件及若干图片素材,压缩包约37.17MB,目录结构清晰,便于按模块阅读与二次开发。目前已有269人学习下载,适合通过源码理解在线问卷系统从后端接口到前端交互的完整实现流程。
1. 从一份问卷系统源码说起:为什么 Spring Boot 项目值得反复拆
很多做后端的朋友拿到一份「基于 Spring Boot 的在线问卷调查系统源码」时,第一反应是打开 IDEA 跑起来看看效果,然后就没有然后了。我见过太多人把这类项目当成练手 Demo,跑通登录、建个问卷、提交一次答案,就丢进硬盘吃灰。但真正有价值的做法,是把它当成一个完整的业务系统来拆——问卷系统看起来简单,实际上覆盖了权限、动态表单、统计聚合、并发提交这几个后端高频场景,麻雀虽小五脏俱全。
这份源码适合三类人:一是刚学完 Spring Boot 基础、想找一个完整项目练手的开发者;二是需要快速搭一套问卷/表单收集工具的后端工程师;三是准备面试、想拿一个能讲清楚业务链路的项目当谈资的人。它解决的核心问题是:把「创建问卷 → 发布 → 用户填写 → 数据回收 → 统计分析」这条链路用一套可运行的代码落地,而不是停留在教程里的增删改查。
2. 拆开看架构:分层、依赖与动态表单的选型逻辑
2.1 先搞清楚项目怎么分层
拿到源码第一步不是跑,是看目录结构。一个规范的 Spring Boot 问卷系统,通常按经典三层走:controller接请求、service写业务、mapper(或repository)碰数据库。但问卷系统有个特殊点——它的「题目」和「选项」是动态的,不是固定表结构能解决的,所以实体设计上会比普通 CRUD 多一层。
我一般会先找这几个关键包:
# 典型目录结构(以 Maven 项目为例) src/main/java/com/example/survey/ ├── controller/ # 接口层:问卷的增删改查、提交、统计 ├── service/ # 业务层:问卷发布逻辑、答案校验、统计聚合 ├── mapper/ # 持久层:MyBatis 或 JPA 接口 ├── entity/ # 实体:Survey、Question、Option、Answer、User ├── dto/ # 传输对象:提交答案的请求体、统计返回体 └── config/ # 配置:拦截器、跨域、MyBatis 配置看目录就能判断技术栈:有mapper包基本是 MyBatis,有repository包大概率是 JPA。这一步决定了你后面改代码的写法,别急着动手。
2.2 动态表单的实体设计是核心
问卷系统最难的地方不在接口,在数据模型。一份问卷有多个题目,每个题目有多个选项,用户提交的答案要能对应回题目和选项。常见做法是四张核心表:
| 表名 | 作用 | 关键字段 |
|---|---|---|
| survey | 问卷主表 | id、title、status、create_time |
| question | 题目表 | id、survey_id、type、content、sort |
| option | 选项表 | id、question_id、content、sort |
| answer | 答案表 | id、survey_id、question_id、option_id、user_id |
这里有个选型细节:question.type字段决定题目是单选、多选还是填空。单选多选走option表关联,填空则直接把文本存进answer的扩展字段。我见过有的实现把填空也塞进 option 表,结果统计时一团乱,这是设计阶段就该避开的坑。
2.3 跑起来之前先确认三件事
在application.yml里,重点看三个配置:数据库连接、端口、以及有没有用 Redis 做缓存或 session。问卷系统如果带登录,session 存储方式直接影响你能不能多端调试。
server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/survey?useSSL=false&serverTimezone=Asia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver # 如果项目用了 Redis 存 session,这里会有 redis 配置 redis: host: localhost port: 6379参数说明:serverTimezone必须设,否则 MySQL 8 以上版本时间字段会报错;useSSL=false本地调试用,生产环境要开。数据库要先手动建好survey库,导入源码里的schema.sql或init.sql,别指望框架自动建表——大部分这类项目没配ddl-auto。
3. 把项目跑起来:建库、导数据、启动与接口验证
3.1 数据库初始化别偷懒
源码包里通常有个sql文件夹,里面是建表语句和初始数据。我一般用命令行导入,比图形化工具稳:
# 登录 MySQL 并创建数据库 mysql -u root -p -e "CREATE DATABASE survey DEFAULT CHARACTER SET utf8mb4;" # 导入建表脚本(假设文件在项目根目录的 sql 文件夹) mysql -u root -p survey < sql/schema.sql # 导入初始数据(如果有) mysql -u root -p survey < sql/data.sql逻辑说明:utf8mb4是为了支持 emoji,问卷里用户填表情很常见,用utf8会存不进去。导入顺序不能反,先建表再插数据。如果脚本里用了DROP TABLE IF EXISTS,重复执行不会报错,但会清空数据,生产环境慎用。
3.2 启动项目与常见启动失败
用 Maven 启动:
# 在项目根目录执行 mvn clean spring-boot:run # 或者打包后运行 mvn clean package -DskipTests java -jar target/survey-system-0.0.1-SNAPSHOT.jar启动失败最常见的原因是端口占用和数据库连不上。端口占用改server.port;数据库连不上先看url里的库名和账号密码,再看 MySQL 服务有没有起。如果报Table 'survey.xxx' doesn't exist,说明建表脚本没导成功,回去检查。
3.3 用接口验证核心链路
项目起来后,别急着开浏览器,先用 curl 或 Postman 把核心接口过一遍。典型接口有这几个:
# 1. 创建问卷 curl -X POST http://localhost:8080/api/survey \ -H "Content-Type: application/json" \ -d '{"title":"测试问卷","questions":[{"type":1,"content":"你常用什么语言?","options":["Java","Python","Go"]}]}' # 2. 获取问卷详情(假设返回的 id 是 1) curl http://localhost:8080/api/survey/1 # 3. 提交答案 curl -X POST http://localhost:8080/api/answer \ -H "Content-Type: application/json" \ -d '{"surveyId":1,"answers":[{"questionId":1,"optionId":1}]}' # 4. 查看统计 curl http://localhost:8080/api/survey/1/statistics参数说明:type:1代表单选,optionId对应选项表的主键。提交答案时questionId和optionId必须成对出现,缺一个后端校验就会拦。统计接口返回的通常是每个选项的计数,如果返回空,先确认answer表里有没有数据。
提示:如果接口返回 401 或 403,说明项目配了权限拦截,先找登录接口拿 token,再在请求头加
Authorization。
4. 避坑排查:这份源码最容易翻车的五个地方
4.1 提交答案报「题目不存在」
现象:调用提交接口时返回题目校验失败,但问卷详情里明明有这道题。 原因:提交的questionId和问卷实际关联的题目 id 对不上,常见于前端传了数组下标而不是数据库主键。 解决:先调问卷详情接口,把返回的questionId原样传给提交接口,别自己拼。
4.2 统计结果全是零
现象:答案表里有数据,但统计接口返回的每个选项计数都是 0。 原因:统计 SQL 的关联条件写错了,常见是answer.option_id和option.id没对上,或者survey_id过滤条件漏了。 解决:打开 mapper 里对应的统计 SQL,先手动在数据库执行一遍,确认能查出数再回来看代码。
4.3 中文乱码
现象:问卷标题或选项存进数据库变成问号。 原因:数据库字符集不是utf8mb4,或者 JDBC url 没加字符集参数。 解决:建库时指定utf8mb4,url 里加characterEncoding=utf8,两边都要改。
4.4 多选答案只存了最后一个
现象:用户选了三个选项,数据库里只有一条记录。 原因:多选提交时前端传了数组,后端只取了第一个元素,或者循环插入时事务没提交。 解决:检查 service 层对多选的处理逻辑,确认是遍历插入而不是取[0],并确认方法上有@Transactional。
4.5 启动报 Redis 连接失败
现象:项目起不来,日志里一堆 Redis 连接异常。 原因:源码里配了 Redis 但本地没装,或者配置的 host 不对。 解决:要么本地起一个 Redis,要么把 Redis 相关配置和依赖暂时注释掉,先跑通主流程。
5. 二次开发技巧:把问卷系统改成你自己的工具
5.1 加一种新题型:评分题
原项目只有单选、多选、填空,想加个 1-5 分的评分题,改动其实很小。核心是在question.type里加一个枚举值,比如4代表评分,然后在前端渲染和后端校验里各加一个分支。
// 在题目类型枚举里加一项 public enum QuestionType { SINGLE(1), MULTI(2), TEXT(3), RATING(4); private final int code; QuestionType(int code) { this.code = code; } public int getCode() { return code; } } // service 层校验时放行评分题 if (type == QuestionType.RATING.getCode()) { // 评分题答案直接存数值,不关联 option 表 answer.setScore(request.getScore()); }逻辑说明:评分题不需要option表,答案直接存分数。参数上要注意分数范围校验,别让用户传个 100 分进来。这个改动涉及实体、service、mapper 三处,改完记得同步更新统计逻辑——评分题统计的是平均分,不是计数。
5.2 用拦截器统一处理问卷状态
问卷有草稿、已发布、已关闭三种状态,如果每个接口都写一遍状态判断,代码会很脏。我一般加一个拦截器,在进入 controller 前统一校验。
@Component public class SurveyStatusInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String uri = request.getRequestURI(); // 只拦截提交答案的接口 if (uri.contains("/api/answer")) { Long surveyId = Long.parseLong(request.getParameter("surveyId")); Survey survey = surveyService.getById(surveyId); if (survey.getStatus() != 1) { // 1 代表已发布 response.setStatus(403); return false; } } return true; } }参数说明:status字段的取值要和数据库里定义的一致,别一边用 0/1 一边用字符串。拦截器注册在WebMvcConfig里,注意放行登录和问卷详情接口,否则用户连问卷都看不到。
5.3 验证改动是否生效的笨办法
改完代码别只看编译通过,我习惯用「三步验证」:第一步,重启项目看日志有没有报错;第二步,用 curl 把改动的接口重新打一遍,看返回结构对不对;第三步,直接查数据库,确认数据真的落库了。这三步走完,基本不会出现「接口通了但数据没存」的玄学问题。
从那以后我每次拆这类源码,都强制自己先跑通核心链路再动代码,改一处验一处,绝不一次性改五个文件再启动。希望这份拆解能帮到你,拿到源码后少走点弯路。
本文还有配套的精品资源,点击获取