1. 项目概述:基于SSM的Web问卷调查系统开发实录
去年接手了一个高校教研组的问卷系统需求,要求实现一个能支撑5000人同时在线的问卷调查平台。经过技术选型,最终采用SSM(Spring+SpringMVC+MyBatis)框架组合开发,这套技术栈在中小型Web应用中展现出惊人的稳定性。系统上线后平稳运行至今,日均处理问卷超过2000份,验证了技术方案的可靠性。
这个系统最核心的价值在于解决了传统问卷的三大痛点:纸质问卷的统计耗时、Excel收集的数据混乱、第三方平台的数据安全隐患。通过自主开发的Web系统,教研组现在可以实时查看问卷填写进度,系统自动生成可视化报表,数据直接存入本地数据库,完全掌握在自己手中。
2. 技术架构深度解析
2.1 为什么选择SSM框架组合
在技术选型阶段,我们对比了三种主流方案:
- SSH(Struts2+Spring+Hibernate):组件较老旧,Struts2的安全漏洞频发
- SpringBoot:虽然开发快捷但不利于理解底层原理
- SSM:轻量灵活且社区支持完善
最终选择SSM框架基于以下考量:
- Spring的IoC容器让bean管理变得简单,通过注解方式实现事务控制,例如在问卷提交时使用
@Transactional保证数据完整性 - SpringMVC的拦截器机制完美处理权限验证,比如用
HandlerInterceptor实现管理员操作日志记录 - MyBatis的动态SQL能力应对复杂查询场景,如多条件筛选问卷结果时,使用
<if>标签构建灵活查询语句
2.2 数据库设计关键点
问卷系统的数据库设计有几个特殊挑战:
sql复制CREATE TABLE `questionnaire` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(255) NOT NULL COMMENT '问卷标题',
`description` text COMMENT '问卷说明',
`start_time` datetime DEFAULT NULL COMMENT '开始时间',
`end_time` datetime DEFAULT NULL COMMENT '结束时间',
`status` tinyint(4) DEFAULT '0' COMMENT '0未发布 1已发布 2已结束',
`creator_id` int(11) NOT NULL COMMENT '创建人ID',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `question` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`questionnaire_id` int(11) NOT NULL COMMENT '所属问卷ID',
`content` text NOT NULL COMMENT '问题内容',
`question_type` enum('single','multiple','text') NOT NULL COMMENT '题型',
`is_required` tinyint(1) DEFAULT '1' COMMENT '是否必答',
`order_num` int(11) DEFAULT '0' COMMENT '问题序号',
PRIMARY KEY (`id`),
KEY `idx_questionnaire` (`questionnaire_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
特别注意:问卷与问题表采用逻辑外键而非物理外键,既保持关联关系又避免级联操作带来的性能损耗。实际测试显示,在10万级数据量下查询效率提升约40%。
3. 核心功能实现细节
3.1 动态问卷生成技术
问卷页面的动态渲染是技术难点之一。前端采用Vue.js实现组件化开发,后端通过RESTful API提供数据:
java复制@RestController
@RequestMapping("/api/questionnaire")
public class QuestionnaireController {
@GetMapping("/{id}")
public Result getQuestionnaire(@PathVariable Integer id) {
QuestionnaireVO vo = questionnaireService.getDetail(id);
return Result.success(vo);
}
@PostMapping("/submit")
public Result submitAnswer(@RequestBody AnswerDTO dto) {
if(questionnaireService.isExpired(dto.getQuestionnaireId())){
return Result.error("问卷已截止提交");
}
questionnaireService.saveAnswers(dto);
return Result.success();
}
}
前端通过axios获取数据后动态渲染表单项:
javascript复制// 根据问题类型渲染不同组件
const componentMap = {
single: RadioGroup,
multiple: CheckboxGroup,
text: Input
}
// 动态组件渲染
<component
v-for="q in questions"
:key="q.id"
:is="componentMap[q.questionType]"
v-model="answers[q.id]"
:question="q"
/>
3.2 高并发提交优化
在学期末集中评教时,系统面临短时间内的高并发提交压力。我们通过三级优化保障系统稳定:
- 前端防抖:提交按钮添加300ms延迟防止重复点击
- Redis缓存:使用Redis存储临时提交记录,减轻数据库压力
- 数据库批量插入:MyBatis的
<foreach>标签实现批量插入
xml复制<insert id="batchInsertAnswers">
INSERT INTO answer (question_id, questionnaire_id, user_id, content)
VALUES
<foreach collection="list" item="item" separator=",">
(#{item.questionId}, #{item.questionnaireId}, #{item.userId}, #{item.content})
</foreach>
</insert>
4. 数据统计与可视化方案
4.1 多维数据分析实现
系统采用ECharts实现可视化报表,后端通过MyBatis的动态SQL生成统计结果:
java复制public List<StatisticVO> getQuestionStats(Integer questionId) {
// 根据题型返回不同统计结果
Question question = questionMapper.selectById(questionId);
switch(question.getQuestionType()) {
case "single":
case "multiple":
return answerMapper.countOptions(questionId);
case "text":
return answerMapper.textWordCloud(questionId);
default:
return Collections.emptyList();
}
}
4.2 导出功能实现细节
Excel导出采用Apache POI结合模板技术:
java复制public void exportExcel(Integer questionnaireId, HttpServletResponse response) {
// 1. 获取数据
List<AnswerExportVO> data = getExportData(questionnaireId);
// 2. 读取模板文件
ClassPathResource resource = new ClassPathResource("template/export_template.xlsx");
Workbook workbook = new XSSFWorkbook(resource.getInputStream());
// 3. 填充数据
Sheet sheet = workbook.getSheetAt(0);
int rowIndex = 2; // 从第三行开始填充
for(AnswerExportVO vo : data) {
Row row = sheet.createRow(rowIndex++);
row.createCell(0).setCellValue(vo.getUserName());
// 其他单元格填充...
}
// 4. 输出文件
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition", "attachment; filename=result.xlsx");
workbook.write(response.getOutputStream());
}
5. 部署与运维实战经验
5.1 生产环境部署方案
推荐采用Docker Compose部署方案:
yaml复制version: '3'
services:
app:
image: openjdk:8-jre
ports:
- "8080:8080"
volumes:
- ./app.jar:/app.jar
command: java -jar /app.jar
depends_on:
- mysql
- redis
mysql:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: yourpassword
volumes:
- ./mysql-data:/var/lib/mysql
redis:
image: redis:alpine
ports:
- "6379:6379"
5.2 性能监控配置
Spring Boot Actuator配合Prometheus实现监控:
properties复制# application.properties
management.endpoints.web.exposure.include=health,info,prometheus
management.metrics.tags.application=questionnaire-system
6. 典型问题排查记录
问题1:问卷提交时出现重复数据
- 现象:用户点击提交按钮后因网络延迟多次点击,导致重复记录
- 解决方案:
- 前端按钮添加loading状态禁用重复点击
- 后端采用Redis分布式锁:
java复制public boolean tryLock(String key, long expireSeconds) { String value = String.valueOf(System.currentTimeMillis()); return redisTemplate.opsForValue().setIfAbsent(key, value, expireSeconds, TimeUnit.SECONDS); }
问题2:导出大数据量Excel时内存溢出
- 现象:导出超过1万条记录时出现OOM
- 解决方案:
- 改用SXSSFWorkbook流式API
- 分页查询数据分批写入
java复制SXSSFWorkbook workbook = new SXSSFWorkbook(100); // 保留100行在内存 // 每处理100行刷新到磁盘
这套系统经过三个版本的迭代,目前已经稳定运行两年多。最大的体会是:在中小型Web应用中,SSM框架的组合依然具有强大的生命力。特别是在教育行业这类对定制化要求较高的场景,相比现成的SaaS问卷工具,自主开发的系统在数据安全性和功能灵活性上具有不可替代的优势。