项目总结 面向小型店面的餐饮 SaaS 平台

前言

项目来源

2023 年春季学期课程设计项目

  • 项目架构选定 完成《云计算与软件服务》课程中本应作为结题作业,但是因为课程安排而废除的 SaaS 项目

  • 项目需求选定 顺接 2023 年春季学期《ERP 和 SCM》课程,巩固对于 EPR 系统的理解,小型餐饮店也存在物料管理的需求,借此复习 ERP 系统中 BOM 这一抽象机制

技术选型

课程设计,选用团队最熟悉的技术

  • 前端

    • Bootstrap:使用现成组件,减少样式相关代码量

    • jQuery: 引入 Ajax,用于实现与后端的接口交互

    • Vue:引入便利的数据绑定机制和标签逻辑控制机制,方便进行数据展示

  • 后端

    • Spring:引入便利的依赖注入机制和强大的插件支持机制

    • Spring MVC:引入 RESTful 架构风格,便于实现简洁的接口形式

    • MyBatis:引入便利的数据库持久层框架,减少 JDBC 相关代码

    • MyBatis Plus:引入内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作

    • MySQL:个人最熟悉的关系型数据库,可以支撑小规模用户量软件系统的数据存取需求

  • 部署

    • Docker:标准化应用发布,可以跨平台和主机使用;节约时间,方便快速部署和启动

需求分析

实地调查

调查校内大学生服务中心中的十多家餐饮店,发现店内职员基本上都在 十人以内不同店内分工方式基本一致,可以抽象为:前台员工、后台员工、店主、采购人员

为什么建立一套面向小型店面的餐饮 SaaS 系统是可行的?
平台开发者视角

有着相近的需求,能采用通用的架构抽象是保障建立 SaaS 平台的可行性的关键

小型餐饮店面对于数字化的需求基本一致,可以抽象为以下五大模块

  • 提供员工管理功能

    • 员工信息管理:能够对员工信息进行录入、修改、查询

    • 员工信息展示:以表格形式展示员工信息

    • 员工权限管理:能够根据员工权限进行操作限制

  • 提供库存管理功能

    • 库存信息管理:能够对库存信息进行录入、修改、查询

    • 库存信息展示:以表格形式展示库存信息

  • 提供菜单管理功能

    • 菜单信息管理:能够对菜单信息进行录入、修改、查询

    • 菜单信息展示:以表格形式展示菜单信息

    • BOM 管理:能维护菜单的物料清单(产品结构表)

  • 提供订单管理功能

    • 订单信息管理:能够对订单信息进行录入、修改、查询

    • 订单信息展示:以表格形式展示订单信息

  • 提供流水展示功能:可视化展示近期销售情况,辅助库存管理

平台使用者视角

有数字化需求的小型店面买断并维护软件产品的成本高,而 SaaS 平台无需本地部署也无需维护,随租随用,按需付费,击中 用户期望数字化但是没有能力维护一套数字化系统 这一痛点。小型店面在引入一套 SaaS 平台之后,可以以较低的成本享受到数字化赋能带来的便利。降低用户引入软件系统的门槛,让更多人以更低的成本用上更优质的软件服务,是软件服务系统开发人员的一大职责

为什么建立一套面向小型店面的餐饮 SaaS 系统是必要的?
软件形态变化趋势视角

服务化是实现软件数字资产有效复用的关键手段,是软件近几年也是未来几年都会流行的新形态

绿色节能视角(社会价值视角)

SaaS 平台可以实现统一管理,绿色高效,将所有店面的数据进行集中管理,避免了数据分散、重复存储等问题,同时还能够实现高效的数据调用和共享,提高了工作效率。

OOD

SaaS 平台视角
领域类图

Model!SaaS 平台领域类图_3

用例图

Model!SaaS 平台用例图_1

用例描述
  • 租户通过注册实现 SaaS 应用配置

  • 用户通过登录使用 SaaS 应用

SaaS 应用视角
领域类图

Model!SaaS 应用领域类图_0

用例图

Model!SaaS 应用用例图_2

用例描述
  • 租户注册界面

    1. 租户录入注册信息

    2. 租户跳转至登录界面

  • 用户登录界面

    1. 用户录入登录信息

    2. 用户跳转至注册界面

  • 新增订单界面

    • 店主或前台工作人员录入订单信息

  • 订单看板界面

    • 店主或后台工作人员修改订单、删除订单或查看订单

