餐厅点餐平台导航
【餐厅点餐平台|一】项目描述+需求分析 https://blog.csdn.net/weixin_46291251/article/details/126414430
【餐厅点餐平台|二】总体设计 https://blog.csdn.net/weixin_46291251/article/details/126422811
【餐厅点餐平台|三】模块设计 https://blog.csdn.net/weixin_46291251/article/details/126422826
【餐厅点餐平台|四】UI设计+效果展示 https://blog.csdn.net/weixin_46291251/article/details/126422844
【源码下载】 https://download.csdn.net/download/weixin_46291251/86404328
四:模块设计
4.1概述
根据我们的架构设计,我们一共有四个大的模块,数据访问操作层DAO,控制层Controller,模型层Model,界面展示视图View。具体每个层的总体设计如下:
DAO (Data Access Operation)
这一层主要为数据访问操作,和数据库进行交互,那么这个过程就需要与数据库建立连接,搭建一个桥梁。这个桥梁我们使用单例模型,系统每次加载,与数据库建立一次连接,在系统运行时,共用这个连接建立不同的会话进行数据库操作。
在该层中,定义每个类对应相应的数据表增删改查操作。共有7个DAO操作分别为:
①OrderDao:对订单表Bill进行增删改查,完成订单信息与数据库的交互。
②OrderRoleDao:对discount表和discountDetail表进行增删改查操作,完成打折信息对象的获取与存储。
③OrganizationDao:对组织机构表organizationInfo进行增删改查操作,完成组织机构对象的获取与存储。
④SaleDao:菜品对象的获取与生成,操作菜品基本表,根据工厂,生成不同的菜品对象。
⑤UserDao:对user表进行增删改查,完成用户对象的获取与存储。
⑥WindowDao:对windowInfo表进行增删改查,完成窗口数据的获取与存储。
⑦ApproveExam:对两个审批表joinApproval,WindowApproval进行增删改查操作,完成各个审批数据的获取与状态更改。
Controller
Controller层主要实现与DAO层交互和View层交互,我们是设计是一个面向对象的Controller,每个Controller完成对应的对象操作,完成对象的数据需求和操作需求,对对象的各操作进行逻辑处理,响应的数据库以及数据返回给View层。在我们的系统中,我们主要抽象出了三个实体,分别为管理员,商家,窗口,系统中主要是这三个对象进行操作,完成整个系统的运作,在这个系统中还需要一个对象,就是支付系统,在系统中,我们抽象成了收费器对象,收费器对象调用其他平台完成支付。商家和管理员对于系统来说,可以抽象出一个上层对象为用户。具体描述如下:
①UserController:是ManagerController和MerchantController的父类,共同属性有User对象和登录方法。
②ManagerController:完成管理员的各个操作响应:登录,注册,查看加盟申请,查看窗口申请,查看菜品申请,以及各个申请响应的审批操作,进行通过DAO层写入到数据库。
③MerchantController:商家控制器,实现商家的主要功能,也就是用例图中的商家的每个对应的用例,登录,注册,查看当前拥有窗口,打开窗口,申请加入机构,申请开辟新窗口,查看已有组织,查看可以申请的窗口,申请窗口,对于商家来说,完成这几个功能即可完成需求。
④WindowController:窗口控制器,是商家打开窗口后产生的一个对象,在窗口控制器中,有自己的窗口说明信息,菜单,打折信息,以及当前订单,所属商户。行为有为订单添加优惠,为窗口添加菜品,为订单添加菜,订单结算功能,窗口控制器属于是食堂中的店员在进行操作,完成顾客的点单操作。
⑤Charge:收费器,更是一个容器,容器中存储了各个平台的支付对象,如二维码支付,扫码支付,打卡器支付,收费器收到收费指令后,向各个支付子系统进行发送指令,监听各支付状态,根据状态完成收费的状态控制。流程图如下图所示:
所有的子支付系统即需要实现支付接口,采用多线程,各个支付同步进行,完成支付的操作,类图描述如下图所示,所有的支付均需要集成于类ChargeBase,实现里面的接口,在run()函数中实现支付的操作,通过修改status状态来告知Charge收费成功状态。
Model
Model层为实体对象的定义,在系统中,较为简单的对象有用户User,订单Order,窗口信息WindowInfo,组织机构信息OrganizationInfo,账单信息Bill,这几个只需要简单的结构体进行存储就可以完成相应的功能。具体定义于数据库中的表定义相同,详细请见4.4.1。
在Model中,需要考虑变化,在菜品上,食堂中的菜品多种多样,需要对各类对象进行抽象出上层实体,通过面向对象的多态,进行派生出多种多样的对象,抽象出上层售卖食物SaleBase,存储一个菜品的基本信息,并实现DaoBase接口,使得在衍生时,需要实现如何调用DAO层中的操作完成对应对象的生成与数据存储。在本次系统中,我们设计了三种售卖食物,单个售卖,称重售卖,以及套餐售卖,也就是三个类SaleUnit,SaleWeight,SaleSetMeal,继承于基类SaleBase,并有自己的特有属性与方法。详细描述见4.4.2
同时,窗口举办的优惠活动信息也具有多样性,有满减活动,有打折活动,不同的对象我们需要使用不同的类型来表示,抽象出上层实体,OrderRoleBase,对于满减活动或者打折活动需要继承基类OrderRoleBase,同时,打折活动也需要实现我们定义的序列化接口DaoBase,完成于数据库的对接,数据的持久化存储。详细描述见4.2.2。
在不同对象的生成,我们需要考虑使用工厂来实现不同对象的生成,在本次设计中,我们的类实现了序列化接口,工厂模式的主要职责是用于分配空间,所以我们采用了改进的简单工厂模式,在进行详细信息的查询时,通过序列化接口进行获取,加载各种不同的菜品。
4.2DAO(Data Access Operation)
ApproveExa
bool selectOrgExamWindow(OrganizationInfo org,vector<WindowInfo>& wins,vector<User>& users,vector<int>& exam_ids);
查询某个组织下的所有待审批的窗口,一个窗口对应一个用户的审批。通过对 WindowInfo, User, windowApproval三个表的联合查询,获取到一个组织全部等待审批的窗口的信息和提交该申请对应的申请人的信息,以及每个审批在系统内部的审批编号(用于前端反馈处理结果)。
bool selectOrgJoin(OrganizationInfo org,vector<User>& users,vector<int> &exam_ids);
查询某个组织下的所有待审批的加盟申请对应的用户。
直接查询joinApproval中org_id为查询条件,同时status为待审批状态,调用UserDao查询用户的信息并返回。
bool selectOrgExamFood(OrganizationInfo org,vector<SaleBase*>& sales);
查询某个组织下的所有审批的菜品。
先通过windowDao查出org对应的所有窗口vec_win_info,然后查询每个窗口对应的所有菜品(调用saleDao中的查询函数),最后根据返回的菜品状态status,将status值为STATUS_exam(审批中)的菜品挑选出来返回。
bool selectExam(int exam_id,int& org_id,int& user_id);
加盟审批查询,根据exam_id查询,返回组织id和userid,单个查询。
根据给出的审查编号,查询joinapproval表,获取对应的组织id和用户id并返回。
bool alterJionExam(int exam_id,int status);
加盟审批状态修改 。
根据给出的审批id修改joinapproval表中的status字段为传递的参数(通过/不通过)
bool addJionExam(User user,OrganizationInfo org);
用户加入组织的请求。
通过给出的user信息和组织信息,提取出用户id和组织id,插入joinapproval表,表示用户要申请加入该组织。
bool addWindowExam(User user,WindowInfo win);
窗口申请。
通过给出的user信息和窗口信息,提取出用户id和窗口id,插入windowapproval表,表示用户要申请加入该窗口。
bool alterWindowExam(int exam_id ,int status);
窗口修改。
根据给出的审批id修改windowapproval表中的status字段为传递的参数(通过/不通过)
orderdao
void insert(Bill bill);
将获取到的订单(bill对象)拆解出不同字段,插入bill表。
void selectDuring(WindowInfo win,QDate start,QDate end,vector<Bill> &bills);
查询某个时间段内的指定窗口(id)订单信息。
根据窗口信息和开始结束时间,从bill表里面查询符合条件的所有订单信息并返回。
orderroledao
vector<OrderRoleBase*> selectAllRole(WindowInfo win);
查询指定窗口中的所有订单优惠。
通过给出的窗口信息提取出window_id,先查询discount表获取到当前窗口的所有优惠信息,然后查询discountDetail表获取优惠信息作用的具体商品列表,最终返回一个包含所有优惠信息的vector。
bool insert(OrderRoleBase* orderRole,WindowInfo win);
为某个窗口添加订单优惠信息。
通过windowInfo参数获取到要插入优惠信息的窗口id,然后解析待插入的所有优惠信息,插入discount表,再将详细的商品打折信息插入discount Detail表
bool del(OrderRoleBase* orderRole,WindowInfo win);
将某个窗口的优惠信息删除。
根据给出的窗口信息和优惠信息,先删除discountDetail表中所有符合的优惠详情信息,然后删除discount表中对应的一条记录。
organizationdao
void addOrg(OrganizationInfo& org);
新增组织。
通过所给的组织信息,向organizationinfo表中插入一条记录。
bool alterOrg(OrganizationInfo org);
修改组织。
通过获取传入的组织信息,获取到组织名称、地址、账户等信息,然后将organizationinfo表中对应的记录更新为传入的新信息。
bool delOrg(OrganizationInfo org);
删除组织。删除指定的一个组织。
vector<OrganizationInfo> selectAll();
查询所有组织。
查询organizationinfo表中的所有信息,每一条记录构造成一个organization Info对象,最终返回一个vector。
vector<OrganizationInfo> selectOrg(User& user);
根据user查询所属的组织。
先根据userID查询joinapproval表获取到用户对应的组织,然后在organizationInfo表中查询该组织的信息。
saledao
void insertSale(SaleBase* sale,WindowInfo win);
为窗口插入售卖的食物。
根据给出的商品信息和窗口信息,将商品上架到对应的窗口内,先把商品的图片信息插入picture表(如果不存在),再将商品信息插入saleUnit表(单品),如果是套餐的话就依次插入saleunit、fodType、setFoodType三个表。
void alterSale(SaleBase*& sale);
修改窗口中的某个食物。
直接将传入的新的食物信息插入saleunit表。
void delSale(SaleBase*& sale);
删除窗口中的某个食物。
先删除saleUnit表中的商品信息,然后再删除foodType表中的信息和setFoodtype表中的套餐信息,最后删除picture表中对应的图片信息。
void selectSale(SaleBase*& sale);
根据id,以及食物类型,查询单个数据。
先从saleUnit查询食物信息,再从picture表查找对应的图片信息。
vector<SaleBase*> selectFoodType(int foodType_id,string& name);
查询某个类型的所有食物id。
从foodType表中查找type_id 为传入商品id的全部食物的信息
vector<SaleBase*> selectAllSale(WindowInfo win);
查询指定窗口中的所有售卖食物。
从saleunit中查找商品的window_id为目标id的全部商品并且返回。
bool examFood(SaleBase* sale,int status);
菜品审批。
修改saleUnit表中的status值为传入的参数(通过/不通过)。
userdao
bool getUser(User& user);
查找用户,根据用户名和密码在user表中查找用户,返回查找结果,用户名和密码对应成功返回true。
bool insertUser(User& user);
用户注册,在user表中插入用户信息,先检查用户是否存在,用户名存在则注册失败。
bool alterUser(User& user);
用户信息修改
bool delUser(User& user);
用户注销,需要删除所有与该用户相关的窗口等信息。
windowdao
bool insertWindow(WindowInfo& win);
窗口添加;
根据窗口信息,在windowInfo表中插入一条记录。
bool alterWindow(WindowInfo win);
窗口修改;根据窗口信息,
将windowinfo表中id对应的一行的信息更新成新的。
bool delWindow(WindowInfo win);
窗口删除;
直接在windowInfo表中删除对应的一行即可。
bool selectWindow(WindowInfo& win);
根据窗口id查询整个窗口信息;
查询windowInfo表中id符合的一条记录并返回。
vector<WindowInfo> selectUserWindow(User user);
查询某个用户名下所有窗口信息,只需要有用户电话号码即可,逻辑上应该判断是否成功登录。
vector<WindowInfo> selectUserApplyWindow(User user);
查询用户申请的所有未通过的窗口;
查找WindowInfo表,找到所有id匹配并且statue为未通过(2)的记录并返回。
vector<WindowInfo> selectOrgWindow(OrganizationInfo org);
查询某个组织下的所有窗口信息;
查找WindowInfo表找到组织id匹配的所有记录,并返回。
vector<WindowInfo> selectSpareWindow(User user);
查询空闲窗口(user用户可以申请加入的窗口);
4.3Controller
managercontroller
bool login(User& user);
用户登录,返回是否登录成功,以及用户信息查询查询,用户名密码错误则返回false;
bool registerUser(User& user,OrganizationInfo& org);
用户注册,存在则返回失败,成功返回true,注册信息需要带有组织信息;
先做合法性检查:电话号码11位、电话号码纯数字、用户类型要匹配、身份证出了X/x外只能有数字、性别只能为M/F。若检查都通过的话,就调用UserDao写入用户信息、调用OrganizationDao写入一条组织信息。
bool addWindow(WindowInfo& win);
添加窗口;
先调用windowDao来判断系统内是否有这个窗口的信息,如果没有再利用windowDao插入一个组织的信息
bool examJoin(int exam_id, int status);
审批加盟申请;
直接调用ApproveExam(Dao)的修改状态函数,将ststus字段改为审批后的状态(通过/不通过)。
bool selectOrgJoin(vector<User>& users,vector<int> &exam_ids);
查询组织下的所有待审批的加盟申请对应的用户,调用approveExam中的同名函数即可。
bool selectOrgExamWindow(vector<WindowInfo>& wins,vector<User>& users,vector<int>& exam_ids);窗口申请的信息查询,调用approveExam中的同名函数即可
bool examWindow(int exam_id,WindowInfo win,int status);
商家开窗口申请审批,调用ApproveExam中的窗口审批状态修改函数。
更改exam_id对应审批的窗口在windowInfo表中的状态(status)信息,(若状态为审批通过,则窗口为运营态,否则为空闲态)
bool selectOrgExamFood(vector<SaleBase*>& sales,vector<WindowInfo>& wins);
查询组织下的所有审批的菜品,调用approveExam中的同名函数即可
bool examFood(SaleBase* sale,int status);
菜品审批,相当于修改表中的status值,调用saleDao中的同名函数
merchantcontroller
bool login(User& user);
用户登录,返回是否登录成功,以及用户信息查询查询,用户名密码错误则返回false
bool registerUser(User& user);
用户注册,存在则返回失败,成功返回true。
先做合法性检查:电话号码11位、电话号码纯数字、用户类型要匹配、身份证出了X/x外只能有数字、性别只能为M/F。若检查都通过的话,就调用UserDao写入用户信息。
bool getWindows(vector<WindowInfo> &wins);
查询用户的所有窗口,调用windowDao查询。
bool openWindow(WindowInfo& win,WindowController& wincol);
打开某个窗口,根据窗口id打开,返回所有的销售食物,以及窗口的优惠状况。
根据已有的windowInfo 查询窗口的所有信息,构造一个WindowController,返回回去即可。windowsinfo 的构造需要调用 类里面的 set函数,将各个字段置为想要的值,目标值用dao操作一个个查询出来。
bool joinOrg(OrganizationInfo org);
申请加入组织,调用approexam向申请表里面插入一条申请记录。
bool getAllOrg(vector<OrganizationInfo>& orgs);
查询所有可加入的组织。
bool getApplyWindow(vector<WindowInfo>& wins);
查询所有可以申请的窗口,调用windowDao函数。
bool applyWindow(WindowInfo win);
窗口申请,指定窗口id,调用approveExam中的addWindowExam函数插入。
windowcontroller
void setWindow(WindowInfo win_){win=win_;}
为保护成员win赋值
void setSales(vector<SaleBase*> sales_){sales=sales_;};
为保护成员sale赋值
void setorderRoles(vector<OrderRoleBase*> orderRoles_){orderRoles=orderRoles_;};
为保护成员orderrole赋值
void setUser(User user_){user=user_;}
为保护成员user赋值
WindowInfo getWindow(){return win;}
获取保护成员win的内容
vector<SaleBase*> getSales(){return sales;}
获取保护成员sales的内容
vector<OrderRoleBase*> getOrderRoles(){return orderRoles;}
获取保护成员orderole的内容
void clearOrder(){order.clear();}
清除保护成员order的内容。
User getUser(){return user;}
获取保护成员user的内容
Order getOrder(){return order;}
获取保护成员order的内容
void addSelectSale(SaleBase* sale);
为订单添加食物,先把食物的状态改为审批中,然后调用orderdao的addfood函数将食物插入数据库。
void addSelectOrderRole(OrderRoleBase* orderRole);
为订单添加优惠。直接调用order.addFood即可。
void delSelectSale(unsigned int i);
删除第i个选择的食物。
void delAllOrderRole();
删除所有的优惠
void delOrderRole(unsigned int i);
删除第i个选择的优惠
bool checkOut();
结算,进行收费操作,数据库操作,返回收费是否成功
bool addSale(SaleBase* sale);
为窗口添加菜;调用saledao,将传入的菜品信息插入到当前对象对应的win id中。
bool addOrderRole(OrderRoleBase* _order);
为窗口添加优惠。调用orderroleDao,将传入的优惠信息插入到当前对象对应的win id中。
##4.4 model
Model中有简单的结构体,也有需要衍生的多样性对象,分为两小节分别详细描述如下。
简单对象
系统中,较为简单的对象有用户User,订单Order,窗口信息WindowInfo,组织机构信息OrganizationInfo,账单信息Bill,这几个只需要简单的结构体进行存储就可以完成相应的功能。具体定义于数据库中的表定义相同,具体属性描述如下:
- User中有如下属性:
string phone;//用户名与电话号码同,
string password;//密码
int type;//用户类型,0:商家 1:管理员
string identityCard;//身份证号
string name;//用户姓名
string sex;//用户性别
string creditCard;//银行卡号,用于账目流转
- Bill账单表中有如下属性:
int order_id;//内部订单id
string username;//商家用户名(电话号码)
int window_id;//窗口id double price;//价格
string tradingTime;//交易时间 int status;//交易状态
string description;//订单详细信息
- WindowInfo窗口信息有如下属性:
int id;
string name;//窗口名称
string address;//窗口地址
int organization_id;//所属组织id
int f_id;//设备id
int status;//窗口状态
- OrgnitionInfo中有如下属性:
int id;
string name; //组织机构名称
string address; //组织地址
string creditCard;//组织银行密码,用于流水走向
多样性对象
首先是菜品对象,菜品对象抽象出基类SaleBase,存储了一个菜品最基本的信息,不管是任何菜品,这些信息均需要全部包含,具体信息如下:
int id;//食物id
int window_id;//所属窗口id
string name;//食物名称
double price;//食物单价
int status;//食物状态 0:售卖中 1:停售 2:审批中
string diningType;//餐饮类型
string unit;//食物单位
QPixmap image;//食物图片
string desciption;//食物描述
对于每个食物的具体价格计算,重写 getPayable()函数进行返回不同的价格,单个售卖,重量售卖和套餐售卖衍生类与基类SaleBase的关系类图如下图所示:
通过这个设计模型,可以很好的增加食物类型的扩展性,在上层,对象的所有生成代码可以不用改变。有效的利用代码的复用性,通过实现DaoBase接口,完成对象与数据库的交互,每个子类的价格计算方式不同。对于函数成员:
virtual double getPayable()//获取用户选择规格重量等后的应付价格
int getClassType()//获取售卖类型
virtual string toString()//转换成字符串描述,在进行对外展示时,通过这个来进行展示
对于打折活动对象的抽象,抽象出OrderRoleBase对象,对象中设置虚函数设置如下:
double priceAdjustment(vector<SaleBase*> sales,double curPrice),
这个函数就是这个活动对象的核心,通过实现这个函数,实现不同的活动作用在价格上,生成最终不同的价格。
virtual string toString(),
这个函数用于描述优惠活动,
int id;
string description;//优惠描述
double value;//作用值
vector<int> scope;//该优惠作用于哪些食物id上
int type;//优惠类型,有几种固定的取值
在本次活动中我们实现了两种优惠活动,一种是减价SubOrderRole,一种是打折MulOrderRole ,继承于基类OrderRoleBase对象。类图描述如下:
单工厂模式
在前面的设计中,涉及到不同对象的生成,虽然我们每个派生类都实现了我们定义的序列化接口,但是在对象的空间分配上,并没有做设计,所以我们需要设计一个工厂,来实现对象的内存空间分配,对象的具体数据生成,由对象自己内部进行生成。在设计工厂时,我们的对象生成较为简单,我们可以只通过简单工厂模式就可以生成,即根据type值的不同,直接生成相应的对象,但是当新增了新的类型时,我们需要修改这个部分代码,违反了开闭原则,所以我们进行了一个小改进,抽象出一个简单工厂父类,当父类中不认识type值的对象时,无法生成对象则直接生成空对象,新增的type由新的工厂继承于父类,重新生成,在类图上描述如下:
在子类的衍生中,先调用父类的生成函数,判断是否为空,若不为空,说明父类已经完成对象生成,若为空,说明是我们子类需要生成的对象,进行判断swich判断生成对象,分配空间即可,结合c++代码描述如下:
SaleBase* SaleFectory::CreateSale(int type)
{
Q_UNUSED(type);
return nullptr;
}
SaleBase* InitSaleFectory::CreateSale(int type)
{
SaleBase* sale=SaleFectory::CreateSale(type);
if(sale!=nullptr)
return sale;
switch (type) {
case SaleBase::CLASS_SALEBASE:
return new SaleBase;
case SaleBase::CLASS_SALEUNIT:
case SaleBase::Class_SIDEDISH://配菜和单品一样
return new SaleUnit;
case SaleBase::CLASS_SALEWEIGHT:
return new SaleWeight;
case SaleBase::CLASS_SALESETMEAL:
return new SaleSetMeal;
default:
return nullptr;
}
}
4.5服务器
收租程序
每周日晚上10点,饮食中心结算每个窗口的经营额,抽取租赁费用后,返还剩余金额。
收取规则:
-
每周收取基础服务费
100
元。 -
营业额2万元以下收取
5%
的租赁费。 -
2万以上的部分收取
10%
的租赁费。
添加系统账户流水记录表collect_rent
一条记录表示,一个组织给一个商家发放一次工资。
字段 | 类型 | 说明 |
---|---|---|
流水id | 自增 | |
组织id | 参考org:id | |
窗口id | 参考win:id | |
该周期营业额(总) | ||
发放给商家的金额 | ||
时间 | 自动获取当前时间 | |
说明 | ||
执行流程
- 首先查询organization Info 找到所有的组织id
- 在bill表里面,遍历所有的组织id
- 遍历bill表里面隶属于组织id的window 对应的全部订单。
- 累加近一周的全部订单金额
- 计算要收取的服务费
- 写入
执行
使用crontab定时执行脚本:
- 运行 sudo vim /etc/crontab
- 添加下述内容:
0 22 * * 0 root python /home/xxx/xxx/collect_rent.py