1. 项目概述与技术栈选型
这套基于SpringBoot2+Vue3+MyBatis-Plus+MySQL8.0的在线问卷调查系统,是当前企业级应用开发的典型技术组合方案。作为前后端分离架构的实践案例,它完美展现了现代Web开发中"后端API服务+前端SPA应用"的协作模式。
技术栈的版本选择颇有讲究:SpringBoot 2.x系列作为长期支持版本(LTS),相比最新的3.x版本对Java版本要求更低(兼容JDK8),更适合企业保守升级策略;Vue3采用Composition API写法,配合TypeScript可获得更好的类型提示;MyBatis-Plus 3.x在简化CRUD操作的同时,其Lambda表达式写法能有效避免SQL注入;MySQL 8.0则带来了JSON支持、窗口函数等现代特性。
提示:实际开发中建议锁定具体小版本号(如SpringBoot 2.7.18),避免自动升级带来的兼容性问题。可通过
<version>标签在Maven中固定版本。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 系统架构设计解析
2.1 后端工程结构
采用经典的三层架构:
code复制src/main/java
├── config # 配置类(跨域、Swagger等)
├── controller # 暴露API接口
├── service # 业务逻辑层
│ ├── impl # 接口实现类
├── mapper # MyBatis-Plus的Mapper接口
├── entity # 数据库实体类
└── util # 工具包(验证码、Excel导出等)
2.2 前端工程结构
Vue3项目采用组合式API规范:
code复制src/
├── api # 接口请求封装
├── assets # 静态资源
├── components # 公共组件
├── router # 路由配置
├── store # Pinia状态管理
├── utils # 工具函数
└── views # 页面组件
2.3 数据库设计要点
核心表结构包括:
survey(问卷主表):包含title、description、start_time、end_time等字段question(问题表):通过survey_id关联,记录问题类型(单选/多选/填空)option(选项表):question_id外键,存储选项内容answer(回答表):用户提交记录,使用JSON格式存储动态答案
注意:MySQL 8.0的JSON类型字段应配合Generated Column建立函数索引,提升查询性能。
3. 核心功能实现细节
3.1 问卷创建流程
- 后端实现:
java复制// SurveyController.java
@PostMapping("/create")
public Result createSurvey(@Valid @RequestBody SurveyDTO dto) {
return surveyService.createSurvey(dto);
}
// SurveyServiceImpl.java
@Override
public Result createSurvey(SurveyDTO dto) {
Survey survey = new Survey();
BeanUtils.copyProperties(dto, survey);
survey.setCreateTime(LocalDateTime.now());
surveyMapper.insert(survey);
// 批量插入问题
List<Question> questions = dto.getQuestions().stream()
.map(q -> {
Question question = new Question();
// ...属性拷贝
question.setSurveyId(survey.getId());
return question;
}).collect(Collectors.toList());
questionService.saveBatch(questions);
return Result.success(survey.getId());
}
- 前端实现:
vue复制<script setup>
const formState = reactive({
title: '',
questions: [
{
type: 'radio',
content: '',
options: ['选项1', '选项2']
}
]
})
const addQuestion = () => {
formState.questions.push({
type: 'radio',
content: '',
options: ['新选项']
})
}
</script>
3.2 动态表单渲染
利用Vue3的<component :is>实现问题类型动态切换:
vue复制<template>
<div v-for="(q, index) in questions" :key="q.id">
<component
:is="questionComponents[q.type]"
:question="q"
@update="handleAnswerUpdate"
/>
</div>
</template>
<script setup>
import RadioQuestion from './RadioQuestion.vue'
import CheckboxQuestion from './CheckboxQuestion.vue'
const questionComponents = {
radio: RadioQuestion,
checkbox: CheckboxQuestion
}
</script>
4. 关键技术难点解决方案
4.1 复杂查询优化
对于问卷统计场景,MyBatis-Plus的Wrapper结合MySQL 8.0窗口函数实现高效分析:
java复制// 获取各选项选择人数统计
List<AnswerStatVO> getAnswerStats(Long surveyId) {
return baseMapper.selectMaps(new LambdaQueryWrapper<Answer>()
.select("question_id, option_id, count(*) as count")
.eq(Answer::getSurveyId, surveyId)
.groupBy("question_id", "option_id"))
.stream()
.map(map -> {
AnswerStatVO vo = new AnswerStatVO();
vo.setQuestionId((Long)map.get("question_id"));
vo.setOptionId((Long)map.get("option_id"));
vo.setCount((Long)map.get("count"));
return vo;
}).collect(Collectors.toList());
}
4.2 并发提交控制
使用Redis + Lua脚本防止重复提交:
java复制public Result submitAnswer(AnswerDTO dto) {
String key = "survey:submit:" + dto.getSurveyId() + ":" + userId;
Long result = redisTemplate.execute(
SUBMIT_SCRIPT,
Collections.singletonList(key),
String.valueOf(System.currentTimeMillis())
);
if (result == 0) {
return Result.fail("请勿重复提交");
}
// ...正常处理逻辑
}
Lua脚本:
lua复制local key = KEYS[1]
local timestamp = ARGV[1]
if redis.call('exists', key) == 1 then
return 0
else
redis.call('set', key, timestamp, 'EX', 86400)
return 1
end
5. 部署与运维实践
5.1 后端部署要点
- 打包SpringBoot应用:
bash复制mvn clean package -DskipTests
- 使用Dockerfile构建镜像:
dockerfile复制FROM openjdk:8-jdk-alpine
VOLUME /tmp
COPY target/survey.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
5.2 前端部署方案
- 生产环境构建:
bash复制npm run build
- Nginx配置示例:
nginx复制server {
listen 80;
server_name survey.example.com;
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass http://backend:8080;
}
}
5.3 MySQL 8.0配置建议
ini复制[mysqld]
default_authentication_plugin=mysql_native_password
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
innodb_buffer_pool_size=1G
innodb_log_file_size=256M
6. 开发环境搭建指南
6.1 后端开发准备
- JDK 8+环境变量配置:
bash复制# 在~/.bashrc或~/.zshrc中添加
export JAVA_HOME=/path/to/jdk
export PATH=$JAVA_HOME/bin:$PATH
- IDEA中Lombok插件安装:
- 通过
Preferences -> Plugins搜索安装 - 开启注解处理:
Build -> Compiler -> Annotation Processors
6.2 前端开发环境
- Node.js版本管理推荐使用nvm:
bash复制nvm install 16
nvm use 16
- 解决Edge浏览器最小化按钮问题:
javascript复制// vite.config.js
export default defineConfig({
css: {
postcss: {
plugins: [
require('autoprefixer')({
overrideBrowserslist: ['last 2 versions']
})
]
}
}
})
7. 扩展功能与二次开发建议
- 问卷模板功能:
- 新增template表存储预设问题
- 实现"从模板创建"接口:
java复制@PostMapping("/create-from-template")
public Result createFromTemplate(@RequestParam Long templateId) {
// 复制模板问题到新问卷
}
- 可视化分析增强:
- 集成ECharts实现动态图表
- 使用Vue3的
<script setup>语法:
vue复制<script setup>
import * as echarts from 'echarts'
const initChart = () => {
const chart = echarts.init(domRef.value)
chart.setOption({
// ...配置项
})
}
</script>
- 微信小程序端适配:
- 新增uni-app项目复用业务逻辑
- 通过条件编译区分平台:
javascript复制// #ifdef MP-WEIXIN
wx.login({...})
// #endif
这套系统源码的价值不仅在于开箱即用的问卷功能,更在于它展示了现代Java Web开发的最佳实践组合。我在实际部署时发现,合理配置MySQL的innodb_buffer_pool_size能显著提升分页查询性能,特别是在处理大量问卷数据时。另外,Vue3的<script setup>语法相比Options API能减少约30%的代码量,但需要团队成员统一学习曲线。
