简介基于Java的咖啡厅管理系统毕业设计文档面向计算机相关专业毕业生及需要快速搭建课程设计框架的开发者。文档以JSPMySQL为技术核心完整呈现咖啡厅业务管理方案覆盖系统管理员、导购员、前台三类角色涉及员工管理、用户管理、新闻管理、商品管理、订单管理、评价管理及在线留言等模块并给出了项目背景、可行性分析、非功能需求分析等完整论证过程。包内共1个docx文件大小1.78MB为吉首大学本科生毕业设计全文结构包含摘要、前言、绪论、相关技术介绍、系统需求分析及可行性分析、总体设计、详细设计与实现、系统测试、总结等部分章节齐全便于直接对照查阅。已有176人学习该资源。文档除功能描述外还包含数据库E-R图、逻辑表结构设计、业务流程分析、处理流程设计以及登录模块、管理员模块、导购员模块、前台模块的具体实现说明系统测试部分涵盖功能测试、可用性测试与结果分析并总结了系统的优缺点可有效支撑论文撰写、系统设计与答辩准备。1. 咖啡厅管理系统JSPMySQL 实现的三角色后台能答辩也能跑通大学里“基于 Java 的咖啡厅管理系统”这类毕设题目每年都在出多数版本用的还是 JSPServletMySQL 这一套。原因很简单页面开发门槛低、数据库操作直观、要求的技术深度刚好到“会用 JDBC 写增删改查”这一层不会踩到框架配置的坑。这套系统把管理员、导购员、用户三个角色拆开管理员管员工、用户、新闻、类别、商品、订单、评价、统计、留言导购员管商品、订单、统计、评价用户则只需要看商品、下单、留言。如果你正在找 Java 课程设计案例源码或者被分到这一类题目不想从零画原型可以直接拿它当底稿改。2. 系统技术栈拆解为什么 JSPServletMySQL 这个组合做毕设最稳2.1 JSP 与 Servlet 的分工一次请求是怎么被处理的JSP 页面最终也会编译成 Servlet。Tomcat 收到一个 .jsp 请求时先找对应的 JSP 文件第一次访问时把它翻译成 .java 源文件再编译成 .class之后每次访问直接执行这个 .class。JSP 文件里 HTML 静态部分原样输出% %里 Java 代码执行后把结果拼到响应里。这样的好处是页面模板和数据渲染在同一个文件里写起来快坏处是业务逻辑和页面混在一起代码长了以后不好维护。所以这套系统实际做的时候会把请求分发交给 Servlet 控制JSP 只负责显示。原项目里的登录流程就是这样前端表单提交到 ServletServlet 调 DAO 查数据库查完把结果放到 session再 forward 到对应页面。下面这段是原项目里典型的 web.xml 注册方式servlet servlet-nameloginServlet/servlet-name servlet-classcom.coffee.servlet.LoginServlet/servlet-class /servlet servlet-mapping servlet-nameloginServlet/servlet-name url-pattern/login/url-pattern /servlet-mapping这个配置把 LoginServlet 绑定到 /login 路径。表单 action 写的是 login提交后 Tomcat 根据 url-pattern 找到 servlet-class 里对应的类调用它的 doGet 或 doPost 方法。如果你的 Tomcat 版本到了 7 以上也可以在 Servlet 类上直接写WebServlet(/login)效果一样少写一段 XML。老版本 Tomcat 6 不带注解支持就要用上面的方式。2.2 MySQL 与 JDBC连接参数决定一半的稳定性数据库这块原项目选的是 MySQL。对这个规模的系统MySQL 够用而且 JDBC 驱动和连接方式非常成熟。JDBC 连接的核心是四个参数driver、url、username、password。常见做法是写一个 DBUtil 工具类把所有连接参数从代码里拆到 db.properties 文件drivercom.mysql.jdbc.Driver urljdbc:mysql://localhost:3306/coffee?useUnicodetruecharacterEncodingutf8useSSLfalse usernameroot password123456然后在代码里通过 ResourceBundle 读取public class DBUtil { private static String url; private static String username; private static String password; static { try { ResourceBundle bundle ResourceBundle.getBundle(db); url bundle.getString(url); username bundle.getString(username); password bundle.getString(password); // 加载驱动只需要一次 Class.forName(bundle.getString(driver)); } catch (Exception e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, username, password); } }这里的 url 一定要带上 useUnicodetrue 和 characterEncodingutf8。如果不带插入中文时字段值会变成问号或乱码。useSSLfalse 是因为本地开发一般不开 SSLMySQL 5.7 之后的驱动默认会对 SSL 告警所以显式关掉。username 和 password 在本地开发用的是 root 和空密码或 123456但部署到服务器时密码一定要改掉这个问题在第 5 章还会再踩一次。2.3 三层结构页面、业务、数据访问分开写JSP 项目结构看起来乱其实拆成三层就好维护表示层JSP 页面 Servlet负责接收请求、渲染页面。业务层Service 类处理订单状态流转、登录校验这类规则。数据访问层DAO 类只负责拼 SQL、执行 SQL、封装结果集。原项目的包结构大致是这样src/ com.coffee.servlet/ LoginServlet.java, ProductServlet.java, OrderServlet.java com.coffee.service/ UserService.java, OrderService.java, ProductService.java com.coffee.dao/ UserDao.java, ProductDao.java, OrderDao.java com.coffee.entity/ User.java, Product.java, Order.java com.coffee.util/ DBUtil.java web/ WEB-INF/web.xml admin/ 管理员页面 guide/ 导购员页面 user/ 用户页面 upload/ 商品图片上传目录这样拆的意义在于导购员和管理员都能操作商品但两个 Servlet 可以复用同一个 ProductService。如果你把 SQL 都写在 JSP 里那么一个页面重复三遍同样的查询后面改表结构就是灾难。所有查询集中在 DAO 层的几个文件里表字段变了只改一两个方法。这里要提醒一下常见误用有人把 DAO 类写成大杂烩一个文件里堆几十个方法看起来省事实际上一张表关联改动时要翻很久。按实体拆 DAO 文件UserDao 只管用户表ProductDao 只管商品表关联查询放到 Service 层组合这是 JSP 时代比较成熟的组织方式后面接 Spring 也顺。3. 需求分析与数据库设计7 张表把管理员、导购员、用户串起来3.1 三个角色的权限边界接口设计要对着功能树走原系统划分成系统管理员、导购员、用户三个角色。管理员管的内容最多有密码管理、员工管理、用户管理、新闻管理、类别管理、商品管理、订单管理、评价管理、统计管理、留言管理十项。导购员只保留密码管理、商品管理、订单管理、统计管理、评价管理五项。用户在网页端完成商品浏览、下单、留言三件事。这个权限边界从数据库层面看并不需要额外建一张权限表而是登录后判断角色字段跳不同的首页、显示不同的菜单。原项目导购员和管理员都能操作商品差别在于导购员看不到员工管理和用户管理这类后台模块。做权限控制时最省事的方案就是在每个 JSP 页面检查 session 里的角色% // 未登录用户直接回登录页 if (session.getAttribute(role) null) { response.sendRedirect(login.jsp); return; } %页面顶部加这种判断未登录用户直接回登录页导购员访问 admin 目录下的页面时也会被拦下来。但要注意这种只做页面级控制直接请求 Servlet 接口还是能绕过所以 Servlet 里也要判断一次同样的 session 状态。员工信息的管理入口在管理员模块里实际存储可以复用 user 表用 d_id 区分员工和普通用户原表结构完全撑得住。3.2 E-R 图到物理表核心表的字段设计与关系原项目的数据库设计部分给出了 7 张表订单表、公告表、类别表、留言表、评价表、商品表、用户表。这里挑三张核心表说一下设计思路。用户表user字段类型说明idint(11)主键自增u_namevarchar(30)登录用户名u_passwordvarchar(20)登录密码emailvarchar(30)邮箱用于找回密码zpasswordvarchar(255)密保答案字段d_idint(11)部门或门店编号numbervarchar(255)手机号或工号商品表product字段类型说明idint(11)主键m_idvarchar(255)类别 ID关联类别表snamevarchar(255)商品名称pricevarchar(255)价格原表用 varchar 存sxvarchar(255)规格或属性描述numbervarchar(20)库存数量miaoshuvarchar(255)商品描述colorvarchar(255)颜色选项photovarchar(2550)商品图片路径订单表orders字段类型说明idint(11)主键s_idint(11)关联商品 IDu_idint(11)关联用户 IDnumbervarchar(20)订单编号t_pricevarchar(20)订单总价statevarchar(255)订单状态datedate下单日期从字段能看出原项目走的是“简单直给”的风格价格和数量都用 varchar 存不用 decimal 和 int。这样省去了类型转换麻烦但查询统计时要 CAST 转换否则排序会出问题。如果你要把它当毕设提交建议把 price 改成 decimal(10,2)number 改成 intt_price 改成 decimal(10,2)这样后面做销售额统计时不用到处写 CAST。这三张表的关系很直观订单表通过 u_id 关联用户表通过 s_id 关联商品表。一个用户可以有多个订单一个商品也会出现在多张订单里所以订单表作为中间表把用户和商品关联起来。评价表也是类似结构既有 s_id 又有 u_id正好对应“买家对某个商品评价”的关系。原系统里 price、t_price、number 都是 varchar这个设计在毕设里很常见好处是前端传什么就能存什么省去类型转换报错坏处是要按价格排序或统计销售额时MySQL 会按字符串排序结果完全不对。改变字段类型要同时改 DAO 里的 resultSet.getString改成 getBigDecimal 或 getInt。如果你只交源码不改答辩提问时能说出这个理由也是一分。3.3 非功能需求参数10000PV 每分钟、浮点两位精度原文的需求分析里写了几条非功能指标虽然带点毕设腔但实现时要认真对待。每分钟 10000PV 的请求量对 JSP 来说属于中等压力只要数据库连接不泄漏、SQL 没有全表扫描基本能扛住。浮点型数据精确到小数点后 2 位对应商品价格和订单总价如果数据库里用 varchar 存价格这个精度就只靠前端输入控制后端要再加一层校验。跨平台要求提到 Linux 和 IE/FF 浏览器。Linux 上部署主要影响路径写法Windows 上的 D:/upload 到 Linux 要改成 /var/www/upload路径拼接不要写死分隔符用 File.separator 或者统一用相对路径。浏览器兼容性方面JSP 页面尽量少用 flex 和 grid 之类的新特性原系统主要是基础表格和表单只要注意不要用 IE 不支持的标签就行。4. 核心模块代码走读登录校验、商品管理、订单提交的落地写法4.1 登录与 Session 权限控制一套代码管三个角色登录模块是所有模块的入口。原项目的登录流程是用户输入用户名密码Servlet 查 user 表把角色写进 session然后按角色跳转。WebServlet(/login) public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 必须在 getParameter 之前设置编码否则中文参数乱码 request.setCharacterEncoding(utf-8); String username request.getParameter(username); String password request.getParameter(password); User user userService.login(username, password); if (user null) { // 登录失败带错误提示回登录页 request.setAttribute(error, 用户名或密码错误); request.getRequestDispatcher(login.jsp).forward(request, response); return; } // 登录成功把角色和用户信息写入 session HttpSession session request.getSession(); session.setAttribute(role, user.getRole()); session.setAttribute(userId, user.getId()); session.setAttribute(username, user.getUName()); // 按三个角色分别跳转不同首页 String role user.getRole(); if (admin.equals(role)) { response.sendRedirect(admin/index.jsp); } else if (guide.equals(role)) { response.sendRedirect(guide/index.jsp); } else { response.sendRedirect(user/index.jsp); } } }这段代码里request.setCharacterEncoding(utf-8) 必须放在 getParameter 之前否则表单提交的中文用户名会乱码。login 方法返回 null 表示用户名密码错误返回对象则把用户信息塞进 session。三个角色跳不同的 index.jsp但菜单是同一个框架页只是不同目录下的页面调用的模块不同。role 字段在 user 表里原项目通常用字符串 admin、guide、user 区分你也可以改成数字 1、2、3但字符串可读性更好不容易填错。4.2 商品管理列表查询和图片路径处理管理员和导购员都有商品管理权限。差别是管理员可以增删改所有商品导购员通常只能改自己录入的商品。列表查询核心是分页。public ListProduct findPage(int pageNum, int pageSize) { String sql select * from product order by id desc limit ?, ?; ListProduct list new ArrayListProduct(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, (pageNum - 1) * pageSize); ps.setInt(2, pageSize); ResultSet rs ps.executeQuery(); while (rs.next()) { Product p new Product(); // 字段名和表结构保持一致避免别名混淆 p.setId(rs.getInt(id)); p.setSname(rs.getString(sname)); p.setPrice(rs.getString(price)); p.setNumber(rs.getString(number)); p.setPhoto(rs.getString(photo)); list.add(p); } } catch (SQLException e) { e.printStackTrace(); } return list; }limit 的第一个参数是偏移量pageNum 从 1 开始所以第一页偏移是 0第二页偏移是 pageSize。这样写有个问题如果数据量很大limit 100000, 10 会先扫描 10 万行再取 10 条性能不好。但这个系统商品量撑死几百条用不上深分页优化。图片路径这里 photo 存的是相对路径比如 upload/coffee.jpg页面显示时要用 request.getContextPath() / photo 拼全路径否则部署到子目录时会找不到图片。商品编辑的表单页要注意原表 photo 字段是 varchar(2550)长度偏大就是为了存多图逗号分隔的场景。前端如果支持传多张图后端要把文件名用逗号拼起来再存读取时 split(,) 循环展示。4.3 订单提交与事务购物车下单要过一次事务订单提交是这个系统里唯一必须用事务的地方。用户购物车里有多个商品提交订单时要同时完成“插入订单记录”和“扣减库存”两步任何一步失败都要回滚。public boolean createOrder(Order order, ListCartItem items) { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 插入订单主表 String orderSql insert into orders(s_id, u_id, number, t_price, state, date) values(?, ?, ?, ?, ?, ?); PreparedStatement ps1 conn.prepareStatement(orderSql); ps1.setInt(1, order.getSId()); ps1.setInt(2, order.getUId()); ps1.setString(3, order.getNumber()); ps1.setString(4, order.getTPrice()); ps1.setString(5, 待付款); ps1.setDate(6, new java.sql.Date(System.currentTimeMillis())); ps1.executeUpdate(); // 扣减库存where 条件里带上数量判断避免超卖 String stockSql update product set number number - ? where id ? and number ?; PreparedStatement ps2 conn.prepareStatement(stockSql); ps2.setInt(1, items.get(0).getCount()); ps2.setInt(2, items.get(0).getProductId()); ps2.setInt(3, items.get(0).getCount()); int rows ps2.executeUpdate(); if (rows 0) { // 库存不足回滚整个订单 conn.rollback(); return false; } conn.commit(); return true; } catch (Exception e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } return false; } finally { if (conn ! null) { // 恢复自动提交再归还连接 try { conn.setAutoCommit(true); conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }扣库存的 SQL 是关键where 条件里带 number ?更新影响行数为 0 说明库存不足直接回滚。这是原项目最容易忽略的点只查库存再更新会出现并发超卖。事务开启后所有操作共用同一个连接commit 之前任何一个 executeUpdate 失败都会 rollback。finally 里要把 autoCommit 改回 true 再关闭连接否则连接归还到连接池时是事务状态下一个人拿到这个连接会出问题。上面代码为了展示流程items 里只取了第一个商品真实场景要循环 items 逐条扣库存其中任意一条失败就整体回滚。订单表 state 字段原项目里是 varchar值有“待付款”“已付款”“已发货”等写法。你完全可以用数字 0、1、2 表示但 varchar 在页面展示直观日志里好排查毕设答辩也好解释。状态流转的逻辑一般放在 OrderService.changeState() 里管理员点击“发货”就把 state 从“待发货”改成“已发货”用户端只允许在“待发货”之前取消过了这个节点要取消只能联系管理员。5. JSP 项目避坑记录连接泄漏、乱码、图片失效的排查清单这五个坑不是凭空想出来的理论是实际跑这个 JSP 项目时反复翻车记录下来的。每一条都按现象、原因、解决三层来写排错时直接对着查就好。下面前四条是数据库和编码最后一条是部署。5.1 连接泄漏页面越跑越慢最后整个 Tomcat 卡死现象系统运行两三天后后台页面打开越来越慢最后直接超时重启 Tomcat 恢复过两天又复发。原因从开发到提交很多人写 JDBC 代码只关 ResultSet 和 StatementConnection 忘了关。更严重的是在 finally 里关闭但 try-with-resources 没用对关闭顺序不对导致连接池里的连接被耗尽。Tomcat 默认的连接池最多 100 个连接泄漏 100 个之后所有请求排队等连接表现就是“假死”。解决所有连接操作统一用 try-with-resources 或者在 finally 里按 ResultSet、Statement、Connection 的顺序关闭。代码风格不统一的项目容易漏建议写一个公共 DAO 基类关闭操作集中在一个 close 方法里。写完每个 DAO 方法后自查一遍凡是 new 出来的连接必须看得到对应的 close。5.2 中文乱码界面正常插进数据库就变问号现象注册用户在表单里输入“拿铁”列表页显示正常打开数据库看到的是“”。原因三层编码不一致。JSP 页面没有指定 pageEncoding表单提交的字符编码默认跟服务器不一致数据库连接 URL 没加 characterEncodingutf8数据库表和字段的 collation 不是 utf8_general_ci。任何一个环节出问题中文就会在传输过程中被转成错误字节。解决JSP 文件头部加% page pageEncodingutf-8 %所有取参数或响应输出前设置 request.setCharacterEncoding(utf-8) 和 response.setContentType(text/html;charsetutf-8)JDBC URL 加 useUnicodetruecharacterEncodingutf8。检查顺序从浏览器到服务器到数据库一层层排除用 curl 登录页测试返回体的编码卡在哪一层改哪一层。5.3 图片上传后 404本地能显示部署到服务器就破图现象商品图片上传成功数据库里 photo 字段也有路径本地访问正常部署到云服务器后图片全部 404。原因上传时用了绝对路径 D:/upload发布后服务器上根本没有 D 盘这个目录或者图片保存到了 Tomcat 的 bin 目录重启后文件被清理。解决图片目录放在项目发布目录下的 webapp/upload上传代码用相对路径。读取路径用 request.getServletContext().getRealPath(/upload) 获取实际绝对路径生成 URL 用 request.getContextPath() /upload/xxx.jpg。如果图片量大会撑爆系统盘就放到 Tomcat 外部配置虚拟路径在 server.xml 的 Host 节点挂一个 DocumentBase。5.4 库存超卖两个人同时下单库存变负数现象商品剩最后 1 件两个用户同时提交订单两个订单都成功库存变成 -1。原因代码先 select 查库存再 update 扣库存两个操作之间有时间差。两个线程同时查到库存 1同时执行 update两边都成功。解决把库存判断和扣减合并成一条 update 语句update product set number number - 1 where id ? and number 0。执行后检查受影响行数返回 0 就是库存不足。这个系统单机部署不需要引入分布式锁一条 SQL 就够了。提示这条“判断库存再扣减”的 SQL 只适用于单个 Tomcat 实例。如果以后多个应用服务器共享同一个 MySQL 库要改成 select ... for update 悲观锁或者引入缓存预减库存否则还是扛不住真正的高并发。5.5 部署后登录不上本机能跑服务器报密码错误现象war 包拷到服务器后数据库连接报 Access denied for user。原因本地开发装的 MySQL 密码是 123456部署时只上传了 war 包数据库是新建的用户名密码还是初始配置配置里却还是本地密码或者反过来本地密码被带到了服务器上。解决数据库连接参数集中放在 db.properties部署前先执行 SQL 创建数据库和账号再把配置改成服务器上的地址、用户名、密码。每次部署前强制走一遍这条清单检查 db.properties、检查数据库字符集、检查图片上传目录权限、检查 Tomcat 版本号。这四个坑在 JSP 项目里出现频率最高清单走完能少踩一半坑。6. 从毕设到可用系统部署前改这三处配置再写个自检脚本6.1 三处必改的配置第一个是数据库连接。打包成 war 之前把 db.properties 改成目标服务器的 IP、端口、库名、账号密码。本地开发用的 root 空密码一定要换掉不然服务器上的 MySQL 默认只允许 localhost远程连接会直接被拒。第二个是上传路径。如果服务器是 Linux上传目录改成 /var/www/coffee/upload并在 Tomcat 的 server.xml 里加虚拟目录映射。不改的话图片文件会写到 Tomcat 安装目录下下次升级 Tomcat 时全丢。第三个是 JSP 页面里的绝对路径。项目里所有 href 和 src 都要用 getContextPath 拼全不要写 /咖啡厅/upload/xx.jpg 这种硬编码。部署到域名根路径没问题一旦放到二级目录下全部 404。6.2 部署后的自检脚本我一般会在部署完跑一个 curl 脚本按顺序验证核心流程# 1. 登录页能访问 curl -I http://localhost:8080/coffee/login.jsp # 2. 提交错误密码应该返回错误提示 curl -d usernameadminpasswordwrong http://localhost:8080/coffee/login # 3. 提交正确密码拿到 302 跳转 curl -d usernameadminpassword123456 -c cookies.txt http://localhost:8080/coffee/login # 4. 带 cookie 访问商品列表返回 200 curl -b cookies.txt -I http://localhost:8080/coffee/user/productList.jsp # 5. 检查图片目录是否存在且可写 ls -l /var/www/coffee/upload这组命令不要跳过。第一个命令验证 Tomcat 和项目部署第二个验证表单编码第三个验证登录会话第四个验证 session 传递最后一个验证上传目录权限。五步全过系统上线的基本链路才算通。我记得第一次部署毕设时本地测试全过传到服务器后台登录失败查了一个多小时发现 db.properties 里密码还带的是本地空密码完全没改。从那以后我每次部署都强制走一遍配置检查清单再跑一遍上面的脚本确认无误才交付。希望帮到你。本文还有配套的精品资源点击获取