简介:这是一份面向计算机相关专业本科生与Java Web初学者的毕业设计文档,主题为基于Java Web技术的图片管理系统,采用B/S架构、JSP前台与MySQL后台,划分管理员与普通用户两类角色,覆盖图片的添加、删除、修改、查询及分类管理,可用于课程设计参考、毕设选题借鉴或Java Web入门练手。资源包共1个doc文件,约328KB,内容为完整的论文正文,含引言、需求分析、系统功能与处理流程设计、用例图、E-R图、数据库表结构与连接技术、用户登录、图像类别与信息管理、图片查询、系统调试与测试及结论等章节,并附中英文摘要与关键词,目录层级清晰,便于按模块查阅与二次整理。目前已有226人学习下载,适合需要完整毕设框架、数据库设计范例与测试思路的读者参考。
1. 从一份本科毕设文档说起:Java-Web 图片管理系统到底能跑出什么
如果你手头正压着一个「基于 Java-Web 技术的图片管理系统」的毕设任务,或者想找一个结构完整、技术栈不过时的 JavaWeb 入门项目来练手,这份文档资料值得先翻一遍再决定要不要动手。它不是那种只给几张截图的空壳论文,而是把需求分析、数据库表结构、E-R 图、处理流程、JDBC 三层访问模型、测试步骤都写进去了的完整设计文档。技术栈是 JSP + Servlet + MySQL + Tomcat,B/S 架构,分管理员和普通用户两个角色,管理员做图片的增删改查和类别管理,用户注册登录后浏览和检索图片。这套东西放在今天看不算新,但正因为「老」,它反而把 JavaWeb 最核心的请求-处理-响应链路暴露得很干净,没有 Spring 那一层封装挡着,适合拿来理解底层到底发生了什么。适合谁?正在做毕设需要参考文档结构和表设计的同学,以及想从零搭一个能跑起来的 JSP 项目、但不想一上来就被框架劝退的开发者。
2. 技术选型与数据库设计:JSP+MySQL 这套组合为什么还值得拆
2.1 为什么是 JSP 而不是纯 Servlet 或前端框架
这份文档选 JSP 作为前台开发工具,MySQL 做后台数据库,服务器用 Tomcat。很多人第一反应是「JSP 都过时了还讲什么」,但如果你真去拆这个项目,会发现它的选型逻辑在毕设场景下是成立的。纯 Servlet 写页面意味着每个 HTML 标签都要用out.println()拼字符串,一个带表格的列表页能写到你怀疑人生;而 JSP 允许你在 HTML 里直接嵌 Java 代码片段和 JSTL 标签,页面结构和动态数据分离得比较自然。文档里也提到了 JSP 的几个特征:内容的生成和演示分离、可重用组件、标识简化页面开发。翻译成实操语言就是——你可以在.jsp文件里用<% %>写逻辑,用<%= %>输出变量,用 JSTL 的<c:forEach>遍历图片列表,不用手动拼 HTML。
MySQL 这边,文档给的理由是体积小、速度快、总体拥有成本低、开放源码。对于毕设这种单机部署、数据量几百到几千条的场景,MySQL 确实是最省事的选择。你不需要配 Oracle 那一堆监听和服务名,装完就能用,mysql -u root -p进去建库建表,十分钟搞定。
提示:文档里写的 Tomcat 5.5 和 JDK 1.6 是那个年代的配置,现在你直接用 Tomcat 9 或 10 加 JDK 8/11 就行,JSP 和 Servlet 的核心 API 没变,但要注意 Tomcat 10 之后
javax.servlet包名改成了jakarta.servlet,老代码直接搬会报 ClassNotFound。
2.2 数据库表结构拆解与建表实操
文档里给了五张核心表:Admin(管理员)、Pic(图片主信息)、Picinfo(图片文件信息)、Pictype(图片类别)、System(系统配置)。这个拆分思路是对的——把图片的元数据(标题、类型、地点、建造者、时间、备注)和实际文件路径分开存,类别单独一张表方便做分类筛选。下面是我根据文档表结构整理的建表 SQL,字段名做了规范化处理(原文档有些拼写如Titel、Creartime明显是笔误,实际建表时建议修正):
-- 管理员表 CREATE TABLE admin ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(100) NOT NULL, password VARCHAR(100) NOT NULL, createtime VARCHAR(100), flag INT DEFAULT 0, isuse INT DEFAULT 1, logintimes INT DEFAULT 0, quanxian VARCHAR(100) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 图片类别表 CREATE TABLE pictype ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(30) NOT NULL, addtime VARCHAR(30) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 图片主信息表 CREATE TABLE pic ( id INT PRIMARY KEY AUTO_INCREMENT, titel VARCHAR(100) NOT NULL, type VARCHAR(100), place VARCHAR(100), builder VARCHAR(100), co VARCHAR(100), btime VARCHAR(100), remark VARCHAR(500), addtime VARCHAR(30) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 图片文件信息表 CREATE TABLE picinfo ( id INT PRIMARY KEY AUTO_INCREMENT, pid INT NOT NULL, title VARCHAR(100), url VARCHAR(150), addtime VARCHAR(30), FOREIGN KEY (pid) REFERENCES pic(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;这里有几个参数需要说明。ENGINE=InnoDB是为了支持外键和事务,picinfo表的pid外键关联到pic表的id,并设置了ON DELETE CASCADE,这样删除一条图片主记录时,对应的文件路径记录会自动清理,不会留下孤儿数据。字符集用utf8mb4而不是utf8,因为图片标题和备注里可能出现 emoji 或特殊字符,utf8三字节存不下。addtime用VARCHAR(30)而不是DATETIME,这是原文档的做法,好处是前端传什么格式就存什么格式,不用做类型转换,坏处是排序和范围查询会走字符串比较,如果要做「按时间倒序」得保证格式统一为yyyy-MM-dd HH:mm:ss。
2.3 JDBC 三层访问模型与连接代码
文档里重点讲了 JDBC 的三层结构:浏览器端 → 中间件(服务器端)→ 数据库。中间件负责权限认证和 SQL 封装,数据库的账号密码不出现在客户端。这个模型在 JSP 项目里的落地方式就是:JSP 页面负责展示,Servlet 负责接收请求和调用 DAO,DAO 层用 JDBC 连数据库。下面是一个典型的数据库连接工具类:
public class DBUtil { private static final String DRIVER = "com.mysql.cj.jdbc.Driver"; private static final String URL = "jdbc:mysql://localhost:3306/picdb?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8"; private static final String USER = "root"; private static final String PASS = "yourpassword"; static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { throw new RuntimeException("MySQL驱动加载失败", e); } } public static Connection getConn() throws SQLException { return DriverManager.getConnection(URL, USER, PASS); } public static void close(Connection conn, PreparedStatement ps, ResultSet rs) { try { if (rs != null) rs.close(); } catch (SQLException ignored) {} try { if (ps != null) ps.close(); } catch (SQLException ignored) {} try { if (conn != null) conn.close(); } catch (SQLException ignored) {} } }Class.forName(DRIVER)这一步就是把 JDBC 驱动类加载到 JVM 里,文档里提到的Class.forName()静态函数干的就是这件事。DriverManager.getConnection()拿到连接对象后,后面的增删改查都通过PreparedStatement来执行。URL 里的useSSL=false是关掉 SSL 连接,本地开发不关的话 MySQL 8 会报 SSL 警告甚至连接失败;serverTimezone=Asia/Shanghai是解决时区不一致导致的时间差八小时问题;characterEncoding=utf8保证中文不乱码。这三个参数是 MySQL 8 连接里最容易翻车的地方,少一个都可能出玄学问题。
3. 从登录到图片增删改查:核心模块的代码落地
3.1 用户登录与权限拦截
登录模块的逻辑不复杂:用户提交用户名和密码,后台查 Admin 表比对,匹配成功就把用户信息写进 Session,后续页面从 Session 里取登录状态做拦截。文档里提到了「检查密码正确」和「错误信息」两个分支,对应到代码就是:
// LoginServlet.java 核心逻辑 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String username = req.getParameter("username"); String password = req.getParameter("password"); AdminDao dao = new AdminDao(); Admin admin = dao.findByUsername(username); if (admin == null) { req.setAttribute("msg", "用户名不存在"); req.getRequestDispatcher("/login.jsp").forward(req, resp); return; } if (!admin.getPassword().equals(password)) { req.setAttribute("msg", "密码错误"); req.getRequestDispatcher("/login.jsp").forward(req, resp); return; } // 登录成功,写入 Session req.getSession().setAttribute("admin", admin); dao.updateLoginTimes(admin.getId()); resp.sendRedirect(req.getContextPath() + "/admin/picList"); }这里有两个细节值得注意。第一,密码比对用的是明文equals,原文档没有提加密,但实际做的时候至少要用 MD5 或 BCrypt 存哈希,不然数据库被拖库就是裸奔。第二,登录成功后更新了logintimes字段,这是文档表结构里有的字段,用来记录登录次数,做统计用。权限拦截一般用一个 Filter 实现,检查 Session 里有没有admin对象,没有就重定向到登录页:
public class AuthFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; String uri = request.getRequestURI(); // 放行登录页和登录接口 if (uri.endsWith("login.jsp") || uri.endsWith("login")) { chain.doFilter(req, resp); return; } Object admin = request.getSession().getAttribute("admin"); if (admin == null) { response.sendRedirect(request.getContextPath() + "/login.jsp"); return; } chain.doFilter(req, resp); } }Filter 在web.xml里配置拦截路径为/admin/*,这样所有后台页面都会被拦一道,不用在每个 JSP 里写重复的登录检查。
3.2 图片上传与信息录入
图片录入是这个系统里最容易出问题的环节。文档里写的是「用户选择本地图片,填写图片相关信息,图片路径存入数据库」。这里涉及两个动作:文件上传到服务器磁盘,路径写入picinfo表的url字段。JSP 时代没有 Spring 的MultipartFile,一般用 Apache Commons FileUpload 或者 Servlet 3.0 自带的Part接口。用Part接口的写法:
@MultipartConfig(maxFileSize = 5 * 1024 * 1024, maxRequestSize = 10 * 1024 * 1024) public class PicAddServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 普通字段 String title = req.getParameter("title"); String type = req.getParameter("type"); String place = req.getParameter("place"); // 文件字段 Part filePart = req.getPart("picFile"); String fileName = System.currentTimeMillis() + "_" + filePart.getSubmittedFileName(); String saveDir = getServletContext().getRealPath("/upload"); File dir = new File(saveDir); if (!dir.exists()) dir.mkdirs(); filePart.write(saveDir + File.separator + fileName); // 入库 Pic pic = new Pic(); pic.setTitel(title); pic.setType(type); pic.setPlace(place); int pid = new PicDao().insert(pic); Picinfo info = new Picinfo(); info.setPid(pid); info.setTitle(title); info.setUrl("upload/" + fileName); new PicinfoDao().insert(info); resp.sendRedirect("picList"); } }@MultipartConfig注解必须加,否则req.getPart()会返回 null。maxFileSize限制单文件 5MB,maxRequestSize限制整个请求 10MB,超了会抛IllegalStateException。文件名用System.currentTimeMillis()加前缀是为了避免同名文件覆盖,原文档没提这一点,但实际部署时用户上传两张都叫photo.jpg的图,不加前缀后一张就把前一张覆盖了。getRealPath("/upload")拿到的是 Web 应用在服务器上的实际部署路径,注意这个路径在 Tomcat 重启或重新部署后可能会被清掉,生产环境应该存到应用外部目录。
3.3 多条件查询与分页
文档里查询功能的要求是「用户填写图片相关信息,系统显示符合条件的所有图片,没有则弹出提示」。实际做的时候不可能把所有结果一次性铺在页面上,得分页。查询条件一般包括标题模糊匹配、类别精确匹配、地点模糊匹配。DAO 层的 SQL 拼接:
public List<Pic> search(String title, String type, String place, int page, int pageSize) { StringBuilder sql = new StringBuilder( "SELECT * FROM pic WHERE 1=1 "); List<Object> params = new ArrayList<>(); if (title != null && !title.isEmpty()) { sql.append("AND titel LIKE ? "); params.add("%" + title + "%"); } if (type != null && !type.isEmpty()) { sql.append("AND type = ? "); params.add(type); } if (place != null && !place.isEmpty()) { sql.append("AND place LIKE ? "); params.add("%" + place + "%"); } sql.append("ORDER BY id DESC LIMIT ?, ?"); params.add((page - 1) * pageSize); params.add(pageSize); // 执行查询,映射结果集... }WHERE 1=1是个常用技巧,后面每个条件都用AND开头,不用判断是不是第一个条件。LIKE '%关键词%'做模糊匹配,但注意这种写法用不上索引,数据量大了会慢,毕设级别几百条数据无所谓。分页用LIMIT offset, size,offset = (page - 1) * pageSize。前端 JSP 页面用 JSTL 遍历结果:
<c:forEach items="${picList}" var="pic"> <tr> <td>${pic.titel}</td> <td>${pic.type}</td> <td>${pic.place}</td> <td> <a href="picEdit?id=${pic.id}">修改</a> <a href="picDelete?id=${pic.id}" onclick="return confirm('确定删除?')">删除</a> </td> </tr> </c:forEach>修改和删除按钮都带上了id参数,删除前用confirm弹窗做二次确认,对应文档里「提示用户是否确定删除」的流程。
4. 避坑与排查:这套 JSP 项目最容易翻车的五个地方
4.1 中文乱码:现象是页面显示问号或方块
原因通常出在三个环节:JSP 页面编码、请求参数编码、数据库连接编码。JSP 页面头部要写<%@ page contentType="text/html;charset=UTF-8" %>,请求参数在 Servlet 里要调req.setCharacterEncoding("UTF-8"),数据库 URL 要带characterEncoding=utf8。三个地方缺一个都可能乱码。最稳妥的做法是在 Filter 里统一设置request.setCharacterEncoding("UTF-8")和response.setContentType("text/html;charset=UTF-8"),所有请求走一遍 Filter,不用在每个 Servlet 里重复写。
4.2 MySQL 8 连接报错:Public Key Retrieval is not allowed
这是 MySQL 8 默认认证插件caching_sha2_password导致的,现象是 JDBC 连接时抛SQLException。解决办法是在连接 URL 里加allowPublicKeyRetrieval=true,或者把用户的认证插件改回mysql_native_password:
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'yourpassword'; FLUSH PRIVILEGES;改完之后重启 MySQL 服务,再用 JDBC 连就正常了。这个坑在 MySQL 5.7 升 8.0 的时候几乎人人都会踩一次。
4.3 文件上传后访问 404:路径对了但文件找不到
现象是上传成功、数据库里也有记录,但页面上图片显示不出来。原因通常是getRealPath("/upload")拿到的路径和 Tomcat 实际部署路径不一致,或者 IDEA 里配置的 Artifact 没有把 upload 目录打进去。排查方法是把saveDir打印出来,去文件系统里确认文件到底存哪了。另一个常见原因是 JSP 里引用的图片路径没加项目上下文,应该用${pageContext.request.contextPath}/upload/xxx.jpg而不是直接写/upload/xxx.jpg。
4.4 删除主记录后文件信息变孤儿数据
文档里pic和picinfo是两张表,如果只删了pic表的记录,picinfo里对应的行还在,查询时就会出问题。解决办法是在建表时给picinfo.pid加外键并设置ON DELETE CASCADE,或者在手写删除逻辑时先删picinfo再删pic。我一般会在 DAO 的删除方法里用事务包起来:
public void delete(int picId) { Connection conn = null; try { conn = DBUtil.getConn(); conn.setAutoCommit(false); // 先删文件信息 String sql1 = "DELETE FROM picinfo WHERE pid = ?"; // 再删主记录 String sql2 = "DELETE FROM pic WHERE id = ?"; // 执行两条 SQL... conn.commit(); } catch (SQLException e) { conn.rollback(); throw new RuntimeException(e); } finally { DBUtil.close(conn, null, null); } }4.5 Tomcat 10 部署报 ClassNotFound:javax 还是 jakarta
如果你用的是 Tomcat 10 及以上版本,所有javax.servlet.*的 import 都要改成jakarta.servlet.*,包括HttpServlet、Filter、ServletRequest等。现象是启动时报java.lang.ClassNotFoundException: javax.servlet.http.HttpServlet。要么把 Tomcat 降到 9,要么全局替换包名。这个坑在从网上抄代码时特别容易遇到,因为大部分 JSP 教程还是基于 Tomcat 8/9 写的。
5. 进阶技巧:把这份文档变成能跑的项目
文档给的是设计层面的东西,要变成能跑的项目,还需要补几个关键动作。第一步是搭环境:装 JDK 8 或 11、Tomcat 9、MySQL 8,IDEA 里新建 Java Enterprise 项目,勾选 Web Application。第二步是把文档里的表结构转成建表 SQL 执行一遍,注意字段名拼写修正和字符集设置。第三步是按「工具类 → 实体类 → DAO → Servlet → JSP」的顺序写代码,每写完一层就测一层,不要全写完再调,不然出问题定位起来很痛苦。
一个具体的验证方法是:先写一个最简单的TestServlet,在里面调DBUtil.getConn()打印连接对象,部署后访问这个 Servlet,如果控制台输出连接成功,说明驱动、URL、账号密码都没问题。然后再逐步加 DAO 查询、JSP 展示。这样每一步都有反馈,不会出现「全写完了但不知道哪里错了」的情况。
另外,文档里提到的System表存的是站点名称、关键词、描述、版权等信息,这个表在实际项目里通常只在首页展示时查一次,可以加个缓存,不用每次请求都查库。图片列表页如果数据量大,可以在pic表的type和place字段上建索引,查询会快很多。
注意:原文档的测试章节只写了「程序调试」和「测试步骤」的理论内容,没有给具体的测试用例。实际做的时候至少要把登录(正确密码、错误密码、空用户名)、增删改查(正常数据、边界数据、空数据)这几条路径手动走一遍,把结果记下来,毕设答辩时老师大概率会问「你测了什么」。
从那以后我每次拿到这种毕设文档,都会先把数据库表结构单独拎出来建一遍,跑通了再写 Java 代码,不然写到一半发现字段对不上,返工的成本比一开始多花十分钟建表高得多。希望帮到你。
本文还有配套的精品资源,点击获取