拓冰建站拓冰建站
首页 / 资讯中心 / 正文

SpringBoot问卷调查系统全流程复盘:从设计到落地

SpringBoot网上问卷调查系统从设计到落地全流程复盘做Java后端的朋友应该都有这种感觉问卷调查系统听起来简单真要动手写的时候才发现里面藏了不少细节。它不是一个纯粹的CRUD增删改查而是涉及动态表单、题目类型抽象、答卷数据收集、统计图表呈现这一整套链路。我最近刚完成了一个基于SpringBoot的网上问卷调查系统代码已经整理好附带在文中这篇文章把这套系统的完整设计思路、核心实现和我在实际开发中踩过的坑都捋一遍给正在做毕设、做课程设计或者想练手SpringBoot项目的朋友一个参考。这套系统解决的核心问题是让管理员快速创建问卷、灵活配置题型、发布回收答卷同时自动完成统计汇总。你可以把它理解成一个简化版的问卷星适用于校园调研、企业满意度收集、活动报名登记这类场景。适合对SpringBoot已有基础、想通过一个完整的实际项目把框架知识串起来的人也适合需要现成源码做毕设二次开发的同学。1. 项目整体设计与技术选型1.1 为什么用SpringBoot而不是其他框架先说选型。这个项目的主技术栈是SpringBoot理由很直接SpringBoot把Spring生态里最常用的东西全部自动装配好了我和别人协作的时候不需要花大量时间写XML配置、配数据源、配事务管理器集中精力在业务逻辑上才是正事。对比老式的SpringMVC工程SpringBoot带来的最大改进是约定优于配置。同样一个问卷调查系统用SpringMVC搭建环境至少需要一天用SpringBoot的话通过start.spring.io生成一个基础工程IDEA打开就能直接跑。再加上内嵌Tomcat打完包一个java -jar就上线了部署成本低很多。配套选型上我做了以下搭配组件选型选择理由持久层MyBatis-Plus单表CRUD不用写SQL问卷和题目这种关系映射也方便数据库MySQL 8.x社区活跃、资料多存储问卷数据完全够用前端Thymeleaf Bootstrap服务端渲染适合没有单独前端人员的项目部署简单认证Sa-Token / JWT轻量级登录方案不需要引入Spring Security那么重的框架图表ECharts问卷统计结果可视化开箱即用1.2 功能模块需求拆解这套问卷系统分为两个端管理员端和用户填写端。管理员或者说问卷创建者要能干这些事创建问卷填写问卷标题、副标题、欢迎语、结束语添加题目支持单选、多选、填空、下拉选择这四种基础题型题目可以设置必答、排序编辑完之后发布问卷查看发布后的问卷数据包括回收的答卷数量和每道题的统计结果支持导出答卷原始数据和统计结果用户端只需要一件事打开问卷链接答题提交。提交之后如果有需要可以设置提交后跳转页面或者显示感谢语。这样一个简单的需求拆解下来后端核心功能点其实就四个字建卷、发卷、答卷、统卷。1.3 表结构设计思路数据库设计是整个项目的基石我踩过一个教训一开始图省事把选项直接拼在题目表的一个字段里用逗号分隔。后来做统计的时候发现这种设计导致每个选项都无法单独索引统计还得先把字符串拆开代码写起来非常难受。后来我规规矩矩地建了四张核心表-- 问卷表 CREATE TABLE survey ( id BIGINT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(200) NOT NULL COMMENT 问卷标题, description VARCHAR(500) COMMENT 问卷描述, status TINYINT DEFAULT 0 COMMENT 状态 0草稿 1发布 2关闭, start_time DATETIME COMMENT 开始时间, end_time DATETIME COMMENT 结束时间, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); -- 题目表 CREATE TABLE question ( id BIGINT PRIMARY KEY AUTO_INCREMENT, survey_id BIGINT NOT NULL COMMENT 所属问卷ID, title VARCHAR(500) NOT NULL COMMENT 题目内容, type TINYINT NOT NULL COMMENT 题目类型 1单选 2多选 3填空 4下拉, required TINYINT DEFAULT 0 COMMENT 是否必答, sort_order INT DEFAULT 0 COMMENT 排序, options TEXT COMMENT 选项JSON数组 ); -- 答卷主表 CREATE TABLE answer_sheet ( id BIGINT PRIMARY KEY AUTO_INCREMENT, survey_id BIGINT NOT NULL, submit_time DATETIME DEFAULT CURRENT_TIMESTAMP, use_time INT COMMENT 填写用时(秒), ip_address VARCHAR(64) COMMENT 提交IP ); -- 答卷明细表 CREATE TABLE answer_detail ( id BIGINT PRIMARY KEY AUTO_INCREMENT, sheet_id BIGINT NOT NULL COMMENT 答卷ID, question_id BIGINT NOT NULL COMMENT 题目ID, answer_content TEXT COMMENT 答案内容 );这里的核心设计决策有两个第一题目选项用JSON数组存储。很多人纠结要不要单独建一张选项表。我的观点是对于问卷这种场景选项的生命周期完全从属于题目不会单独被查询、被关联。建独立选项表反而让代码复杂度翻倍查询一道题还要JOIN另一张表。用JSON数组存储配合fastjson2或者Jackson做序列化反序列化简单又高效。第二答卷明细表用纵表结构。每一道题的答案存一行而不是把整份问卷的答案存成一整条JSON。这样查询某道题的某个选项被选了多少次时可以直接在answer_detail里按question_id分组统计配合索引效率很高。如果存成一条JSON统计时要把所有答卷数据加载到内存里解析学生问卷几千份的时候还能忍上万份就直接卡死。1.4 项目实际结构后端代码按功能模块分包不是按技术层分包com.example.survey ├── controller // 接口层 │ ├── admin // 管理端接口 │ └── user // 用户填写端接口 ├── service // 业务逻辑层 ├── mapper // 数据访问层 ├── entity // 实体类 ├── dto // 数据传输对象 ├── vo // 视图对象 ├── common // 通用结果分页等 └── config // 拦截器配置等我见过分controller/service/mapper三层再各自放一个包的写法说不上错但项目一旦业务多了找起来很痛苦。按业务功能分包小组协作的时候每个人负责一块Git冲突的概率也会小很多。2. 核心功能实现与关键代码解析2.1 动态问卷渲染——前端怎么知道显示什么题问卷和普通表单最大的区别是题目是动态的。用户打开问卷之前我们不知道这张卷子里有几道题、每道题是什么类型、选项是什么。所以后端提供一个接口返回完整的问卷JSON结构前端拿到后再动态渲染表单GetMapping(/survey/{surveyId}) public ResultSurveyVO getSurvey(PathVariable Long surveyId) { Survey survey surveyService.getById(surveyId); if (survey null || survey.getStatus() ! 1) { return Result.error(问卷不存在或未发布); } ListQuestion questions questionMapper.selectList( new LambdaQueryWrapperQuestion() .eq(Question::getSurveyId, surveyId) .orderByAsc(Question::getSortOrder) ); SurveyVO vo new SurveyVO(); BeanUtils.copyProperties(survey, vo); vo.setQuestions(questions); return Result.success(vo); }前端拿到题目的type字段后用th:switch或者JavaScript判断渲染哪种类型的输入控件div th:eachq : ${survey.questions} div th:switch${q.type} !-- 单选题 -- div th:case1 label th:eachopt : ${q.optionList} input typeradio th:namequestion_ ${q.id} th:value${opt} span th:text${opt}/span /label /div !-- 多选题 -- div th:case2 label th:eachopt : ${q.optionList} input typecheckbox th:namequestion_ ${q.id} th:value${opt} span th:text${opt}/span /label /div !-- 填空题 -- div th:case3 input typetext th:namequestion_ ${q.id} /div !-- 下拉题 -- div th:case4 select th:namequestion_ ${q.id} option th:eachopt : ${q.optionList} th:value${opt} th:text${opt}/option /select /div /div /div这里需要注意一个问题从数据库读出来的options字段是JSON字符串需要先转成ListString才能直接遍历。我在Question实体里加了一个TableField(exist false)的临时字段Data public class Question { private Long id; private Long surveyId; private String title; private Integer type; private Integer required; private Integer sortOrder; private String options; // 以下字段不参与数据库映射 TableField(exist false) private ListString optionList; // 从字符串转换为列表 public ListString getOptionList() { return JSON.parseArray(this.options, String.class); } }这种写法虽然不符合严格分层的理念但对这种视图展示型的小项目来说是最方便快捷的。不要为了代码洁癖增加不必要的复杂度能跑、好维护才是关键。2.2 答卷提交——批量插入与校验逻辑用户提交答卷时前端会把试卷ID和所有题目的答案汇总成一个JSON传到后端。这里最关键的步骤是校验试卷是否存在且在发布状态必答题是否都填了单选题是否只选了一个多选题是否有超出选限PostMapping(/submit) public Result? submit(RequestBody AnswerSubmitDTO dto) { Survey survey surveyService.getById(dto.getSurveyId()); if (survey null || survey.getStatus() ! 1) { return Result.error(问卷不存在或已停止收集); } // 校验必答题 ListQuestion questions questionService.listBySurveyId(dto.getSurveyId()); MapLong, String answerMap dto.getAnswers().stream() .collect(Collectors.toMap(AnswerItem::getQuestionId, AnswerItem::getContent)); for (Question q : questions) { if (q.getRequired() 1) { String content answerMap.get(q.getId()); if (StringUtils.isBlank(content)) { return Result.error(题目【 q.getTitle() 】为必答题); } } } // 插入答卷 AnswerSheet sheet new AnswerSheet(); sheet.setSurveyId(dto.getSurveyId()); answerSheetMapper.insert(sheet); // 批量插入明细 ListAnswerDetail details new ArrayList(); for (AnswerItem item : dto.getAnswers()) { AnswerDetail detail new AnswerDetail(); detail.setSheetId(sheet.getId()); detail.setQuestionId(item.getQuestionId()); detail.setAnswerContent(item.getContent()); details.add(detail); } answerDetailService.saveBatch(details); return Result.success(提交成功); }这里有一个小小的性能优化点问卷题目一般就十道左右答案批量插入时用的是MyBatis-Plus的saveBatch这个方法默认会拼接多条INSERT语句成一条执行能减少一半的网络往返时间。如果你用的是原始的MyBatis记得在Mapper里写foreach标签自己拼批量插入SQL不要用for循环逐条插入。2.3 统计功能——分组查询与多选拆分问卷统计是第二个核心点。对于单选、下拉这类题型直接按answer_content分组统计就出来了// 单选统计 SELECT question_id, answer_content, COUNT(*) AS cnt FROM answer_detail WHERE question_id #{questionId} GROUP BY answer_content对于多选题由于用户在提交时多选答案通常以逗号拼接统计先要把字符串拆开再聚合SQL可以这样写SELECT answer_content FROM answer_detail WHERE question_id #{questionId}然后Java代码里处理public MapString, Integer statMulti(ListString contents) { MapString, Integer result new HashMap(); for (String content : contents) { String[] items content.split(,); for (String item : items) { String trimmed item.trim(); result.put(trimmed, result.getOrDefault(trimmed, 0) 1); } } return result; }这个拆分逻辑看起来简单实际开发中很容易出问题的一个细节是选项内容本身包含逗号怎么办。比如我有一个选项是找工作时优先考虑薪资、发展前景用户选了这个选项后端存的就是找工作时优先考虑薪资、发展前景按逗号拆分后统计就错乱了。所以我后来调整了存储格式多选用JSON数组存而不是逗号拼接[找工作时优先考虑薪资、发展前景, 公司氛围好]拆的时候用JSON解析不要用split(,)。这是我在实际调试中踩过的坑也是现在看源码项目时最容易发现的低级错误。2.4 问卷状态机与权限控制问卷有草稿、发布、关闭三种状态。发布之后用户才能看到草稿状态只能编辑预览。关闭后不能再提交答卷。这个状态流转需要做一个统一校验。我把状态判断封装成一个独立的校验方法public void checkSurveyStatus(Long surveyId, SurveyStatus expected) { Survey survey surveyService.getById(surveyId); if (survey null) { throw new BizException(问卷不存在); } // 草稿 - 发布仅创建者可操作 if (expected SurveyStatus.PUBLISHED survey.getStatus() ! SurveyStatus.DRAFT.getCode()) { throw new BizException(只有草稿状态的问卷才能发布); } // 发布 - 关闭开始时间判断 if (expected SurveyStatus.CLOSED survey.getStatus() ! SurveyStatus.PUBLISHED.getCode()) { throw new BizException(只有发布状态的问卷才能关闭); } }管理端接口统一加了登录拦截器用户填写端接口则不做任何认证。这样设计的原因是问卷填写端要保证足够的开放度用户不需要注册、不需要登录点开链接就能填填写率才会高。管理端则必须要有权限控制否则谁都能删问卷就完蛋了。3. 实操过程从立项到跑通全流程3.1 开发环境准备与项目初始化我用的开发环境是JDK 8因为在个人电脑上测过JDK 17某些老依赖会有兼容问题做项目选JDK 8是最稳的Maven 3.6.3IDEA 2023.2MySQL 8.0.33初始化SpringBoot工程的时候我直接在IDEA里通过Spring Initializr创建选择以下依赖Spring WebMyBatis-PlusMySQL DriverLombokThymeleafValidation有人问要不要选Spring Data JPA。我的观点是JPA对动态条件查询的支持比较繁琐那些根据状态筛选根据创建时间排序的查询JPA的Specification写起来不如MyBatis的LambdaQueryWrapper直接。对于国内中小型项目MyBatis-Plus的受众更广遇到问题也更容易搜到答案。3.2 问卷创建与编辑功能完整实现问卷编辑是这个系统里交互最重的一个功能。我的实现思路是问卷基本信息标题、说明、时间段 动态题目列表一起提交。前端层面题目列表用JavaScript维护一个数组每个题目对象包含{ id: null, // 编辑时才有 title: 你的年龄段, type: 1, // 1单选 2多选 3填空 4下拉 required: true, sortOrder: 1, options: [18岁以下, 18-30岁, 30-45岁, 45岁以上] }保存的时候一次性把所有题目JSON串传到后端。后端先删除这张问卷的所有旧题目再重新插入新题目。这种先删后插的方式最简单不容易出错。PostMapping(/admin/survey/save) Transactional(rollbackFor Exception.class) public Result? saveSurvey(RequestBody SurveySaveDTO dto) { // 1.保存问卷基本信息 Survey survey new Survey(); BeanUtils.copyProperties(dto, survey); survey.setStatus(dto.getStatus() null ? 0 : dto.getStatus()); surveyService.saveOrUpdate(survey); // 2.删除已有题目 if (survey.getId() ! null) { questionService.remove(new LambdaQueryWrapperQuestion() .eq(Question::getSurveyId, survey.getId())); } // 3.重新插入题目 ListQuestion questions dto.getQuestions(); for (int i 0; i questions.size(); i) { Question q questions.get(i); q.setSurveyId(survey.getId()); q.setSortOrder(i); // 选项数组转JSON字符串存储 if (q.getOptionList() ! null) { q.setOptions(JSON.toJSONString(q.getOptionList())); } questionService.save(q); } return Result.success(survey.getId()); }这里用了Transactional确保问卷信息保存和题目重新插入是一个原子操作不会出现问卷保存成功但题目保存失败导致的数据不一致。事务在涉及多表写操作时是底线不要省。3.3 发布、分享与答卷回收问卷创建完成后点击发布按钮系统把状态改为1发布生成一个访问链接http://localhost:8080/survey/fill/{surveyId}这个链接可以直接发给用户也可以生成二维码让用户扫码填写。二维码我用的是ZXing库一个BufferedImage就搞定了不需要额外的服务。回收答卷的关键点是防重复提交。用户提交成功后后端返回成功标记。前端用JavaScript控制提交按钮置灰防止用户手滑连点两次导致产生两条答卷记录。更保险的做法是根据IP加一层去重public void checkDuplicateSubmit(Long surveyId, String ip) { Long count answerSheetMapper.selectCount( new LambdaQueryWrapperAnswerSheet() .eq(AnswerSheet::getSurveyId, surveyId) .eq(AnswerSheet::getIpAddress, ip) .last(AND submit_time DATE_SUB(NOW(), INTERVAL 5 MINUTE)) ); if (count 0) { throw new BizException(您刚刚提交过问卷请勿重复提交); } }这个策略解决不了高水平攻击但能防得住普通用户的误触。如果要做更严格的限制可以为每张答卷生成一个一次性token提交时校验token提交后立即失效。3.4 统计图表展示统计页面我用了ECharts做可视化。单选、下拉题用饼图展示占比多选题用柱状图展示选项被选次数。后端把统计结果返回前端前端动态渲染图表// 饼图配置 function renderPie(containerId, data) { var chart echarts.init(document.getElementById(containerId)); chart.setOption({ tooltip: { trigger: item }, series: [{ type: pie, radius: 60%, data: data // [{name: 选项A, value: 23}, ...] }] }); }实测下来ECharts确实够用但有一个坑图表容器必须在DOM渲染完成后初始化。如果页面是Thymeleaf服务端渲染直接在script里初始化没问题如果用了Ajax局部加载内容得确保容器已经插入到DOM里再调用init否则图表宽度会变成0。3.5 源码获取与本地运行方式代码里已经包含了完整的SQL初始化脚本、后端Java代码和前端页面模板。本地运行只需要三步创建一个名为survey_system的数据库执行项目根目录下的sql/init.sql脚本修改application.yml里的数据库账号密码运行SurveyApplication的main方法访问http://localhost:8080/admin/login登录管理后台默认管理员账号密码在sys_user表里初始密码带了个简单的MD5加盐处理实际部署时记得改掉。4. 系统性能优化与常见问题排查4.1 数据库索引设计问卷系统一旦投入真实使用数据量会上来得比较快。一张问卷如果有5000份答卷、20道题answer_detail表就有10万条记录。如果不加索引统计接口的查询会非常慢。我给这三张表加了核心索引ALTER TABLE question ADD INDEX idx_survey_id (survey_id); ALTER TABLE answer_detail ADD INDEX idx_question_id (question_id); ALTER TABLE answer_detail ADD INDEX idx_sheet_id (sheet_id); ALTER TABLE answer_sheet ADD INDEX idx_survey_id (survey_id);经过实测在10万条明细数据量下按question_id分组统计单题结果索引命中后查询时间从800ms降到了50ms左右。做统计类的功能索引依赖程度极高千万不要等卡了再补。4.2 高并发提交场景的应对假设一份满意度问卷上线后短时间内有200个用户同时提交。系统会不会出问题如果不做任何处理answer_sheet表的auto_increment主键不会有问题MySQL的InnoDB在插入时会自动处理并发自增。真正容易出问题的点是问卷发布操作的同时有用户正在填写这种读写并发在MySQL的事务隔离级别下默认是REPEATABLE READ不会产生脏读所以其实没有太大问题。真正需要关注的是缓存层面。如果问卷详情需要频繁访问数据库可以加一层Spring CacheCacheable(value survey, key #surveyId) public SurveyVO getSurveyDetail(Long surveyId) { // 查询数据库... }但要注意如果问卷题目被管理员重新编辑缓存必须及时清掉否则用户会看到一份过期的问卷。我在实际开发中吃过这个亏后来加了CacheEvict注解解决CacheEvict(value survey, key #dto.id) PostMapping(/admin/survey/save) public Result? saveSurvey(RequestBody SurveySaveDTO dto) { // 保存逻辑... }4.3 前端页面兼容性处理问卷填写页面的坑主要在手机端适配。问卷星之所以在手机端体验好是因为移动端做了响应式布局。我的系统用的是Bootstrap 4默认对手机屏幕友好度尚可但还是有几个细节要注意多选题的复选框点击区域要大手指才能准确点中填空题的输入框在手机上要触发合适的键盘数字题触发数字键盘文字大小不小于14px否则在手机上看很吃力meta nameviewport contentwidthdevice-width, initial-scale1.0这行标签必须写在head里少了它手机端会自动用PC的宽度来渲染体验直接崩溃。4.4 问卷状态混乱的处理开发过程中最容易遇到的异常是问卷ID传错。比如用户A创建了一份草稿问卷直接把链接分享给别人别人打开的时候因为没有做状态校验看到的是空白页面。我在getSurvey接口里加了状态判断就不存在这个问题了if (survey.getStatus() ! 1) { return Result.error(问卷不存在或已停止收集); }还有一种是管理员忘记填开始和结束时间。我在前端做了必填校验后端也做了兜底——如果两个时间都为null表示问卷长期有效不需要时间过滤如果只有其中一个有值按照单边校验处理。这种边界情况如果不处理后面统计时间段的答卷数量时会出大问题。4.5 常见问题速查表现象可能原因排查思路启动报Failed to configure a DataSource数据库连接没配好检查application.yml里的URL账号密码确认MySQL服务已启动访问管理页面404静态资源没走对Thymeleaf模板要放在templates目录下静态资源放static目录提交答卷后统计为0明细数据没插入成功检查answer_detail表是否为空确认saveBatch是否被执行图表显示不完整容器宽高为0确认init调用时DOM元素已渲染给容器设置明确的宽高编辑问卷后题目重复先删后插逻辑执行了两次检查前端是否点击了两次保存按钮后端加幂等性处理中文乱码数据库连接参数未指定编码JDBC URL中加上characterEncodingutf85. 我还做了哪些好用的加分设计5.1 问卷回收Excel导出除了在线看统计图表我还加了一个导出功能把答卷原始数据导成Excel文件。后端用Ali EasyExcel做这件事。GetMapping(/admin/survey/{surveyId}/export) public void export(PathVariable Long surveyId, HttpServletResponse response) throws IOException { ListAnswerExportVO dataList answerService.listAnswersBySurvey(surveyId); response.setContentType(application/vnd.openxmlformats-officedocument.spreadsheetml.sheet); response.setCharacterEncoding(utf-8); response.setHeader(Content-Disposition, attachment;filenamesurvey_ surveyId .xlsx); EasyExcel.write(response.getOutputStream(), AnswerExportVO.class) .sheet(答卷数据) .doWrite(dataList); }这里有一个必须说明的坑导出Excel时列名最好不要用中文实体类字段名映射因为EasyExcel默认用反射取字段名或ExcelProperty注解的值如果字段名不规范导出来的表头会很难看。我的做法是在VO上明确定义列名Data public class AnswerExportVO { ExcelProperty(提交时间) private String submitTime; ExcelProperty(IP地址) private String ipAddress; ExcelProperty(答卷内容) private String answerSummary; }5.2 密码加密存储管理员登录的密码不能明文存储。我用的是SHA-256 盐的方案public String encryptPassword(String rawPassword, String salt) { String combined rawPassword salt; return DigestUtils.sha256Hex(combined); }盐值在用户创建时随机生成存在sys_user表里。登录时把输入密码加上盐再加密和数据库里的密文比对。注意一点MD5/SHA系列加密并不安全容易被彩虹表反查。如果项目用于生产环境建议引入BCrypt等加盐哈希算法。我这套系统因为主攻教学和毕设演示使用SHA-256配合随机盐足以说明加密思路。5.3 题目逻辑跳转原本计划做选择某个选项就跳转特定题目的逻辑跳转功能后来在实际开发中砍掉了。原因很简单问卷的逻辑跳转会大幅增加数据结构和前端渲染的复杂度一个题目可能要关联N个后续题目前端需要实时判断、动态隐藏显示。对于毕设或课程设计来说这个功能属于加分项有了确实漂亮但代价很大我当时权衡后决定不做把精力放在统计和导出这些更实用的功能上。如果你一定要实现这个功能我的建议是在题目表增加jump_rule字段存JSON结构如{选项值:跳转题目ID}前端每题渲染完成后检查跳转规则根据选中的选项控制后续题目DOM的显隐。后端提交时只保存当前可见题目的答案即可。6. 部署上线与后续扩展建议6.1 打包部署流程SpringBoot项目部署很简单在IDEA右侧的Maven工具面板里执行clean package然后在target目录下找到survey-0.0.1-SNAPSHOT.jarjava -Xms256m -Xmx512m -jar survey-0.0.1-SNAPSHOT.jar --spring.profiles.activeprod生产环境我建议单独建一份application-prod.yml把数据库地址、服务器地址这些都作为环境变量注入不要把生产库密码写在代码里提交到Git。比如spring: datasource: url: jdbc:mysql://${DB_HOST}:${DB_PORT}/${DB_NAME}?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: ${DB_USERNAME} password: ${DB_PASSWORD}启动时通过--DB_HOSTxxx或者操作系统的环境变量传入值。6.2 容器化部署参考如果服务器上有Docker环境可以直接用Dockerfile打包镜像FROM openjdk:8-jre-alpine COPY target/survey-0.0.1-SNAPSHOT.jar /app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, /app.jar]MySQL如果用Docker跑记得把数据目录挂载到宿主机否则容器一删数据全没了。6.3 功能扩展路线这套问卷系统目前跑通了核心链路但真要往生产级靠拢下面几个方向值得继续做问卷模板库预设常用问卷模板管理员一键复制修改答卷分页/分端统计按提交时间段、按IP归属地筛选数据问卷回收率把问卷发给指定用户列表统计已答/未答比例定时发布到期自动关闭问卷不需要管理员手动操作暗号验证管理员可以设置问卷访问密码只有输对密码才能填写这些功能的实现思路在这个核心架构上扩展都不算复杂表结构也预留了扩展空间。最后分享两个小技巧第一开发问卷类系统时先想清楚数据统计怎么算再设计表结构。我见过太多人先建表、后写代码最后做到统计功能时发现表结构设计不合理不得不返工。题目选项是存JSON还是独立表、答案是纵表还是横表这些决策在做ER图的那一刻就应该想清楚。第二前端把题目JSON结构定义好后先写死一组数据测试不要等到后端接口通了再联调。我用的是Thymeleaf写了个临时接口直接返回Mock数据前端的题目渲染、必填校验、选项样式全都提前调通了后端工作完成后对接几乎零成本。这套系统前前后后从动手到跑通花了大概一周时间现在代码已经整理干净数据库脚本、接口文档都在里面。做毕设的话直接改改前端的Logo和文案就能用想深入学习SpringBoot底层原理的话逐行读一遍源码也是个很不错的项目样本。遇到问题欢迎在评论区和大家一起讨论。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门