团队分工

按模块进行分工,本人负责 SaaS 基础环境的搭建和订单模块的实现

个人作为团队组长,除了负责上述开发职责以外,额外负责团队工作计划安排的职责

工作计划安排

小组工期
时间任务完成情况成果物
20230530 开题 完成项目的需求分析和整体设计,并做好组内分工,以及团队开发环境的建立(GitHub 共享仓库创建、ECS 基础环境搭建) 任务书
20230605 中期 完成项目的主要功能(单租户项目) 本地项目
20230609 结题 完成项目的 SaaS 化(引入动态生成和绑定数据库表的机制),将项目打包成 Docker 镜像,并将完整的项目部署到阿里云 ECS 上 已部署项目 项目源码
个人工期
时间任务完成情况成果物
20230530 开题 完成项目选题、模块设计、详细设计,组织组内分工,完成 GitHub 共享仓库的创建 任务书
20230605 中期 完成 ECS 环境搭建,完成订单管理模块 本地项目
20230609 结题 完成租户信息管理模块、数据表生成模块,完成项目的打包与部署 已部署项目 项目源码

系统设计

数据库设计

本人负责 SaaS 基础环境的搭建和订单模块的实现,相关的数据库表包括

  • tenant 表:租户信息表,存储租户信息

  • order 表:订单信息表,存储订单信息

  • order_dish_relation 表:订单菜品关系信息表,存储订单与菜品之间的关联信息

下面针对个人负责部分相关的数据库表进行完整性约束的说明

tenant 表
列名说明数据类型主键唯一性约束非空约束
shop_name 店面名称 varchar(255) UNIQUE约束 NOT NULL约束
tenant_name 租户姓名 varchar(255) NOT NULL约束
registration_number 营业执照注册号 varchar(255) PRIMARY KEY NOT NULL约束
tel 租户电话 varchar(255) UNIQUE约束 可空
tenant_pwd 租户密码 varchar(255) NOT NULL约束
order 表
列名说明数据类型主键唯一性约束非空约束
id 订单Id bigint PRIMARY KEY NOT NULL约束
custom_name 客户姓名 varchar(20) NOT NULL约束
custom_phone 客户电话 varchar(11) NOT NULL约束
payment_mode 付款模式 varchar(20) NOT NULL约束
cost 总价 decimal(10, 2) NOT NULL约束
time 下单时间 varchar(50) NOT NULL约束
state 订单状态 int NOT NULL约束,DEFAULT约束(默认值为0)
order_dish_relation 表
列名说明数据类型主键唯一性约束非空约束
id 订单菜品关系Id bigint PRIMARY KEY NOT NULL约束
dish_id 订单Id varchar(32) NOT NULL约束
material_id 原料Id varchar(32) NOT NULL约束
material_count 原料数量 decimal(10, 2) NOT NULL约束

数据流图

 
数据获取
数据传递
数据库操作发送
数据库操作结果
数据处理完毕
把数据发送到界面
用户界面
前端处理
后端服务器
数据库系统
 

数据字典

本人负责 SaaS 基础环境的搭建和订单模块的实现,相关的数据库表包括

  • tenant 表:租户信息表,存储租户信息

  • order 表:订单信息表,存储订单信息

  • order_dish_relation 表:订单菜品关系信息表,存储订单与菜品之间的关联信息

下面针对个人负责部分相关的数据库表进行数据字典的说明

tenant 表
字段名数据类型是否为 NULL主键唯一约束描述
shop_name varchar(255) NOT NULL - UNIQUE 餐饮店店名(唯一)
tenant_name varchar(255) NOT NULL - - 租户姓名
registration_number varchar(255) NOT NULL - 营业执照注册号(主键)
tel varchar(255) NULL - UNIQUE 电话号码(唯一)
tenant_pwd varchar(255) NOT NULL - - 租户密码
order 表
字段名数据类型是否为 NULL主键唯一约束描述
id bigint NOT NULL - 订单编号(主键)
custom_name varchar(20) NOT NULL - - 客户姓名
custom_phone varchar(11) NOT NULL - - 客户电话
payment_mode varchar(20) NOT NULL - - 支付方式
cost decimal(10, 2) NOT NULL - - 订单金额
time varchar(50) NOT NULL - - 下单时间
state int default 0 NOT NULL - - 订单状态(默认值为0)
order_dish_relation 表
字段名数据类型是否为 NULL主键唯一约束描述
id bigint NOT NULL - 订单详情编号(主键)
order_id varchar(32) NOT NULL - - 订单编号
dish_id varchar(32) NOT NULL - - 菜品编号
dish_count int NOT NULL - - 菜品数量

