简介:基于JavaWeb的作业管理网站完整项目源码,适配JavaWeb课程设计、毕业设计参考及初学者综合实践场景。项目综合运用Servlet、JSP、JDBC与MVC分层设计思想,实现教师发布作业、学生查看并提交作业、管理员统一管理的完整业务流程,代码中涵盖登录、作业发布、作业收集、作业提交等核心模块。压缩包共53个文件,包含9个JSP动态页面(对应作业发布、提交、汇总展示等界面)、9个Java业务逻辑处理类、9个Jar依赖库及10个映射配置文件,另有CSS样式、JavaScript脚本与数据库连接配置等辅助资源,整体约7.73MB,解压后目录结构规整,前后端代码分层放置,便于对照阅读。已有97人学习下载,适合需要快速启动JavaWeb项目开发或完成课程设计任务的学习者,从数据库表设计到Servlet控制层再到JSP展示层均有完整实现,能帮助理解JavaWeb项目从零搭建到功能落地的全过程。
1. 基于 JavaWeb 的作业管理网站:一个能跑通全链路的练手项目
很多人在学完 Servlet、JSP、JDBC 之后卡在同一个地方:语法都知道,但不知道这些东西组合起来长什么样。这个基于 JavaWeb 的作业管理网站就是一个现成的完整案例,从 login.jsp 登录开始,到教师发布作业、学生提交作业、管理员汇总查看,整条业务链路都覆盖了,还自带 Bootstrap 4.3.1 的响应式前端。项目不依赖 Spring,代码量不大,正好用来验证 IDEA 运行 JavaWeb 项目配置、JavaWeb 连接 MySQL 数据库这些实操环节。适合刚学完 JavaWeb 基础、正在为课程设计发愁、或者想补一个完整项目经验的人。拿到压缩包先别急着跑,跟着下面的拆解把结构和坑摸一遍,能省不少时间。
2. 项目骨架与技术选型:从文件清单看懂 Servlet/JSP/JDBC 的分工
2.1 文件清单反推项目结构:gdes-master 里每层放什么
解压之后根目录是 gdes-master,先别急着往 IDEA 里拖,花两分钟对着文件清单把结构理清楚。src 下是 Java 源码和 druid.properties,web 目录下是 JSP 页面和静态资源,META-INF 里一般放的是 context.xml 这类容器级配置。这种布局是 JavaWeb 项目的标准姿势:Java 代码和页面代码物理隔离,Web 容器只认 web 目录下的内容,打包部署也是这个结构。
| 文件/目录 | 在项目里的职责 |
|---|---|
| src/com | Servlet 控制器、JDBC 工具类、实体类所在的 Java 包 |
| druid.properties | Druid 连接池配置:数据库地址、账号、连接池参数 |
| web/META-INF | 容器级配置目录,常见的有 context.xml |
| web/login.jsp | 登录页,提交用户名密码到 LoginServlet |
| web/index.jsp | 首页,登录后的导航入口和功能聚合 |
| web/issue.jsp | 教师发布作业的表单页 |
| web/work.jsp | 作业列表页,展示标题和截止时间 |
| web/submit.jsp | 学生提交作业页,含文件上传表单 |
| web/gather.jsp | 汇总查看页,统计提交和迟交情况 |
| web/information.jsp | 作业详情 / 学生信息展示页 |
| web/bootstrap-4.3.1-dist | Bootstrap 4.3.1 前端框架 |
| web/css、web/jq、web/images | 自定义样式、jQuery 库、图片资源 |
从这份清单能看出业务线的设计:issue 对教师,submit 对学生,gather 对管理员,work 和 information 做中间展示。页面角色划分清楚之后,数据库表怎么建、Servlet 要写几个,基本就一目了然了。Bootstrap 4.3.1 和 jq 的存在说明前端已经有现成样式库,不用从零写 CSS,这也是这类课程设计项目的通用配置。
2.2 为什么选 Servlet + JSP + JDBC 而不是 Spring Boot
现在很多新项目一上来就是 Spring Boot + MyBatis,但作业管理这类课程设计用纯 JavaWeb 反而是更好的选择。原因有三:第一,Servlet 和 JSP 是 JavaWeb 的地基,Spring MVC 的 DispatcherServlet 本质还是 Servlet,过滤器、监听器、session 作用域这些概念在纯 Servlet 项目里看得最清楚;第二,项目够小,不需要 Spring 的依赖注入和 AOP 来解耦,硬上框架反而把学习重点带偏;第三,部署和排错链路短,Tomcat 启动、jar 包冲突、中文乱码这类问题在简单项目里暴露得更直接,一次翻车顶十次看文档。
这套技术栈跟黑马那套 JavaWeb 笔记里强调的 Servlet 生命周期、连接池理念是同一套东西,区别在于这里把业务闭环做完了,而不是只停留在增删改查示例。对考核者来说,能讲清楚一个页面请求从浏览器到 Servlet 再到数据库的完整路径,比会抄一段 Spring Boot 代码更有说服力。
2.3 druid.properties 与 JavaWeb 连接 MySQL 的标配写法
项目里数据库连接用的是阿里 Druid 连接池,配置文件是 druid.properties,放在 src 根目录下,通过类加载器读取。典型配置长这样:
# 驱动类:MySQL 8 换成 com.mysql.cj.jdbc.Driver driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/homework_db?useUnicode=true&characterEncoding=utf8&useSSL=false username=root password=123456 initialSize=5 minIdle=5 maxActive=20 maxWait=60000这里几个参数注意一下:driverClassName 连 MySQL 8.x 时需要改成 com.mysql.cj.jdbc.Driver,同时 url 里要补 serverTimezone=Asia/Shanghai,否则连接报时区异常。useUnicode=true&characterEncoding=utf8 是中文不乱码的关键,少一个后面作业内容存进数据库就变成问号。initialSize、minIdle、maxActive 控制连接池的初始连接数、最小空闲连接和最大活跃连接,课程设计环境保持 5/5/20 就行,maxWait 是获取连接的超时毫秒数。
对应的工具类一般写在 com 包里,用 DruidDataSourceFactory 加载配置并对外提供连接:
public class DruidUtils { private static DataSource ds; static { try (InputStream in = DruidUtils.class.getClassLoader() .getResourceAsStream("druid.properties")) { Properties props = new Properties(); props.load(in); ds = DruidDataSourceFactory.createDataSource(props); } catch (Exception e) { throw new ExceptionInInitializerError(e); } } public static Connection getConnection() throws SQLException { return ds.getConnection(); } }加载配置用 getResourceAsStream 而不是 new FileInputStream,是因为 Web 应用部署后配置文件在 classes 目录下,用相对路径很容易踩坑,类路径加载最稳。getConnection 拿走的连接用完要 close,Druid 的 close 是归还连接池而不是真正关闭连接,这也是连接池省资源的核心机制。写 DAO 时记住一个原则:连接在 try-with-resources 里拿,方法结束自动归还,不要写一堆手动关闭还容易漏。
3. 本机复现:IDEA 运行 JavaWeb 项目配置与 MySQL 建库
3.1 环境版本搭配:JDK、Tomcat、MySQL 怎么选不打架
上手前先把版本对齐,一个常见的翻车原因是版本乱搭。我一般建议 JDK 1.8 + Tomcat 8.5 或 9.0 + MySQL 5.7 这套组合,兼容性最好,网上能搜到的资料也最多。如果本机装的是 MySQL 8,就用 8.0 的驱动 jar,driverClassName 换成 com.mysql.cj.jdbc.Driver。特别注意 Tomcat 10 开始把包名从 javax.* 换成了 jakarta.*,老项目直接扔进去会编译不过,新手别追新,先让项目跑起来再说。
JDBC 驱动 jar 放哪也有讲究:放在 Tomcat 的 lib 目录是全局生效,放在项目 WEB-INF/lib 是只对当前应用生效。课程设计环境我建议放项目里,换一台机器部署时不会因为漏装驱动而报 ClassNotFoundException。同理,druid 的 jar、JSTL 的 jar 都统一丢进 WEB-INF/lib,打 war 包时这些依赖会自动带进去。
3.2 建库建表:作业管理系统的三张核心表
在 MySQL 里执行下面的建表脚本,库名要和 druid.properties 里的 url 对应,对不上会直接连接失败:
CREATE DATABASE IF NOT EXISTS homework_db DEFAULT CHARACTER SET utf8; USE homework_db; CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(50) NOT NULL, role TINYINT NOT NULL DEFAULT 0 COMMENT '0学生 1教师 2管理员' ); CREATE TABLE t_homework ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(100) NOT NULL, content TEXT, teacher_id INT NOT NULL, deadline DATETIME, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE t_submit ( id INT PRIMARY KEY AUTO_INCREMENT, homework_id INT NOT NULL, student_id INT NOT NULL, file_path VARCHAR(200), submit_time DATETIME DEFAULT CURRENT_TIMESTAMP, note VARCHAR(500), KEY idx_homework (homework_id) ) DEFAULT CHARSET=utf8;三张表对应三条业务线:t_user 管登录和角色,t_homework 管教师发布的作业,t_submit 管学生提交记录。role 字段用数字而不是字符串,是为了后续做权限判断时直接比较 int,少一层转换。t_submit 里 homework_id 建了普通索引,因为 gather.jsp 汇总时主要按作业 ID 查提交记录,没有索引数据量大了会慢。
插入测试数据时注意两点:一是密码字段如果项目里用的是 MD5 存储,就要把密文插进去,直接插明文会导致登录永远失败,这是很多人第一步就卡住的地方;二是作业的 deadline 别填成过去时间,否则测试提交时会被判定为迟交,影响你观察正常流程。
3.3 IDEA 里的部署三步:Artifact、Tomcat、context path
IDEA 里跑 JavaWeb 项目和跑普通 Java 程序不一样,必须走 Web 服务器这一步。按我常用的做法拆成三步。
第一步,打开 Project Structure(Ctrl+Alt+Shift+S)→ Artifacts → 点加号 → Web Application: Exploded,把 web 目录选为 Web Resource Directory。Exploded 是解压目录形式,支持热部署,开发阶段比 war 方便。
第二步,打开 Run/Debug Configurations → 点加号 → Tomcat Server → Local,在 Server 标签页选择本地 Tomcat 的安装路径;切到 Deployment 标签,点加号选 Artifact,把当前项目的 Artifact 加进部署列表。
第三步,设置 Application context,也就是访问路径。默认是 /项目名,我习惯改成 /homework,这样启动后访问 http://localhost:8080/homework/login.jsp。路径和数据库库名区分开,避免排查问题时混淆。
配置完先点绿色三角启动,盯着 Tomcat 控制台输出,看到 "Connected to server" 和 "Server startup" 字样基本就成了。浏览器直接访问 login.jsp,能出页面说明静态资源和 JSP 编译都没问题。如果控制台飘红,先看异常发生在启动阶段还是请求阶段:启动阶段的错多半是端口占用或配置缺失,请求阶段的错才跟代码相关。
提示:如果访问时 404,优先检查 Deployment 标签里 Artifact 有没有加进去,以及 context path 和你实际访问的路径是否一致。这两个是 IDEA 部署 JavaWeb 项目最常见的坑,比代码本身更容易让你怀疑人生。
4. 核心业务链路走读:发布、提交、汇总三条线的代码逻辑
4.1 登录与首页:session 角色控制是怎么做的
login.jsp 把表单提交到 LoginServlet,核心逻辑是查 t_user 表、比对密码、写 session。我按这份项目最常见的写法给一个等价实现,重点看参数获取和转发/重定向的区别:
@WebServlet("/login") public class LoginServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 必须在读取任何参数之前设置编码 req.setCharacterEncoding("UTF-8"); String username = req.getParameter("username"); String password = req.getParameter("password"); String sql = "SELECT id, role FROM t_user WHERE username=? AND password=?"; try (Connection conn = DruidUtils.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, password); try (ResultSet rs = ps.executeQuery()) { if (rs.next()) { HttpSession session = req.getSession(); session.setAttribute("userId", rs.getInt("id")); session.setAttribute("role", rs.getInt("role")); // 登录成功:重定向到首页,避免刷新重复提交 resp.sendRedirect(req.getContextPath() + "/index.jsp"); } else { req.setAttribute("msg", "用户名或密码错误"); req.getRequestDispatcher("login.jsp").forward(req, resp); } } } catch (SQLException e) { throw new ServletException("登录查询失败", e); } } }登录成功用 sendRedirect,失败用 forward,这个区别很关键:sendRedirect 是浏览器重新发一次请求,地址栏会变成 index.jsp,用户刷新页面不会重复提交表单;forward 是服务器内部转发,地址栏仍停留在 login,请求参数还在,可以把错误信息回显到页面上。role 存进 session 后,index.jsp 里写一段角色判断,教师看到 issue.jsp 的发布入口,学生看到 work.jsp 的作业列表入口,这就是最简单的权限分流。
4.2 发布与提交:表单参数、文件上传两个典型场景
issue.jsp 是教师发布作业的页面,表单里常见字段是标题、内容、截止时间,提交到后端后 INSERT 进 t_homework。这里最容易忽略的是截止时间的格式:页面用 datetime-local 类型的输入框,传到后端是 "2025-03-20T23:59" 这种带 T 的字符串,JDBC 的 setTimestamp 不直接认这个格式,需要先替换掉 T 再解析,或者干脆用 setString 交给 MySQL 的 datetime 类型自动转换。
submit.jsp 的作业提交涉及文件上传,表单必须写成 multipart 编码:
<form action="${pageContext.request.contextPath}/submit" method="post" enctype="multipart/form-data"> <!-- enctype 缺失是文件上传为空的第一原因 --> <input type="hidden" name="homeworkId" value="${param.id}"> <div class="form-group"> <label>选择作业文件</label> <input type="file" name="file" class="form-control-file" accept=".zip,.pdf,.doc,.docx"> </div> <div class="form-group"> <textarea name="note" rows="4" class="form-control" placeholder="给老师的备注"></textarea> </div> <button type="submit" class="btn btn-primary">提交作业</button> </form>enctype="multipart/form-data" 不能漏,漏了之后后端 getParameter 能拿到 homeworkId,但文件部分永远是空的。JavaEE 的 HttpServletRequest 提供了 Part API,处理上传不需要引第三方 jar:
Part filePart = req.getPart("file"); String fileName = Paths.get(filePart.getSubmittedFileName()).getFileName().toString(); String saveDir = req.getServletContext().getRealPath("/uploads"); File dir = new File(saveDir); if (!dir.exists()) { dir.mkdirs(); } // 时间戳拼前缀,防止同名文件互相覆盖 filePart.write(saveDir + File.separator + System.currentTimeMillis() + "_" + fileName);getRealPath 拿到的是 Tomcat 部署目录下的绝对路径,注意这个 uploads 目录在重启 Tomcat 时有可能被清理,正式环境一般把上传目录配置到服务器磁盘的固定位置。文件名前面拼 System.currentTimeMillis() 时间戳,是为了防止两个学生传同名文件互相覆盖。write 之后再把 homeworkId、studentId、文件名落库,完成一次提交闭环。
4.3 汇总与详情:gather.jsp 的联表查询
gather.jsp 是管理员看全局的地方,按作业聚合提交情况。最典型的需求是:左侧作业列表,右侧每个作业的提交人数、迟交人数和未交名单。这个用一条 LEFT JOIN 就能拿到大部分数据:
SELECT h.id, h.title, COUNT(s.id) AS submit_count, SUM(CASE WHEN s.submit_time > h.deadline THEN 1 ELSE 0 END) AS late_count FROM t_homework h LEFT JOIN t_submit s ON s.homework_id = h.id GROUP BY h.id, h.title;注意 LEFT JOIN 保证没有任何人提交的作业也出现在结果里,COUNT(s.id) 只统计非空的提交记录,因为 LEFT JOIN 下没有匹配时 s.id 是 NULL。迟交判断用 SUM 配合 CASE 表达式,直接在 SQL 里算出每个作业的迟交人数,省得在 Java 里取回全表再循环判断,数据量大时性能差距很明显。
work.jsp 和 information.jsp 走的是另一条查询线:work.jsp 展示所有作业的标题和截止时间,点进某一条之后跳到 information.jsp 看详情。详情页的 URL 一般带参数,形如 information.jsp?id=3,JSP 里用 request.getParameter("id") 接收,再查 t_homework 和 t_submit 的关联数据,把已交名单和未交名单都列出来。
注意:information.jsp 这类详情页,不要用字符串拼接的方式把 ?id= 拼进 SQL。用 PreparedStatement 的占位符是底线要求,字符串拼接等于把 SQL 注入漏洞写在明面上,这是课程设计答辩里最容易被问倒、也最掉分的问题。
5. 避坑排查:JavaWeb 项目运行最常见的五类问题
JavaWeb 项目的坑翻来覆去就那么几类,下面五条是我在实际复现这类项目时踩过、也帮别人排查过的,按出现频率排了序。每条都按现象、原因、解决的顺序写,排查时可以直接对照。
5.1 启动后访问 404:context path 和 Artifact 没对上
**现象:**IDEA 启动 Tomcat 没有报错,但浏览器访问 http://localhost:8080/login.jsp 一直 404,控制台也看不到请求日志。
**原因:**绝大多数情况是 Deployment 标签页里没加 Artifact,或者 context path 与访问路径不一致。Tomcat 默认把应用挂在 context path 之下,不会自动挂到根路径,你访问根路径自然是 404。
**解决:**打开 Run/Debug Configurations → Deployment,确认 Artifact 已经添加;Application context 填 /homework 就去访问 /homework/login.jsp。如果不想带路径前缀,可以临时把 context 填成 /,但同一个 Tomcat 跑多个项目时会冲突,不推荐长期这么干。还有一个细节:IDEA 里修改 context path 后要重启 Tomcat,热重载有时不会刷新上下文。
5.2 数据库中文变问号:编码在三个环节断裂
**现象:**页面上输入中文作业标题,提交后数据库里存进去变成 ??,或者反过来,从数据库查出来的中文在页面上显示成乱码。
**原因:**这条链路有三处编码必须对齐——MySQL 表的字符集、JDBC 连接串的编码参数、JSP 页面的 pageEncoding。任何一环断了,中文就保不住。另外 Servlet 里如果没有在读取参数前调用 setCharacterEncoding,请求参数默认按 ISO-8859-1 解析,中文必乱。
**解决:**建表时加 DEFAULT CHARSET=utf8;连接串加 useUnicode=true&characterEncoding=utf8;每个 JSP 头部写 <%@ page pageEncoding="UTF-8" %>;Servlet 里在 getParameter 之前调用 req.setCharacterEncoding("UTF-8"),注意这个调用必须在第一次 getParameter 之前,否则对已经读取的请求不生效。
5.3 启动报 ClassNotFoundException:mysql 驱动放错位置
**现象:**Tomcat 能启动,但一执行到 JDBC 相关代码就抛 ClassNotFoundException: com.mysql.jdbc.Driver,或者 Druid 初始化时报找不到驱动类。
**原因:**MySQL 驱动 jar 没有进 WEB-INF/lib。IDEA 里如果在 Project Structure 的 Libraries 里加了驱动,但没有确认它被打包进 Artifact,运行时依然找不到。这个坑特别隐蔽,编译期不报错,一运行就炸。
**解决:**把 mysql-connector-java 的 jar 直接复制到 web/WEB-INF/lib 目录,重新构建 Artifact,然后在 Artifact 的 Output Layout 里确认 jar 在列表里。MySQL 8 还要把驱动类改成 com.mysql.cj.jdbc.Driver,同时 url 里加 serverTimezone 参数,二者缺一不可。
5.4 文件上传拿到 null:表单缺 enctype 或用了 getParameter
**现象:**submit.jsp 点击提交后,后端 filePart 一直为 null,或者文件保存下来大小为 0。
**原因:**一是 form 标签漏了 enctype="multipart/form-data",浏览器不会把请求体编码成 multipart 格式,服务器拿不到 Part;二是对 multipart 请求用 getParameter("file") 去取文件字段,这个方法在 multipart 模式下取不到文件内容。
**解决:**确认 form 里有 enctype="multipart/form-data",并且服务器端用 getPart("file") 获取。另外老项目如果用了 commons-fileupload,注意 Tomcat 8 以上内置 Part API,两种方式不要混用,混用会出现一边能取到一边抛 IllegalStateException 的情况。
5.5 不登录直接访问 JSP:缺一个权限过滤器
**现象:**不经过登录页,直接在地址栏输入 http://localhost:8080/homework/submit.jsp,页面居然能正常打开,提交记录里还能看到别人的作业。
**原因:**纯 JSP 项目里,Web 容器默认不拦截任何页面,所有 JSP 都是公开的。session 里有没有用户、用户是什么角色,全靠代码自己检查,不写就完全没有保护。这是很多课程设计项目的通病,也是安全扣分点。
**解决:**写一个 LoginFilter,在 doFilter 里检查 session:
public class LoginFilter implements Filter { @Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; // 用 getSession(false):不自动创建 session,避免每次请求都新建 HttpSession session = request.getSession(false); if (session != null && session.getAttribute("userId") != null) { chain.doFilter(req, resp); } else { // 未登录:弹回登录页,而不是直接返回 401 ((HttpServletResponse) resp).sendRedirect( request.getContextPath() + "/login.jsp"); } } }然后在 web.xml 里配置过滤器映射,把 submit.jsp、gather.jsp、information.jsp 这类需要登录的路径拦截,login.jsp 和 css、js、images 静态资源放行。加了过滤器之后,地址栏直敲 URL 会被弹回登录页,session 过期也会被踢出去,这个行为本身就是答辩时的加分说明。
6. 进阶改造:从纯 JSP 到 MVC 分层与安全加固
项目跑通之后,想让它从「能运行」升级成「能讲清楚、能扛住提问」,建议做两个改造。
第一个是补全权限过滤。5.5 的 LoginFilter 写成完整代码就是十几行:取 session,判断有没有登录标记,有就放行,没有就重定向。真正要注意的是放行名单——login.jsp 和静态资源必须排除在外,否则登录页自己都进不去,这种低级错误一旦发生,排查起来比写过滤器还费时间。
第二个是把 JSP 页面里的 Java 脚本搬出去。检查 information.jsp、work.jsp 顶部,很可能有 <% Connection conn = ... %> 这样的脚本片段。按 MVC 的思路拆:实体类放 com.entity,JDBC 操作放 com.dao,Servlet 放 com.servlet,JSP 只保留 HTML 和 EL 表达式。拆完之后页面不再直接依赖数据库,以后把 JDBC 换成 MyBatis,JSP 一行都不用改。顺手把字符串拼接的 SQL 全部换成 PreparedStatement 占位符——这不是玄学,SQL 注入是真实存在的攻击方式,PreparedStatement 是防它的标准手段。
改造完怎么验证?我一般强制自己走三条检查:退出登录后直接访问受保护页面,确认被重定向回 login.jsp;在登录框输入 ' or '1'='1 这类注入串,确认查询不到数据;打开 JSP 源码,确认没有 <% 脚本片段。三条都过了,这份作业管理网站就能作为完整案例放心展示。
做这个项目时我吃过一次亏:图省事把 MySQL 密码直接写死在 Servlet 里,换机器部署时一改密码全项目翻车。从那以后我每次拿到 JavaWeb 项目,第一件事就是把账号密码收进 druid.properties,强制核对连接池、字符集、驱动版本三个参数一致,走完这套检查再谈功能。希望帮到你。
本文还有配套的精品资源,点击获取