我在毕业设计和课程设计这块泡了很多年,见过太多同学拿到“JSP在线课堂互动教学系统设计与实现”这类题目就发怵。其实这类项目是所有Java Web课设里最经典、也最适合用来把整个技术栈串起来的一类题目,因为它同时牵扯到用户管理、数据权限、文件上传、前后端交互这些必修课。今天这篇就把这套系统的设计思路、源码结构、数据库表设计到最后的调试部署全部掰开揉碎讲一遍,适合即将做课设、准备毕设答辩、或者单纯想用JSP把SSH之前的技术栈重新熟练一下的同学。
1. 项目拆解:这套在线课堂系统到底在做什么
1.1 需求背后的核心逻辑
先别急着看代码,我们要先搞清楚“在线课堂互动教学系统”这个词拆开了意味着什么。课堂,指的是老师可以创建课程、发布课件、布置作业;互动,指的是学生能在课程下面提问、回复、签到、提交作业;系统,说明它有登录、注册、权限控制、后台管理。这样一个系统最终落到数据库层面,其实就那么几张表:用户表、课程表、选课关系表、签到表、问答讨论表、作业提交表、公告表。
你把这个结构想清楚了,整个源码逻辑就顺了。很多人拿到一份源码就跑起来看效果,其实效果只是表象,核心是你要能明白一个请求从浏览器出发,经历了JSP页面、Servlet控制器、Service业务层、DAO数据库层,最后返回结果渲染回页面。这个过程就是我下面要重点展开的内容。
1.2 为什么JSP这种“老技术”还值得认真做
有的同学会问,现在都前后端分离了,SpringBoot加Vue不香吗,为什么还要用JSP。这个问题我在带项目的时候被问了无数次。答案是:教学场景和真实生产场景要求的东西不一样。JSP项目能让你把HTTP协议、Session会话、请求转发与重定向、数据库连接这些底层原理看得清清楚楚,因为这些环节在JSP时代全都需要你手动处理。而SpringBoot默认帮你把这些全部封装掉了,反而容易让人学完还是一头雾水。
还有一层现实原因:很多学校的课程大纲和考核方案里,JSP还是指定的教学内容和考核范围,课程设计甚至毕业设计题目就是按JSP定的,你绕不开。与其抵触,不如把它当成一次把基础夯实的机会。这套系统的技术栈锁定在JSP + Servlet + JavaBean + JDBC + MySQL + Tomcat,这是最经典的组合,也是绝大多数学校机房和教材的默认配置。
1.3 使用者与功能模块的整体视角
系统里至少要包含两种角色:管理员(或者叫教师)和学生。管理员或教师端做的事情是课程管理、学生管理、发布作业、查看签到记录、回复学生提问;学生端做的事情是选课、查看课程资料、签到、提问、回复讨论、提交作业、查看成绩。
从页面维度来说,除了登录注册页,还要有首页、课程列表页、课程详情页(含课件列表和作业列表)、问题讨论页、签到页面、个人中心。从技术维度来说,登录之后的权限控制靠Session和过滤器解决,这是整个项目里最容易踩坑的地方,后面我会专门讲。
2. 核心难点拆解:为什么选这套技术方案
2.1 MVC三层架构,JSP项目的地基
这套系统从分层上必须严格遵循MVC,也就是Model(模型)、View(视图)、Controller(控制器)分离。对应到具体代码:
- View是JSP页面,负责展示和收集用户输入,里面尽量不要写Java业务代码,页面上只保留
c:forEach这种JSTL标签或者简单的<%= %>输出。 - Controller是Servlet,负责接收请求、调用业务逻辑、决定跳转到哪个JSP。
- Model是JavaBean和DAO类,JavaBean对应数据库表结构,DAO负责封装所有JDBC数据库操作。
为什么要这么分?因为如果你把数据库查询代码直接写在JSP里,一开始写起来确实快,但项目一旦超过十个页面你就会彻底失控,改一个字段名可能要翻遍十多个页面。分层之后,改数据库逻辑只用动DAO层,改页面只用动JSP,互不干扰。我见过太多课设代码所有SQL都写在JSP里,最后答辩老师一问就露馅,这属于典型的“能跑但完全不能维护”的代码。
2.2 数据库访问层为什么选JDBC加连接池
现在很多教程推荐直接上MyBatis或者Hibernate,但这套系统我坚持用原生JDBC封装的DAO层。原因很简单:你自己手写过一遍 Connection、PreparedStatement、ResultSet 的完整生命周期,你才能真正理解ORM框架替你做掉了什么。而且课程设计答辩的时候,老师最喜欢问的就是“你的数据库连接是怎么管理的”,如果你能答出“我用Druid连接池管理连接,避免频繁开关连接”,这个印象分直接就上来了。
连接池我推荐用阿里巴巴的Druid,配置非常简单,一个 druid.properties 文件加上一个工具类就能用。它自带监控页面,可以在调试部署阶段直接通过浏览器看SQL执行情况,排查问题特别方便。你也可以用C3P0或者DBCP,但是Druid的中文文档最全,遇到问题好搜。
2.3 开发环境的版本配对,最容易翻车的一环
我把话说在前面:JSP项目百分之六十的启动失败问题都出在环境版本不匹配上。我自己常用的稳定组合是:
| 组件 | 推荐版本 | 说明 |
|---|---|---|
| JDK | 1.8 | 兼容性最好,Tomcat和Eclipse/IDEA都没脾气 |
| Tomcat | 8.5.51 或 9.0 | JSP 2.3规范,Servlet 3.1规范,够用了 |
| MySQL | 5.7 或 8.0 | 5.7对新手更友好,8.0要注意驱动和时区问题 |
| MySQL驱动 | mysql-connector-java 5.1.49 | 对应MySQL 5.7;8.0请用 8.0.x 驱动 |
| IDE | IDEA 2020以上或 Eclipse | 无所谓,顺手就行 |
| 项目管理 | Maven 或 纯lib目录 | 课设建议直接用lib目录,少折腾 |
关于JDK版本有个细节:如果你的Tomcat是9.0,JDK用8到11都行,但是如果你用了JDK17,Tomcat8.5会直接起不来。出了这种问题不要慌,不是代码的事,把JDK降回8就好。另外MySQL8.0默认的认证插件是 caching_sha2_password,JDBC连接的时候经常报 Public Key Retrieval is not allowed,解决办法是在连接URL上加 allowPublicKeyRetrieval=true&useSSL=false。这个坑我当年踩了一个下午,先放在这里给你省时间。
3. 数据库设计:五张表的精雕细琢
3.1 从业务需求反推表结构
数据库是整个系统的灵魂,表结构设计得好,后面的代码写起来就顺手。我建议你动手写代码前,先坐下来把系统里每一个交互动作都列出来,然后反推需要哪些表。比如“学生选了某门课”这个动作,最少需要用户表、课程表、选课表三张表来支撑,选课表里外键关联用户和课程。
标准参考设计是这样的:
t_user用户表:用户ID、用户名、密码、真实姓名、角色(0管理员/1教师/2学生)、邮箱、注册时间、头像路径。t_course课程表:课程ID、课程名、课程简介、教师ID(关联用户表)、封面图、创建时间。t_stu_course选课表:ID、学生ID、课程ID、选课时间。t_sign签到表:ID、学生ID、课程ID、签到日期、签到时间。t_discuss讨论表:ID、课程ID、用户ID、标题、内容、发帖时间、回复数量或者回复时间(冗余字段)。t_reply回复表:ID、讨论ID、用户ID、回复内容、回复时间。t_homework作业表:ID、课程ID、教师ID、作业标题、要求描述、截止时间。t_submit作业提交表:ID、作业ID、学生ID、提交内容、附件路径、提交时间、分数、评语。
有了这八张表,整个系统的功能基本就闭环了。你拿到的源码大概率也就是这个结构,只是命名可能略有不同。
3.2 核心建表SQL,直接照着改
下面给出一份精简可跑的建表脚本,字段注释都写好了,你拿到之后把数据库名改成自己的就行。注意表名和字段名不要用 user、order 这种MySQL保留字,所以我统一加了 t_ 前缀,这也是一个避免踩坑的好习惯。
sql复制CREATE DATABASE IF NOT EXISTS online_class DEFAULT CHARACTER SET utf8mb4;
USE online_class;
-- 用户表
CREATE TABLE t_user (
uid INT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID',
username VARCHAR(50) NOT NULL UNIQUE COMMENT '登录名',
password VARCHAR(64) NOT NULL COMMENT 'MD5加密后的密码',
real_name VARCHAR(50) COMMENT '真实姓名',
role TINYINT DEFAULT 2 COMMENT '0-管理员 1-教师 2-学生',
email VARCHAR(100),
avatar VARCHAR(255) COMMENT '头像路径',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 课程表
CREATE TABLE t_course (
cid INT PRIMARY KEY AUTO_INCREMENT,
course_name VARCHAR(100) NOT NULL,
course_desc TEXT,
teacher_id INT NOT NULL COMMENT '关联t_user.uid',
cover VARCHAR(255),
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_course_teacher FOREIGN KEY(teacher_id) REFERENCES t_user(uid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 选课表
CREATE TABLE t_stu_course (
id INT PRIMARY KEY AUTO_INCREMENT,
student_id INT NOT NULL,
course_id INT NOT NULL,
select_time DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_stu_course(student_id, course_id),
CONSTRAINT fk_sc_student FOREIGN KEY(student_id) REFERENCES t_user(uid),
CONSTRAINT fk_sc_course FOREIGN KEY(course_id) REFERENCES t_course(cid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 签到表
CREATE TABLE t_sign (
id INT PRIMARY KEY AUTO_INCREMENT,
student_id INT NOT NULL,
course_id INT NOT NULL,
sign_date DATE NOT NULL,
sign_time DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_sign(student_id, course_id, sign_date)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 讨论表
CREATE TABLE t_discuss (
did INT PRIMARY KEY AUTO_INCREMENT,
course_id INT NOT NULL,
user_id INT NOT NULL,
title VARCHAR(150) NOT NULL,
content TEXT,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
reply_count INT DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 回复表
CREATE TABLE t_reply (
rid INT PRIMARY KEY AUTO_INCREMENT,
discuss_id INT NOT NULL,
user_id INT NOT NULL,
reply_content TEXT,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 作业表
CREATE TABLE t_homework (
hid INT PRIMARY KEY AUTO_INCREMENT,
course_id INT NOT NULL,
teacher_id INT NOT NULL,
title VARCHAR(100) NOT NULL,
requirement TEXT,
deadline DATETIME
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 作业提交表
CREATE TABLE t_submit (
sid INT PRIMARY KEY AUTO_INCREMENT,
homework_id INT NOT NULL,
student_id INT NOT NULL,
submit_content TEXT,
file_path VARCHAR(255) COMMENT '附件保存路径',
score INT DEFAULT NULL,
comment_text VARCHAR(255),
submit_time DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_submit(homework_id, student_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3.3 为什么强调逻辑外键和冗余字段
你可能注意到了,我在外键上用了 CONSTRAINT 物理外键,但实际在DAO层写代码的时候,我建议你尽量少在业务SQL里依赖物理外键的级联操作。原因很实在:物理外键在删除时会受到约束限制,你在做“老师删除课程”这种功能时很容易报外键冲突的错。我在实际项目中倾向于保留物理外键作为数据完整性的最后一道防线,但业务代码里手动控制删除顺序,比如先删除讨论和回复,再删除选课记录,最后删除课程。
还有 reply_count 这个字段是冗余设计。本来回复数量可以通过 COUNT(*) 查出来,但我特意在讨论表里加了一个计数字段,每次新增回复时顺带 UPDATE t_discuss SET reply_count = reply_count + 1 WHERE did = ?。这样做的理由很朴素:列表页展示讨论列表时不需要再统计回复表,查询会快很多。这个技巧拿去写在设计说明书里,属于加分项。
4. 功能实现:从登录到课堂互动的完整链路
4.1 登录鉴权与Session管理,项目的命门
整套系统里最容易被问、也最值得认真写的就是登录鉴权。我的实现思路是这样:用户提交用户名密码后,Servlet调用用户DAO的 findByUsernameAndPassword 方法查库。密码要存的是MD5值,前端明文传到后台,后台取到后用 MD5(password) 再去比较,绝不允许把明文密码存进数据库。这里补充一句,正规生产环境用的是BCrypt加盐,但是课设用MD5能讲清楚单向散列的概念就够了。
登录成功之后,把用户对象放进Session:
java复制User user = userDao.findByUsernameAndPassword(username, md5Password);
if (user != null) {
HttpSession session = request.getSession();
session.setAttribute("loginUser", user);
session.setMaxInactiveInterval(30 * 60); // 30分钟免操作过期
response.sendRedirect(request.getContextPath() + "/course/list");
} else {
request.setAttribute("msg", "用户名或密码错误");
request.getRequestDispatcher("/login.jsp").forward(request, response);
}
不要小看这段代码,里面有两个关键点值得讲。第一个是登录成功之后要用 sendRedirect 重定向而不是 forward,因为如果你用转发,用户按F5刷新页面就会重复提交登录表单,造成重复登录。第二个是 getContextPath(),这是动态获取项目部署名,避免你把项目部署名改了之后路径全部失效。
权限控制这块用Filter做。写一个 AuthFilter 拦截所有 .do 请求,然后判断Session里有没有用户、用户的角色能不能访问当前资源:
java复制public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
HttpSession session = request.getSession(false);
User user = (session == null) ? null : (User) session.getAttribute("loginUser");
String uri = request.getRequestURI();
if (uri.endsWith("login.jsp") || uri.contains("login.do") || uri.endsWith(".css") || uri.endsWith(".js")) {
chain.doFilter(req, resp);
return;
}
if (user == null) {
response.sendRedirect(request.getContextPath() + "/login.jsp");
return;
}
if (request.getRequestURI().contains("/admin/") && user.getRole() > 1) {
request.setAttribute("msg", "没有管理员权限");
request.getRequestDispatcher("/error.jsp").forward(request, response);
return;
}
chain.doFilter(req, resp);
}
这里唯一要注意的是放行规则,登录页、登录请求、静态资源都要放行,否则你的CSS和JS全部加载不出来,页面会丑得没法看。我自己当年在这里被自己坑过好多次,每次都是登录页纯HTML样式全丢。
4.2 课程列表与详情,搞懂列表的通用写法
课程列表页是每个学生进入系统看到的第一个核心页面。它的主要工作是从当前登录的学生的选课表里查出他选了哪些课,展示成卡片。DAO层的SQL大概是:
sql复制SELECT c.*, t.real_name AS teacher_name
FROM t_course c
JOIN t_stu_course sc ON c.cid = sc.course_id
JOIN t_user t ON c.teacher_id = t.uid
WHERE sc.student_id = ?
在Servlet里查完List,用 request.setAttribute("courseList", list) 存好,再转发到 course_list.jsp。JSP页面用JSTL来遍历:
jsp复制<c:forEach items="${courseList}" var="course">
<div class="course-card">
<h3>${course.courseName}</h3>
<p>授课教师:${course.teacherName}</p>
<a href="${pageContext.request.contextPath}/course/detail?cid=${course.cid}">进入课堂</a>
</div>
</c:forEach>
用 ${course.courseName} 这种方式其实是EL表达式在调用JavaBean的getter方法,所以你的Course类里必须要有对应的 getCourseName() 方法,属性名和数据库字段一一对应。经常有同学在这个地方写错参数名,报一堆500错误,其实就是EL表达式找不到属性。
4.3 提问与回复,把数据库操作串成完整链路
互动模块是这个系统的灵魂。学生在课程详情页点“发起提问”,填写标题和内容,提交到 discuss/add.do。Servlet接收参数后,从Session里取当前用户ID,再把课程ID、用户ID、标题、内容一起封装成一个Discuss对象,调用DAO插入数据库,最后重定向回课程详情页的讨论列表。
这里要注意一个问题:JSP表单提交过来的参数默认全是String,课程ID如果直接 Integer.parseInt(courseId) 有可能会因为传了非法字符报NumberFormatException。规范做法是在Servlet里做一次参数预校验:
java复制String cidParam = request.getParameter("cid");
if (cidParam == null || !cidParam.matches("\\d+")) {
request.setAttribute("msg", "参数错误");
request.getRequestDispatcher("/error.jsp").forward(request, response);
return;
}
int courseId = Integer.parseInt(cidParam);
回复列表的实现是另一个经典场景。点击一条讨论进入详情页,需要同时查出该讨论下的所有回复并按照时间正序排列。这一步在DAO里写一个方法 findRepliesByDiscussId(int discussId) 查询t_reply表,然后同样放到request域里交给JSP循环展示。有同学会觉得这种情况用转发比重定向好,因为刷新页面不会产生重复插入,我同意。所以在“查看讨论详情”这个请求里用 forward 是合理的,不需要还回去。
4.4 签到模块,从APP扫码那种做法简化而来
真实的在线课堂互动系统签到经常用地理位置或者二维码,但我们作为课程设计不用那么复杂,核心就是一张表去重。把学生ID、课程ID、当前日期三个字段查一下,如果存在就不允许重复签到,不存在就插入一条。查询语句很直接:
sql复制SELECT COUNT(*) FROM t_sign
WHERE student_id = ? AND course_id = ? AND sign_date = CURDATE()
这里有一个SQL细节:用 CURDATE() 取数据库当前日期,不要用Java的 new Date() 拼进去,因为应用服务器和数据库服务器如果不在同一个时区(或者部署环境没有做好时间同步),签到日期会错乱。用数据库时间最稳妥。如果你还想要更精细的控制,可以在同一个事务里先查后插,就是 Connection.setAutoCommit(false) 然后查重再插入最后提交。课设做到这一步,已经比大多数人优秀了。
4.5 作业上传与下载,文件操作的标准姿势
作业上传是另一个容易出问题的点。表单要加 enctype="multipart/form-data",后端用Apache Commons FileUpload组件解析。先配置一个上传目录,我推荐放在WebContent(或者webapp)下uploads文件夹里,用绝对路径和相对路径都好处理。核心代码如下:
java复制// 确保上传目录存在
String uploadDir = getServletContext().getRealPath("/") + "uploads";
File dirFile = new File(uploadDir);
if (!dirFile.exists()) {
dirFile.mkdirs();
}
DiskFileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
upload.setFileSizeMax(10 * 1024 * 1024); // 10MB
upload.setSizeMax(50 * 1024 * 1024);
List<FileItem> items = upload.parseRequest(request);
for (FileItem item : items) {
if (!item.isFormField()) {
String fileName = item.getName();
// 只取文件名部分,去掉路径前缀
fileName = fileName.substring(fileName.lastIndexOf("\\") + 1);
// 用时间戳重命名,防止重名覆盖
String newName = System.currentTimeMillis() + "_" + fileName;
item.write(new File(dirFile, newName));
// 数据库里只存相对的访问路径
submit.setFilePath("uploads/" + newName);
} else {
// 处理普通表单字段,比如作业ID
}
}
这里面最容易被忽略的两点:第一,item.getName() 在有的浏览器里会带完整路径,比如 C:\fakepath\a.doc,所以必须截取最后一个反斜杠后面的部分。第二,上传文件名不能直接用原始名字,两个人同时传 report.docx 就会互相覆盖,所以统一加时间戳前缀重命名。下载的时候用 request.getContextPath() + "/" + filePath 拼一个HTTP访问路径就行,不需要单独写下载Servlet(除非你想做权限控制)。
5. 调试部署全流程:从零到能跑通
5.1 开发环境搭建细节
这一步我讲细一点,因为很多人卡在环境。先说JDK:直接安装JDK8,配好 JAVA_HOME 环境变量和 PATH。装完在命令行敲 java -version,确认输出版本里有1.8字样再继续。
然后是Tomcat解压版(不是安装版)。解压后到 conf/server.xml 里确认端口。默认8080,如果端口被占,改成8081。启动方式是直接双击Linux或macOS的 startup.sh、Windows的 startup.bat。如果双击闪退,别慌,去命令行窗口执行startup脚本,把报错信息打出来,十有八九是 JAVA_HOME 没配对或者端口冲突。注意IDEA里也需要配置Tomcat Server,选到Tomcat解压目录就行。
MySQL建议用5.7版本安装包,一路下一步。注意安装过程中选字符集时直接选utf8mb4,密码设置完一定要记住。然后新建一个连接,把上面那段建表脚本执行一遍。执行完后命令行里 use online_class; show tables; 能列出你建的表就算成功。
5.2 配置数据库连接和三方包
在项目源码里找到 db.properties 或者 jdbc.properties 这类配置文件,改成你自己的数据库信息。用Druid的格式示例:
properties复制driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/online_class?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username=root
password=你的密码
initialSize=5
maxActive=20
关于url参数,useUnicode=true&characterEncoding=utf8 是解决乱码的,必须带上。serverTimezone=Asia/Shanghai 是MySQL8的时区要求,用5.7可以不加,加了也不报错。
然后是lib目录。如果是Maven管理的项目,直接把依赖写进pom.xml,然后刷新Maven仓库引包。如果是传统lib目录项目,把 mysql-connector-java、druid、jstl、standard、commons-fileupload、commons-io 这些jar包全部复制到 WebContent/WEB-INF/lib 下面。这个步骤千万别漏了,漏了就报 ClassNotFoundException,而实际你的代码一个字都没错。
5.3 IDEA部署调试的关键操作
在IDEA里打开项目后,右上角找到Tomcat配置添加一个本地Tomcat,然后在Deployment页签里点加号选Artifact,选war exploded模式(就是解压目录模式),Application context设置成 / 或者 /online_class,建议设成/省心,这样访问路径就是 http://localhost:8080/login.jsp,不用记一串上下文路径。
配好之后启动Tomcat,IDEA会自动把项目部署到Tomcat的webapps下并开始加载。如果控制台出现 INFO: Server startup in [xxx] milliseconds 就说明部署成功。马上用浏览器访问登录页,然后你大概率会遇到下面这一节里的坑。
我特别建议调试阶段在DAO层临时加几个 System.out.println,把查到的结果集打印出来,因为浏览器页面报错可能只显示500,但控制台能看到完整堆栈和SQL参数。定位问题要会看报错的第一行Caused by,不要只看Exception above。
6. 常见问题与排查技巧实录
6.1 中文乱码,永远的痛
乱码分三种,分别处理。页面乱码:检查JSP第一行 pageEncoding="UTF-8" 有没有写对,加上 contentType="text/html; charset=UTF-8"。提交乱码:POST请求要在Servlet里加 request.setCharacterEncoding("UTF-8"),这个必须在读取任何参数之前调用;GET请求则要去Tomcat的conf/server.xml里面给Connector加 URIEncoding="UTF-8"。数据库乱码:建表时统一用utf8mb4,且JDBC url里要有 characterEncoding=utf8。这三条都做到了,乱码基本就绝了。
6.2 Tomcat启动闪退或端口被占用
双击startup.bat闪退,用命令行工具切到Tomcat的bin目录,执行startup.bat把错误信息留在窗口里。常见的错误是 CATALINA_HOME 变量错误或者 JAVA_HOME 没指到JDK。端口被占用就换端口,Windows下用 netstat -ano | findstr 8080 查看哪个进程占了8080,能关就关,不能关就改Tomcat端口。注意改端口后访问URL也要跟着变。
6.3 JDBC连接报错,两种高危场景
第一种,MySQL8驱动连MySQL5库,或者反过来,会直接报错或者连接成功但SQL语法不兼容。你要严格做到数据库版本和驱动jar版本一一对应。第二种,报 Public Key Retrieval is not allowed 就是MySQL8的SSL问题,加上 allowPublicKeyRetrieval=true&useSSL=false 立竿见影。还有报 Access denied for user 的,那基本就是用户名密码写错了,去检查配置文件,注意密码里如果带特殊字符要转义。
6.4 上传文件后访问404
上传成功后页面能显示文件名,但点下载是404,说明文件路径有问题。最常见的就是你存进数据库的是相对路径 uploads/xxx.doc,但JSP页面用绝对路径访问时没有加项目上下文前缀。正确拼法是 ${pageContext.request.contextPath}/uploads/xxx.doc,或者存数据库的时候就直接存成 request.getContextPath() 加相对路径拼好的完整路径。这个坑特别隐蔽,因为页面显示路径看起来是对的,但缺少了项目名前缀就是找不到。
6.5 问题速查表
| 现象 | 大概率原因 | 解决方向 |
|---|---|---|
| 登录后刷新页面重复提交 | 用了forward而不是redirect | 登录和增删改操作改用sendRedirect |
| 所有CSS图片丢失 | Filter拦截了静态资源 | 在过滤器里放行 .css .js .jpg等后缀 |
| 列表页显示不出任何数据 | 循环标签路径错误或者DAO查询SQL有错 | 看控制台堆栈,打印SQL和结果集 |
| 部署时ClassNotFoundException | 缺少jar包或者lib目录没被编译进去 | 检查WEB-INF/lib和Artifact设置 |
| 错误提示全部是英文乱码 | 控制台编码问题 | 设置IDEA和Tomcat日志为UTF-8,不影响功能 |
7. 项目做完之后的升级方向
数据库同步、页面美化、性能优化这些我就不一一细说了,但免费送你几个靠谱的扩展方向。第一,给登录加一个验证码,用Java生成图片验证码,这个功能很出彩,答辩老师很吃这一套。第二,把讨论模块做成异步刷新,用户发完提问不跳转页面直接局部刷新,用AJAX调用一个返回JSON的Servlet,这是纯JSP项目里少见的现代感。第三,课程资料和作业提交改用数据库存文件路径而不是直接存二进制文件,保持现在的设计就不错。
如果你愿意花两周时间,还可以把这个系统整体迁移到Maven工程管理,再加上Filter解决跨域中文乱码,把分层再细化成Service接口加Impl实现。那就不只是课设水平了,已经接近一个真正的团队协作项目了。不过课设本身,先把现有的跑通跑稳,比什么都强。
我个人在实际操作中的体会是,这种JSP项目最忌讳的就是还没跑起来就琢磨怎么加功能。先把环境配好、把数据库跑通、把一个登录到课程列表到互动的完整链路走通,你自然就理解了整个项目的地图。等你亲手把那几个最常见的坑都踩一遍,答辩的时候你会比谁都自信。