数据库逻辑结构设计

本人负责 SaaS 基础环境的搭建和订单模块的实现,相关的数据库表包括

  • tenant 表:租户信息表,存储租户信息

  • order 表:订单信息表,存储订单信息

  • order_dish_relation 表:订单菜品关系信息表,存储订单与菜品之间的关联信息

下面针对个人负责部分相关的数据库表的数据库逻辑结构设计进行说明

tenant 表
  1. 实体:租户(Tenant)

  2. 属性:

    • 租户ID(Registration_Number):主键,唯一标识一个租户

    • 餐饮店店名(Shop_Name):唯一,一个餐饮店只有一个店名

    • 租户姓名(Tenant_Name)

    • 电话号码(Tel):唯一,一个电话号码只能对应一个租户

    • 租户密码(Tenant_Pwd)

因此,租户实体与属性的关系为一对多关系,即一个租户对应一个餐饮店店名、一个租户姓名、一个电话号码和一个租户密码,而一个餐饮店只能对应一个租户。

order 表
  1. 实体:订单菜品关系(OrderDishRelation)

  2. 属性:

    • 关系ID(ID):主键,唯一标识一个订单菜品关系

    • 订单ID(Order_ID):对应订单表中的订单ID

    • 菜品ID(Dish_ID):对应菜品表中的菜品ID

    • 菜品数量(Dish_Count)

因此,订单菜品关系实体与属性的关系为多对一关系,即一个订单菜品关系对应一个订单ID、一个菜品ID和一个菜品数量,而一个订单可以对应多个订单菜品关系。

order_dish_relation 表
  1. 实体:订单(Order)

  2. 属性:

    • 订单ID(ID):主键,唯一标识一个订单

    • 客户姓名(Custom_Name)

    • 客户电话(Custom_Phone)

    • 支付方式(Payment_Mode)

    • 订单金额(Cost)

    • 下单时间(Time)

    • 订单状态(State):默认为0,表示未处理

因此,订单实体与属性的关系为一对多关系,即一个订单对应一个客户姓名、一个客户电话、一个支付方式、一个订单金额、一个下单时间和一个订单状态,而一个客户可以对应多个订单。

完整性约束设计与实现

本人负责 SaaS 基础环境的搭建和订单模块的实现,相关的数据库表包括

  • tenant 表:租户信息表,存储租户信息

  • order 表:订单信息表,存储订单信息

  • order_dish_relation 表:订单菜品关系信息表,存储订单与菜品之间的关联信息

下面针对个人负责部分相关的数据库表的完整性约束的设计与实现进行说明

设计
  1. 租户表(Tenant):

    • 注册号(Registration_Number):作为主键,不能为空值,不能重复

    • 餐饮店店名(Shop_Name):不能为空值,不能重复

    • 租户姓名(Tenant_Name):不能为空值

    • 电话号码(Tel):可以为空值,但若填写则不能重复

    • 租户密码(Tenant_Pwd):不能为空值

  2. 订单菜品关系表(OrderDishRelation):

    • 关系ID(ID):作为主键,不能为空值

    • 订单ID(Order_ID):不能为空值,且必须是订单表中已有的订单ID

    • 菜品ID(Dish_ID):不能为空值,且必须是菜品表中已有的菜品ID

    • 菜品数量(Dish_Count):不能为空值,且必须大于0

  3. 订单表(Order):

    • 订单ID(ID):作为主键,不能为空值

    • 客户姓名(Custom_Name):不能为空值

    • 客户电话(Custom_Phone):不能为空值

    • 支付方式(Payment_Mode):不能为空值

    • 订单金额(Cost):不能为空值,且必须大于0

    • 下单时间(Time):不能为空值

    • 订单状态(State):不能为空值,且只能为0(未处理)或1(已完成)

实现

实际实现时,可以通过在建表时添加约束条件的方式来实现上述完整性约束。

  1. 租户表(Tenant):

