简介:这是一份基于JavaWeb技术栈的在线问卷调查系统课程设计资源,覆盖用户注册登录、问卷创建、多题型作答(单选、多选、文本)、管理员管理、问卷发布/暂停/结束以及数据统计分析等完整业务场景,适合Java课程设计、毕业设计及前后端分离初学者参考。包内共1366个文件,总大小93.77MB,以Java源码、HTML/CSS/JS前端页面、MySQL SQL脚本为主体,辅以数百张PNG/SVG界面素材与图标资源,项目目录结构清晰,便于定位核心代码与配置。当前已有50人学习下载。其中不仅提供可运行的前后端源码,还附带数据库建表语句、Maven配置等,可直接导入IDE调试并连接数据库,也可部署到Tomcat或Spring Boot环境。通过该项目可系统掌握Servlet/JSP分层开发、用户权限校验、问卷数据采集与统计分析的完整实现思路,是一份适合答辩展示与二次开发的完整案例。
1. 从“能跑”到“敢答辩”的一段真实距离
答辩前一周,你把《基于JavaWeb实现在线问卷调查系统》的压缩包解压,导入数据库、跑起Tomcat,然后在“新增问卷”页面点了一下保存草稿,发现数据库里只写了半张表——这种场景太常见了。标题里的“前后端源码+数据库”并不是一份能直接交付的成绩,而是一套需要你自己理解、改装和讲清楚的技术骨架。它涵盖了JavaWeb开发里最基本的链路:JSP/Servlet负责交互,MySQL存问卷和答卷,Filter控制访问权限。下面按一套课程设计验收标准展开:体系怎么搭、表怎么建、接口怎么调、坑在哪,以及答辩时怎么从“会运行”讲到“会设计”。
2. 先把技术栈和验收链路定下来:一个能答辩的最小JavaWeb方案
2.1 两套常见选型对比:Servlet时代和Spring Boot时代的课设
打开压缩包源码之前,我的第一个问题是“这套项目用什么技术写的”。标题只写了“Javaweb”,这个说法在课程设计圈里可以指两代东西:JSP+Servlet+JDBC,以及Spring Boot+MyBatis。两者都能做出在线问卷,但课程设计的验收体验差很多。
先说结论:如果距离答辩不足三周、目标是“全链路讲清楚”,我一般首选JSP+Servlet+JDBC。理由不是它先进,而是它“黑匣子少”。Servlet生命周期、request/session作用域、JDBC的Connection/PreparedStatement,都是面试和答辩可以直接讲的问题。Spring Boot把容器、自动配置、依赖版本都包掉了,看起来高级,但一旦碰到“为什么接口突然404”“为什么MyBatis映射不到实体”这类问题,排查链路立刻变长。以下是两套方案在课设场景里的对比:
| 对比项 | JSP + Servlet + JDBC | Spring Boot + MyBatis |
|---|---|---|
| 外部依赖 | javax.servlet-api、mysql-connector-java,最多加一个连接池 | 十几个starter,版本匹配有成本 |
| 答辩追问点 | Servlet生命周期、Session、PreparedStatement防注入、事务 | 自动配置原理、MyBatis代理、Spring容器 |
| 跑通风险 | 低,配置集中在web.xml和连接串 | 中高,依赖版本或配置错误很难一眼看出 |
| 二开速度 | 前期慢,后面越来越顺手 | 前期快,但改错一个注解就卡住 |
如果你的课程设计文档里正好是Servlet那一代的工程,本文后半部分的步骤可以直接照抄;如果里面用的Spring Boot,也不冲突——表结构设计、状态机、防重复提交的思路是通用的,只是Controller层换成@PostMapping、数据访问层换成Mapper接口。
2.2 最小验收链路:从压缩包到localhost:8080
拿到一个Javaweb项目完整案例mysql的压缩包,我建议先不要急着在IDEA里点Run。先完成三步“地基”动作:建库、确认表、确认连接配置。
# 1. 用源码包里的SQL文件建库建表 mysql -uroot -p < survey.sql # 2. 确认表真的建出来了 mysql -uroot -p -e "use survey; show tables;"第一条命令执行时注意路径,如果survey.sql里有create database语句,就不要再手动建库;如果没有,需要先执行create database survey default charset utf8mb4;再导入。第二条命令是血泪经验:跳过它直接启动项目,最常见的结果是Tomcat起来了、页面上报“Table doesn't exist”,又回头排查半天。
建库之后改连接配置。这份配置在Javaweb工程里通常住在src/main/resources/db.properties或者src/db.properties里,如果不是Maven工程,就在WEB-INF/classes下找。
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/survey?useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai jdbc.username=root jdbc.password=123456这里最容易翻车的三个参数是useSSL=false、allowPublicKeyRetrieval=true、serverTimezone=Asia/Shanghai。MySQL 8.0默认使用caching_sha2_password认证,不打开allowPublicKeyRetrieval,首次连接会直接抛出认证异常;不指定serverTimezone,8.x驱动按UTC来算时间,入库时间会差8小时。这些参数写好了之后,再进下一步。
2.3 从IDEA启动到浏览器访问:JavaWeb项目配置要点
很多第一次用IDEA跑Javaweb项目的同学,把代码导入后直接执行main方法,然后一脸茫然:没有main。Servlet时代的工程必须交给Tomcat去加载,IDEA里对应的动作叫“配置Tomcat Server Local”。步骤是:Run菜单 → Edit Configurations → 左上角加号 → Tomcat Server Local → 选本地Tomcat路径 → Deployment里把Web exploded artifact加进去 → Application context填/survey。
这个环节里的关键检查点是Application context。填/survey,访问地址就是http://localhost:8080/survey/;填成/,才是http://localhost:8080/。很多项目的登录跳转、静态资源路径都按根路径写死,context填错,CSS样式全丢、跳转全404,不是代码问题,是部署路径问题。另外如果源码里用Tomcat 10+,Servlet包名已经被改成了jakarta.servlet.*,用JDK8编译的老代码会在IDEA里报“找不到javax.servlet”的错误,这种情况我的经验是直接用Tomcat 9,少改代码。
3. 数据模型与核心接口:从建表脚本到一次带事务的提交
3.1 五张表怎么建:问卷、题目、选项、提交批次、答案明细
问卷系统的核心数据模型不复杂,但课程设计最常见的问题是把“答案”设计成一张没有批次概念的宽表:survey_id、question_id、user_id、answer_text,一个用户答十道题就插十行,最后想去重、想统计“有多少人参与”都得靠select count(distinct user_id)这种写法硬凑。更规范的做法是拆出一个“提交批次”表,一次提交生成一条批次记录,每个题目的答案再挂到批次下。
-- 管理员表:登录用,密码建议存MD5加盐后的值 create table sys_user ( id int primary key auto_increment, username varchar(50) not null, password varchar(64) not null, nickname varchar(50) ) engine=InnoDB default charset=utf8mb4; -- 问卷主表:status用来做状态机 create table survey ( id int primary key auto_increment, title varchar(100) not null, description text, status tinyint not null default 0 comment '0草稿 1已发布 2已结束', creator_id int not null, created_at datetime not null default current_timestamp, key idx_creator_id(creator_id) ) engine=InnoDB default charset=utf8mb4; -- 题目表:qtype区分单选/多选/文本题 create table question ( id int primary key auto_increment, survey_id int not null, qtype tinyint not null comment '1单选 2多选 3文本', content varchar(255) not null, sort_order int not null default 0, is_required tinyint not null default 1, key idx_survey_id(survey_id) ) engine=InnoDB default charset=utf8mb4; -- 选项表:只有选择题才用 create table option ( id int primary key auto_increment, question_id int not null, option_text varchar(255) not null, sort_order int not null default 0, key idx_question_id(question_id) ) engine=InnoDB default charset=utf8mb4; -- 提交批次表:一次提交一条记录 create table submit ( id int primary key auto_increment, survey_id int not null, user_key varchar(64) not null comment '匿名用户标识:sessionId或IP', submit_time datetime not null default current_timestamp, unique key uk_survey_user(survey_id, user_key) ) engine=InnoDB default charset=utf8mb4; -- 答案明细表:每个题目一条答案,挂到submit_id下 create table answer ( id int primary key auto_increment, submit_id int not null, question_id int not null, option_id int null comment '选择题选中的选项ID', content text null comment '文本题的内容', unique key uk_submit_question(submit_id, question_id) ) engine=InnoDB default charset=utf8mb4;设计上最值得讲的一点是submit表和answer表的一对多关系。把user_key放到submit表中,并用unique key uk_survey_user(survey_id, user_key)做唯一约束,用户重复提交时数据库会直接拒绝,这是后端最底层的“防重复”防线。answer表再加uk_submit_question(submit_id, question_id),保证同一批提交里同一道题不会出现两条答案。这两条唯一索引在答辩时被问到的概率极高,答不上来会显得项目没有设计感。
3.2 JDBC层的事务处理:一个提交动作里不能丢任何一条答案
表建好之后,最核心的Java代码是怎么把一次提交完整地写进数据库。我见过不少课设的写法是循环里执行十次insert,中间某条失败也不管,提交记录插进去了,答案只存了一半——在答辩老师面前当场演示“问卷填到一半丢失”是非常尴尬的事。处理这个问题只需要一件事:一个方法里开事务,所有insert成功才commit,任何一条失败都rollback。
public int submitAnswers(Submit submit, List<Answer> answers) throws SQLException { Connection conn = DBUtils.getConnection(); try { // 把自动提交关掉,后续操作都归到一个事务里 conn.setAutoCommit(false); // 1. 插入提交批次,拿到自增主键submitId String insertSubmit = "insert into submit(survey_id, user_key) values(?, ?)"; PreparedStatement ps1 = conn.prepareStatement( insertSubmit, Statement.RETURN_GENERATED_KEYS); ps1.setInt(1, submit.getSurveyId()); ps1.setString(2, submit.getUserKey()); ps1.executeUpdate(); ResultSet keys = ps1.getGeneratedKeys(); int submitId = -1; if (keys.next()) { submitId = keys.getInt(1); } // 2. 批量插入答案明细,所有答案属于同一个submitId String insertAnswer = "insert into answer(submit_id, question_id, option_id, content) values(?, ?, ?, ?)"; PreparedStatement ps2 = conn.prepareStatement(insertAnswer); for (Answer a : answers) { ps2.setInt(1, submitId); ps2.setInt(2, a.getQuestionId()); if (a.getOptionId() == null) { ps2.setNull(3, Types.INTEGER); } else { ps2.setInt(3, a.getOptionId()); } ps2.setString(4, a.getContent()); ps2.addBatch(); } ps2.executeBatch(); conn.commit(); return submitId; } catch (SQLException e) { conn.rollback(); throw e; } finally { conn.setAutoCommit(true); DBUtils.close(conn); } }逻辑说明:RETURN_GENERATED_KEYS告诉数据库要返回自增主键,这是拿到submitId的关键;setNull(3, Types.INTEGER)处理文本题的情况——文本题没有optionId,不写这一行会多一个运行时异常;addBatch()和executeBatch()批量执行,不只是更快,更重要的是让十道题的insert在同一个事务提交点一起落库。即便这十道题来自同一个用户,也要注意PreparedStatement不能在循环里重复prepare,否则性能很差,也不利于讲“参数化查询”。
3.3 问卷状态机:草稿、发布、结束三个状态的流转规则
在线问卷不是所有操作都允许随时进行,至少要区分草稿、发布、结束三个状态。我通常用survey表里的status字段表示:0是草稿,可以编辑题目;1是已发布,用户可以填写;2是已结束,拒绝一切新提交。状态流转的规则写在Servlet的业务入口,而不是让前端页面决定。这在课程设计里是一个可以主动展示的“设计点”。
int status = survey.getStatus(); String action = request.getParameter("action"); // 已发布的问卷不能再改题目 if (status == Survey.STATUS_PUBLISHED && "editQuestion".equals(action)) { response.getWriter().write("{\"code\":403,\"msg\":\"问卷已发布,不能修改题目\"}"); return; } // 已结束的问卷拒绝一切提交 if (status == Survey.STATUS_CLOSED && "submit".equals(action)) { response.getWriter().write("{\"code\":403,\"msg\":\"问卷已结束,感谢参与\"}"); return; }这里的Survey.STATUS_PUBLISHED建议用常量定义,而不是在代码里到处写魔法数字1、2。答辩时被问“如果用户用脚本绕过页面直接POST提交怎么办”,就可以回答:状态判断在后端,前端按钮禁用只是用户体验,真正拦住在Servlet里。题目类型的分支也是一样:单选答案落到option_id,多选答案要在前端拼成“1,3,5”这样的格式再存到content,文本题只写content。这个分支逻辑清理清楚,后面做统计报表时会少很多麻烦。
4. 前后端交互与权限控制:URL设计、AuthFilter和防重复提交
4.1 从JSP页面到Servlet的URL路径设计
老式JavaWeb工程的页面交互有两种常见姿势:一种是在JSP里直接写<% %>拼Java代码,另一种是页面发请求到Servlet,Servlet处理完再forward或redirect。课程设计如果要讲“前后端分离”的思路,即使没有Vue,也可以把Servlet当成一个JSON接口来用,JSP只负责渲染页面和发起AJAX请求。我一般这样分配URL:
| 路径 | 作用 | 是否需要登录 |
|---|---|---|
/admin/login.jsp | 管理员登录页 | 否 |
/admin/index.jsp | 问卷列表管理页 | 是 |
/survey/publish | 发布/停用问卷 | 是 |
/survey/result?id=1 | 查看统计结果页 | 是 |
/api/survey/list | 接口:返回问卷列表JSON | 否 |
/api/survey/submit | 接口:用户提交答案 | 否 |
这样分的理由是:/api/开头的路径返回JSON,给前端页面AJAX调用,不必要求普通用户登录;/survey/下的操作涉及修改数据,必须经过权限控制。Servlet的路径映射在web.xml里用通配符配置:
<servlet> <servlet-name>SurveyServlet</servlet-name> <servlet-class>com.course.survey.web.SurveyServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>SurveyServlet</servlet-name> <url-pattern>/survey/*</url-pattern> </servlet-mapping>url-pattern里的/survey/*让所有以这个前缀开头的请求都进同一个Servlet,再靠request.getPathInfo()或action参数分发到不同方法。这种结构比给每个功能配一个Servlet更好维护,而且面试时能讲清楚“一个入口Servlet分发”这个模式。
4.2 用AuthFilter管理登录态:白名单之外的地址都要拦截
权限控制的实现方式有三类:在每个Servlet里重复判断Session、写一个Filter统一判断、引入Spring Security。课程设计选Filter最合适,代码量少、道理直观、还能答出“AOP的雏形”这种加分句。我的做法是一个AuthFilter,在web.xml里拦/admin/*、/survey/*,但放行/api/*、登录页和静态资源。
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse resp = (HttpServletResponse) response; String uri = req.getRequestURI(); // 白名单:登录页、静态资源、用户填卷接口不需要登录 if (uri.endsWith("login.jsp") || uri.startsWith(req.getContextPath() + "/static/") || uri.contains("/api/survey/")) { chain.doFilter(request, response); return; } HttpSession session = req.getSession(false); if (session == null || session.getAttribute("loginUser") == null) { resp.sendRedirect(req.getContextPath() + "/admin/login.jsp"); return; } chain.doFilter(request, response); }逻辑说明:req.getSession(false)不会主动创建Session,用户没登录时返回null,否则会产生一堆无意义的空Session;resp.sendRedirect用的是带contextPath的相对路径,保证部署到任意Application context下都能跳对;静态资源必须放行,不然页面CSS全空白,很多人会误以为是样式问题,实际是过滤器把所有CSS都拦了。
4.3 防重复提交怎么做才算完整:前端禁用远远不够
问卷系统有一个高频答辩问题:用户点击“提交”没反应,连点三下,结果数据库里出现三条记录?要回答这个问题,得先承认前端禁用按钮只是第一道闸。真正解决问题是三层防线:前端状态锁、submit表唯一索引、后端捕获DuplicateKeyException。
let submitting = false; document.getElementById('submitBtn').addEventListener('click', function () { // 第一次点击后,把后续点击全部忽略 if (submitting) { return; } submitting = true; const btn = this; btn.disabled = true; btn.innerText = '提交中...'; fetch('/survey/api/submit', { method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: new URLSearchParams(new FormData(document.getElementById('surveyForm'))) }) .then(r => r.json()) .then(res => { if (res.code === 0) { alert('提交成功'); } else { btn.disabled = false; btn.innerText = '重新提交'; alert(res.msg); } }) .catch(() => { btn.disabled = false; btn.innerText = '重新提交'; }) .finally(() => { submitting = false; }); });submitting变量是页面内的锁,它只能挡住正常用户连续点击;真正面对绕过JS的恶意请求,后端必须靠数据库唯一索引兜底。在submit表上已经建了unique key uk_survey_user(survey_id, user_key),重复插入同一个用户的提交记录时,JDBC会抛DuplicateKeyException。在Servlet里捕获后返回“你已经参与过这份问卷”,而不是把它当成系统异常抛给用户。这里还有一个小细节:user_key我用的是SessionId加IP组合,比只取IP可靠,同一局域网内多台机器共用出口IP时不会误判。
5. 部署与排查:五个必踩的坑和当场定位法
5.1 中文乱码:JSP、数据库连接、响应头三层都要配utf-8
现象:页面标题、问卷题目、用户提交的文本答案全是“???”,或者一部分乱一部分正常。 原因:字符集乱是层层叠加的结果,三层里任何一层是latin1,整体就乱。 解决:JSP文件顶部加<%@ page contentType="text/html; charset=utf-8" pageEncoding="utf-8" %>;数据库连接串里带characterEncoding=utf8;再用下面这段Filter强制设置请求和响应编码。
public class EncodingFilter implements Filter { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=UTF-8"); chain.doFilter(request, response); } }排查顺序是从外往里:先看浏览器页面源码里是不是utf-8,再看JSP编译后的响应头,最后直接往数据库里insert一条中文做对照。如果数据库客户端里显示正常、页面上乱,问题在连接串;如果数据库里就是乱码,建表语句没写成utf8mb4的锅更大。
5.2 连接MySQL报Public Key Retrieval is not allowed
现象:项目启动后第一次连接数据库直接抛java.sql.SQLNonTransientConnectionException: Public Key Retrieval is not allowed。 原因:MySQL 8.0的caching_sha2_password认证需要从服务器获取公钥,驱动默认不允许这种方式。 解决:在连接串里追加allowPublicKeyRetrieval=true&useSSL=false,或者把MySQL用户的认证插件改回mysql_native_password。我推荐前者,改数据库用户密码策略会影响其他项目。顺带说明,如果你看到的是Communications link failure,先别查驱动,先ping、再telnet 3306,网络通不通和权限是两个完全不同的坑。
5.3 IDEA运行JavaWeb项目配置导致的404
现象:IDEA里Tomcat启动了,浏览器却404;或者首页能开,点进某个页面又404。 原因:JavaWeb项目没有按Web工程结构部署。常见原因是Artifacts里没有选Web exploded,或Application context和项目代码里写死的路径不一致。 解决:打开Project Structure → Artifacts,确认输出是一个包含WEB-INF/classes的文件夹;在Run Configuration的Deployment里选择这个exploded artifact,Application context填/survey;重启后直接访问http://localhost:8080/survey/admin/login.jsp验证。判断是不是路径问题时,可以把context临时改成/再试,如果都能访问,那就是源码里所有路径都在等你把context配对了。
5.4 Session反复失效:管理员登录状态丢得莫名其妙
现象:登录成功后点两下就跳回登录页,或者刷新一下就没登录状态。 原因:要么Session超时时间配得极短,要么跟前端请求没带Cookie有关。浏览器直接访问页面时Cookie会自动带,真正的问题是前后端分离调试时,AJAX请求默认不带credentials,后端Set-Cookie失败。 解决:web.xml里明确写Session超时时间,单位是分钟:
<session-config> <session-timeout>30</session-timeout> </session-config>如果页面和接口同源,重点检查浏览器Network面板里请求是否带JSESSIONID;如果用了跨域,前端请求要开fetch('/survey/api/submit', {credentials: 'include'}),后端Filter里也要明确Access-Control-Allow-Credentials=true。这是课程设计项目里最常见的“登录失效”根因。
5.5 数据库连接泄漏:系统跑一会就too many connections
现象:项目开始正常,经过几次增删改查后,任何页面都报Too many connections,重启Tomcat又好一阵。 原因:连接没关闭。JDBC的Connection、Statement、ResultSet任何一个没close,连接池或MySQL端就会积压,直到撑爆。 解决:把所有数据库访问封装到DBUtils,统一在finally里关闭资源,用带资源的try-with-resources更好。注意ResultSet要在Statement之前关,顺序是rs -> ps -> conn。如果系统里已经有Druid或C3P0,可以去监控页面看当前活动连接数;如果没接连接池,就先从“每一条SQL是否都close”查起。
6. 答辩前做一次代码体检:三个能讲出深度的改动
6.1 用ECharts把统计结果画出来
统计页如果只用表格展示所有选项被选了多少次,答辩老师通常只点头。把同一份数据画成柱状图,就能多讲一段“后端查count、前端渲染ECharts”的完整链路。在/survey/result.jsp里引入ECharts的JS文件,用AJAX拿到[{optionId:1,count:32},...]的JSON,再chart.setOption({xAxis: {data: labels}, series: [{data: counts}]})。这个改动的成本不高,但直观展示了“数据可视化”的能力。
6.2 把连接管理换成Druid连接池
纯JDBC的DBUtils.getConnection()每次都是新连接,课设规模看不出问题,但老师问“上线怎么办”时容易露怯。用Druid替代很简单:初始化时DruidDataSource ds = new DruidDataSource(); ds.setUrl(...); ds.setUsername(...); ds.setPassword(...);,DBUtils.getConnection()改成返回ds.getConnection()。这本身就是可讲的改进点:连接复用、批量请求不卡、能看监控。
6.3 给每个关键请求打一行日志
答辩现场最怕的是“老师点了个按钮,报错了,你不知道错在哪”。我吃过这个亏:以前交课设时只跑通正常功能,现场演示时网络一抖动,整个流程直接黑屏。后来我在每个Servlet的入口加一行日志,格式统一成[SurveyServlet] action=submit, surveyId=2, result=success。排查时看Tomcat控制台就能定位是哪一步出了问题。这不算高大上的功能,但养成了这个习惯,任何项目都顺手。
做到这三件事,你的系统就不是“源码能跑”,而是“能说清楚为什么这么设计”。把事务、唯一索引、参数化查询三条事实讲明白,比把十页PPT念完更让老师信服。希望帮到你。
本文还有配套的精品资源,点击获取