简介:面向JavaWeb学习者与课程设计者的网上评教系统项目,基于JavaEE、JSP和MySQL构建完整的在线评价平台,覆盖学生在线评教、教师信息管理、课程选择以及评教结果统计等典型功能,可作为毕业设计或教学管理二次开发的参考蓝本。压缩包为RAR格式,共651个文件,约11.71MB,包含101个JSP动态页面、391个GIF动图,以及CSS样式、JavaScript脚本、HTML页面、SQL数据库脚本、Java源码、项目配置和部署说明等;其中动态页面负责界面展示与前后端交互,数据库脚本可直接建立评教所需的用户、课程、教师、评教结果等核心表结构,目录规划清晰,便于按模块对照学习。目前已有110人学习/下载。通过逐行阅读源码和运行调试,可深入理解JavaEE分层架构、Servlet控制层、JDBC数据访问与MVC设计模式,并掌握项目从需求分析、编码实现到测试部署的完整流程,有助于快速提高基于JSP+MySQL的Web应用开发能力。
1. 为什么高校里还在传阅这版「基于JavaEE的网上评教系统」:不是新技术,但能完整跑通毕业设计
做 Java 课程设计的人,十个里有八个都被「基于JavaEE的网上评教系统」这个题目安排过。JSP 网站设计、MySQL 数据库设计,听起来就是老一套——Servlet 做控制层、JSP 做视图层、JDBC 连 MySQL,没有 Spring Boot,更没有微服务。但这版项目在高校里被反复传阅的原因恰恰是它「老得够完整」:学生在线登录、多维度评分、教师查看评教结果、管理员管理师生数据,一条线全部打通。你拿到的压缩包里,源代码、SQL 初始化脚本、部署说明都在,缺的不是代码,而是「怎么把它跑起来、讲清楚、答得上答辩」。这篇笔记就是冲这个来的:帮你把这套 JSP + Servlet + MySQL 的老项目,在一天之内变成你手上能演示、能讲原理、答辩能过关的东西。
2. 拿到源码包先别急着导入 IDE:先搞懂目录结构与环境搭配
2.1 解压后的第一件事:按「部署视角」而不是「阅读视角」过目录
大多数人拿到 .rar 解压后,第一反应是双击 .sql 文件,或者把整个文件夹直接拖进 Eclipse。我不建议这么做。你先用资源管理器把目录结构完整看一遍,心里画出一张「哪些文件是运行时必须的,哪些文件只是源码参考」的图。典型的结构是这样:
OnlineEvaluation/ ├── src/ # Java 源码 │ ├── com/xxx/entity/ # 实体类(Student, Teacher, Course, Evaluate) │ ├── com/xxx/dao/ # JDBC 数据访问层 │ ├── com/xxx/servlet/ # 控制器 Servlet │ └── com/xxx/filter/ # 编码与登录过滤 ├── WebContent/ # Web 根目录(或 webapp/,取决于 IDE) │ ├── jsp/ # 页面文件 │ ├── css/ js/ images/ # 静态资源 │ ├── WEB-INF/ │ │ ├── web.xml # 部署描述符 │ │ └── lib/ # 数据库驱动 JAR(关键!) │ └── index.jsp ├── database/ │ ├── init.sql # 建库建表脚本 │ └── data.sql # 初始化数据 └── README.txt # 部署说明(大概率是复制粘贴的老文档)这个项目的部署路径依赖比 Spring Boot 项目脆得多:JSP 页面里写的绝对路径、Servlet 的 url-pattern、WEB-INF/lib 下的驱动 JAR,三者只要有一个对不上,整个系统就白屏报 404。所以你把目录过一遍后,重点确认三件事:lib目录里有没有 MySQL 驱动 JAR;web.xml里 Servlet 映射是否完整;init.sql和data.sql是否分开——这决定了你导入数据库时能不能先建空表再灌数据,而不是一条脚本从头跑到尾中途报错。
2.2 环境选型:JDK 1.8 + Tomcat 8.5 + MySQL 5.7 是这套源码的最低摩擦组合
这个项目写于 JSP 尚在流行的年代,代码里可能用了java.sql.DriverManager直连,也可能用了你自己后期接的数据源。无论哪种,环境版本都会决定你第一个小时是「顺利启动」还是「陷入排错」。我一般会直接锁死这套组合:
| 组件 | 推荐版本 | 理由 |
|---|---|---|
| JDK | 1.8(8u202 或更早) | JSP/Servlet 老代码在 JDK 11+ 下容易出现 java.xml.bind 模块缺失等问题 |
| Tomcat | 8.5.x | 支持 Servlet 3.1,和 JSP 2.3 匹配,配置数据源语法最稳 |
| MySQL | 5.7 | 与 mysql-connector-java 5.1.x 驱动配合最成熟,避免 MySQL 8 的认证插件坑 |
| Eclipse IDE | Eclipse IDE for Enterprise Java | 自带 Tomcat 插件,右键 Run on Server 就能跑,比 IDEA 配 Web 工程省事 |
注意:如果你偏要用 MySQL 8.0+,那么驱动类名要从com.mysql.jdbc.Driver改成com.mysql.cj.jdbc.Driver,连接 URL 还要追一个时区参数serverTimezone=Asia/Shanghai,否则一启动就报时区异常。这不是源代码的问题,是 MySQL 8 把 JDBC 驱动行为改掉了。我在模拟项目X里就吃过这个亏:同一个.war包,MySQL 5.7 上跑得好好的,换到 8.0 就报CommunicationsException,排查半天才发现是驱动版本和时区双重问题。
第 2 章的实操结论:先装 MySQL 5.7,再装 JDK 1.8,最后解压 Tomcat 8.5,顺序不能乱;导入源码前先把init.sql用文本编辑器打开扫一眼,确认建库语句里没有你不认识的高版本语法(比如utf8mb4_0900_ai_ci这种排序规则,5.7 只认utf8mb4_general_ci)。
3. 数据库设计拆解:评教系统背后的表结构、外键关系与初始化脚本
3.1 核心表拆解:学生、教师、课程、评教记录四件套
网上评教系统的业务本质很简单:「某个学生对某门课的某位教师打若干维度分数并留下评论,且同一学生对同一教师同一课程只允许评一次」。理解了这句话,你再看数据库设计就不会晕。标准且好讲的一套表结构如下:
-- 用户表:学生和统一管理的登录账号合一 CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(20) NOT NULL UNIQUE, password VARCHAR(32) NOT NULL, role TINYINT NOT NULL DEFAULT 2 COMMENT '1=管理员,2=学生,3=教师', real_name VARCHAR(20) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- 教师表 CREATE TABLE t_teacher ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, dept_name VARCHAR(30) COMMENT '学院', title VARCHAR(20) COMMENT '职称', FOREIGN KEY (user_id) REFERENCES t_user(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- 课程表 CREATE TABLE t_course ( id INT PRIMARY KEY AUTO_INCREMENT, course_name VARCHAR(50) NOT NULL, teacher_id INT NOT NULL, semester VARCHAR(20) COMMENT '如:2023-2024-1', FOREIGN KEY (teacher_id) REFERENCES t_teacher(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- 评教记录表 CREATE TABLE t_evaluate ( id INT PRIMARY KEY AUTO_INCREMENT, student_user_id INT NOT NULL, course_id INT NOT NULL, score_teaching TINYINT COMMENT '教学态度得分1-5', score_content TINYINT COMMENT '教学内容得分1-5', score_method TINYINT COMMENT '教学方法得分1-5', comment_text VARCHAR(500) COMMENT '主观评语', evaluate_time DATETIME, -- 注意:MySQL 5.7 不写默认值也能插入 UNIQUE KEY uk_stu_course (student_user_id, course_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;这里最关键的设计决策是uk_stu_course这个联合唯一索引。它直接从数据库层面保证了「一个学生对一门课只能评一次」,就算你的 Servlet 逻辑写漏了判断,数据库也会在第二次 insert 时抛 DuplicateKeyException 来兜底。这招在答辩时是一个加分点,你可以主动说「防重复评教我做了两层:业务层先查记录是否存在,数据库层加唯一约束兜底」。
另外一个值得注意的设计是FOREIGN KEY。这套老代码里外键约束往往写不写全,很多版本为了图省事根本不建外键,表与表之间的关联全靠程序里处理。我的建议是你保留外键,但要做三处检查:t_evaluate引用的student_user_id到底指向t_user还是t_student;删除教师时课程要不要级联删除;外键列的字符集必须和主键列一致,否则建外键会报Cannot add foreign key constraint这样的错。
3.2 初始化脚本的执行顺序与字符集:这里有个 90% 的人都会踩的坑
拿到init.sql和data.sql,很多人是直接在 Navicat 里选中整个文件一键执行。对这套老项目来说,建议按步骤来执行,先建库再建表,最后灌模拟数据。字符集很重要,建库语句严格写成下面这样:
CREATE DATABASE IF NOT EXISTS online_eval DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE online_eval;为什么我坚持用utf8mb4而不是utf8?因为 MySQL 的utf8是阉割版,最多存 3 字节,像 emoji 或生僻字一插入就直接报错或变成问号。评教系统里学生的评语是自由文本,你不能赌没人会输个 emoji。用utf8mb4_general_ci做排序规则,兼容性也最好。
数据脚本执行失败还有一种很隐蔽的情况:data.sql里有中文字符,但你的 MySQL 服务端字符集是 latin1。执行前先查一下:
SHOW VARIABLES LIKE 'character_set_server';如果输出不是utf8mb4,就在my.cnf或my.ini的[mysqld]段下加一行character_set_server = utf8mb4,然后重启 MySQL 服务。这一步不做,哪怕建库时写了 DEFAULT CHARACTER SET,数据表仍然可能继承服务端默认的 latin1,到时候 JSP 页面上全是一排的问号,你会以为代码逻辑有问题,实际上是数据根本没存进去。
第 3 章到这里,你应该已经能回答「这套评教系统的数据从哪来、存在哪、怎么保证不重复」这三个问题了。接下来要处理的是核心链路:学生登录、选题、打分、提交这一串动作在代码里是怎么流转的。
4. 评教主流程代码走读:从登录到提交评教,一条 MVC 链路如何打通
4.1 登录过滤器与权限控制:JSP 项目里最容易被夸大的技术点
老式 JSP 项目做权限控制,很多人以为要写 Spring Security——其实课程设计项目里用 Filter 搞定就足够,也更好讲。这套网上评教系统的标准做法是在web.xml里注册一个LoginFilter,把/jsp/student/*、/jsp/teacher/*、/jsp/admin/*这些目录下的页面全部拦截,重点就是校验 session 里的登录标记:
// LoginFilter.java —— 核心逻辑 public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; HttpSession session = request.getSession(false); // 校验 session 中是否存在已登录用户 Object loginUser = (session != null) ? session.getAttribute("loginUser") : null; if (loginUser == null) { // 未登录一律弹回登录页 response.sendRedirect(request.getContextPath() + "/login.jsp"); return; } // 如果已经登录,继续放行,让后续 Servlet 处理业务 chain.doFilter(request, response); }这里有一个细节值得讲清楚:request.getSession(false)里的false表示「如果当前请求没有 session,就返回 null,而不是新建一个」。很多翻车现场就是因为这里用了getSession()默认写法,导致未登录请求每次都被创建出一个空 session,过滤器判断永远不拦截。你可以把这条逻辑在答辩时说成「我先判断是否已有会话,而不是无脑创建会话,这可以避免无效的 session 堆积」。
这个 Filter 在web.xml里的配置路径也要确认一遍:<url-pattern>/jsp/*</url-pattern>这种写法会把静态资源 css/js 也拦住,有些版本里 jsp 页面引不到样式就是这原因。建议把静态资源单独放在/static/目录下,过滤路径精确到/jsp/student/*这类业务目录,这样登录页和静态资源不拦,业务页面全拦,逻辑又清楚又不出错。
4.2 提交评教的核心 Servlet:接收请求、嵌套校验、写入记录
评教提交是整个系统里最值得「逐行读懂」的代码。教学态度、教学内容、教学方法这三项得分一般在页面上是 radio 或 select,提交时以score_teaching、score_content、score_method参数名传到 Servlet。
一个典型的提交 Servlet 是这样的:
// EvaluateSubmitServlet.java —— 防重 + 入库 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); // 必须在取参数前设置 HttpSession session = request.getSession(false); User loginUser = (User) session.getAttribute("loginUser"); String courseIdStr = request.getParameter("courseId"); String scoreTeaching = request.getParameter("score_teaching"); String scoreContent = request.getParameter("score_content"); String scoreMethod = request.getParameter("score_method"); String commentText = request.getParameter("comment_text"); // 第一层校验:参数非空 if (courseIdStr == null || scoreTeaching == null || scoreContent == null || scoreMethod == null) { request.setAttribute("msg", "评教数据不完整,请重新选择"); request.getRequestDispatcher("/jsp/student/evaluate.jsp").forward(request, response); return; } // 第二层校验:防重复评教 EvaluateDAO dao = new EvaluateDAO(); boolean alreadyEvaluated = dao.checkExist( loginUser.getId(), Integer.parseInt(courseIdStr)); if (alreadyEvaluated) { request.setAttribute("msg", "该课程已经评教过,不能重复提交"); request.getRequestDispatcher("/jsp/student/evaluate_result.jsp").forward(request, response); return; } // 第三层:写库 Evaluate ev = new Evaluate(); ev.setStudentUserId(loginUser.getId()); ev.setCourseId(Integer.parseInt(courseIdStr)); ev.setScoreTeaching(Byte.parseByte(scoreTeaching)); ev.setScoreContent(Byte.parseByte(scoreContent)); ev.setScoreMethod(Byte.parseByte(scoreMethod)); ev.setCommentText(commentText); ev.setEvaluateTime(new Date()); dao.insert(ev); response.sendRedirect(request.getContextPath() + "/jsp/student/evaluate_result.jsp"); }这段代码里有几个参数层面的坑,提前帮你指出来。第一,request.setCharacterEncoding("UTF-8")这一行必须在第一次调用getParameter()之前执行,否则中文评语在你拿到时就已经是乱码了;如果你项目里有编码过滤器,那这一步可以省略,但老版本的过滤器可能只拦截了部分 URL,我建议直接在 Servlet 里写这句作为兜底。第二,courseId从页面到 Servlet 是字符串,转int时如果页面传空或传了非法字符,会直接抛 NumberFormatException 导致 500;你可以在Integer.parseInt()外包一层 try-catch,但课程设计阶段至少要做到「空值跳过转换」这层防护。第三,dao.insert()执行后应该返回主键 ID,如果你拿到手的是老代码,可能返回void或boolean,这不影响功能,但答辩时讲「插入成功数会影响我后续跳转逻辑」会更有说服力。
4.3 教师端查看评教结果:SQL 里的坑比 Java 里多
教师登录后看到的是「自己教的每门课的平均分和评语列表」,这是典型的分组聚合查询。有的版本把评教结果存在一张独立的统计表里,管理端每天跑定时任务去刷新;但课程设计项目一般不做定时任务,而是实时汇总。核心 SQL 是这样:
SELECT c.course_name, ROUND(AVG(e.score_teaching), 2) AS avg_teaching, ROUND(AVG(e.score_content), 2) AS avg_content, ROUND(AVG(e.score_method), 2) AS avg_method, ROUND((AVG(e.score_teaching) + AVG(e.score_content) + AVG(e.score_method)) / 3, 2) AS avg_overall, COUNT(*) AS evaluate_count FROM t_evaluate e JOIN t_course c ON e.course_id = c.id JOIN t_teacher t ON c.teacher_id = t.id JOIN t_user u ON t.user_id = u.id WHERE u.username = ? GROUP BY c.id, c.course_name ORDER BY avg_overall DESC;这个 SQL 有两个地方很容易出问题。一是WHERE u.username = ?这里,教师表的user_id与t_user关联,如果数据库脚本里没建外键,你必须确保两边的类型一致,比如都是 INT UNSIGNED,否则关联条件不成立,查出来永远是空表。二是ROUND(..., 2)保留两位小数,但 JSP 页面上显示时经常用的是fmt:formatNumber,如果avg_overall是字符串类型,格式化时会报错。老项目里很多教师端页面是${result.avg_overall}直接输出,这没问题;但如果你看到avg_overall在 bean 属性里被定义为String,一定要检查 DAO 层是不是用了String.valueOf(rs.getFloat())这类转换,避免页面输出成null。
第 4 章的核心收获是:这条链路在代码上不复杂,但它把 MVC 的三个角色体现得很完整——JSP 收集参数、Servlet 做流程控制、DAO 做数据持久化。把这三层的位置和职责讲清楚,答辩时你就不会只说「系统能跑」,而是能讲出「我理解三层架构为什么这么划分」。
5. 部署与排错避坑:Tomcat 配置、JDBC 连接与乱码问题的五段血泪经验
5.1 经验一:JDBC 直连与数据源连接池的取舍,以及换版本后的连锁反应
这套老代码相当一部分版本用的是DriverManager.getConnection(url, user, password)直连,代码简单但也导致每个页面请求都会创建一次物理连接,访问量稍大 MySQL 就报Too many connections。你要是只拿来做课设答辩,通常并发量在个位数,直连也能工作;但如果想让它显得更正规,把直连改成 Tomcat 的 JNDI 数据源是一个性价比很高的改动。
在META-INF/context.xml里配置如下:
<Context> <Resource name="jdbc/OnlineEvalDS" auth="Container" type="javax.sql.DataSource" maxActive="20" maxIdle="5" maxWait="10000" username="root" password="你的密码" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/online_eval?useUnicode=true&characterEncoding=UTF-8"/> </Context>然后 DAO 里取连接的方式从DriverManager.getConnection()改成:
Context ctx = new InitialContext(); DataSource ds = (DataSource) ctx.lookup("java:/comp/env/jdbc/OnlineEvalDS"); Connection conn = ds.getConnection();这个改动的关键在于:URL 里characterEncoding=UTF-8必须保留,而且在 XML 里&符号要转义成&,否则 Tomcat 解析context.xml直接失败,启动就报Parse Fatal Error——我见过有人在这上面卡了半小时,原因只是 XML 转义没处理。
5.2 经验二:中文乱码是三方问题,别只盯 JSP 页面
网上评教系统是中文界面,乱码是重灾区。乱码分三处:页面显示乱码、请求参数乱码、数据库存储乱码,三者往往同时爆发。依次检查:
页面显示乱码在 JSP 头部必须有这两行:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>请求参数乱码,除了前面说的request.setCharacterEncoding("UTF-8"),还要检查服务器端 Tomcat 的server.xml里 Connector 是否配置了URIEncoding="UTF-8":
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="UTF-8" />数据库存储乱码,检查连接 URL 和表字符集。三处全部统一成 UTF-8 后,乱码基本消除。有一个常见的玄学坑:MySQL 的jdbc连接串里characterEncoding写成了charEncoding,少一个acter,连不上或直接忽略,数据库照样能存,但存进去是问号。
5.3 经验三:ClassNotFoundException 不一定是你少了 jar,可能是驱动类名写错了
报java.lang.ClassNotFoundException: com.mysql.jdbc.Driver时,好多人第一反应是去下载驱动 jar。但对你这个项目来说,先检查WEB-INF/lib下到底有没有驱动文件,再检查驱动类名是不是匹配驱动版本。MySQL 8+ 的驱动类名已经改成com.mysql.cj.jdbc.Driver,老 jar 配新 MySQL 服务端也可能在连接阶段报认证插件错误。
我维护这类老项目时,会把驱动 jar 统一放到本机 Maven 仓库的固定路径,然后按 MySQL 版本选择:
| MySQL 服务端版本 | 驱动 jar 版本 | 驱动类名 |
|---|---|---|
| 5.5 / 5.6 / 5.7 | mysql-connector-java 5.1.47 | com.mysql.jdbc.Driver |
| 8.0+ | mysql-connector-java 8.0.x | com.mysql.cj.jdbc.Driver |
每次部署都先确认「服务端版本 → 驱动jar → 驱动类名」三个环节对齐,就能避开这个经典报错。
5.4 经验四:Tomcat 端口被占用,不要盲目改 8080
启动 Tomcat 时日志报Port 8080 required by Tomcat v8.5 Server at localhost is already in use,这是最常见的启动失败场景。问题根源多半是上次 Tomcat 没有干净关闭,或者本机其他进程占用了端口。我不建议直接改端口绕过,正确顺序是:先netstat -ano | findstr 8080找出占用进程的 PID,再决定是杀掉还是重新规划端口。随便改成 8081 后,你会发现原来的项目里所有绝对路径(如果写死了端口)全部失效,页面全 404。
5.5 经验五:数据库连接超时导致偶发 500,先查连接池不是先查代码
系统跑着跑着偶尔刷一次页面变成 500,日志里是Communications link failure或Connection is not available, request timed out。这种偶发性问题,代码本身没有明显 bug,玄学感很强。常见原因是 MySQL 的wait_timeout默认 8 小时断开空闲连接,连接池里的连接成了死连接。课程设计阶段我给你的解决方法是:改 JDBC 连接 URL 加两个参数:
jdbc:mysql://localhost:3306/online_eval?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&failOverReadOnly=false如果改成 JNDI 数据源,注意testOnBorrow="true"和validationQuery="SELECT 1"这两个池参数,能有效避免拿到死连接。这不是什么高深技术,但老项目里配置缺失是常态,你补上这一手,运行稳定性立刻上一个台阶。
6. 进阶改造:把课设从「能用」升级成「能讲」的三个关键动作
拿到这套源码并让它跑通之后,你手里的是「能演示的课程设计」。如果想在答辩或面试时让老师觉得「这个学生真理解这个项目」,我建议你花一个下午做三件事,而不是去重写整个架构。
第一件事:把散落在各 Servlet 里的response.sendRedirect("somepage.jsp")整理成统一的后端路径跳转。比如提交评教成功后跳转到/result/success,这个路径由一个ResultServlet统一处理,内部决定分配哪个 JSP。这一改动会让你的项目在「控制器统一管理视图跳转」这个点上讲出层次,而不只是把 Servlet 当跳转工具用。
第二件事:自己写一个极简的 JSON 工具类替代某些 JSP 页面里用分隔符拼接数据的做法。老项目前后端不分离,但你可以把教师端评教统计的 Servlet 改成先输出一段 JSON,再由页面里的fetch去渲染数据。不需要引第三方的 fastjson,十几行代码手动拼接 JSON 足够课程设计展示:
StringBuilder json = new StringBuilder(); json.append("{\"avg_overall\":").append(avgOverall) .append(",\"evaluate_count\":").append(count) .append("}"); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(json.toString());这段代码在讲「后端返回结构化数据、前端负责渲染」时非常拿得出手,也证明你不只会写 JSP 脚本。
第三件事:给数据库补一个统计视图v_teacher_evaluation,把第 4 章那个聚合 SQL 封装成视图,然后让学生端的查询代码只SELECT * FROM v_teacher_evaluation WHERE teacher_id = ?。这个小动作能讲清楚「复杂查询封装进视图、应用层只关心业务参数」的思想,老师听到这通常会点头。
从那以后我每次拿这类老 JSP 项目做演示,都强制先跑一遍「环境版本对齐 → 数据库脚本按序执行 → 页面中文确认无乱码」这三板斧,再来谈代码改造。这套习惯帮我避掉过太多环境层面的坑,也让我在给模拟项目X做部署时不再手忙脚乱。希望这篇拆解能让你少走我走过的弯路,把这套 JavaEE 老项目稳稳地变成你自己的作品。
本文还有配套的精品资源,点击获取