create table tenant
(
  shop_name           varchar(255) not null comment '餐饮店店名(唯一)',
  tenant_name         varchar(255) not null comment '租户姓名',
  registration_number varchar(255) not null comment '营业执照注册号(主键)'
       primary key,
  tel                 varchar(255) null comment '电话号码(唯一)',
  tenant_pwd          varchar(255) not null comment '租户密码',
   constraint unique_tel
       unique (tel)
);
  1. 订单菜品关系表(OrderDishRelation):

create table order_dish_relation
(
  id         bigint      not null
       primary key,
  order_id   varchar(32) not null,
  dish_id    varchar(32) not null,
  dish_count int         not null,
   constraint fk_order
       foreign key (order_id) references orders(id),
   constraint fk_dish
       foreign key (dish_id) references dish(id)
);
  1. 订单表(Order):

create table orders
(
  id           bigint         not null
       primary key,
  custom_name  varchar(20)    not null,
  custom_phone varchar(11)    not null,
  payment_mode varchar(20)    not null,
  cost         decimal(10, 2) not null,
   time         varchar(50)    not null,
  state        int default 0  not null check (state in (0, 1))
);

模块设计

租户管理模块

租户管理模块的主要功能是

  • 实现租户信息的录入

  • 利用租户信息生成一组数据库表 表名前缀如下,后缀为租户信息中的企业营业执照注册号

    • dish

    • dish_material_relation

    • material

    • material_buys

    • order_dish_relation

    • orders

    • user

  • 将租户信息存入新生成的 user 表中

订单管理模块
  • 订单信息录入

  • 订单信息更改:将订单状态从未完成修改为已完成

  • 订单删除

功能模块图

 
 
 
 
 
 
租户管理模块
生成数据库表功能
租户信息存入用户表功能
订单管理模块
录入订单信息功能
更改订单状态功能
订单删除功能
 

类图

边界类 Controller

Model!边界类_11

控制类 Service

Model!控制类_12

实体类 Entity

Model!实体类_13

租户管理模块相关类图

Model!租户管理模块类图_9

订单管理模块相关类图

Model!订单管理模块类图_10

系统体系结构设计

SaaS 系统通用双层架构

SaaS 平台.drawio

 

SaaS 应用软件体系结构

架构图.drawio

 

系统核心模块实现

类别选项
编程语言 Java(后端业务实现) JavaScript(前端逻辑实现)
开发环境 IDEA(后端代码编写) Visual Studio Code(前端代码编写)
支撑软件 Navicat(数据库图形化界面) Git(团队代码管理) Postman(后端接口测试) Docker(提供一套容器化的打包和部署方案)

核心模块功能描述

租户表生成功能描述

租户注册后,自动为租户生成一套数据库表,以实现 Schema 级别的隔离

订单联动库存更新功能描述

修改订单状态为已完成后,自动更新库存信息,以实现实时可行订单的生成

核心模块工作流程

租户表生成流程
  1. 判断租户是否存在

  2. 若租户不存在,继续执行 3,若租户存在,则抛出 BusinessException ,向前端返回错误信息

  3. 在租户表中插入租户信息

  4. 利用租户信息,生成一套租户独占的表格 表名前缀如下,后缀为租户信息中的企业营业执照注册号

    • dish

    • dish_material_relation

    • material

    • material_buys

    • order_dish_relation

    • orders

    • user

  5. 在生成的 user 表中存入租户信息(租户本身也是其应用的用户之一)

  6. 向前端返回租户信息,表示租户注册成功,租户独占表格生成成功

 
 
租户不存在
租户存在
 
 
 
 
开始
判断租户是否存在
利用租户信息,生成一套租户独占的表格
抛出 BusinessException,返回错误信息
在生成的 user 表中存入租户信息
向前端返回租户信息,表示租户注册成功,租户独占表格生成成功
结束
 

 

