[toc]


CMS项目设计说明书

演示视频:B站

【学年设计】餐饮管理系统

一,需求分析

1.1 项目介绍

项目名:餐饮管理系统1.0/CMS(CateringManagementSystem)

项目特色:基于Java SE 和 MySQL的小型餐饮行业管理系统的控制台程序

作者:SWU——21信管5龙箫, 21信管4黄锦,21信管3李敏灿(leader)

分工:

龙箫:

  1. properties文件配置
  2. libs下druid,mysql-connector-java,commons-dbutils等资源jar收集和准备
  3. utils工具类的设计和编写
  4. DAO执行类的设计和编写

黄锦:

  1. 设计 MySQL 各张表
  2. domain类的设计和编写
  3. service各类的功能代码的完成

李敏灿:

  1. 设计项目架构方案
  2. 设计接口和父类以及各类属性和方法的制定
  3. 编写View界面类,并联合团队同学把程序跑起来
  4. 编写项目设计说明书

合作方式:Git配合GitHub的分布式版本控制开发

代码量:1200行

1.2 项目背景

该项目适用于个体户餐饮行业的日常客户商家管理,商家可以通过使用该系统,更加方便的进行贸易活动。

1.3 项目目的

巩固Java SE 和 MySQL 相关知识,提升小组人员对于复杂业务需求下的实践开发能力

二,总体设计

采用分层结构,整个项目主要用 Java SE 和 MySQL 写成。

2.1 功能介绍

系统登录:通过JDBC与MySQL数据库核实检验(做了防SQL注入处理)

image-20220703184246606

酒店管理系统(二级菜单)

image-20220703184355874

显示餐桌状态

image-20220703184443056

预定餐桌

image-20220703184530249

显示所有菜品

image-20220703184603479

点餐

image-20220703184640806

查看账单

image-20220703184709147

结账

image-20220703184733594

退出系统

image-20220703184804515

2.2 程序架构图

image-20220703164322035

2.3 项目分层

View层:界面层。负责调用Service层获取数据和行为进行显示

Service层:业务层。主要是各类业务衔接和操作

DAO层:具体的去执行Service层下达的指令,

domain层:主要存放Javabean和MySQL中表的对应关系

MySQL数据库:具体存放数据

utils类:常用工具类,提高工作效率

三,详细设计

3.1 体现原则

1、单一职责原则(Single Responsibility Principle)

2、依赖倒置原则(Dependence Inversion Principle)

3.2 体现模式

代理模式

各司其责模式

3.3 结构详细讲解

MySQL 数据库

需要不同的表就在目标数据库下建立不同的表

domain类

这个主要是在Java程序段形成Javabean和MySQL库中的表的类型对应。比如,你数据有个表是 student ,然后你读了一个 student 的数据到了程序来,需要载体去接受。这时候我们就可以直接 new 一个 domain 中对应的 Javabean 就可以了。

DAO层

这层全是实际操作数据的,实际干活的,他们从 Service 得到指令,然后去具体执行,去操作数据库。我们的原则表示,这里会有一个 BasicDAO 这个是所有 DAO 共有的操作,就放在这里面。然后我们每多了一个 domain 类 就建立一个 对应去操作这种对象的 DAO ,然后继承 BasicDAO ,之后再在本 DAO 中加上自己的特殊操作,如果有的话。

这里的 BasicDAO 有点讲究,它和下面那个JDBCUtilsByDruid 一样,都是包装好的,这个是把DML增删改用个 update就行了。但是对于查询,我们做了根据实际应用做了更加具体的分类:queryScalar查询单行单列的方法,即返回单值的方法;querySingle查询单行结果 的通用方法;queryMulti返回多个对象(即查询的结果是多行), 针对任意表。更加具体的实现我们在编码解释。

image-20220703171805319

utils 类

这是一个工具类。里面有两个工具。

image-20220703170338788

  • 一个是 Utility

    功能:处理各种情况的用户输入,并且能够按照程序员的需求,得到用户的控制台输入。看下图,具体都是干啥的,我们代码阶段讲解。

