ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于Java的咖啡厅管理系统毕设源码拆解:从JSP到JDBC的实战

基于Java的咖啡厅管理系统毕设源码拆解:从JSP到JDBC的实战 简介面向计算机专业毕业设计及课程设计者这份《基于 Java 的咖啡厅管理系统设计与实现》文档完整呈现了一个基于 JSP MySQL 的典型 Web 管理系统方案。文档以吉首大学本科生毕业设计标准结构撰写涵盖项目背景、研究意义、相关技术介绍JSP、MySQL、可行性分析与非功能需求并细致设计了系统总体结构、业务流程和处理流程核心部分围绕管理员、导购员、前台三类角色展开覆盖员工管理、用户管理、新闻管理、商品管理、订单管理、评价管理、在线留言、商品分类与排行、商品购买等功能数据库设计部分给出了 E-R 图和逻辑表结构最后包含功能测试、可用性测试及系统优缺点分析是一份可参照的完整论文模板。资源包包含 1 个 docx 文件大小约 1.78MB目录结构清晰便于按章查阅。目前已有 177 人学习可快速掌握咖啡厅管理系统的需求分析、数据库建模与 Java Web 实现思路。1. 基于 Java 的咖啡厅管理系统这份毕设源码到底能拆出什么拿到这份《基于 Java 的咖啡厅管理系统设计与实现》毕设文档时我第一反应是又一个 JSP 老项目。但完整捋了一遍系统功能结构图和数据表设计之后我改变了判断——它虽然技术栈偏老但角色权限划分管理员、导购员、前台用户三角色和业务闭环商品→订单→评价→留言是完整的正好适合拿来練手 Java Web 的 MVC 分层、Session 权限控制、JDBC 数据库操作这几项基本功。文档里最有价值的部分不是代码而是第 4 章的 7 张数据表字段定义和登录、增删改的处理流程设计这两块直接决定了你复刻这个系统时工作量能砍掉一半。适合谁准备做 Java Web 课程设计、正在写毕设开题报告、或者想从 Servlet/JSP 过渡到 Spring Boot 的初学者都能从这份资源里找到可以直接抄的作业。2. 系统架构与角色权限先把三个入口的边界划清楚2.1 三层角色权限模型这份毕设的核心设计思路是「一个后台三种身份」。系统功能结构图里明确写了三个入口系统管理员、导购员也就是门店营业员、前台也就是 C 端用户三者登录后进入完全不同的功能菜单。这是典型的 RBAC 简化版虽然没用到 Spring Security 这类框架但用 Session 加拦截器也能实现。我一般会先画一张权限矩阵再动手写代码这里直接整理出来功能模块管理员导购员前台用户密码管理✅✅❌员工管理✅❌❌用户管理✅❌❌新闻/公告管理✅❌❌类别管理✅❌❌商品管理✅✅❌订单管理✅✅❌仅查看自己订单评价管理✅✅✅发表评价统计管理✅✅❌留言管理✅❌✅发表留言购买商品❌❌✅这份矩阵直接决定了数据库的外键关系和页面跳转逻辑建议在动手前先对着它把每个 JSP 页面需要的 Session 校验条件列出来免得写到后面角色串权限。2.2 Session 权限控制的落地写法JSP 项目最常见的权限控制方式就是登录时往 Session 里放一个用户类型标记然后在每个需要鉴权的页面头部做判断。这里用一个简单的 Filter 来实现比在每个 JSP 里重复写 if 判断要干净得多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; HttpSession session request.getSession(); // 从 Session 中取出登录时存入的用户角色 String role (String) session.getAttribute(role); // 获取当前请求的访问路径 String path request.getServletPath(); // 放行登录页面、静态资源和登录处理 Servlet if (path.endsWith(login.jsp) || path.endsWith(.css) || path.endsWith(.js) || path.contains(LoginServlet)) { chain.doFilter(req, resp); return; } // 未登录用户统一拦截回登录页 if (role null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } // 管理员访问导购员或用户模块时需要拦截 if (path.startsWith(/admin/) !admin.equals(role)) { response.sendRedirect(request.getContextPath() /error.jsp); return; } chain.doFilter(req, resp); } }这段代码的逻辑是先用getServletPath()拿到用户请求的资源路径然后对登录页、静态资源和登录 Servlet 放行剩下的请求全部检查 Session 里有没有role这个属性。没有就是未登录直接重定向回登录页有的话再按路径前缀区分管理员和普通用户。这里要特别注意一个点role的取值建议直接用字符串常量比如admin、seller、user不要用数字 1、2、3 代替。毕设答辩时老师问起来「为什么用字符串不用数字」你可以答「数字可读性差而且后期加角色类型时容易把判断逻辑改乱」这比答「方便」要有说服力得多。2.3 登录流程的校验顺序不能乱文档第 4.3 节把登录流程画得很细先判断用户名是否为空 → 再判断密码是否为空 → 最后检测账号密码是否正确。这个顺序看起来简单但在实际的 LoginServlet 里很多人会写成先查数据库再判断空值导致用户没输入就抛 NullPointerException。正确的处理顺序是前端 JS 先做非空校验提示友好后端 Servlet 再兜底做一次非空判断。后端拿到用户名和密码后调用 DAO 层的findUserByAccount()方法返回 null 说明账号不存在返回对象则用password.equals(user.getPassword())比对密码。密码一致才算登录成功设置 Session 后按角色重定向到对应主页。文档里还画了数据增加、修改、删除三个处理流程核心共同点是「前端校验 → 后端校验 → 操作数据库 → 失败回滚并提示」。这块在后面的代码实现里会反复用到先有个印象就行。3. 数据库设计与 JDBC 连接7 张表就能撑起整个业务3.1 建表语句与字段说明文档第 4.4 节给出了 7 张核心表的字段定义这是整个项目最值钱的部分。我把表格转换成可以直接执行的 MySQL 建表语句注意商品信息表里的photo字段原文写的varchar(2550)明显是笔误我改成了 255-- 用户信息表 CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, u_name VARCHAR(30) NOT NULL, u_password VARCHAR(20) NOT NULL, email VARCHAR(30), u_photo VARCHAR(2000), zpassword VARCHAR(255), d_id INT, number VARCHAR(255) ); -- 商品信息表 CREATE TABLE t_goods ( id INT PRIMARY KEY AUTO_INCREMENT, m_id VARCHAR(255), sname VARCHAR(255) NOT NULL COMMENT 商品名称, price VARCHAR(255) COMMENT 单价, sx VARCHAR(255) COMMENT 属性/规格, s_id INT COMMENT 所属类别, state VARCHAR(255) COMMENT 状态, jstate VARCHAR(255) COMMENT 推荐状态, number VARCHAR(20) COMMENT 库存, miaoshu VARCHAR(255) COMMENT 描述, color VARCHAR(255), photo VARCHAR(255) ); -- 订单信息表 CREATE TABLE t_order ( id INT PRIMARY KEY AUTO_INCREMENT, s_id INT COMMENT 商品ID, u_id INT COMMENT 用户ID, number VARCHAR(20) COMMENT 订单编号, t_price VARCHAR(20) COMMENT 总价, state VARCHAR(255) COMMENT 订单状态, date DATE ); -- 类别信息表 CREATE TABLE t_category ( id INT PRIMARY KEY AUTO_INCREMENT, mname VARCHAR(255) COMMENT 类别名称 ); -- 留言信息表 CREATE TABLE t_message ( id INT PRIMARY KEY AUTO_INCREMENT, account VARCHAR(255), title VARCHAR(255), content VARCHAR(255), date DATE, u_id VARCHAR(255) ); -- 评价信息表 CREATE TABLE t_comment ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(255) COMMENT 用户昵称, date DATETIME, content VARCHAR(255), s_id VARCHAR(255) COMMENT 商品ID, u_id INT ); -- 公告信息表 CREATE TABLE t_notice ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(255), content VARCHAR(255), date DATE, fname VARCHAR(255) );建表时我把每个字段的中文注释都写进去了这个习惯在毕设答辩时很加分。原文档里number、state、date这些字段名直接用了 MySQL 的保留字或常用函数名虽然在本项目能跑通但我建议改成order_number、order_state、order_date这种带前缀的命名方式避免以后做联表查询时出现歧义。3.2 JDBC 工具类的封装整个项目的数据访问层不需要引入 MyBatis 这类框架用原生的 JDBC 就能搞定。关键在于把重复代码抽到一个工具类里否则每张表的 DAO 都要写一遍获取连接、关闭资源的代码代码量会翻倍public class DBUtil { private static final String DRIVER com.mysql.jdbc.Driver; private static final String URL jdbc:mysql://localhost:3306/coffee?useUnicodetruecharacterEncodingutf-8; private static final String USERNAME root; private static final String PASSWORD 123456; // 使用 ThreadLocal 保证同一线程内获取的是同一个连接 private static ThreadLocalConnection tl new ThreadLocal(); static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { Connection conn tl.get(); if (conn null || conn.isClosed()) { conn DriverManager.getConnection(URL, USERNAME, PASSWORD); tl.set(conn); } return conn; } public static void closeAll(Connection conn, Statement stmt, ResultSet rs) { try { if (rs ! null) rs.close(); if (stmt ! null) stmt.close(); if (conn ! null) { conn.close(); tl.remove(); // 线程结束前移除 ThreadLocal避免内存泄漏 } } catch (SQLException e) { e.printStackTrace(); } } }这段工具类的核心是ThreadLocal。每个 HTTP 请求在 Tomcat 里是由一个独立线程处理的线程内多次调用getConnection()拿到的是同一个连接这样在一个请求的处理链路里事务不会因为连接不一致而失效。关闭连接时顺手tl.remove()是一个很容易被忽略的细节不写的话高并发场景下 Tomcat 线程池复用线程时会拿到一个已经被关闭的旧连接。URL 里的characterEncodingutf-8必须加上否则插入中文数据到 MySQL 后查出来全是问号。这是 JSP 项目里出现频率最高的中文乱码坑之一。3.3 DAO 层的增删改查模板以商品管理为例导购员模块需要实现商品的增删改查。我建议每张表对应一个 DAO 接口和实现类接口里定义add()、delete()、update()、findAll()、findById()五个基本方法实现类里用 JDBC 的PreparedStatement完成具体操作public class GoodsDaoImpl implements GoodsDao { // 添加商品 public int add(Goods goods) throws SQLException { String sql INSERT INTO t_goods (m_id, sname, price, sx, s_id, state, jstate, number, miaoshu, color, photo) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ps.setString(1, goods.getMId()); ps.setString(2, goods.getSname()); ps.setString(3, goods.getPrice()); // ... 其他字段设置 int rows ps.executeUpdate(); DBUtil.closeAll(conn, ps, null); return rows; } // 根据ID删除商品 public int delete(int id) throws SQLException { String sql DELETE FROM t_goods WHERE id ?; Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ps.setInt(1, id); int rows ps.executeUpdate(); DBUtil.closeAll(conn, ps, null); return rows; } }这段代码里PreparedStatement的作用是预编译 SQL能防止 SQL 注入攻击。毕设答辩时老师必问「你的系统怎么防 SQL 注入」回答「所有 SQL 都通过 PreparedStatement 的占位符传参用户输入只作为参数传递不直接拼接 SQL 字符串」就能拿分。注意到add()方法返回值是int这个返回值表示受影响的行数。Service 层拿到这个值后判断大于 0 就是操作成功否则就是失败这是 Java Web 项目里最朴素也最实用的结果判断方式。4. 核心功能模块实现从登录到下单的完整链路4.1 登录 Servlet 与 MD5 加密登录功能是三个角色共用的入口逻辑写在LoginServlet的doPost()方法里。流程是从请求中取username和password参数 → 判断非空 → 调用UserDao.findByUsername()查库 → 比对密码 → 成功则写 Session → 按角色重定向。密码存储这里要强调一下文档里用户表有u_password和zpassword两个字段我理解前者是登录密码后者是支付密码或确认密码。原项目的密码大概率是明文存储的这在毕设里勉强能交代但如果你想让项目有点亮点建议在注册时用 MD5 加盐处理// 注册时对密码做 MD5 加密 public static String md5(String input) { try { MessageDigest md MessageDigest.getInstance(MD5); byte[] digest md.digest(input.getBytes(UTF-8)); StringBuilder sb new StringBuilder(); for (byte b : digest) { sb.append(String.format(%02x, b)); } return sb.toString(); } catch (Exception e) { throw new RuntimeException(MD5加密失败, e); } } // 登录校验时将明文密码加密后与库中密文比对 String encrypted md5(password salt); if (encrypted.equals(user.getPassword())) { // 登录成功 }MD5 的作用是把明文密码变成固定长度的十六进制串即使数据库泄露攻击者拿到的也是密文而不是明文。加盐salt是为了防止彩虹表碰撞破解盐值可以用用户名或者一个固定字符串。这里我用了password salt的方式实际项目中盐值应该是每个用户随机生成并单独存储的。4.2 购物车与下单流程前台用户的核心操作流是「浏览商品 → 加入购物车 → 提交订单 → 生成评价」。购物车在 JSP 项目里最常见的实现方式是用 Session 存一个HashMapInteger, Integerkey 是商品 IDvalue 是购买数量// 加入购物车 public String addToCart(HttpServletRequest request) { HttpSession session request.getSession(); MapInteger, Integer cart (MapInteger, Integer) session.getAttribute(cart); if (cart null) { cart new HashMap(); session.setAttribute(cart, cart); } int goodsId Integer.parseInt(request.getParameter(goodsId)); Integer count cart.get(goodsId); if (count null) { cart.put(goodsId, 1); } else { cart.put(goodsId, count 1); } return cart.jsp; // 重定向到购物车页面 } // 提交订单 public boolean submitOrder(HttpServletRequest request) { HttpSession session request.getSession(); MapInteger, Integer cart (MapInteger, Integer) session.getAttribute(cart); User user (User) session.getAttribute(user); // 遍历购物车逐条写入订单表 for (Map.EntryInteger, Integer entry : cart.entrySet()) { int goodsId entry.getKey(); int count entry.getValue(); // 查询商品价格计算小计 Goods goods goodsDao.findById(goodsId); double total Double.parseDouble(goods.getPrice()) * count; // 生成订单记录 Order order new Order(); order.setSId(goodsId); order.setUId(user.getId()); order.setNumber(ORD System.currentTimeMillis()); order.setTPrice(String.valueOf(total)); order.setState(待付款); order.setDate(new Date()); orderDao.add(order); // 扣减库存 goodsDao.updateStock(goodsId, goods.getNumber() - count); } // 下单成功后清空购物车 session.removeAttribute(cart); return true; }这段逻辑里有个关键细节System.currentTimeMillis()生成订单号在并发量小的时候够用但如果同一毫秒内有两个订单就可能撞号。毕设场景不管这个但如果想让代码更严谨可以用UUID.randomUUID().toString().replace(-, )生成更唯一的订单号。下单时的库存扣减应该放到一个事务里先查库存是否充足 → 再扣库存 → 写订单表 → 全部成功才提交事务任何一个环节失败就回滚。但 JSP 项目里很多人会忽略事务控制导致库存扣了订单没生成或者反过来。这个坑在第 5 章单独说。4.3 多条件查询与排行榜前台首页需要展示商品分类、特价商品和销售排行榜。销售排行榜的实现思路是统计每个商品的订单数量按订单数倒序排列。SQL 可以这样写SELECT g.id, g.sname, g.price, g.photo, COUNT(o.id) AS sale_count FROM t_goods g LEFT JOIN t_order o ON g.id o.s_id GROUP BY g.id, g.sname, g.price, g.photo ORDER BY sale_count DESC LIMIT 10;这条 SQL 的关键在LEFT JOIN它保证没有卖出去的商品也会出现在结果里销量为 0而不是被内连接过滤掉。GROUP BY后面必须跟查询的所有非聚合列否则 MySQL 在开启ONLY_FULL_GROUP_BY模式时会直接报错。多条件查询则是把商品名称、类别、价格区间这些参数拼成一个动态 SQL。这里有个常见的坑用字符串拼接 SQL 时每个条件前面要加AND但第一个条件前面不应该有AND。我一般用ListString conditions收集条件子句最后用WHERE加String.join( AND , conditions)拼出来避免出现WHERE AND name ?这种语法错误。5. 避坑指南JSP 项目最常见的 5 个翻车现场5.1 中文乱码页面、请求、数据库三层都要设置 UTF-8现象JSP 页面显示正常但插入到 MySQL 里的中文变成??或者 Servlet 从请求里拿到的中文参数是乱码。原因MySQL 连接的 URL 没有指定字符集或者请求和响应的编码设置不一致。解决三层各做一次设置。JSP 页面顶部加% page contentTypetext/html;charsetUTF-8 %Servlet 里在doPost()第一行写request.setCharacterEncoding(UTF-8)JDBC 连接串加useUnicodetruecharacterEncodingutf-8。注意doGet()的乱码问题用request.setCharacterEncoding()解决不了需要手动new String(param.getBytes(ISO-8859-1), UTF-8)转码所以写表单提交时尽量统一用doPost()。5.2 按了删除按钮提示成功但数据还在现象点击删除订单页面提示删除成功刷新后数据还在。原因删除 Servlet 执行了但事务没有提交或者删除的是子表数据主表外键约束阻止了删除但异常被吞掉了。解决检查 DAO 的delete()方法里是否调用了conn.commit()。JDBC 默认是自动提交的但如果前面代码执行过conn.setAutoCommit(false)当前线程后续的所有操作都不会自动提交。我习惯在DBUtil的getConnection()里统一加上conn.setAutoCommit(true)确保每次拿到的连接都是干净的。5.3 Session 里的用户对象丢失页面反复跳登录现象登录后正常操作几分钟点击某个页面又被踢回登录页。原因Tomcat 默认 Session 超时时间是 30 分钟但如果项目部署时改了配置文件导致超时太小另一个可能是用户对象没有正确存入 Session或者密码修改后 Session 里的旧对象被清掉了。解决登录成功后用session.setAttribute(user, user)存完整对象不要只存用户名。页面校验时判断session.getAttribute(user) ! null而不是判断某个 JSP 页面的标记。如果需要延长在线时间在web.xml里配置session-configsession-timeout60/session-timeout/session-config单位是分钟。5.4 上传商品图片后不显示现象图片上传到服务器了但页面img标签显示裂图。原因图片访问路径不对。Tomcat 默认部署时上传到项目目录下的图片在 IDE 里看不到因为 IDE 编译后的classes目录和源文件的web目录是两份。解决图片存到WebContent/upload/目录下页面用相对路径如upload/xxx.jpg来引用不要用磁盘绝对路径。毕设级系统用这种本地存储方式够了如果图片多建议把上传文件路径抽成一个常量类统一管理。5.5 通过 URL 直接访问后台 JSP 页面现象未登录用户直接在浏览器地址栏输/admin/goodsManage.jsp成功进入管理页面。原因JSP 页面本身没有权限校验拦截器只拦了 Servlet 没拦 JSP。Tomcat 对 JSP 文件的访问不经过你写的 Servlet 映射。解决把 JSP 页面放到WEB-INF目录下Tomcat 默认禁止外部直接访问该目录下的 JSP只能通过 Servlet 内部forward或include来跳转。或者在每个后台 JSP 页面顶部用 Java 代码块判断 Session 角色不符合就response.sendRedirect到登录页。应用了第 2.2 章的 Filter 方案时注意 Filter 的url-pattern要配成/*而不是/servlet/*这样才能拦住 JSP 请求。6. 从毕设到可部署三个让项目更完整的改造方向6.1 用连接池替换原生 JDBC 的 DriverManager原生 JDBC 每次操作都新建连接在课程设计里感觉不到问题但部署到服务器后高并发场景下频繁创建和销毁连接会让 Tomcat 线程阻塞。用Druid连接池替换后连接是复用而不是重建的响应速度会明显改善。改造方式很简单DBUtil里不再用DriverManager.getConnection()改成从DruidDataSource获取public class DBUtil { private static DruidDataSource dataSource new DruidDataSource(); static { dataSource.setDriverClassName(com.mysql.jdbc.Driver); dataSource.setUrl(jdbc:mysql://localhost:3306/coffee?useUnicodetruecharacterEncodingutf-8); dataSource.setUsername(root); dataSource.setPassword(123456); dataSource.setInitialSize(5); // 初始连接数 dataSource.setMaxActive(20); // 最大活跃连接数 dataSource.setMinIdle(5); // 最小空闲连接数 dataSource.setMaxWait(60000); // 获取连接最大等待时间毫秒 } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } public static void closeAll(Connection conn, Statement stmt, ResultSet rs) { // 注意连接池的连接关闭不是真的断开而是归还给池子 // 所以这里只需要关闭 stmt 和 rsconn 调用 close() 表示归还 try { if (rs ! null) rs.close(); if (stmt ! null) stmt.close(); if (conn ! null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }连接池的setMaxWait(60000)意思是应用向连接池要连接时最多等 60 秒超时抛异常。这个参数要结合数据库连接超时时间一起调如果设大了数据库已经不可用前端还在傻等。close()在连接池场景下的语义是「归还连接」而不是「销毁连接」这也是新手最容易误解的地方。6.2 订单状态机把字符串状态改成可追踪的流程原项目的订单表state字段直接存 待付款、已付款、已发货 这种中文能跑通但后期想统计「转化率」「未支付订单占比」时会发现没法筛。我改造惯用的做法是存数字状态码页面展示时再映射成中文状态码含义可执行操作0待付款用户取消、用户付款1已付款导购员发货2已发货用户确认收货3已完成用户评价4已取消无这样在导购员模块的订单管理页面下拉框筛选只要写WHERE state 1而不是WHERE state 已付款。状态码是数字还能直接做统计报表比如「今日新增订单数 COUNT(*) WHERE state IN (0, 1, 2)」。改的时候注意订单表的t_price字段原文是varchar(20)我强烈建议改成DECIMAL(10,2)。用字符串存金额排序时会出现9.9 10.5这种反直觉的比较结果而且无法做 SUM 聚合。同理商品表的price也应该改成DECIMAL(10,2)。这个改动在答辩时说「金额字段应该用定点数而不是浮点数或字符串避免精度丢失」是一个很好的加分项。6.3 验证系统是否正常三个必测路径项目做完之后不要只点一遍页面就交差。我每次部署完都会强制走这三条路径第一条是全链路下单测试前台用户注册 → 浏览商品 → 加入购物车 → 提交订单 → 管理员后台看到订单 → 导购员发货 → 用户确认收货 → 用户发表评价 → 管理员看到评价。这条链路任何一个环节断了都说明模块间的数据传递有问题。第二条是权限越界测试分别用三个角色登录手动在地址栏输入不属于自己角色的页面 URL看是否被拦截。如果没拦截说明 Filter 的拦截规则有漏洞需要用第 5.5 章的方式补上。第三条是数据库备份恢复测试用mysqldump -u root -p coffee backup.sql备份数据库然后删掉几张表的数据再用mysql -u root -p coffee backup.sql恢复。这条能验证系统在数据丢失场景下的自救能力毕设文档里第 3.1 节提到的「数据库必须有一个备份和恢复功能」在演示时用得上。如果这个项目你打算部署到服务器给同学实际用还有两个不太起眼但很实际的点一是把 Tomcat 的端口从默认的 8080 改成 80这样访问时不用输端口号二是把 Tomcat 的maxThreads根据服务器内存调大一点默认 200不然同学集中访问时页面会卡住。从那以后我每次拿这种课程设计和毕设项目练手时都强制先访问一遍每张表的 CRUD 页面再试一次越权访问最后跑一遍备份恢复这三步走完基本不会带着明显的坑去提交答辩——这已经变成了我的习惯动作希望帮到你。本文还有配套的精品资源点击获取
返回列表