订单联动库存更新流程
  1. 首先,开始函数,开始处理散客点单全链路服务;

  2. 接着,根据订单的 ID 直接更新订单状态;

  3. 然后,获取对应订单 ID 下的菜品和订单关系列表,即 OrderDishRelation 列表;

  4. 然后,根据每个 OrderDishRelation 中的 dishId 去 DishMaterialRelation 表中查询该菜品所需的原料信息列表,即 DishMaterialRelation 列表;

  5. 接下来,对于每一个 DishMaterialRelation 条目,从 Material 表中根据其 materialId 获取原有的 count 数量,并将该值记录为oldCount;

  6. 然后,使用 DishMaterialRelation 和 dish_count 两个参数去修改 Material 的 count 属性,得到新的 count 值,并将该值记录为newCount;

  7. 最后,使用 updateCountByIdByMyBatis 函数,将新的 count 值保存到 Material 表中,以完成对修改后的材料库存数量的更新。循环执行步骤 4 至步骤 7,直到处理完所有的 DishMaterialRelation 条目。

  8. 当所有订单处理完毕后,程序结束,整个更新订单的过程也就结束了。

 
 
 
 
 
 
 
有未处理的 OrderDishRelation,继续处理
所有 OrderDishRelation 都已处理完毕,结束本次操作
开始
根据订单ID直接更新订单状态
获取 OrderDishRelation 列表
获取 DishMaterialRelation 列表
获取 Material 原有的 count
修改 Material 的 count
检查是否还有未处理的 OrderDishRelation
结束
 

核心模块代码详解

租户表生成代码详解

函数名称:register

输入参数:

  • Tenant tenant:一个Tenant对象,表示待注册的租户信息。

输出结果:

  • Tenant:注册成功后返回注册好的租户对象。

核心算法:

  1. 判断输入的租户对象是否为空,如果是则抛出空对象异常。

  2. 向数据库中插入新的租户,如果插入失败则抛出参数错误异常。

  3. 查询数据库中是否已经存在相同名称的租户,如果存在则抛出参数错误异常,并返回已经存在的租户对象。

  4. 根据新注册的租户编号,生成对应的数据库表。

  5. 向新生成的租户表中插入管理员用户信息。

  6. 返回新注册的租户对象。

关键函数定义代码及注解:

@Override
public Tenant register(Tenant tenant) {
   //判断输入租户对象是否为空,若为空则抛出空对象异常
   if (tenant == null) throw new BusinessException(ErrorCode.NULL_ERROR);

   //查询数据库中是否已经存在相同名称的租户,如果存在则抛出参数错误异常,并返回已经存在的租户对象
   QueryWrapper<Tenant> wrapper = new QueryWrapper<>();
   wrapper.eq("shop_name", tenant.getShopName());
   Tenant existTenant = tenantMapper.selectOne(wrapper);
   if (existTenant != null) {
       throw new BusinessException(ErrorCode.PARAMS_ERROR, "租户已存在" + existTenant);
  }

   //向数据库中插入新的租户,如果插入失败则抛出参数错误异常
   int result = tenantMapper.insert(tenant);
   if (result != 1) {
       throw new BusinessException(ErrorCode.PARAMS_ERROR, "新增租户失败");
  }

   //根据新注册的租户编号,生成对应的数据库表
 generatedTableService.createTablesByRegistrationNumber(tenant.getRegistrationNumber());

   //向新生成的租户表中插入管理员用户信息
   TenantTableNameHandler.setData(tenant.getRegistrationNumber());
   User user = new User();
   user.setName(tenant.getTenantName());
   user.setJobNumber(BOSS_EMPLOYEE_ID);
   user.setPhone(tenant.getTel());
   user.setPassword(tenant.getTenantPwd());
   user.setSex("");
   user.setPower(0);
   userMapper.insert(user);
   TenantTableNameHandler.removeData();

   //返回新注册的租户对象
   return tenant;
}
订单联动库存更新代码详解

函数名称:updateOrdersByMyBatis

输入参数:

  • registrationNumber: String类型,表示订单所在的编号

  • orders: List<Order>类型,表示需要更新的订单列表

输出结果:无

函数功能:此函数用于更新指定编号下的订单信息,并修改订单状态为FINISHED。在更新订单状态之后,还需要根据订单关联的菜品和食材信息,更新相应的库存数量。

核心算法:

  1. 遍历订单列表orders中每一个订单order。

  2. 根据订单id更新订单状态为FINISHED。

  3. 根据订单关联的菜品和食材信息,更新相应的库存数量。

    1. 获取order关联的菜品和订单的关系orderDishRelations,遍历每一个orderDishRelation。

    2. 获取orderDishRelation关联的菜品和食材关系dishMaterialRelations,遍历每一个dishMaterialRelation。

    3. 获取dishMaterialRelation关联的食材material,计算新的库存数量newCount,更新material的库存。