image-20220703170543304

  • 另一个是封装的 JDBCUtils

    连接池用的阿里的 Druid (德鲁伊)。因为用Java操作MySQL获取连接很麻烦,所以就整个这个。它就一个getConnection 返回一个连接。还有个 close 这个是关闭资源用的,包括连接,statement,结果集等/具体的我们编码阶段讲。

image-20220703171455864

Service层

这个也是根据实际业务需求来写,比如有那个DAO我们就写个对应专门操作他的Service,专事专办。当然这个是看具体任务,一个Service可以控制两个DAO也是正常。

View层

这个就是表层次的页面了,这个里面我们有一下几个方法,分别行驶不同的功能,比如listDiningTable是显示目前店内的餐桌适用情况;orderDiningTable是预定餐桌;listMune是列举菜单等等,具体的我们在编码给大家讲解。

image-20220703173131039

四,编码

4.1 项目构成

image-20220703180608831

1.包名

如下,总包为team.swu.lmc.HMS

符合规范:全部小写,按照team.学校.作者.项目名.模块名格式命名

意为:该项目为团队项目,是 SWU ;学校项目队长为 lmc; 项目名为 HMS


2. 包含

该项目由dao;domain,service,utils,view五个包和一个start启动文件组成

start文件:是程序运行入口

view包:里面只有一个HMSView是项目的界面

service包:全是相应的项目服务,是具体业务逻辑实现的地方。其中有四个文件

				1. BillService:账单服务
				1. DiningTableService:预定桌子服务
				1. EmployeeService:登录人员管理服务
				1. MenuService:菜单服务

dao包:主要是执行层具体干活的类。包含六个文件:

  1. BasicDAO:基本DAO,其他所有DAO的父类,包含大家的共用方法
  2. BillDAO:专门操作Bill账单的DAO
  3. DiningTableDAO:专门预定桌子的DAO
  4. EmployeeDAO:专门执行有关雇员操作的DAO
  5. MenuDAO:专门执行菜单有关的操作DAO
  6. MultiTableDAO:这个是在Bill表的基础上联合多表查询的专门DAO

domain包:和 MySQL 数据库中的表对应的 Javabean 类,具体有五个:

  1. Bill:账单
  2. DiningTable:预定餐桌
  3. Employee:雇员
  4. Menu:菜单
  5. MultiTableBean:联合多张表查询Bill数据的Javabean

utils包:该项目需要的工具包,主要就两个工具类:

  1. Utility类:处理各种情况的用户输入,并且能够按照程序员的需求,得到用户的控制台输入
  2. JDBCUtilsByDruid:一个封装的数据库工具,里面数据连接池用的阿里的德鲁伊。

4.2 每个代码块详细讲解

4.2.1 MySQL数据库

1.结构

image-20220703185005136

2.解释

bill:账单表。

id是第几单,billID是订单号(做了防止重复的设计),menuId是菜品号,nums是点的菜品数目,diningTableId是几号桌子的,billDate是那个时间下单的,state是订单状态,如果支付了会有详细支付方式,如果没有买单就是未支付

image-20220703185228043

diningTable:餐桌单。

id是桌号;state是餐桌状态,已预约就是预约好了但是还没有用餐;正在用餐就是已经上菜,在吃;空就是空闲的,orderName是定桌人姓名,orderTel是定餐人电话

image-20220703185614640

employee:雇员表

empId是工号,pwd是密码(这里使用了MD5加密);name是雇员姓名;job是他的职位。

image-20220703185828159

menu:菜单表

id是菜品号,name是菜品名,type是菜品类别,price是菜品价格

image-20220703190012035

