1. 项目概述与核心需求
这个调查问卷管理系统是我去年为一个教育机构开发的线上数据收集平台。核心需求源于该机构需要定期对学生、教师和家长进行各类问卷调查,但传统纸质问卷存在回收率低、数据统计困难等问题。系统采用前后端分离架构,后端基于SpringBoot+MySQL实现业务逻辑和数据存储,前端使用Vue+ElementUI构建响应式管理界面。
从技术选型来看,SpringBoot的自动配置特性大幅简化了Web应用的初始化工作,Vue+ElementUI的组合则能快速搭建美观的管理后台。系统需要实现问卷创建、问题设计、发布回收、数据统计全流程功能,同时要保证高并发下的稳定性——在教育机构集中填写问卷时,可能会出现短时间内大量提交的情况。
2. 技术架构设计
2.1 后端技术栈
后端采用经典的SpringBoot+MyBatis组合,这是经过多个项目验证的稳定方案。SpringBoot版本选用2.7.3(LTS版本),MySQL使用8.0.26社区版。项目结构采用Maven多模块设计:
code复制survey-system
├── survey-admin -- 管理后台接口模块
├── survey-common -- 公共组件模块
├── survey-generator -- 代码生成模块
└── survey-questionnaire -- 核心业务模块
数据库连接池选用HikariCP,实测性能比传统的Druid高出20%左右。JSON序列化使用FastJson2,特别针对问卷结果这种可能包含大量文本数据的场景做了优化配置:
java复制@Bean
public HttpMessageConverters fastJsonHttpMessageConverters() {
FastJsonConfig config = new FastJsonConfig();
config.setSerializerFeatures(
SerializerFeature.WriteMapNullValue,
SerializerFeature.DisableCircularReferenceDetect
);
return new HttpMessageConverters(
new FastJsonHttpMessageConverter());
}
2.2 前端技术方案
前端采用Vue3+ElementPlus的组合,通过axios与后端通信。项目结构如下:
code复制src
├── api -- 接口定义
├── assets -- 静态资源
├── components -- 公共组件
├── router -- 路由配置
├── store -- Vuex状态管理
├── styles -- 全局样式
└── views -- 页面组件
特别开发了几个核心组件:
- QuestionEditor:可视化问题设计器
- SurveyPreview:问卷预览组件
- DataDashboard:数据看板组件
3. 核心功能实现
3.1 问卷建模与数据库设计
问卷系统的核心难点在于如何灵活支持多种题型(单选、多选、填空、评分等),同时保证查询效率。最终设计的核心表结构如下:
sql复制CREATE TABLE `survey` (
`id` bigint NOT NULL AUTO_INCREMENT,
`title` varchar(100) NOT NULL,
`description` text,
`start_time` datetime DEFAULT NULL,
`end_time` datetime DEFAULT NULL,
`status` tinyint DEFAULT '0' COMMENT '0-未发布 1-已发布 2-已结束',
`creator_id` bigint NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `question` (
`id` bigint NOT NULL AUTO_INCREMENT,
`survey_id` bigint NOT NULL,
`content` text NOT NULL,
`type` tinyint NOT NULL COMMENT '1-单选 2-多选 3-填空 4-评分',
`is_required` tinyint DEFAULT '0',
`order_num` int DEFAULT '0',
PRIMARY KEY (`id`),
KEY `idx_survey` (`survey_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `question_option` (
`id` bigint NOT NULL AUTO_INCREMENT,
`question_id` bigint NOT NULL,
`content` varchar(255) NOT NULL,
`order_num` int DEFAULT '0',
PRIMARY KEY (`id`),
KEY `idx_question` (`question_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3.2 问卷发布流程实现
发布流程需要考虑并发控制和状态管理。核心代码如下:
java复制@Transactional
public void publishSurvey(Long surveyId) {
Survey survey = surveyMapper.selectById(surveyId);
if (survey == null) {
throw new BusinessException("问卷不存在");
}
if (survey.getStatus() != SurveyStatus.DRAFT) {
throw new BusinessException("只有草稿状态的问卷可以发布");
}
survey.setStatus(SurveyStatus.PUBLISHED);
survey.setPublishTime(LocalDateTime.now());
surveyMapper.updateById(survey);
// 生成访问令牌
String token = TokenUtil.generateSurveyToken(surveyId);
redisTemplate.opsForValue().set(
"survey:access:" + surveyId,
token,
30, TimeUnit.DAYS);
}
3.3 数据收集与存储优化
问卷提交采用异步处理模式,先写入Redis队列再持久化到MySQL,防止高并发时数据库压力过大:
java复制@PostMapping("/submit")
public Result submitAnswer(@RequestBody AnswerDTO dto) {
// 验证问卷状态
Survey survey = surveyService.getById(dto.getSurveyId());
if (survey.getStatus() != SurveyStatus.PUBLISHED) {
return Result.error("问卷未开放");
}
// 异步处理
redisTemplate.opsForList().rightPush(
"survey:submit:queue",
JSON.toJSONString(dto));
return Result.success();
}
@Scheduled(fixedRate = 5000)
public void processSubmitQueue() {
// 批量处理队列中的提交
List<String> items = redisTemplate.opsForList()
.range("survey:submit:queue", 0, 99);
if (!items.isEmpty()) {
List<Answer> answers = items.stream()
.map(item -> parseAnswer(item))
.collect(Collectors.toList());
answerService.batchInsert(answers);
redisTemplate.opsForList().trim(
"survey:submit:queue",
100, -1);
}
}
4. 关键问题与解决方案
4.1 高并发提交处理
在实际压力测试中,当300人同时提交问卷时出现了以下问题:
- 数据库连接池耗尽
- 响应时间超过5秒
- 部分提交丢失
最终解决方案:
- 引入Redis作为缓冲层
- 采用批量插入替代单条插入
- 增加连接池大小和超时设置
yaml复制spring:
datasource:
hikari:
maximum-pool-size: 50
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
4.2 复杂题型的数据存储
对于矩阵题、排序题等复杂题型,采用JSON格式存储答案:
java复制@Entity
@Table(name = "answer")
public class Answer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "survey_id")
private Long surveyId;
@Column(name = "question_id")
private Long questionId;
@Column(name = "content", columnDefinition = "json")
private String content; // 存储JSON格式答案
// getters & setters
}
4.3 数据统计性能优化
问卷结果统计采用预聚合方案,每天凌晨通过定时任务计算各类统计指标:
java复制@Scheduled(cron = "0 0 3 * * ?")
public void calculateStatistics() {
List<Long> surveyIds = surveyMapper.getActiveSurveyIds();
for (Long surveyId : surveyIds) {
Map<Long, QuestionStats> stats =
answerMapper.calculateQuestionStats(surveyId);
stats.forEach((questionId, stat) -> {
redisTemplate.opsForValue().set(
"survey:stats:" + surveyId + ":" + questionId,
JSON.toJSONString(stat));
});
}
}
5. 前端实现细节
5.1 可视化问卷设计器
基于Vue的draggable组件实现题目拖拽排序:
vue复制<template>
<draggable
v-model="questions"
@end="onSortEnd">
<QuestionItem
v-for="(q, index) in questions"
:key="q.tempId"
:question="q"
@remove="removeQuestion(index)"
@update="updateQuestion(index, $event)"/>
</draggable>
</template>
<script>
import draggable from 'vuedraggable'
export default {
components: { draggable },
methods: {
onSortEnd() {
this.questions.forEach((q, i) => {
q.orderNum = i + 1
})
}
}
}
</script>
5.2 响应式表单验证
针对不同题型实现动态验证规则:
javascript复制const rules = {
[QuestionType.SINGLE_CHOICE]: (value) => {
return !!value || '请选择一项'
},
[QuestionType.MULTIPLE_CHOICE]: (value) => {
return (value && value.length > 0) || '请至少选择一项'
},
[QuestionType.TEXT]: (value) => {
return (value && value.trim().length > 0) || '请输入内容'
}
}
export function getValidator(type) {
return rules[type] || (() => true)
}
6. 部署与性能调优
6.1 生产环境部署方案
采用Docker Compose部署,包含以下服务:
- 应用服务(SpringBoot)
- MySQL数据库
- Redis缓存
- Nginx反向代理
yaml复制version: '3'
services:
app:
image: survey-system:1.0
ports:
- "8080:8080"
depends_on:
- mysql
- redis
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
volumes:
- ./mysql-data:/var/lib/mysql
redis:
image: redis:6.2
ports:
- "6379:6379"
6.2 JVM参数优化
通过多次压测确定的JVM参数:
bash复制java -jar \
-Xms1024m -Xmx2048m \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-XX:InitiatingHeapOccupancyPercent=35 \
-XX:+ExplicitGCInvokesConcurrent \
-Dspring.profiles.active=prod \
survey-system.jar
7. 项目总结与改进方向
经过三个月的开发和优化,系统目前稳定支持日均10万次问卷提交,主要成功经验包括:
- 异步处理架构有效应对了提交高峰
- Redis的多场景应用(缓存、队列、统计)
- 前后端分离带来的开发效率提升
下一步计划:
- 增加实时数据分析看板
- 支持问卷逻辑跳转
- 优化移动端填写体验
在开发过程中特别要注意的是,问卷系统的核心在于灵活性和稳定性之间的平衡。太灵活的设计会导致实现复杂,太简单的设计又无法满足多样化的问卷需求。我们的方案是通过定义有限的题型组合,配合自定义属性来达到平衡。