关键函数定义代码及注解:

//更新订单,用于修改状态 UNFINISHED → FINISHED
//加入事务,保证一组数据库均完成后才完成,否则回滚
@Transactional
@Override
public void updateOrdersByMyBatis(String registrationNumber, List<Order> orders) {
   System.out.println("updateOrdersByMyBatis");
   System.out.println("orders.size()");
   System.out.println(orders.size());
   //更新时,前端传入修改后的对象,此处根据 ID 直接更新即可
   for (Order order : orders) {
       System.out.println(order.toString());
       // 获取 OrderDishRelation By orderId From Table OrderDishRelation
       List<OrderDishRelation> orderDishRelations = orderDishRelationMapperByMyBatis.selectListByOrderId(registrationNumber, order);
       for (OrderDishRelation orderDishRelation : orderDishRelations) {
           System.out.println("orderDishRelations.size()");
           System.out.println(orderDishRelations.size());
           System.out.println(orderDishRelation.toString());
           // 获得 DishMaterialRelation By OrderDishRelation 的 dishId From Table DishMaterialRelation
           List<DishMaterialRelation> dishMaterialRelations = dishMaterialRelationMapperByMyBatis.selectListByDishId(registrationNumber, orderDishRelation);
           for (DishMaterialRelation dishMaterialRelation : dishMaterialRelations) {
               String MaterialId = dishMaterialRelation.getMaterialId();
               // 获取 Material 原有的 count By materialId From Table Material
               Material material = materialMapperByMyBatis.selectByIdByMyBatis(registrationNumber, MaterialId);
               BigDecimal oldCount = material.getCount();
               // 根据 DishMaterialRelation 和 dish_count 修改 Material 的 count
               float newCount = oldCount.floatValue()
                   - dishMaterialRelation.getMaterialCount().floatValue() * orderDishRelation.getDishCount();
               //可行订单控制
               // 一个菜品对应多种原材料,不保证每一种原材料数量都不够,也不能保证原材料的顺序(即不能保证不够的放在前面)
               // 潜在风险 1:只要有一种原材料数量够,就会使得订单的状态置为已完成
               // 解决方案 1:订单状态修改应该在可行订单状态判断之后
               // 潜在风险 2:只要有一种原材料数量够,该原材料就会被扣除
               // 解决方案 2:保证所有数据库操作要么同时执行,要么同时不执行 By 引入事务
               if (newCount < 0) {
                   //状态不更新,保持未完成状态
                   throw new BusinessException(ErrorCode.OVER_SELL);
              } else {
                   // 更新 Material
                   materialMapperByMyBatis.updateCountByIdByMyBatis(registrationNumber, MaterialId, newCount);
              }
          }
           // 订单状态修改应该在可行订单状态判断之后(即所有 Material 更新之后)
           // 根据 id 修改 state
           orderMapperByMyBatis.updateByIdByMyBatis(registrationNumber, order);
      }
  }
}

主要功能界面截图及对应文字概述

租户注册相关界面

租户注册相关界面包括:租户注册页面

租户注册页面

image-20230609233706957

注册前数据库表

image-20230609233741523

注册后数据库表

对比可以发现,增加了以注册时填写的营业执照注册号为后缀的新表

image-20230609233850168

订单联动库存更新相关界面

订单联动库存更新相关界面包括

  • 订单看板界面

  • 新建订单界面

新建订单界面(订单时)

订单页面输入客户信息和订单内容,输入完成后点击生成订单

image-20230609232032612

订单看板界面(订单状态修改前)

点击生成订单后,页面会刷新,此时点击左侧菜单栏中的“订单 → 订单看板”,跳转至订单看板,可以看到新建的未完成订单

image-20230609231154331

订单看板界面(订单状态修改后)

点击修改,页面刷新,所有订单均变成已完成状态

image-20230609232547763

新建订单界面(订单状态修改后)

点击左侧菜单栏中的“订单 → 新建订单”,跳转回新建订单页面

image-20230609232613603

可以注意到,珍珠超浓绿茶剩余量由 108 份变为 106 份

运行测试与分析