3.history代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
-- 创建HMS数据库
create database HMS
-- 创建表 employee
create table employee(
id int primary key auto_increment,#自增
empId varchar(50)unique not null default '',#员工号
pwd char(32) not null default '',#密码md5
`name` varchar(50) not null default '',#姓名
job varchar(50) not null default ''#岗位
)
charset=utf8;
drop table menu
#添加测试数据
insert into employee values
(null,'001',md5('123456'),'张三丰','经理'),
(NULL,'002',MD5('123456'),'小龙女','服务员'),
(NULL,'003',MD5('123456'),'张无忌','收银员'),
(NULL,'666',MD5('123456'),'烂子','经理');

select * from employee;

-- 创建diningTable表
create table diningTable(
id int primary key auto_increment,#自增,表示餐桌编号
state varchar(20)not null default '',#餐桌状态
orderName varchar(50) not null default '',#预定人的名字
orderTel varchar(11) not null default ''
)charset=utf8;
#测试数据
insert into diningTable values
(null,'空','',''),
(NULL,'空','',''),
(NULL,'空','','');
select * from diningTable

select id,state from diningTable

select * from diningTable where id = 1

update diningTable set state='空',orderName='',orderTel='' where id=1

#菜谱
create table menu (
id int primary key auto_increment, #自增主键,作为菜谱编号(唯一)
`name` varchar(50) not null default '',#菜品名称
`type` varchar(50) not null default '', #菜品种类
price double not null default 0#价格
)charset=utf8;

insert into menu values(null, '八宝饭', '主食', 10);
insert into menu values(null, '叉烧包', '主食', 20);
insert into menu values(null, '宫保鸡丁', '热菜', 30);
insert into menu values(null, '山药拨鱼', '凉菜', 14);
insert into menu values(null, '银丝卷', '甜食', 9);
insert into menu values(null, '水煮鱼', '热菜', 26);
insert into menu values(null, '甲鱼汤', '汤类', 100);
insert into menu values(null, '鸡蛋汤', '汤类', 16);

`bill`

#账单流水, 考虑可以分开结账, 并考虑将来分别统计各个不同菜品的销售情况
create table bill (
id int primary key auto_increment, #自增主键
billId varchar(50) not null default '',#账单号可以按照自己规则生成 UUID
menuId int not null default 0,#菜品的编号, 也可以使用外键
nums SMALLINT not null default 0,#份数
money double not null default 0, #金额
diningTableId int not null default 0, #餐桌
billDate datetime not null ,#订单日期
state varchar(50) not null default '' # 状态 '未结账' , '已经结账', '挂单'
)charset=utf8;

4.2.2 utils

1. Utility

处理各种情况的用户输入,并且能够按照程序员的需求,得到用户的控制台输入。

  1. 构成结构

    开头一个静态私有的Scanner

    readKeyBoard私有静态方法

    其他读取公开静态方法

  2. readKeyBoard

    image-20220703200847053

  3. readConfirmSelection

    image-20220703200955283

  4. readString

    image-20220703201038157

  5. readInt

    image-20220703201128969

  6. readChar

    image-20220703201221854

  7. readMenuSelection

    image-20220703201241413

image-20220703200309459

2. JDBCUtilsByDruid

基于druid数据库连接池的工具类

为什么?

因为 Druid 集齐了 C3P0和其他数据库连接池的所有有点,更快更稳健,更加适配。

  1. 构成结构

    一个私有静态的 DataSource+static代码块初始化+getConnection获取连接+close关闭资源

    image-20220703201400491

  2. static代码块

    主要用途就是完成那个 DataSource 的初始化。

    相关信息是 src 下面的 druid.properties 配置文件 load 过来的,然后用 Druid数据池拿一个数据源给刚才的 DataSource 完成初始化。有问题就被try_catch 了。

    image-20220703201853519

  3. getConnection 获取连接

    直接用从 Druid 数据源中拿一个出来返回连接

    image-20220703202220274

  4. close 回收资源

    如果需要关闭就对应位置传入对象,没有就 null

    里面先会判空然后再关闭

    注意这里的关闭是把连接放回 Druid 数据库连接池

    image-20220703202331738

  5. Druid 的配置文件

    image-20220703202544834

4.2.3 domain

与 MySQL 对应的 Javabean ,