(要有多组实际测试数据,和测试用例并给出相应运行结果截图。具体: (1)完整性约束测试过程设计,结果验证;(2)能给出软件操作的界面截图来。(3)软件开发调试过程中遇到的问题及解决过程;符合实际情况的数据测试及功能的改进设想等)

本人负责 SaaS 基础环境的搭建(即租户管理模块)和订单模块的实现,故针对这两个模块进行测试与分析

完整性约束测试

主要测试已识别的异常情况

过程设计

按照以下顺序进行

  1. 注册页面

    1. 提交空表单

    2. 输入错误格式的营业执照注册号

    3. 输入错误格式的电话号码

    4. 输入与租户密码不一致的确认密码

    5. 输入已存在的租户信息

  2. 登录页面

    1. 提交空表单

    2. 提交不存在的店铺名称

    3. 提交不匹配的账号密码

  3. 新建订单页面

    1. 提交空表单

    2. 提交无效订单

      1. 点单数大于可行订单量

      2. 点单数为0

    3. 输入错误格式的电话号码

  4. 订单看板页面

    1. 执行超卖订单

结果验证

按照上述设计的测试过程执行测试,展示过程截图,总结成测试用例表

注册页面 提交空表单

通过弹窗提示错误信息:存在未填项

image-20230613084056445

注册页面 输入错误格式的营业执照注册号

输入时,通过文字提示错误信息:注册号必须为 15 位数字

image-20230613084456790

提交后,通过弹窗提示错误信息:营业执照注册号格式错误

image-20230613084621465

注册页面 输入错误格式的电话号码

输入时,通过文字提示错误信息:请输入正确的手机号格式

image-20230613084739583

提交后,通过弹窗提示错误信息:电话号码格式错误

image-20230613085333104

注册页面 输入与租户密码不一致的确认密码

租户密码输入 123456,确认密码输入 1234567

提交后,通过弹窗提示错误信息:两次输入的密码不一致,请重新输入

image-20230613085439262

注册页面 输入已存在的租户信息

提交后,通过弹窗提示错误信息:请求参数错误,租户已存在:<租户店名> <租户营业执照注册号>

image-20230613090012938

登录页面 提交空表单

通过弹窗提示错误信息:请求数据为空

image-20230613090131922

登录页面 提交不存在的店铺名称

通过弹窗提示错误信息:请求参数错误,店铺不存在

image-20230613090209679

登录页面 提交不匹配的账号密码

通过弹窗提示错误信息:请求参数错误,账号密码不匹配

image-20230613090244520

新建订单页面 提交空表单

不填写点单日期、客户名称、客户电话、支付方式,只填写订单信息

通过弹窗提示错误信息:存在未填项,订单无效

image-20230613090330844

新建订单页面 提交无效订单 点单数大于可行订单量

剩余 30 份珍珠超浓绿茶,但是点了 31 份

image-20230613090827989

通过弹窗提示错误信息:存在超额订单,订单无效

image-20230613090909411

新建订单页面 提交无效订单 点单数为0

填写点单日期、客户名称、客户电话、支付方式,不填写订单信息

通过弹窗提示错误信息:金额为 0,订单无效

image-20230613090452503

订单看板页面 执行超卖订单

业务逻辑:只有在订单变为已完成状态时,库存信息会进行过更新,所以存在超卖的可能

剩余 30 份珍珠超浓绿茶,每次都点 30 份(单次点单恰好不超额),连点两次单(订单量之和超额)

image-20230613091020020

订单看板页面显示两次订单

image-20230613091401794

先修改的订单成功进行,后修改的订单无法进行,弹窗提示错误信息:超过可行订单数

image-20230613091433480

点击确认后,后修改的订单的订单状态仍为未完成状态

image-20230613091558461

回到新增订单页面,可以发现可行订单中不再显示珍珠超浓绿茶的菜品信息

image-20230613091638744

测试用例表
测试页面异常输入预期输出结果结论
注册页面 提交空表单 通过弹窗提示错误信息:存在未填项 预期输出与实际输出一致 测试通过
注册页面 输入错误格式的营业执照注册号:a 输入时,通过文字提示错误信息:注册号必须为 15 位数字 提交后,通过弹窗提示错误信息:营业执照注册号格式错误 预期输出与实际输出一致 测试通过
注册页面 输入错误格式的电话号码:1 输入时,通过文字提示错误信息:请输入正确的手机号格式 提交后,通过弹窗提示错误信息:电话号码格式错误 预期输出与实际输出一致 测试通过
注册页面 输入与租户密码不一致的确认密码: 租户密码:123456 确认密码:1234567 提交后,通过弹窗提示错误信息:两次输入的密码不一致,请重新输入 预期输出与实际输出一致 测试通过
注册页面 输入已存在的租户信息: 店铺名称:工大奶茶店 营业执照注册号:123456789012303 提交后,通过弹窗提示错误信息:请求参数错误,租户已存在:<租户店名> <租户营业执照注册号> 预期输出与实际输出一致 测试通过
登录页面 提交空表单 通过弹窗提示错误信息:请求数据为空 预期输出与实际输出一致 测试通过
登录页面 提交不存在的店铺名称:山大奶茶店 通过弹窗提示错误信息:请求参数错误,店铺不存在 预期输出与实际输出一致 测试通过
登录页面 提交不匹配的账号密码: 正确的工号密码:0 123456 输入的工号密码:0 1234567 通过弹窗提示错误信息:请求参数错误,账号密码不匹配 预期输出与实际输出一致 测试通过
新建订单页面 提交空表单 通过弹窗提示错误信息:存在未填项,订单无效 预期输出与实际输出一致 测试通过
新建订单页面 提交无效订单:点单数大于可行订单量 通过弹窗提示错误信息:存在超额订单,订单无效 预期输出与实际输出一致 测试通过
新建订单页面 提交无效订单:点单数为0 通过弹窗提示错误信息:金额为 0,订单无效 预期输出与实际输出一致 测试通过
订单看板页面 执行超卖订单 弹窗提示错误信息:超过可行订单数 点击确认后,后修改的订单的订单状态仍为未完成状态 回到新增订单页面,可以发现可行订单中不再显示珍珠超浓绿茶的菜品信息 预期输出与实际输出一致 测试通过

正常流程展示

过程设计
  1. 注册新账号

  2. 数据库展示

  3. 登录新账号(店主)

  4. 展示全空页面

  5. 退出新账号

  6. 登录旧账号(店主)

  7. 订单管理功能演示

    1. 订单看板展示

    2. 新增订单

    3. 订单修改

    4. 新增订单

    5. 订单删除

  8. 退出旧账号(店主)

  9. 登录旧账号(前台员工)

  10. 退出旧账号(前台员工)

  11. 登录旧账号(后台员工)

  12. 退出旧账号(后台员工)

  13. 登录旧账号(采购人员)

  14. 退出旧账号(采购人员)

结果展示
注册新账号

image-20230613103429018

image-20230613103445782

数据库展示

注册前

image-20230613103409303

注册后

image-20230613103506874

登录新账号(店主)

image-20230613103527754

展示全空页面

image-20230613103538293

image-20230613103553955

image-20230613103604403

退出新账号

image-20230613103622024

登录旧账号(店主)

image-20230613103657131

订单看板展示

image-20230613103727777

订单删除

image-20230613103800751

新增订单

image-20230613103827862

image-20230613103839042

订单修改

修改前

image-20230613103854429

修改后

image-20230613103921908

退出旧账号(店主)

image-20230613104010706

切换旧账号(前台员工)

前台员工权限限制:只能使用订单模块

image-20230613103948876

切换旧账号(后台员工)

后台员工权限限制:只能使用订单模块

image-20230613104105725

切换旧账号(采购人员)

采购人员权限限制:只能使用库存模块

image-20230613104158323

功能的改进设想

用户权限管理机制弱

问题描述

权限管理功能薄弱

  • 角色和权限一对一,不可配置

  • 用户和角色一对一,不可配置

改进设想

权限原子化,可以自定义角色,各个角色可以拥有数量可选的权限(角色权限一对多)

引入用户和角色的绑定机制,每个用户可以通过设置为多种角色,间接获取操作权限(用户角色一对多)

不带负载均衡的单体系统

问题描述

虽然做了 Schema 级别的数据隔离,但是系统本身仍是单体系统,不是集群,也没做负载均衡

改进设想

集群化,同时引入数据同步机制和负载均衡机制,将单体系统改为集群

项目源码

本项目为课程设计作业且为团队项目,设置为私有仓库,不作公开

 
posted @ 2023-09-03 11:08  Ba11ooner  阅读(122)  评论(0编辑  收藏  举报