构成:

  1. MySQL 表中的对应元素(全部私有化),

  2. 然后构造器有有参和无参两种,

  3. 然后就是所有元素的getter&setter方法

  4. 还有一个便于打印该类元素的 toString

image-20220703190313907

这里需要注意的是:

  1. 属性名最好和MySQL表中的列名保持一致
  2. 而且无参构造器和setter&getter方法必须要
  3. 否则会因为反射出现Bugs

4.2.4 DAO

这个是针对每个表的实际执行人。

构成
  1. domain中对应类的 XXDAO
  2. BasicDAO
  3. XXDAO都继承了BasicDAO

BasicDAO实现:

结构

一个内部私有new的驱动+一个增删改方法+3个数据库查询方法

image-20220703191051896

update

SQL语句中的增删改DML

首先利用 JDBCUtilsByDruid 获取连接,然后用 QueryRunner 的update 传入相关参数去操作数据库。如果有异常捕捉后转换为运行异常并抛出。最后还是用 JDBCUtilsByDruid 的 close关闭相应的资源。

image-20220703202745061

query类

这是用来操作数据库,查询信息的,根据用途分为查多条信息,查一条信息,一条信息中的一个数据三种

这个的原理和上面的 update 过程是相似的,唯一的区别是 用的是 query。区别三种查询类型的是第二个参数是(下图红框Bean),Bean的类型不同,返回的数据包装成不同的 Javabean。

image-20220703203424523

4.2.5 service

这个是项目的主题,也是重头。主要是各个板块的服务逻辑书写

1. BillService

image-20220703204214582

结构:

  • 属性:

    BillDAO;multiTableDAO;(给这些DAO下指令,去执行对数据库的操作)

    menuService;diningTableService;(需要借助这两个类中)

  • 方法:

    orderMenu:点餐

    image-20220703205024246

    list,list2:返回所有菜单(后面那个是多表查询)

    image-20220703205052147

    hasPayBillByDiningTableId:查看某个餐桌是否有未结算的菜单

    根据餐桌号返回符合条件的 Bill 对象,非空即为有未记账的账单

    image-20220703205116429

    payBill:结账行为

    首先在 Bill 表设置账单state,然后diningTable表中把桌子状态还原

    image-20220703205235073

2.DiningTableService

image-20220703205409214

结构:

  • 一个 diningTableDAO 私有对象

  • 五个订餐相关方法

  • list:返回所有餐桌信息

    image-20220703205751052

  • getDiningTableById:根据 id ,查询对应的餐桌DiningTable 对象
    如果返回null就是表示这个餐桌不存在

    image-20220703205808059

  • orderDiningTable:如果餐桌可以预定,调用方法,对其状态进行更新(包括预定人的姓名和电话)

    image-20220703205823674

  • updateDiningTableState:更新餐桌状态

    image-20220703205842153

  • updateDiningTableToFree:设置指定餐桌为空闲可用状态

    image-20220703205857412

3.EmployeeService

image-20220703205929261

一个执行命令的 EmployeeDAO

一个按empId 和pwd 返回 Employee 对象的方法

唯一需要注意的就是下图标红的,需要用MD5加密否则匹配不上。

image-20220703210059063

4.MenuService

image-20220703210202430

一个执行命令的私有 MenuDAO

list:返回所有的菜品,返回给界面使用

image-20220703210321784

getMenuById:根据菜品 Id 获取 Menu 对象

image-20220703210411065

4.2.6 View

image-20220703210518347

元素

image-20220703210826752

loop:控制是否退出主页面

key:接受用户在主菜单的选择的

employeeService,diningTableService,menuService,billService:在 view 界面层中调用这些服务层

方法

image-20220703210841699

listDiningTable

显示所有餐桌状态

image-20220703211108662

核心就是:diningTableService.list()

orderDiningTable

完成定座

image-20220703211242754

开始的时候判断两个需要推出的情况:输入 -1 和确定时输入 N

这两个没问题接着下面,先排除没这个桌子,和桌子在空的不可用情况,然后预定改变桌子状态在 餐桌表。

核心函数:diningTableService.getDiningTableById(orderId);diningTableService.orderDiningTable(orderId, orderName, orderTel)

image-20220703211620170

listMune

显示所有菜品

image-20220703211844728

核心就是:上图红框 menuService.list()

orderMenu

点餐

首先是确定点餐的三次没有输入 -1 退出

image-20220703212118104

这些都 ok 的话,就要确认餐桌号存在,顾客点的菜品存在,之后才点菜。

主要就是下图红框的三个函数:

diningTableService.getDiningTableById(orderDiningTableId);

menuService.getMenuById(orderMenuId);
billService.orderMenu(orderMenuId, orderNums, orderDiningTableId)

image-20220703212222220

listBill

显示账单信息

image-20220703212506520

注释掉的是旧版本的,现在用了 MySQL 多表查询

payBill

完成结账

首先排除 -1 退出情况

image-20220703212656241

再排除结账餐桌不存在和指定餐桌没有需要结账账单的两种情况

image-20220703212803155

如果顾客再选择结账方式时候回车也是退出,最后确认输入 N 也是退出,或者支付账单出错也会取消,只有以上情况都排除才会顺利结账。

核心:就是排除各种需要终止结账的情况和下图红框方法

image-20220703212942520

主菜单,用来显示的主页面

登录界面,衔接下一步选择是否登录

image-20220703213240148

登录账号密码输入,然后会去数据库核实,都没有出错才会登录系统。数据库验证考虑到 SQL 注入,所以做了相关处理

核心:

  1. 就是第一个红框的数据库验证函数
  2. 各种拒绝登录的业务逻辑实现

image-20220703213615851

系统二级选择菜单,选具体操作。对操作的处理在那个红框中

image-20220703213900423

通过 switch 输入什么就执行什么业务逻辑,这里的方法是我们同类下面上面讲解的那些个静态方法

核心:红框函数

image-20220703214023586

4.3 运行分析

登录

输入1:让你输入账号密码,正确了就会显示登录成功[XX],XX是登录者名字,然后显示系统的二级菜单,让你选择下一步操作。

image-20220703220644782

输入1,但是账号密码没有全对,会提醒登录失败,然后让你重新选择

image-20220703220940035

输入2:退出系统

image-20220703220105002

输入1,2之外的数字:提示请正确输入指令

image-20220703220441492

能登陆的都是 MySQL 表中的员工,密码不可见,因为用了 MD5 加密

image-20220703221046643

1 查看餐桌情况

image-20220703221224837

2 预定餐桌

顺利预定

image-20220703221435715

顺利预定后查看,确实预定了

image-20220703221550090

预定无法预定的餐桌

image-20220703221337119

预定中途退出

image-20220703221713395

3.显示所有菜品

image-20220703222042604

4.点餐

顺利点餐

image-20220703221947302

不顺利点餐

没餐桌

image-20220703222128013

菜品不存在

image-20220703222231735

5 查看账单

image-20220703222004701

6 结账

顺利结账

image-20220703222421083

不顺利结账

该餐桌没有账单

image-20220703222327883

没有该餐桌

image-20220703222351914

9.退出系统

image-20220703222454669

4.4 遇到 Bugs

Bugs_20220702 - LMC_Blog

1.Druid配置文件错误

问题记录

测试两个工具类时,爆出 druid.properties 配置文件错误。

image-20220701205452907

image-20220701205432855

查看

原来是拷文件的时候没有改配置文件中的 url中的数据库名

image-20220701205721845

解决

新建一个 HMS 数据库,然后 druid.properties 中的目标位置girl改为HMS

再次测试,ok

image-20220701210000201

2.Jniut测试一直卡着跑不出来

情况记录

写完登录页面后用Jniut进行测试,Run 之后却一直卡在运行测试给你转圈。程序也只跑了一些。

image-20220701213323601

原因

Jnuit 做单元测试的时候是不允许控制台输入的,第30行有句向控制台读入,Jniut在这里卡住了。

解决办法

帮助 -> 编辑自定义VM选项

image-20220701214109295

在末尾加上一行:-Deditable.java.test.console=true

image-20220701214234029

然后重启 IDEA,问题解决

image-20220701214433873

3.domain是干什么的?

一般来说在数据库建了一个表后都会在旁建立一个domain类,然后里面写对应的 Javabean。

这个的意思就是,数据库中的那个表所要表达的对象形式在 Java 程序中的体现。比如你 MySQL 中有个学生表,有很多元素。那你读取在 Java 端怎么接受?必须有一个对应的对象类去表示它。这就是 domain。

4.在做显示所有菜品时,查出来全是null

情况描述

这里我们是用 用阿里Druid做的一个BasicDAO来批量查询,把数据一丢进去,以为会出来结果,结果全是null

image-20220702165739495

出问题的地方:image-20220702171449781

问题所在

查询数据的那张菜单表的domain对应Javabean类没有getter&setter

image-20220702171637966

加上getter&setter

image-20220702171937928

原因

联系Java反射不难猜出这个BasicDAO底层从数据库返回数据再注入到javabean中,利用的反射的getMethod,getDeclaredMethod等取得各字段的setter,getter方法.没有setter,getter方法就无法给javabean注入属性,他们输出到控制台就都是初始值null。验证方法,在开头给name赋初始值大哥,他就会只有第二个名字都是:大哥,然后其他的都是null.

结论:建立domain类以及表示对象类,无参构造器,setter&getter都给默认带上,不然很多框架方法底层反射实现的东西都会报错或者异常。

五,测试

我们项目用的测试

单元测试框架junit

简介:

JUnit 是一个 Java 编程语言的单元测试框架。JUnit 在测试驱动的开发方面有很重要的发展,是起源于 JUnit 的一个统称为 xUnit 的单元测试框架之一。

优点:

  1. 可以书写一系列的测试方法,对项目所有的接口或者方法进行单元测试。
  2. 启动后,自动化测试,并判断执行结果, 不需要人为的干预。
  3. 只需要查看最后结果,就知道整个项目的方法接口是否通畅。
  4. 每个单元测试用例相对独立,由Junit 启动,自动调用。不需要添加额外的调用语句。
  5. 添加,删除,屏蔽测试方法,不影响其他的测试方法。 开源框架都对JUnit 有相应的支持。

策略:写一个功能就在 MainMenu 上加一个 Test 跑一下,形成单元性测试

六,软件打包

开发背景

开发IDE:IDEAIntelliJ IDEA 2020.2 (Ultimate Edition)

JDK:11.0.14 LTS

MySQL: Ver 14.14 Distrib 5.7.19, for Win64 (x86_64)

MySQL页面APP:SQLyog

访问方式

项目jar包:蓝奏云
密码:3vca

libs中的jar包:蓝奏云

密码:g0as

GitHub:仓库地址

七,学年设计总结报告

通过本次学年设计实践,我们小组成员都收获颇丰。我们学会了:

  1. Java SE 相关知识在项目中的具体实践;
  2. Java 通过 JDBC 操作 MySQL 的具体做法;
  3. 利用 Git 和 GitHub 进行多人团队协作版本控制开发;
  4. 自己写常用的 utils 工具类,可以几大的提升我们的开发效率;
  5. 学会了项目开发的模块化思想和 DAO分层理念,用界面层,服务层,DAO层,domain层,数据层,工具层协作开发,各司其责,条理清晰,结构严谨;
  6. 我们学会了 JDBC 进阶操作,怎么制作 JDBCUtils,利用 Apach——utils和阿里的Druid连接池做成一个DAO模式,极大的提高了,Java 操作数据库的效率,也极大地方便了开发;
  7. 在项目测试方面我们采用了 junit,而且我们通过试错Bug对他的适用和了解进一步加深了;
  8. 我们还通过 Bug 进一步深入了解了,反射在 JDBC 底部,和 Druid 连接池底池的作用和重要性