学习目标
-
能够说出什么是saas平台,及其特点
-
能够说出餐掌柜核心架构、项目模块功能、
-
了解微服务平台中工作协调相关事宜
-
掌握基础组件安装、及项目启动
-
掌握餐掌柜项目的开发规范
第一章 餐掌柜项目概述
1、SaaS平台
SaaS平台:供应商将应用软件统一部署在自己的服务器上,客户可以根据工作实际需求,通过互联网向厂商定购所需的应用软件服务,按定购的服务多少和时间长短向厂商支付费用,并通过互联网获得Saas平台供应商提供的服务
互联网特性
SaaS软件行业知名产品NetSuite所提供的在线ERP、在线CRM等模块产品都是基于网络的,这样的优势在于不必投入任何硬件费用,也不用请专业的系统维护人员就能上网,有浏览器就可以进行ERP、CRM系统的使用。快速的实施、便捷的使用、低廉的价格都有赖于SaaS产品的互联网特性。
多重租赁(Multi-tenancy)特性
SaaS服务通常基于一套标准软件系统为成百上千的不同客户(又称为租户)提供服务。这要求SaaS服务能够支持不同租户之间数据和配置的隔离,从而保证每个租户数据的安全与隐私,以及用户对诸如界面、业务逻辑、数据结构等的个性化需求。由于SaaS同时支持多个租户,每个租户又有很多用户,这对支撑软件的基础设施平台的性能、稳定性和扩展性提出很大挑战。SaaS作为一种基于互联网的软件交付模式,优化软件大规模应用后的性能和运营成本是架构师的核心任务。
服务(Service)特性
SaaS使软件以互联网为载体的服务形式被客户使用,所以很多服务合约的签订、服务使用的计量、在线服务质量的保证和服务费用的收取等问题都必须加以考虑。而这些问题通常是传统软件没有考虑到的。
可扩展(Scalable)特性
可扩展性意味着最大限度地提高系统的并发性,更有效地使用系统资源。比如应用:优化资源锁的持久性,使用无状态的进程,使用资源池来共享线和数据库连接等关键资源,缓存参考数据,为大型数据库分区。
2、业务概述
餐饮SaaS管理系统就是【运营商】将餐饮管理系统部署到云端,商家只需要进行付费申请使用,而无需对技术、硬件、运维等各个方面再次投入,平台一般具有下列模块:
-
==运营平台==:运营商管理基础数据模块【统一权限、日志、图片、数字字典】以及商家管理的平台
-
==商家平台==:点餐后台核心业务,提供 员工、店铺、桌台、菜品、订单、结算等功能
-
==点餐平台==:H5点餐平台,客户实现开桌、点餐、追加菜品等功能
相对传统餐饮行业来说:连锁店数据还不能共享,老板管理店铺不方便,而SaaS餐饮管理软件,具有餐饮O2O应用场景所有功能,如自助点餐、线上外卖、自动收银等,既节约了顾客的用餐时间又节省了成本,还方便了店铺之间的管理,更提高了店铺线上与线下的数据实时更新。
3、核心架构
3.1、通用服务
对于一个公司架构设计来说,必须思考的问题是,这个功能在现在或者将来能满足多少业务场景?如果将来有新的业务出现,是不是能够复用?或者说,需要做多大的调整才可以复用?甚至于,这个功能有没有可能对外输出,提供SaaS化的服务建立通用服务业务为了业务的敏捷,创新!有下面三个特征:
-
敏捷 业务需求变化快,变更以天甚至更短的频率计算,一个单体大型应用,庞大的开发团队对单一应用的变更变得越来越困难。将大应用变为多个小的应用组合,才能适应外部的快速变化,实现业务的敏捷。
-
解耦 随着业务的发展,业务系统之间的交互通常会变得越来越复杂。一个功能的修改可能会影响很多方面。只有将需要大量交互的功能独立,从应用中拆解出来,这样可以使得应用之间耦合度大幅下降。
-
复用 一些公共的能力通过复用,大大提高了开发效率,避免了重复建设。同时使得数据和流程可以集中得以管理和优化。
餐掌柜项目中提供的通用服务
3.2、核心业务
请求接入:
H5:客户点餐接入
运营商:运营管理接入
商家:商家主业务接入
阿里云:OSS、ECS、部署平台接入
网关:
Nginx:反向代理,路由承压力
Gateway:第二代网关服务,路由分发、权限鉴定
核心业务:
点餐平台:负责客户点餐业务,为Android、IOS、H5提供统一服务接口
商家平台:负责基础数据配置,同时提供店员及交易结算服务
运营平台:负责商家管理及运营报表系统
通用业务:
通用非业务系统的中台系统:权限、支付、图片、数字自动、日志中心、报表等
3.3、系统架构
餐掌柜项目是基于spring-cloud-alibaba的架构体系,关于spring-cloud-alibaba,其核心组件如下:
Sentinel
阿里巴巴开源产品,把流量作为切入点,从流量控制,熔断降级,系统负载保护等多个维度保护服务的稳定性.
Nacos
阿里巴巴开源产品,一个更易于构建云原生应用的动态服务发现,配置管理和服务管理平台.
RocketMQ
Apache RocketMQ基于Java的高性能,高吞吐量的分布式消息和流计算平台.
Dubbo
Apache Dubbo是一款高性能的Java RPC框架.
Seata
阿里巴巴开源产品,一个易于使用的高性能微服务分布式事务解决方案.
Alibaba Cloud OSS
阿里云对象存储服务器(Object Storage Service,简称OSS),是阿里云提供的海量,安全,低成本,高可靠的云存储服务.
Alibaba Cloud Schedulerx
阿里中间件团队开发的一款分布式调度产品,支持周期性的任务与固定时间点触发任务.
展现层:负载与用户的交互,分为Android、IOS、web应用,他们都是通过访问统一的gateway网关来实现业务数据的访问
代理层:选用高性能的nginx服务,通过域名与不同servrce的绑定,通过gateway路由到不同的服务器组
权限控制层:使用无状态的JWT认证,结合Spring Security实现统一的权限控制
服务治理:使用nacos注册中心,配置中心实现服务的治理
服务调用:使用Spring Cloud alibaba 的核心组件dubbo进行服务之间的调用
流量控制:使用 Sentinel把流量作为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性
缓冲层:spring cache 配合redis轻松无侵入的实现业务数据的缓冲
基础业务支撑:基于spring boot脚手架,轻松集成OSS图片存储、sharding-jdbc分库分表、mybatis-plush 、docker、接口文档swagger2、分布式事务seate、MySQL、RocketMQ等组件
4、项目模块
在餐掌柜项目中采用maven的分层构架,来维护整体的项目架构,各个模块相对独立,达到功能及组件的复用,从而减少开发成本的投入,避免反复造轮子的现象,整体的分层构建如下图所示:
主项目结构说明:
|——restkeeper-super 负责整个项目的模块定义,pom.xml文件定义 | |———— restkeeper-framework 核心组件模块,主要是对各个框架集成:mybatis-push、seata、jwt、redis等等 | |———— restkeeper-gateway 前后端分离的边界,对外的统一接口,集成对日志client、鉴权client、knife4j组件 | |———— restkeeper-model-basic 基础服务模块,与业务无关的组件都在这里集成 | |———— restkeeper-model-report 报表模块,提供统一的日志报表,对各个子系统报表提供接口支撑 | |———— restkeeper-model-security 统一鉴权模块,依赖spring-security各个gateway只需要引入简单鉴权client则实现权限控制 | |———— restkeeper-model-shop 商家中心模块:各个主业务功能的实现,并且提供H5点餐端的dubbo接口服务 | |———— restkeeper-model-trading 交易平台,提供商家平台支付业务的结算功能
5、数据库结构
5.1、数据库概述
遵循领域模型设计【DDD】,我们按照功能模块领域垂直把数据库分为5个库,具体库的职能以及内部所含有的表详细情况如下图所示:
带来的提升:
-
解决业务层面的耦合,业务清晰
-
能对不同业务的数据进行分级管理、维护、监控、扩展等
-
高并发场景下,垂直分库一定程度的提升IO、数据库连接数、降低单机硬件资源的瓶颈
关于==【垂直分库、水平分库、垂直分表、水平分表】==在后续的项目中我们会使用sharding-jdbc来实现
5.2、表数据冗余
根据数据库设计的第三范式:在数据库设计过程中,应该尽量消除冗余。即设计数据库时,某一个字段属于一张表,但它同时出现在另一个或多个表,且完全等同于它在其本来所属表的意义表示,那么这个字段就是一个冗余字段。
随着企业数据量与并发量不断的增加,冗余字段的存在到底是好还是坏呢?
创建一个关系型数据库设计,我们有两种选择:
尽量遵循范式理论的规约,尽可能少的冗余字段,让数据库设计看起来精致、优雅、让人心醉。
合理的加入冗余字段这个润滑剂,减少join,让数据库执行性能更高更快。
所有问题出现必然因为场景问题,针对冗余字段问题,分为两个场景:
==快照场景(副本场景)==:交易场景大部分是数据快照,而不是冗余,用户下单时候的用户名、地址、商品名称、商品描述等,若采用关联,商品在下单后发生了更新的话再去关联查询就会导致和用户操作时的数据不一致,从而产生纠纷,例如我们在项目中的设计:
冗余场景:==一般数据改动的可能性少,而查询多的场景会使用冗余==,例如淘宝的店铺名称,淘宝商家中心会有这个字段,可能里面的商家论坛也有,再假设聚划算这种独立的大业务自己也存一份,再来个垂直频道电器城的后台管理也独立存一份,这种场景是由于对查询性能要求高产生的,所以必须要冗余,在业务的取舍上,肯定是对让用户更快看到信息,那么不可避免的是带来维护成本的增加,对于数据一致性问题,只要做到最终一致就可以了,分布式的CAP原则的实际应用基本都是通过牺牲数据一致性(C)来保证高可用(A)和高可靠(P), 因为这种场景大部分都是可以接受短暂的数据不一致的,对业务的影响及其微小。
在餐掌柜的项目我们遵循的原则:
项目全部采用逻辑关联,没有采用主外键约束
尽可能少使用多表关联查询。冗余是为了效率,减少join
尽可能服务独立化,查询单表化,例如:查询用户信息又需要用户的头像,处理的原则是调用【用户服务】和【附件服务】在dubbo层组装数据
5.3、数据库导入
我们使用docker-compose第一次安装mysql的时候,会自动执行初始化脚本,具体会在【第二章-基础组件安装】中演示
6、工作协调
6.1、团队组织及分工
微服务,顾名思义,微服务得从两个方面去理解,什么是"微"、什么是"服务", 微 狭义来讲就是体积小、著名的"2 pizza 团队"很好的诠释了这一解释【2 pizza 团队最早是亚马逊 CEO Bezos提出来的,意思是说单个服务的设计,所有参与人从设计、开发、测试、运维所有人加起来 只需要2个披萨就够了 】,而做为微服务,具备有下列四个要素:
-
小:微服务体积小,2 pizza 团队。
-
独:能够独立的部署和运行。
-
轻:使用轻量级的通信机制和架构。
-
松:为服务之间是松耦合的。
微服务的特点:
-
单一职责:微服务中每一个服务都对应唯一的业务能力,做到单一职责
-
微:微服务的服务拆分粒度很小,例如一个用户管理就可以作为一个服务。每个服务虽小,但“五脏俱全”。
-
面向服务:面向服务是说每个服务都要对外暴露Rest风格服务接口API。并不关心服务的技术实现,做到与平台和语言无关,也不限定用什么技术实现,只要提供Rest的接口即可。
-
自治:自治是说服务间互相独立,互不干扰
-
团队独立:每个服务都是一个独立的开发团队,人数不能过多。
-
技术独立:因为是面向服务,提供Rest接口,使用什么技术没有别人干涉
-
前后端分离:采用前后端分离开发,提供统一Rest接口,后端不用再为PC、移动段开发不同接口
-
数据库分离:每个服务都使用自己的数据源
-
部署独立,服务间虽然有调用,但要做到服务重启不影响其它服务。有利于持续集成和持续交付。每个服务都是独立的组件,可复用,可替换,降低耦合,易维护
-
6.2、gitee代码管理
为了方便开发小组的成员们进行代码共享,我们使用给Git来管理小组内的代码,Git是目前世界上最先进的分布式文件版本控制系统(没有之一)。对于我们java程序员而言,管理的就是代码文件版本,其主体架构图下:
这里我们使用gitee平台来管理代码,同学们可用访问【Gitee - 基于 Git 的代码托管和研发协作平台】进行代码管理
6.2.1、创建组织
登录自己的gitee账号后,在左侧选择【组织】点击【+】号
6.2.2、创建仓库
选择【仓库】—>【所有的】然后点击【+】号
点击【创建】,完成创建
6.2.3、升级账号
主页面选择【企业版】
点击【开始免费使用】
点击【马上升级】
点击【组织升级企业】—>选择刚刚建立的组织点击【升级】
填写验证码,点击【升级】
完成升级,在【我参与的仓库】中含有刚刚升级的项目
6.2.4、项目上传
在【餐掌柜-项目\restkeeper-day-01\项目代码\restkeeper-super】中有为各位提供的基础代码,这里我们需要把项目代码上传到gitee上:
拷贝【餐掌柜-项目\restkeeper-day-01\项目代码】到你自己的硬盘上,这里我拷贝到【F:\restkeeper-prent\restkeeper-super】盘下
使用IDEA打开【F:\restkeeper-prent\restkeeper-super】项目
下面,我们需要先创建本地仓库
初始化完成后,进行第一次本地提交,提交时间比较长,请耐心等待
关联远程仓库
点击复制,填写入上一步中的URL
先拉取远程仓库代码
==如果出现以下异常==
点击右下角【Git:master】,做合并代码操作
合并完成
提交代码
提交完成
查看远程gitee,可用看见刚刚上传的代码
第二章 项目快速启动
1、基础组件安装
首先想启动餐掌柜SaaS平台,则需要安装下列三方组件:
缓存服务:redis组件
注册配置中心:nacos平台
数据库:Mysql
消息中间件:rabbitMq
分布式缓存:seata-server
分布式调度中心:xxl-job-admin
反向服务器:nginx
这里我们采用docker-compose来进行安装,需要特别注意的:==宿主机地址请设置为:192.168.112.77==,【宿主机地址设置见课程资料】
1.1、docker安装
-
卸载老版本docker
yum remove docker \ docker-client \ docker-client-latest \ docker-common \ docker-latest \ docker-latest-logrotate \ docker-logrotate \ docker-selinux \ docker-engine-selinux \ docker-engine \ docker-ce
-
设置仓库
yum install -y yum-utils device-mapper-persistent-data lvm2
-
更新本地镜像库
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
-
更新镜像源缓存
sed -i 's/download.docker.com/mirrors.ustc.edu.cn\/docker-ce/g' /etc/yum.repos.d/docker-ce.repo
yum makecache fast
-
安装docker
yum install -y docker-ce
-
关闭防火墙
systemctl stop firewalld
systemctl disable firewalld
-
启动docker
systemctl start docker
-
开机自启动docker
systemctl enable docker
执行 docker version,查看安装情况
国内从 DockerHub 拉取镜像有时会遇到困难,此时可以配置镜像加速器。Docker 官方和国内很多云服务商都提供了国内加速器服务,
当配置某一个加速器地址之后,若发现拉取不到镜像,请切换到另一个加速器地址。国内各大云服务商均提供了 Docker 镜像加速服务,建议根据运行 Docker 的云平台选择对应的镜像加速服务。
阿里云镜像获取地址:阿里云登录 - 欢迎登录阿里云,安全稳定的云计算服务平台,登陆后,左侧菜单选中镜像加速器就可以看到你的专属地址了:
之前还有 Docker 官方加速器 https://registry.docker-cn.com ,现在好像已经不能使用了,我们可以多添加几个国内的镜像,如果有不能使用的,会切换到可以使用个的镜像来拉取。
-
配置镜像加速器
mkdir -p /etc/docker
-
配置镜像加速地址
tee /etc/docker/daemon.json <<-'EOF' { "registry-mirrors": ["https://3h1x6xgs.mirror.aliyuncs.com"] } EOF
-
重启docker服务
systemctl daemon-reload systemctl restart docker
1.2、docker-compose安装
-
安装docker-compose
curl -L "https://get.daocloud.io/docker/compose/releases/download/1.27.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
-
修改docker-compose权限
chmod +x /usr/local/bin/docker-compose ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
执行docker-compose version 查看安装情况
docker-compose --help 仓库其命令
[root@localhost docker-demo]# docker-compose --help 利用Docker来定义和构建一个多容器的应用 使用方式: docker-compose [-f <arg>...] [options] [COMMAND] [ARGS...] docker-compose -h|--help Options: -f, --file FILE 指定一个 compose 文件, (默认: docker-compose.yml) -p, --project-name NAME 指定project名字 (默认: 目录名称) --verbose 显示更多日志 --log-level LEVEL 日志级别 (DEBUG, INFO, WARNING, ERROR, CRITICAL) -v, --version 打印版本并退出 -H, --host HOST Daemon socket to connect to Commands: build 构建多个service config 校验 Compose 文件,格式是否正确,若正确则显示配置, 若格式错误显示错误原因 down 停止并删除 容器, 网络, 镜像, 和 数据卷 exec 进入一个指定的容器 help Get help on a command images 列出该Compose中包含的各个镜像 kill 通过发送 SIGKILL 信号来强制停止服务容器 格式为 docker-compose kill [options] [SERVICE...] logs 查看服务容器的输出日志 格式为 docker-compose logs [options] [SERVICE...]。 pause 暂停一个容器 port 打印某个容器端口所映射的公共端口 ps 列出项目中目前的所有容器 pull 拉取服务依赖的镜像 push 推送服务依赖的镜像到 Docker 镜像仓库 restart 重启项目中的服务 rm 删除停止的容器(要先停止容器) run 在某个服务上运行指令 scale 设定某个容器的运行个数 start 启动多个 services stop 停止多个 services top 查看各个服务容器内运行的进程。 unpause 恢复处于暂停状态中的服务。 up 创建并启动多个service的容器 version Show the Docker-Compose version information
1.3、初始化组件
上面我们完成docker和docker-compose的安装,下面我们来安装基础组件,首先把【restkeeper-day-01\03-项目资料】的文件上传到你centos中的==home==目录:
其中docker-compose.yml文件内容如下:
version: '3.5' services: #mysql数据库脚本 mysql: image: mysql:5.7 container_name: mysql restart: always ports: - 3306:3306 volumes: - ./mysql/mydir:/mydir - ./mysql/data:/var/lib/mysql - ./mysql/conf/my.cnf:/etc/my.cnf - ./mysql/source:/docker-entrypoint-initdb.d/ environment: MYSQL_ROOT_PASSWORD: pass networks: extnetwork: ipv4_address: 172.21.0.2 #nacos服务脚本 nacos: image: nacos/nacos-server:1.4.0 container_name: nacos restart: always ports: - "8848:8848" environment: SPRING_DATASOURCE_PLATFORM: mysql #数据源平台 仅支持mysql或不保存empty MODE: standalone MYSQL_SERVICE_HOST: mysql MYSQL_SERVICE_DB_NAME: nacos MYSQL_SERVICE_PORT: 3306 MYSQL_SERVICE_USER: root MYSQL_SERVICE_PASSWORD: pass NACOS_APPLICATION_PORT: 8848 JVM_XMS: 512m JVM_MMS: 256m JVM_XMN: 128m networks: extnetwork: ipv4_address: 172.21.0.3 depends_on: - mysql #seata服务脚本 seata-server: image: seataio/seata-server:1.3.0 container_name: seata-server restart: always ports: - "9200:9200" volumes: - ./seata-server/config:/root/seata-config environment: SEATA_IP: 192.168.112.77 SEATA_CONFIG_NAME: file:/root/seata-config/registry SEATA_PORT: 9200 networks: extnetwork: ipv4_address: 172.21.0.4 depends_on: - nacos #rabbitmq脚本 rabbitmq: image: rabbitmq:3.8.3-management container_name: rabbitmq restart: always ports: - 15672:15672 - 5672:5672 volumes: - ./rabbitmq/data:/var/lib/rabbitmq environment: RABBITMQ_DEFAULT_USER: admin RABBITMQ_DEFAULT_PASS: pass networks: extnetwork: ipv4_address: 172.21.0.5 xxl-job: image: xuxueli/xxl-job-admin:2.1.2 container_name: xxl-job-admin restart: always ports: - 8280:8080 volumes: - ./xxl-job/data:/data/applogs environment: PARAMS: "--spring.datasource.url=jdbc:mysql://mysql:3306/xxl-job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai --spring.datasource.username=root --spring.datasource.password=pass" networks: extnetwork: ipv4_address: 172.21.0.9 depends_on: - mysql redis: image: redis:5.0.0 container_name: redis restart: always command: redis-server --requirepass pass ports: - 6379:6379 volumes: - ./redis/data:/data networks: extnetwork: ipv4_address: 172.21.0.10 # docker容器内网地址 networks: extnetwork: name: extnetwork ipam: config: - subnet: 172.21.0.0/16
在home目录下运行下列命令,整个创建过程需要一定的时间【5分钟左右】,启动完成执行命令:
docker-compose up -d
查看启动情况:
docker-compose ps -a
校验mysql数据库
同步mysql时间
docker cp /usr/share/zoneinfo/Asia/Shanghai mysql:/etc/localtime
==账号:root 密码:pass==
校验nacos平台
==http://192.168.112.77:8848/nacos 账号:nacos 密码:nacos==
校验rabbitMQ平台
校验xxl-job平台
==http://192.168.112.77:8280/xxl-job-admin/ 账号:admin 密码:123456==
校验seata-server:
seata-server我们使用的是nacos,只需要在服务列表中看见seata-server
校验redis平台
使用RedisDesktopManager连接192.168.112.77端口6379
至此我们的基本组件安装完成
2、运营平台启动
2.1、调用链路
上图为运营平台的调用链路,从中我们可用看到:
-
用户发起请求访问restkeeper-vue-operator,在vue中有网关设置
-
vue中网关会调用restkeeper-gateway-operator网关,然后由restkeeper-gateway-operator网关路由对应业务系统web项目
-
model-basic-job-listen模块主要负责日志的收集【后面分析】
-
运营平台中有2个核心web模块:model-security-web【统一权限】、model-basic-web【基础服务】的消费者模块
-
运营平台中有2个核心producer模块:model-security-producer【统一权限】、model-basic-producer【基础服务】的生产者模块
我们需要启动上述的6个模块才可把运营平台启动
2.2、服务启动
2.2.1、后端服务启动
找到需要启动的模块下com.itheima.restkeeper包下以***Start
结尾的启动类,直接启动即可,例如:
启动后端服务,包含下列6个模块,按下列顺序启动:
-
model-basic-job-listen
-
model-security-producer
-
model-basic-producer
-
model-security-web
-
model-basic-web
-
restkeeper-gateway-operato
启动完成访问:http://192.168.112.77:8848/nacos,注意:model-basic-job-listen模块不需要注册到nacos中
2.2.2、vue项目启动
想使用vue必须先安装node.js:Node.js — 在任何地方运行 JavaScript,这里我下载的版本为v12.21版本,课程资料中有对应的安装包
安装直接全部点击下一步最后完成安装 安装结束后打开cmd命令窗口 输入以下命令验证是否安装成功,如出现版本号则安装成功
#查看node版本 node -v #查看npm版本 npm -v
安装成功后,在cmd命令窗口使用如下命令安装npm的国内镜像cnmp
npm install -g cnpm --registry=http://registry.npm.taobao.org
第一次运行项目之前我们需要执行下列命令,安装项目
npm install
在cmd窗口中切换到restkeeper-vue-operator项目所在模块:
==【非必须执行】==如果安装中有失败,这边可用在host
中做如下配置,这里是让github更快加载
140.82.114.4 github.com 199.232.69.194 github.global.ssl.fastly.net 185.199.108.153 assets-cdn.github.com 185.199.110.153 assets-cdn.github.com 185.199.111.153 assets-cdn.github.com
cmd中切换restkeeper-vue-operator项目所在目录,使用下列命令启动项目:
npm run dev
==注意:这里默认打开的访问路径为:http://localhost:8888/#/login,但是我们的系统是基于saas系统,这里需要绑定域名,但是我们没有域名,所以这里需要设置hosts,再次找到hosts==
添加如下配置:
127.0.0.1 www.eehp.cn 127.0.0.1 ppsk.shop.eehp.cn
访问http:// www.eehp.cn 账号:58948528@qq.com 密码:pass
2.2.3、IDEA启动vue项目
使用idea启动vue项目,在上面安装好node.js环境并初始化完成和安装好依赖的前提下,打开idea,然后在File–Settings–Plugins–Makerplace下找到vue.js插件,安装并重启idea
重启idea后,进行如下配置
第三章 项目开发规范
在项目开发过程中,如果开发人员过多又没有一个开发统一规范,在后期的维护、迭代升级的过程中会相当痛苦,也不利于新人接收项目,项目规范是为了统一一个开发标准,让代码易懂便于维护升级,下面我们为整个系统指定如下规范
1、基础父类定义
1.1、BasicPojo
==各个模块中POJO对象继承的基础父类==
隶属模块:framework-mybatis-plus
包路径:com.itheima.restkeeper.basic
作用:所有实体类公共字段的定义
其中结构如下:
package com.itheima.restkeeper.basic; import com.baomidou.mybatisplus.annotation.FieldFill; import com.baomidou.mybatisplus.annotation.TableField; import com.fasterxml.jackson.annotation.JsonFormat; import com.itheima.restkeeper.utils.ToString; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.format.annotation.DateTimeFormat; import java.io.Serializable; import java.util.Date; /** * @Description:实体基础类 */ @Data @NoArgsConstructor public class BasicPojo implements Serializable { //主键 @JsonFormat(shape = JsonFormat.Shape.STRING) public Long id; //分片键 @TableField(fill = FieldFill.INSERT) @JsonFormat(shape = JsonFormat.Shape.STRING) public Long shardingId; //创建时间 @TableField(fill = FieldFill.INSERT)//INSERT代表只在插入时填充 @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")//set @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")//get public Date createdTime; //修改时间 @TableField(fill = FieldFill.INSERT_UPDATE)// INSERT_UPDATE 首次插入、其次更新时填充(或修改) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")//set @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")//get public Date updatedTime; //是否有效 @TableField(fill = FieldFill.INSERT) public String enableFlag; //构造函数 public BasicPojo(Long id) { this.id = id; } }
1.2、BasicVo
各个模块中VO对象继承的基础父类
隶属模块:framework-vo
包路径:com.itheima.restkeeper.basic
作用:所有VO对象公共字段的定义
其中结构如下:
package com.itheima.restkeeper.basic; import com.fasterxml.jackson.annotation.JsonFormat; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; import java.util.Date; /** * @ClassName BasicVo.java * @Description 基础请求 */ @Data @NoArgsConstructor public class BasicVo implements Serializable { @ApiModelProperty(value = "主键") @JsonFormat(shape = JsonFormat.Shape.STRING) private Long id; @ApiModelProperty(value = "数据源分片Id") @JsonFormat(shape = JsonFormat.Shape.STRING) private Long shardingId; @ApiModelProperty(value = "创建时间") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")//get protected Date createdTime; @ApiModelProperty(value = "修改时间") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")//get protected Date updatedTime; @ApiModelProperty(value = "是否有效") protected String enableFlag; public BasicVo(Long id) { this.id = id; } }
1.3、IBasicEnum
=枚举接口使用 搜索百度=
==各个模块中枚举对象实现的基础父接口==
隶属模块:framework-vo
包路径:com.itheima.restkeeper.basic
作用:枚举对象实现的基础方法定义
其中结构如下:
package com.itheima.restkeeper.basic; /** * @Description:枚举接口 */ public interface IBasicEnum { //编码 public String getCode(); //信息 public String getMsg(); }
2、统一返回数据
2.1、ResponseWrap
==所有对前端接口的返回对象==
隶属模块:framework-vo
包路径:com.itheima.restkeeper.basic
作用:定义返回对象的包装
其中结构如下:
package com.itheima.restkeeper.basic; import com.fasterxml.jackson.annotation.JsonFormat; import io.swagger.annotations.ApiModelProperty; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; import java.util.Date; /** * @Description 返回结果 */ @Data @Builder @AllArgsConstructor @NoArgsConstructor public class ResponseWrap<T> implements Serializable { //响应返回编码 @ApiModelProperty(value = "状态码") private String code; //响应返回信息 @ApiModelProperty(value = "状态信息") private String msg; //返回结果 @ApiModelProperty(value = "返回结果") private T datas; //操作用户 @ApiModelProperty(value = "操作人ID") private Long userId; //操作用户名称 @ApiModelProperty(value = "操作人姓名") private String userName; //创建时间,处理json的时间参数解析 @JsonFormat(pattern = "yyyy-MM-dd hh:mm:ss",timezone = "GMT+8") @ApiModelProperty(value = "操作时间") private Date operationTime; }
2.2、ResponseWrapBuild
==构造ResponseWrap工具==
隶属模块:framework-commons
包路径:com.itheima.restkeeper.utils
作用:构造ResponseWrap工具,并且从UserVoContext上下文中获取当前登录对象作为操作人信息填入
其中结构如下:
package com.itheima.restkeeper.utils; import com.alibaba.fastjson.JSONObject; import com.itheima.restkeeper.basic.IBasicEnum; import com.itheima.restkeeper.basic.ResponseWrap; import com.itheima.restkeeper.req.UserVo; import java.util.Date; /** * @Description 构造ResponseWrap工具 */ public class ResponseWrapBuild { public static <T>ResponseWrap<T> build(IBasicEnum basicEnumIntface, T t){ //从UserVoContext中拿到userVoString String userVoString = UserVoContext.getUserVoString(); UserVo userVo = null; if (!EmptyUtil.isNullOrEmpty(userVoString)){ userVo = JSONObject.parseObject(userVoString, UserVo.class); }else { userVo = new UserVo(); } //构建对象 return ResponseWrap.<T>builder() .code(basicEnumIntface.getCode()) .msg(basicEnumIntface.getMsg()) .operationTime(new Date()) .userId(userVo.getId()) .userName(userVo.getUsername()) .datas(t) .build(); } }
3、vo与pojo
VO :value object 值对象 / view object 表现层对象,主要职能
作用:
对应页面显示的数据对象 返回给前端
页面请求参数封装
dubbo层调用多个服务,服务返回结果封装 例如:用户信息——>【用户服务】返回VO+【附件服务】返回图片Vo (餐掌柜项目 涉及的属于 分库分表 把用户信息和图片都分开保存 所以最后我们需要封装在一起)
POJO :plain ordinary java object 无规则简单java对象,在餐掌柜中严格遵循POJO的实体类结构使用【驼峰规则】映射数据库字段
被生产者和消费者依赖的 AM 10.00
produce实现interface produce调用service
produce生产者
web消费者
VO==>POJO POJO==>VO 相互转换使用到工具类
3.1、作用范围
以restkeeper-model-security模块为例,一个标准的模块其模块结构如下
|——restkeeper-model-security 统一权限模块 | |———— model-security-interface dubbo接口定义层【被生产者、消费者依赖】 | |———— model-security-producer dubbo接口实现【生产者】 | |———— model-security-service 核心业务开发【最小开发单元】 | |———— model-security-web 对外接口服务层【消费者】
VO和POJO的负责区域入下图所示:
其中model-security-producer是VO与POJO对象转换层。
3.2、对象转换工具
==VO对象与POJO对象属成员变量拷贝工具==
隶属模块:framework-commons
包路径:com.itheima.restkeeper.basic
作用:VO对象与POJO对象属成员变量拷贝工具
注意:==VO对象与POJO对象属成员变量【类型】和【成员变量名称】必须保持一致,支持父类属性拷贝==
oriaka==>百度搜索 拷贝速度经过验证 速度特别快的
===工作中 建议 使用 BeanConv转换工具 不建议使用beanUtils 因为 BeanConv速度比较快
package com.itheima.restkeeper.utils; import lombok.extern.slf4j.Slf4j; import ma.glasnost.orika.MapperFacade; import ma.glasnost.orika.MapperFactory; import ma.glasnost.orika.MappingContext; import ma.glasnost.orika.converter.BidirectionalConverter; import ma.glasnost.orika.converter.ConverterFactory; import ma.glasnost.orika.impl.DefaultMapperFactory; import ma.glasnost.orika.metadata.Type; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.util.List; /** * @Description 对象转换工具 */ @Slf4j public class BeanConv { private static MapperFacade mapper; private static MapperFacade notNullMapper; static { MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build(); ConverterFactory converterFactory = mapperFactory.getConverterFactory(); converterFactory.registerConverter(new LocalDateTimeConverter()); converterFactory.registerConverter(new LocalDateConverter()); converterFactory.registerConverter(new LocalTimeConverter()); mapper = mapperFactory.getMapperFacade(); MapperFactory notNullMapperFactory = new DefaultMapperFactory.Builder().mapNulls(false).build(); notNullMapper = notNullMapperFactory.getMapperFacade(); } private static class LocalDateTimeConverter extends BidirectionalConverter<LocalDateTime, LocalDateTime> { @Override public LocalDateTime convertTo( LocalDateTime localDateTime, Type<LocalDateTime> type, MappingContext mappingContext) { return LocalDateTime.from(localDateTime); } @Override public LocalDateTime convertFrom( LocalDateTime localDateTime, Type<LocalDateTime> type, MappingContext mappingContext) { return LocalDateTime.from(localDateTime); } } private static class LocalDateConverter extends BidirectionalConverter<LocalDate, LocalDate> { @Override public LocalDate convertTo( LocalDate localDate, Type<LocalDate> type, MappingContext mappingContext) { return LocalDate.from(localDate); } @Override public LocalDate convertFrom( LocalDate localDate, Type<LocalDate> type, MappingContext mappingContext) { return LocalDate.from(localDate); } } private static class LocalTimeConverter extends BidirectionalConverter<LocalTime, LocalTime> { @Override public LocalTime convertTo( LocalTime localTime, Type<LocalTime> type, MappingContext mappingContext) { return LocalTime.from(localTime); } @Override public LocalTime convertFrom( LocalTime localTime, Type<LocalTime> type, MappingContext mappingContext) { return LocalTime.from(localTime); } } /** * 复制对象所有属性 * @param source 源对象 * @param destination 目标对象 */ public static void toBean(Object source, Object destination) { mapper.map(source, destination); } /** * 复制对象非null属性 * * @param source 源对象 * @param destination 目标对象 */ public static void toBeanNotNull(Object source, Object destination) { notNullMapper.map(source, destination); } /** * 深度复制对象 * @param source 源对象 * @param destinationClass 目标类型 * @return 复制出的目标对象 */ public static <T> T toBean(Object source, Class<T> destinationClass) { if (EmptyUtil.isNullOrEmpty(source)){ return null; } return mapper.map(source, destinationClass); } /** * 复制List * @param sourceList 源List * @param destinationClass 目标List的元素类型 * @return 复制出的目标List */ public static <T> List<T> toBeanList(List<?> sourceList, Class<T> destinationClass) { if (EmptyUtil.isNullOrEmpty(sourceList)){ return null; } return mapper.mapAsList(sourceList,destinationClass); } }
单个对象拷贝:
List对象拷贝:
4、异常处理
4.1、统一异常处理
在项目开发过程中,异常是需要统一,如图所示,service—>producer—>interface—>web逐层向上抛出:
首先,自定义异常类ProjectException
隶属模块:framework-web
包路径:com.itheima.restkeeper.exception
作用:统一异常处理自定义异常类
package com.itheima.restkeeper.exception; import com.itheima.restkeeper.basic.IBasicEnum; /** * @Description:自定义异常 */ public class ProjectException extends RuntimeException { //错误编码 private String code; //提示信息 private String message; //异常接口 private IBasicEnum basicEnumIntface; public ProjectException() { } public ProjectException(IBasicEnum basicEnumIntface) { this.code = basicEnumIntface.getCode(); this.message = basicEnumIntface.getMsg(); this.basicEnumIntface = basicEnumIntface; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } @Override public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public IBasicEnum getBasicEnumIntface() { return basicEnumIntface; } public void setBasicEnumIntface(IBasicEnum basicEnumIntface) { this.basicEnumIntface = basicEnumIntface; } @Override public String toString() { return "ProjectException{" + "code='" + code + '\'' + ", message='" + message + '\'' + '}'; } }
再定义BaseController类采用SpringMVC的统一异常处理,使用@ControllerAdvice、@ExceptionHandler注解,优雅的处理异常
隶属模块:framework-web
包路径:com.itheima.restkeeper.web
作用:统一异常处理
package com.itheima.restkeeper.web; import com.alibaba.fastjson.JSON; import com.itheima.restkeeper.basic.ResponseWrap; import com.itheima.restkeeper.enums.BasicEnum; import com.itheima.restkeeper.exception.ProjectException; import com.itheima.restkeeper.utils.ResponseWrapBuild; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @ClassName BaseController.java * @Description 基础的controller * ControllerAdvice:对controller层的增强,其他的controller则不需要继承,也会被拦截处理 */ @ControllerAdvice public class BaseController { //表示当请求发生异常时,被ExceptionHandler注释的方法会去处理 @ExceptionHandler public void ExceptionHandler(Exception ex, HttpServletResponse response) throws IOException { ResponseWrap<Object> responseWrap = null; //自定义异常 if (ex instanceof ProjectException){ ProjectException projectException = (ProjectException) ex; responseWrap = ResponseWrapBuild.build(projectException.getBasicEnumIntface(), null); }else { //系统异常 responseWrap = ResponseWrapBuild.build(BasicEnum.SYSYTEM_FAIL, null); } //编码防止中文问题 response.setContentType("application/json;charset =utf-8"); response.getWriter().write(JSON.toJSONString(responseWrap)); } }
4.2、异常枚举
为了规范异常处理后返回的信息,我们为每一个模块定义了异常处理枚举:
隶属模块:framework-vo
包路径:com.itheima.restkeeper.enums
作用:各模块的异常枚举
需要注意,每个enum都需要实现IBasicEnum接口,以UserEnum为例:
package com.itheima.restkeeper.enums; import com.itheima.restkeeper.basic.IBasicEnum; /** * @ClassName UserEnum.java * @Description TODO */ public enum UserEnum implements IBasicEnum { SUCCEED("200","操作成功"), LOGOUT_SUCCEED("1004","退出成功"), FAIL("1000","操作失败"), PAGE_FAIL("70005", "查询用户列表失败"), CREATE_FAIL("70007", "保存用户失败"), UPDATE_FAIL("70008", "修改用户失败"), DELETE_FAIL("70009", "修改用户失败"), SELECT_USER_FAIL("70010", "查询用户失败"), SELECT_ROLE_FAIL("70011", "查询用户对应角色失败"), SELECT_RESOURCE_FAIL("70012", "查询用户对应资源失败"), SELECT_CURRENT_USER("70013", "查询当前用户失败"), SELECT_USER_LIST_FAIL("70014", "查询用户list失败"), ; private String code; private String msg; UserEnum(String code, String msg) { this.code = code; this.msg = msg; } public String getCode() { return code; } public String getMsg() { return msg; } }
学习目标
1、掌握Mybatis-push代码生成器的使用
2、掌握springcloud-alibaba-dubbo的使用
3、完成品牌管理开发
4、完成门店管理开发
5、完成用户管理开发
第一章 Mybatis-plus代码生成器 了解掌握 后续可练习
AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率
1、CodeGenerator核心类
下面我们看下餐掌柜平台中是如何集成AutoGenerator ,首相我们找CodeGenerator类,目录如下
在CodeGenerator中我们使用了AutoGenerator,下面我们逐行解释:
package com.itheima.restkeeper.generator; import com.baomidou.mybatisplus.generator.AutoGenerator; import com.baomidou.mybatisplus.generator.InjectionConfig; import com.baomidou.mybatisplus.generator.config.*; import com.baomidou.mybatisplus.generator.config.rules.DateType; import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy; import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine; import com.itheima.restkeeper.utils.EmptyUtil; import java.util.ArrayList; import java.util.List; import java.util.ResourceBundle; /** * @Description:代码生成器 */ public class CodeGenerator { public static void autoGenerator(){ //用来获取Mybatis-Plus.properties文件的配置信息 final ResourceBundle rb = ResourceBundle.getBundle("mybatis-plus-generrator"); // 代码生成器 AutoGenerator mpg = new AutoGenerator(); // 全局配置 GlobalConfig gc = new GlobalConfig(); String projectPath =rb.getString("projectPath"); gc.setOutputDir(projectPath + "/src/main/java"); gc.setAuthor(rb.getString("author")); gc.setOpen(false); gc.setFileOverride(true); //指定时间处理类型 gc.setDateType(DateType.ONLY_DATE); gc.setSwagger2(true); //实体属性 Swagger2 注解 mpg.setGlobalConfig(gc); //数据源配置 DataSourceConfig dsc = new DataSourceConfig(); dsc.setUrl(rb.getString("url")); // dsc.setSchemaName("public"); dsc.setDriverName("com.mysql.jdbc.Driver"); dsc.setUsername(rb.getString("userName")); dsc.setPassword(rb.getString("password")); mpg.setDataSource(dsc); //包配置 PackageConfig pc = new PackageConfig(); pc.setModuleName(rb.getString("moduleName")); pc.setParent(rb.getString("parent")); pc.setController("web"); pc.setService("service"); pc.setServiceImpl("service.impl"); pc.setEntity("pojo"); pc.setMapper("mapper"); mpg.setPackageInfo(pc); // 自定义配置 InjectionConfig cfg = new InjectionConfig() { @Override public void initMap() { // to do nothing } }; // 如果模板引擎是 freemarker String templatePath = "/templates/mapper.xml.ftl"; // 自定义输出配置 List<FileOutConfig> focList = new ArrayList<>(); cfg.setFileOutConfigList(focList); mpg.setCfg(cfg); // 配置模板 TemplateConfig templateConfig = new TemplateConfig(); if ("true".equals(rb.getString("entity"))){ String entityFtlPath = rb.getString("entity.ftl.path"); if (!EmptyUtil.isNullOrEmpty(entityFtlPath)){ templateConfig.setEntity(entityFtlPath); } }else { templateConfig.setEntity(null); } if ("true".equals(rb.getString("mapper"))){ String mapperFtlPath = rb.getString("mapper.ftl.path"); if (!EmptyUtil.isNullOrEmpty(mapperFtlPath)){ templateConfig.setMapper(mapperFtlPath); } }else { templateConfig.setMapper(null); } if ("true".equals(rb.getString("service"))){ String serviceFtlPath = rb.getString("service.ftl.path"); if (!EmptyUtil.isNullOrEmpty(serviceFtlPath)){ templateConfig.setService(serviceFtlPath); } }else { templateConfig.setService(null); } if ("true".equals(rb.getString("serviceImp"))){ String serviceImpFtlPath = rb.getString("serviceImp.ftl.path"); if (!EmptyUtil.isNullOrEmpty(serviceImpFtlPath)){ templateConfig.setServiceImpl(serviceImpFtlPath); } }else { templateConfig.setServiceImpl(null); } if ("true".equals(rb.getString("controller"))){ String controllerFtlPath = rb.getString("controller.ftl.path"); if (!EmptyUtil.isNullOrEmpty(controllerFtlPath)){ templateConfig.setController(controllerFtlPath); } }else { templateConfig.setController(null); } templateConfig.setXml(null); mpg.setTemplate(templateConfig); // 策略配置 StrategyConfig strategy = new StrategyConfig(); strategy.setNaming(NamingStrategy.underline_to_camel); strategy.setColumnNaming(NamingStrategy.underline_to_camel); strategy.setSuperEntityClass(rb.getString("SuperEntityClass")); strategy.setEntityLombokModel(true); strategy.setRestControllerStyle(true); // 写于父类中的公共字段 String[] SuperEntityColumns = rb.getString("superEntityColumns").split(","); strategy.setSuperEntityColumns(SuperEntityColumns); strategy.setInclude(rb.getString("tableName").split(",")); strategy.setControllerMappingHyphenStyle(true); String tablePrefix = rb.getString("tablePrefix"); if (tablePrefix!=null){ strategy.setTablePrefix(tablePrefix); } mpg.setStrategy(strategy); mpg.setTemplateEngine(new FreemarkerTemplateEngine()); mpg.execute(); } }
2、生成器快速入门
上面我们的定义了CodeGenerator核心类,下面我们需要在项目中使用,各位在使用代码生成器之前,需要明确我们使用的模块,这里我们所有的pojo、mapper、service层都定义在==model-***-service==类型的模块中,这里以model-shop-service为例,其他模块使用方式也类似,在使用之前我们需要定义2个资源:
-
==mybatis-plus-generrator.properties:关于数据库及生成策略定义==
-
==templates:代码生成器模板信息==
,定义信息如下:
2.1、generrator
generrator.properties此文件的作用主要是定义关于数据库及生成策略定义
要想快速生成 只要 维护 generrator.porperties 即可
点击运行即可生成
数据库表 可以快速生成
#数据库地址 url=jdbc:mysql://192.168.112.77:3306/restkeeper-shop?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&tinyInt1isBit=false #数据库账号 userName=root #数据库密码 password=root #此处为本项目src所在路径(代码生成器输出路径) projectPath=F:/restkeeper-prent/restkeeper-super/restkeeper-model-shop/model-shop-service #设置作者 author=Admin #自定义包路径 parent=com.itheima #装代码的文件夹名 moduleName=restkeeper #设置表前缀,不设置则默认无前缀 tablePrefix =tab_ #数据库表名(此处切不可为空,如果为空,则默认读取数据库的所有表名) tableName=tab_brand,tab_category,tab_dish,tab_dish_flavor,tab_order,tab_order_item,tab_printer,tab_printer_dish,tab_store,tab_table,tab_table_area #pojo的超类 SuperEntityClass = com.itheima.restkeeper.basic.BasicPojo #pojo的超类公用字段 superEntityColumns = id,created_time,updated_time,sharding_id,enable_flag #生成的层级 entity=true entity.ftl.path=/templates/entity.java mapper=false mapper.ftl.path=/templates/mapper.java service=false service.ftl.path=/templates/service.java serviceImp=false serviceImp.ftl.path=/templates/serviceImpl.java controller=false controller.ftl.path=/templates/controller.java
2.2、templates
templates里面的内容如下,其主要作用是根据模板生成对应代码,当然这里的文件不需要各位维护,如果你想维护,请先学习freemarker模板引擎,这里不做讲解
2.3、生成器使用
直接在test目录中简历单元测试类直接执行即可
package com.itheima.restkeeper; import com.itheima.restkeeper.generator.CodeGenerator; import org.junit.Test; /** * @Description:代码生成器 */ public class ShopGenerator { @Test public void test(){ CodeGenerator.autoGenerator(); } }
这时我们查看日志可以发现在model-shop-service模块中代码已经自动生成
第二章 springcloud-alibaba-dubbo
1、dubbo快速入门
长连接、单一链接
随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进。
单一应用架构
当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。
垂直应用架构
当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,提升效率的方法之一是将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。
分布式服务架构
当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。
流动计算架构
当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键。
下面我们看下dubbo架构
名词解释
节点 | 角色说明 |
---|---|
Provider | 暴露服务的服务提供方【生产者】 |
Consumer | 调用远程服务的服务消费方【消费者】 |
Registry | 服务注册与发现的注册中心【nacos】 |
Monitor | 统计服务的调用次数和调用时间的监控中心【监控出现问题不影响服务调用】 |
Container | 服务运行容器 |
调用关系说明
-
服务容器负责启动,加载,运行服务提供者。
-
服务提供者在启动时,向添加中心添加自己提供的服务。
-
服务消费者在启动时,向添加中心订阅自己所需的服务。
-
添加中心返回服务提供者地址列表给消费者,如果有变更,添加中心将基于长连接推送变更数据给消费者。
-
服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
-
服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心
2、dubbo快速集成
上面我们介绍了dubbo的架构,下面我们来使用dubbo来进行开发,在开发之前,我们先看下面的图解:
从上图我们可用看出在一个标准的dubbo服务调用中,他分为3个部分
dubbo-interface:
负责接口的定义,这里我们通常定义***Face结构的接口类,例如:UserFace
dubbo-producer:
【生产者】负责接口的实现,这里我们通常用==@DubboService==定义***FaceImpl结构的接口类,例如:UserFaceImpl
dubbo-web:
【消费者】负责调用接口,通常我们在web层使用==@DubboReference==调用接口
下面我们来构建第一个dubbo服务,我们需要在dubbo-parent中pom.xml引入下列依赖:
<dependencies> <!--接口定义层--> <dependency> <groupId>com.itheima.dubbo</groupId> <artifactId>dubbo-interface</artifactId> <version>${interFace.version}</version> </dependency> <!---spring-cloud-alibaba主配置--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>${spring-cloud-alibaba.version}</version> <type>pom</type> <scope>import</scope> </dependency> <!---springboot主配置--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring.boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies>
在dubbo-producer和dubbo-web中pom.xml导入:
<dependencies> <!--接口定义层--> <dependency> <groupId>com.itheima.dubbo</groupId> <artifactId>dubbo-interface</artifactId> </dependency> <!--web支持--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--nacos支持--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!--dubbo支持--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-dubbo</artifactId> </dependency> </dependencies>
2.1、生产者配置
生产者:dubbo-producer负责服务的提供,我们需要把他添加到nacos添加中心中,在application.yml添加:
#服务配置 server: #端口 port: 8080 #服务编码 tomcat: uri-encoding: UTF-8 #spring相关配置 spring: #应用配置 application: #应用名称 name: dubbo-producer main: allow-bean-definition-overriding: true cloud: #nacos添加中心 nacos: discovery: server-addr: 192.168.112.77:8848 namespace: public group: SEATA_GROUP dubbo: #dubbo服务版本 application: version: 1.0.0 logger: slf4j #dubbo接口扫描路径 scan: base-packages: com.itheima.dubbo #dubbo服务添加 registry: address: spring-cloud://192.168.112.77 #dubbo服务协议类型及端口,线程数【这里是默认配置】 protocol: name: dubbo port: 28080 threads: 200 accesslog: D:/logs/dubbo-producer-01.log
这里我们实现dubbo-interface的Userface接口:
package com.itheima.dubbo; import org.apache.dubbo.config.annotation.DubboService; /** * @ClassName UserFaceImpl.java * @Description 用户接口实现 */ @DubboService(version = "${dubbo.application.version}",timeout = 5000) public class UserFaceImpl implements UserFace { @Override public String helloUser(String userName) { return "Hello!"+userName; } }
2.2、消费者配置
消费者:dubbo-web负责服务的接口消费,我们需要把他添加到nacos添加中心中,在application.yml添加:
#服务配置 server: #端口 port: 8081 #服务编码 tomcat: uri-encoding: UTF-8 #spring相关配置 spring: #应用配置 application: #应用名称 name: dubbo-web main: allow-bean-definition-overriding: true #nacos添加中心 cloud: nacos: discovery: server-addr: 192.168.112.77:8848 namespace: public group: SEATA_GROUP #dubbo消费端配置 dubbo: application: version: 1.0.0 logger: slf4j cloud: #表示要订阅服务的服务名,可以配置'*',代表订阅所有服务,不推荐使用。若需订阅多应用,使用 "," 分割。 subscribed-services: dubbo-producer scan: #扫描路径 base-packages: com.itheima.dubbo.web registry: address: spring-cloud://192.168.112.77 #dubbo服务协议类型及端口,线程数【这里是默认配置】 protocol: name: dubbo port: 28081 threads: 200 accesslog: D:/logs/dubbo-web-01.log
这里我们调用dubbo-interface的Userface接口:
package com.itheima.dubbo.web; import com.itheima.dubbo.UserFace; import org.apache.dubbo.config.annotation.DubboReference; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; /** * @ClassName UserController.java * @Description 用户controller */ @RestController public class UserController { @DubboReference(version = "${dubbo.application.version}",check = false) UserFace userFace; @GetMapping("{userName}") public String helloUser(@PathVariable("userName") String userName){ return userFace.helloUser(userName); } }
启动dubbo-producer和dubbo-web模块,访问http://127.0.0.1:8081/itheima
3、业务模块开发
3.1、业务调用链路
在开始业务开发之前,我们首先看一下系统的调用链路,以restkeeper-model-shop模块为例,其调用的时序图如下所示:
以restkeeper-model-shop模块为例,一个标准的模块其模块结构如下
|——restkeeper-model-shop 商家服务平台 | |———— model-shop-applet ==H5点餐业务dubbo接口实现【生产者】== | |———— model-shop-interface ==商家平台所有dubbo接口定义== | |———— model-shop-job-listen ==商家服务平台定时任务及监听模块【监听消费、定义任务】== | |———— model-shop-producer ==后端业务dubbo接口实现【生产者】== | |———— model-shop-service ==核心业务层【被所有生产者、消费者、监听、定时任务依赖】== | |———— model-shop-user ==用户业务依赖于model-security-service的业务实现【生产者】== | |———— model-shop-web ==对外商家服务平台web层,被restkeeper-gateway-shop系统调用【消费者者】==
3.2、dubbo生产者
在restkeeper-model-shop模块中有3个【生产者】模块:model-shop-applet、model-shop-producer、model-shop-user ,这里以model-shop-producer为例,首先查看模块依赖关系:
其中model-shop-producer模块他有以下职能:
dubbo微服务【生产者】
对象转换:从POJO对象转换为VO对象
调用server【核心业务层】实现dubbo服务接口的业务逻辑
==注意:餐掌柜中为避免模块职能混乱,禁止生产者模块调用生产者模块,当产生跨服务接口调用,例如一个接口需要多个接口来支持,我们会放到web层进行服务调用然后业务组装,如果牵涉分布式事务问题,我们会采用seata方式来解决==
下面我们对model-shop-producer进行dubbo的集成,首先在model-shop-producer的pom.xml导入下列依赖:
<!-- Dubbo Spring Cloud Starter --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-dubbo</artifactId> </dependency>
再在model-shop-producer的application.yml添加定义如下:
dubbo: #dubbo应用服务定义 application: #版本 version: 1.0.0 #日志 logger: slf4j scan: #扫描路径 base-packages: com.itheima.restkeeper registry: #添加中心:这里采用nacos添加中心 address: spring-cloud://192.168.112.77 #服务协议定义 protocol: #服务协议名称 name: dubbo #协议端口 port: 27077 #线程数 threads: 200 #dubbo调用日志 accesslog: D:/logs/model-shop-producer-01.log
3.3、dubbo消费者
在restkeeper-model-shop模块中有1个【消费者】模块:model-shop-web,这里以model-shop-web为例,首先查看模块依赖关系:
其中model-shop-web模块他有以下职能:
传入参数的接收及校验工作
调用对应业务的dubbo服务,本身不负责业务逻辑的处理【消费者】
返回参数的封装,以及当下层发生异常,则抛出指定的自定义异常
定义swagger2的接口暴露,方便测试
下面我们对model-shop-web进行dubbo的集成,首先在model-shop-producer的pom.xml导入下列依赖:
<!-- Dubbo Spring Cloud Starter --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-dubbo</artifactId> </dependency>
再在model-shop-web的application.yml添加定义如下:
dubbo: #dubbo应用服务定义 application: #版本 version: 1.0.0 #日志 logger: slf4j cloud: #表示要订阅服务的服务名,可以配置'*',代表订阅所有服务,不推荐使用。若需订阅多应用,使用 "," 分割。 subscribed-services: model-shop-producer,model-basic-producer,model-shop-applet,model-shop-user,model-trading-producer scan: #扫描路径 base-packages: com.itheima.restkeeper.web registry: #添加中心 address: spring-cloud://192.168.112.77 #服务协议定义 protocol: #服务协议名称 name: dubbo #协议端口 port: 27078 #线程数 threads: 200 #dubbo调用日志 accesslog: D:/logs/model-shop-web-01.log
相比【生产者】来说【消费者】就多subscribed-services属性配置,此属性为订阅服务的服务名
测试
3.4、dubbo接口定义
可能在以往的开发中我们只是知道三层架构【mapper、service、web】,那这里的face层是什么意思呢?大家都知道dubbo服务的调用逻辑,【消费者】调用【生成者】,那他们直接能调用的集成也就是声明统一的接口定义,在餐掌柜系统中dubbo层接口就起到此作用:
定义dubbo服务接口
被生产者依赖,按照face层的dubbo接口定义实现业务
被消费者依赖,从face层的dubbo中选择自己的业务接口
首先我们需要定义一个dubbo接口,那我们在哪里写能?从餐掌柜maven分层构建中我们可用发现,每个以==restkeeper-model-==开头的项目都是一个二级模块,并且模块中都有一个model--interface的模块,例如:
没有错,这里就是我们定义face接口的三级模块,在定义dubbo接口的时,都需要找到类似:model-***-interface的模块去书写
3.5、service核心业务
上面我们再介绍【dubbo层接口实现】提到dubbo层接口实现会调用核心业务,这个核心也就是这里的service层,还是以restkeeper-model-shop为例:
如果模块以model-***-service的格式出现,则表示此模块为核心模块,职能:
pojo、mapper、service层定义
使用mybatis-push持久化框架完成CRUD操作
作为【核心业务层】被dubbo服务接口实现所调用
提供代码生成器的支持
在定义service接口时都需要继承IService,IService为我们提供了基本的操作:
Save
// 插入一条记录(选择字段,策略插入) boolean save(T entity); // 插入(批量) boolean saveBatch(Collection<T> entityList); // 插入(批量) boolean saveBatch(Collection<T> entityList, int batchSize);
SaveOrUpdate
// TableId 注解存在更新记录,否插入一条记录 boolean saveOrUpdate(T entity); // 根据updateWrapper尝试更新,否继续执行saveOrUpdate(T)方法 boolean saveOrUpdate(T entity, Wrapper<T> updateWrapper); // 批量修改插入 boolean saveOrUpdateBatch(Collection<T> entityList); // 批量修改插入 boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize);
Remove
// 根据 entity 条件,删除记录 boolean remove(Wrapper<T> queryWrapper); // 根据 ID 删除 boolean removeById(Serializable id); // 根据 columnMap 条件,删除记录 boolean removeByMap(Map<String, Object> columnMap); // 删除(根据ID 批量删除) boolean removeByIds(Collection<? extends Serializable> idList);
Update
// 根据 UpdateWrapper 条件,更新记录 需要设置 boolean update(Wrapper<T> updateWrapper); // 根据 whereWrapper 条件,更新记录 boolean update(T updateEntity, Wrapper<T> whereWrapper); // 根据 ID 选择修改 boolean updateById(T entity); // 根据ID 批量更新 boolean updateBatchById(Collection<T> entityList); // 根据ID 批量更新 boolean updateBatchById(Collection<T> entityList, int batchSize);
Get
// 根据 ID 查询 T getById(Serializable id); // 根据 Wrapper,查询一条记录。结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last("LIMIT 1") T getOne(Wrapper<T> queryWrapper); // 根据 Wrapper,查询一条记录 T getOne(Wrapper<T> queryWrapper, boolean throwEx); // 根据 Wrapper,查询一条记录 Map<String, Object> getMap(Wrapper<T> queryWrapper); // 根据 Wrapper,查询一条记录 <V> V getObj(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);
List
// 查询所有 List<T> list(); // 查询列表 List<T> list(Wrapper<T> queryWrapper); // 查询(根据ID 批量查询) Collection<T> listByIds(Collection<? extends Serializable> idList); // 查询(根据 columnMap 条件) Collection<T> listByMap(Map<String, Object> columnMap); // 查询所有列表 List<Map<String, Object>> listMaps(); // 查询列表 List<Map<String, Object>> listMaps(Wrapper<T> queryWrapper); // 查询全部记录 List<Object> listObjs(); // 查询全部记录 <V> List<V> listObjs(Function<? super Object, V> mapper); // 根据 Wrapper 条件,查询全部记录 List<Object> listObjs(Wrapper<T> queryWrapper); // 根据 Wrapper 条件,查询全部记录 <V> List<V> listObjs(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);
Page
// 无条件分页查询 IPage<T> page(IPage<T> page); // 条件分页查询 IPage<T> page(IPage<T> page, Wrapper<T> queryWrapper); // 无条件分页查询 IPage<Map<String, Object>> pageMaps(IPage<T> page); // 条件分页查询 IPage<Map<String, Object>> pageMaps(IPage<T> page, Wrapper<T> queryWrapper);
Count
// 查询总记录数 int count(); // 根据 Wrapper 条件,查询总记录数 int count(Wrapper<T> queryWrapper);
Chain
query
// 链式查询 普通 QueryChainWrapper<T> query(); // 链式查询 lambda 式。注意:不支持 Kotlin LambdaQueryChainWrapper<T> lambdaQuery(); // 示例: query().eq("column", value).one(); lambdaQuery().eq(Entity::getId, value).list();
update
// 链式更改 普通 UpdateChainWrapper<T> update(); // 链式更改 lambda 式。注意:不支持 Kotlin LambdaUpdateChainWrapper<T> lambdaUpdate(); // 示例: update().eq("column", value).remove(); lambdaUpdate().eq(Entity::getId, value).update(entity);
mapper继承BaseMapper的方法:
Insert
// 插入一条记录int insert(T entity);
Delete
// 根据 entity 条件,删除记录 int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper); // 删除(根据ID 批量删除) int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList); // 根据 ID 删除 int deleteById(Serializable id); // 根据 columnMap 条件,删除记录 int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
Update
// 根据 whereWrapper 条件,更新记录 int update(@Param(Constants.ENTITY) T updateEntity, @Param(Constants.WRAPPER) Wrapper<T> whereWrapper); // 根据 ID 修改 int updateById(@Param(Constants.ENTITY) T entity);
Select
// 根据 ID 查询 T selectById(Serializable id); // 根据 entity 条件,查询一条记录 T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 查询(根据ID 批量查询) List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList); // 根据 entity 条件,查询全部记录 List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 查询(根据 columnMap 条件) List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap); // 根据 Wrapper 条件,查询全部记录 List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 根据 Wrapper 条件,查询全部记录。注意: 只返回第一个字段的值 List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 根据 entity 条件,查询全部记录(并翻页) IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 根据 Wrapper 条件,查询全部记录(并翻页) IPage<Map<String, Object>> selectMapsPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 根据 Wrapper 条件,查询总记录数 Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
商家平台启动依赖:
-Dfile.encoding=utf-8 -server -XX:PermSize=150M -XX:MaxPermSize=150M
第三章 商家平台-品牌管理
餐掌柜是一个SAAS系统,商家入驻系统后,运营平台可以为商家开通管理账号,商家可以独立维护自己的多个品牌,例如
从图中我们可以看到:商家【润润餐饮集团】下就有多个品牌,可以对品牌做管理,下面我们就来实现品牌的管理功能
1、功能区拆解
红色区域:此处为查询条件功能区,筛选列表信息
功能 | 说明 |
---|---|
品牌 | 输入品牌名称,键盘按enter触发搜索列表 |
分类 | ==从数据字典加载数据==,change触发搜索列表 |
状态 | 下拉框,显示【启用、禁用】,change触发搜索列表 |
蓝色区域:品牌信息维护功能
功能 | 说明 |
---|---|
添加 | 新增品牌信息,包含品牌图片上传,分类、状态设置等功能 |
修改 | 修改品牌信息,包含品牌图片上传,分类、状态设置等功能 |
删除 | 删除品牌信息==【真实删除】==,删除时,会有再次确认提示 |
禁用、启用 | 禁用、启用品牌 |
2、数据库结构
CREATE TABLE `tab_brand` ( `id` bigint(18) NOT NULL COMMENT '品牌id', `brand_name` varchar(255) COLLATE utf8_bin NOT NULL COMMENT '品牌名称', `category` varchar(32) COLLATE utf8_bin NOT NULL COMMENT '品牌分类', `enable_flag` varchar(18) CHARACTER SET utf8 DEFAULT NULL COMMENT '是否有效', `created_time` datetime DEFAULT NULL COMMENT '创建时间', `updated_time` datetime DEFAULT NULL COMMENT '创建时间', `sharding_id` bigint(18) DEFAULT NULL COMMENT '分库id', `enterprise_id` bigint(18) NOT NULL COMMENT '商户号', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='品牌管理';
自动填充:
下列字段,无需手动填充,系统会自动填充下列字段
字段 | 注释 | 填充方式 |
---|---|---|
id | 主键 | 雪花算法 |
enterprise_id | 商户号 | mybatis-plus-多租户拦截 |
created_time | 创建时间 | mybatis-plus-自动填充组件 |
updated_time | 修改时间 | mybatis-plus-自动填充组件 |
sharding_id | 分库id | mybatis-plus-自动填充组件 |
3、功能开发
在开始业务开发之前,我们首先看一下品牌的UML图
BrandController:对AffixFace和BrandFace接口进行dubbo的RPC调用,为dubbo服务的消费者
AffixFace【公用功能】:附件上传dubbo接口,我们在创建品牌时,需要上传品牌图片到图片中心
BrandFace:品牌dubbo接口定义
BrandFaceImpl:品牌dubbo接口定义实现,这里做VO和POJO的转换
IBrandService:品牌的业务接口定义,为BrandFaceImpl提供核心业务逻辑的定义
BrandServiceImpl:品牌的业务接口定义实现
3.1、BrandFace接口
在开发中,我们以往都是先写controller层业务,而在实际开发中我们应该先定义服务接口,因为我们一致提倡的是面向接口开发,而这里,我们应该先定义dubbo服务的接口,只有统一接口在做微服务调用时才能保证逻辑的清晰
package com.itheima.restkeeper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.itheima.restkeeper.req.BrandVo; import java.util.List; /** * @ClassName BrandFace.java * @Description 品牌服务 */ public interface BrandFace { /** * @Description 品牌列表 * @param brandVo 查询条件 * @param pageNum 页码 * @param pageSize 每页条数 * @return Page<BrandVo> */ Page<BrandVo> findBrandVoPage(BrandVo brandVo, int pageNum, int pageSize); /** * @Description 创建品牌 * @param brandVo 对象信息 * @return BrandVo */ BrandVo createBrand(BrandVo brandVo); /** * @Description 修改品牌 * @param brandVo 对象信息 * @return Boolean */ Boolean updateBrand(BrandVo brandVo); /** * @Description 删除品牌 * @param checkedIds 选择中对象Ids * @return Boolean */ Boolean deleteBrand(String[] checkedIds); /** * @Description 查找品牌 * @param brandId 选择对象信息Id * @return BrandVo */ BrandVo findBrandByBrandId(Long brandId); /*** * @description 查询品牌下拉框 * @return: List<BrandVo> */ List<BrandVo> findBrandVoList(); }
3.2、BrandFaceImpl接口实现
BrandFace接口定义实现,注意这里我们对VO和POJO的转换都在此类中进行处理,下面我们看下具体实现:
package com.itheima.restkeeper.face; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.itheima.restkeeper.BrandFace; import com.itheima.restkeeper.pojo.Brand; import com.itheima.restkeeper.req.BrandVo; import com.itheima.restkeeper.service.IBrandService; import com.itheima.restkeeper.utils.BeanConv; import org.apache.dubbo.config.annotation.DubboService; import org.apache.dubbo.config.annotation.Method; import org.springframework.beans.factory.annotation.Autowired; import java.util.List; /** * @ClassName BrandFaceImpl.java * @Description 品牌dubbo接口定义实现 */ @DubboService(version = "${dubbo.application.version}",timeout = 5000, methods ={ @Method(name = "findBrandVoPage",retries = 2), @Method(name = "createBrand",retries = 0), @Method(name = "updateBrand",retries = 0), @Method(name = "deleteBrand",retries = 0) }) public class BrandFaceImpl implements BrandFace { @Autowired IBrandService brandService; @Override public Page<BrandVo> findBrandVoPage(BrandVo brandVo, int pageNum, int pageSize) { //查询Page<Brand>品牌分页 Page<Brand> page = brandService.findBrandVoPage(brandVo, pageNum, pageSize); //转化Page<Brand>为Page<BrandVo> Page<BrandVo> pageVo = new Page<>(); BeanConv.toBean(page,pageVo); //转换List<Brand>为 List<BrandVo> List<Brand> brandList = page.getRecords(); List<BrandVo> brandVoList = BeanConv.toBeanList(brandList,BrandVo.class); pageVo.setRecords(brandVoList); //返回结果 return pageVo; } @Override public BrandVo createBrand(BrandVo brandVo) { return BeanConv.toBean( brandService.createBrand(brandVo), BrandVo.class); } @Override public Boolean updateBrand(BrandVo brandVo) { return brandService.updateBrand(brandVo); } @Override public Boolean deleteBrand(String[] checkedIds) { return brandService.deleteBrand(checkedIds); } @Override public BrandVo findBrandByBrandId(Long brandId) { return BeanConv.toBean(brandService.getById(brandId),BrandVo.class) } @Override public List<BrandVo> findBrandVoList() { return BeanConv.toBeanList(brandService.findBrandVoList(),BrandVo.class); } }
3.3、IBrandService业务接口
品牌的业务接口定义,为BrandFaceImpl提供核心业务逻辑的定义,此接口继承了IService<Brand>接口,IService<Brand>里面有很多基础的方法可以直接使用
package com.itheima.restkeeper.service; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.itheima.restkeeper.pojo.Brand; import com.baomidou.mybatisplus.extension.service.IService; import com.itheima.restkeeper.req.BrandVo; import java.util.List; /** * @Description:品牌管理 服务类 */ public interface IBrandService extends IService<Brand> { /** * @Description 品牌列表 * @param brandVo 查询条件 * @param pageNum 页码 * @param pageSize 每页条数 * @return Page<BrandVo> */ Page<Brand> findBrandVoPage(BrandVo brandVo, int pageNum, int pageSize); /** * @Description 创建品牌 * @param brandVo 对象信息 * @return */ Brand createBrand(BrandVo brandVo); /** * @Description 修改品牌 * @param brandVo 对象信息 * @return Boolean */ Boolean updateBrand(BrandVo brandVo); /** * @Description 删除品牌 * @param checkedIds 选择中对象Ids * @return Boolean */ Boolean deleteBrand(String[] checkedIds); /*** * @description 查询品牌下拉框 * @return: List<BrandVo> */ List<Brand> findBrandVoList(); }
3.4、BrandServiceImpl接口实现
品牌的业务接口定义实现,这里继承ServiceImpl<BrandMapper, Brand> 类,实现 IBrandService接口
package com.itheima.restkeeper.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.itheima.restkeeper.basic.BasicPojo; import com.itheima.restkeeper.constant.SuperConstant; import com.itheima.restkeeper.pojo.Brand; import com.itheima.restkeeper.mapper.BrandMapper; import com.itheima.restkeeper.req.BrandVo; import com.itheima.restkeeper.service.IBrandService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.itheima.restkeeper.utils.BeanConv; import com.itheima.restkeeper.utils.EmptyUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * @Description:品牌管理 服务实现类 */ @Service public class BrandServiceImpl extends ServiceImpl<BrandMapper, Brand> implements IBrandService { @Override public Page<Brand> findBrandVoPage(BrandVo brandVo, int pageNum, int pageSize) { //构建分页对象 Page<Brand> page = new Page<>(pageNum,pageSize); //构建查询条件 QueryWrapper<Brand> queryWrapper = new QueryWrapper<>(); //按品牌名称查询 if (!EmptyUtil.isNullOrEmpty(brandVo.getBrandName())) { queryWrapper.lambda().likeRight(Brand::getBrandName,brandVo.getBrandName()); } //按品牌分类查询 if (!EmptyUtil.isNullOrEmpty(brandVo.getCategory())) { queryWrapper.lambda().likeRight(Brand::getCategory,brandVo.getCategory()); } //按品牌状态查询 if (!EmptyUtil.isNullOrEmpty(brandVo.getEnableFlag())) { queryWrapper.lambda().eq(Brand::getEnableFlag,brandVo.getEnableFlag()); } //按创建时间降序 queryWrapper.lambda().orderByDesc(Brand::getCreatedTime); //执行分页查询 return page(page, queryWrapper); } @Override public Brand createBrand(BrandVo brandVo) { //转换BrandVo为Brand Brand brand = BeanConv.toBean(brandVo, Brand.class); //执行保存 boolean flag = save(brand); if (flag){ return brand; } return null; } @Override public Boolean updateBrand(BrandVo brandVo) { //转换BrandVo为Brand Brand brand = BeanConv.toBean(brandVo, Brand.class); //按ID执行修改 return updateById(brand); } @Override public Boolean deleteBrand(String[] checkedIds) { //转换数组为集合 List<String> ids = Arrays.asList(checkedIds); List<Long> idsLong = new ArrayList<>(); ids.forEach(n->{ idsLong.add(Long.valueOf(n)); }); //批量删除 return removeByIds(idsLong); } @Override public List<Brand> findBrandVoList() { //构建查询条件 QueryWrapper<Brand> queryWrapper = new QueryWrapper<>(); queryWrapper.lambda().eq(Brand::getEnableFlag, SuperConstant.YES); //查询有效状态 return list(queryWrapper); } }
3.5、BrandController类
此类为服务的【消费者】,对AffixFace和BrandFace接口进行dubbo的RPC调用
package com.itheima.restkeeper.web; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.itheima.restkeeper.AffixFace; import com.itheima.restkeeper.BrandFace; import com.itheima.restkeeper.basic.ResponseWrap; import com.itheima.restkeeper.enums.BrandEnum; import com.itheima.restkeeper.exception.ProjectException; import com.itheima.restkeeper.req.AffixVo; import com.itheima.restkeeper.req.BrandVo; import com.itheima.restkeeper.utils.EmptyUtil; import com.itheima.restkeeper.utils.ExceptionsUtil; import com.itheima.restkeeper.utils.ResponseWrapBuild; import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParams; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.apache.dubbo.config.annotation.DubboReference; import org.springframework.web.bind.annotation.*; import java.util.List; import java.util.stream.Collectors; /** * @ClassName BrandController.java * @Description 品牌Controller */ @RestController @RequestMapping("brand") @Slf4j @Api(tags = "品牌controller") public class BrandController { @DubboReference(version = "${dubbo.application.version}",check = false) BrandFace brandFace; @DubboReference(version = "${dubbo.application.version}",check = false) AffixFace affixFace; /** * @Description 品牌列表 * @param brandVo 查询条件 * @return */ @PostMapping("page/{pageNum}/{pageSize}") @ApiOperation(value = "查询品牌分页",notes = "查询品牌分页") @ApiImplicitParams({ @ApiImplicitParam(name = "brandVo",value = "品牌查询对象",required = true,dataType = "BrandVo"), @ApiImplicitParam(paramType = "path",name = "pageNum",value = "页码",dataType = "Integer"), @ApiImplicitParam(paramType = "path",name = "pageSize",value = "每页条数",dataType = "Integer") }) public ResponseWrap<Page<BrandVo>> findBrandVoPage( @RequestBody BrandVo brandVo, @PathVariable("pageNum") int pageNum, @PathVariable("pageSize") int pageSize) throws ProjectException { try { //查询分页 Page<BrandVo> brandVoPage = brandFace.findBrandVoPage(brandVo, pageNum, pageSize); //如果结果不为空,则为分页指定附件信息 if (!EmptyUtil.isNullOrEmpty(brandVoPage)&& !EmptyUtil.isNullOrEmpty(brandVoPage.getRecords())){ List<BrandVo> brandVoList = brandVoPage.getRecords(); //循环处理结果集 brandVoList.forEach(n->{ List<AffixVo> affixVoList = affixFace.findAffixVoByBusinessId(n.getId()); if (!EmptyUtil.isNullOrEmpty(affixVoList)){ n.setAffixVo(affixVoList.get(0)); } }); brandVoPage.setRecords(brandVoList); } //返回分页结果 return ResponseWrapBuild.build(BrandEnum.SUCCEED,brandVoPage); } catch (Exception e) { log.error("查询品牌列表异常:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(BrandEnum.PAGE_FAIL); } } /** * @Description 查询品牌下拉框 * @return */ @GetMapping("list") @ApiOperation(value = "查询品牌下拉框",notes = "查询品牌下拉框") public ResponseWrap<List<BrandVo>> findBrandVoList() throws ProjectException { try { //查询下拉框 List<BrandVo> brandVos = brandFace.findBrandVoList(); //返回结果 return ResponseWrapBuild.build(BrandEnum.SUCCEED,brandVos); } catch (Exception e) { log.error("查询品牌列表异常:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(BrandEnum.PAGE_FAIL); } } /** * @Description 添加品牌 * @param brandVo 对象信息 * @return */ @PostMapping @ApiOperation(value = "添加品牌",notes = "添加品牌") @ApiImplicitParam(name = "brandVo",value = "品牌对象",required = true,dataType = "BrandVo") ResponseWrap<BrandVo> createBrand(@RequestBody BrandVo brandVo) throws ProjectException { try { //添加品牌 BrandVo brandVoResult = brandFace.createBrand(brandVo); //绑定附件 if (!EmptyUtil.isNullOrEmpty(brandVoResult)){ affixFace.bindBusinessId( AffixVo.builder() .businessId(brandVoResult.getId()) .id(brandVo.getAffixVo().getId()) .build()); } //指定附件信息 brandVoResult.setAffixVo(AffixVo.builder() .pathUrl(brandVo.getAffixVo().getPathUrl()) .businessId(brandVoResult.getId()) .id(brandVo.getAffixVo().getId()).build()); //返回结果 return ResponseWrapBuild.build(BrandEnum.SUCCEED,brandVoResult); } catch (Exception e) { log.error("保存品牌异常:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(BrandEnum.CREATE_FAIL); } } /** * @Description 修改品牌 * @param brandVo 对象信息 * @return */ @PatchMapping @ApiOperation(value = "修改品牌",notes = "修改品牌") @ApiImplicitParam(name = "brandVo",value = "品牌对象",required = true,dataType = "BrandVo") ResponseWrap<Boolean> updateBrand(@RequestBody BrandVo brandVo) throws ProjectException { try { //按ID修改品牌信息 Boolean flag = brandFace.updateBrand(brandVo); //修改成功后,处理附件图片信息 if (flag){ List<AffixVo> affixVoList = affixFace.findAffixVoByBusinessId(brandVo.getId()); List<Long> affixIds = affixVoList.stream().map(AffixVo::getId).collect(Collectors.toList()); if (!affixIds.contains(brandVo.getAffixVo().getId())){ //删除图片 flag = affixFace.deleteAffixVoByBusinessId(brandVo.getId()); //绑定新图片 affixFace.bindBusinessId(AffixVo.builder() .businessId(brandVo.getId()) .id(brandVo.getAffixVo().getId()) .build()); } } //返回结果 return ResponseWrapBuild.build(BrandEnum.SUCCEED,flag); } catch (Exception e) { log.error("保存品牌异常:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(BrandEnum.UPDATE_FAIL); } } /** * @Description 删除品牌 * @param brandVo 查询对象 * @return */ @DeleteMapping @ApiOperation(value = "删除品牌",notes = "删除品牌") @ApiImplicitParam(name = "brandVo",value = "品牌查询对象",required = true,dataType = "BrandVo") ResponseWrap<Boolean> deleteBrand(@RequestBody BrandVo brandVo ) throws ProjectException { try { //删除选中节点 String[] checkedIds = brandVo.getCheckedIds(); Boolean flag = brandFace.deleteBrand(checkedIds); //删除图片 for (String checkedId : checkedIds) { affixFace.deleteAffixVoByBusinessId(Long.valueOf(checkedId)); } //返回结果 return ResponseWrapBuild.build(BrandEnum.SUCCEED,flag); } catch (Exception e) { log.error("删除品牌异常:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(BrandEnum.DELETE_FAIL); } } /** * @Description 查找品牌 * @param brandId 品牌ID * @return */ @GetMapping("{brandId}") @ApiOperation(value = "查找品牌",notes = "查找品牌") @ApiImplicitParam(paramType = "path",name = "brandId",value = "品牌Id",dataType = "Long") ResponseWrap<BrandVo> findBrandByBrandId(@PathVariable("brandId") Long brandId) throws ProjectException { //品牌是否为空判断 if (EmptyUtil.isNullOrEmpty(brandId)){ throw new ProjectException(BrandEnum.SELECT_BRAND_FAIL); } try { //安装ID查询品牌信息 BrandVo brandVo = brandFace.findBrandByBrandId(brandId); //返回结果 return ResponseWrapBuild.build(BrandEnum.SUCCEED,brandVo); } catch (Exception e) { log.error("查找品牌所有品牌异常:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(BrandEnum.SELECT_BRAND_FAIL); } } @PostMapping("update-brand-enableFlag") @ApiOperation(value = "修改品牌状态",notes = "修改品牌状态") @ApiImplicitParam(name = "brandVo",value = "品牌查询对象",required = true,dataType = "BrandVo") ResponseWrap<Boolean> updateBrandEnableFlag(@RequestBody BrandVo brandVo) throws ProjectException { try { //修改品牌状态 Boolean flag = brandFace.updateBrand(brandVo); //返回结果 return ResponseWrapBuild.build(BrandEnum.SUCCEED,flag); } catch (Exception e) { log.error("修改品牌状态:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(BrandEnum.UPDATE_FAIL); } } }
第四章 商家平台-门店管理
上一章节中,我们开发了品牌管理,为每个入住的商家提供品牌维护的功能,从下面的图中我们知道,品牌下面有对应的门店,下面我们来开发商家平台-门店管理
1、功能区拆解
红色区域:此处为查询条件功能区,筛选列表信息
功能 | 说明 |
---|---|
门店名称 | 输入门店名称,键盘按enter触发搜索列表 |
状态 | 下拉框,显示【启用、禁用】,change触发搜索列表 |
蓝色区域:品牌信息维护功能
功能 | 说明 |
---|---|
添加 | 新增门店信息,包含品牌,负责人、状态设置等功能 |
修改 | 修改门店信息,包含品牌,负责人、状态设置等功能 |
删除 | 删除门店信息==【真实删除】==,删除时,会有再次确认提示 |
禁用、启用 | 禁用、启用门店 |
2、数据库结构
CREATE TABLE `tab_store` ( `id` bigint(18) NOT NULL COMMENT '门店主键id', `brand_id` bigint(18) DEFAULT NULL COMMENT '品牌', `store_name` varchar(255) COLLATE utf8_bin NOT NULL COMMENT '门店名称', `province` varchar(32) COLLATE utf8_bin NOT NULL COMMENT '地址(省)', `city` varchar(32) COLLATE utf8_bin NOT NULL COMMENT '地址(市)', `area` varchar(32) COLLATE utf8_bin NOT NULL COMMENT '地址(区)', `address` varchar(500) COLLATE utf8_bin NOT NULL COMMENT '详细地址', `manager_id` varchar(32) COLLATE utf8_bin DEFAULT NULL COMMENT '管理员id', `enable_flag` varchar(18) CHARACTER SET utf8 DEFAULT NULL COMMENT '是否有效', `created_time` datetime DEFAULT NULL COMMENT '创建时间', `updated_time` datetime DEFAULT NULL COMMENT '创建时间', `sharding_id` bigint(18) DEFAULT NULL COMMENT '分库id', `enterprise_id` bigint(18) NOT NULL COMMENT '商户号', `longitude` double(9,6) DEFAULT NULL COMMENT '经度', `dimensionality` double(9,6) DEFAULT NULL COMMENT '维度', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='门店信息账号';
自动填充:
下列字段,无需手动填充,系统会自动填充下列字段
字段 | 注释 | 填充方式 |
---|---|---|
id | 主键 | 雪花算法 |
enterprise_id | 商户号 | mybatis-plus-多租户拦截 |
created_time | 创建时间 | mybatis-plus-自动填充组件 |
updated_time | 修改时间 | mybatis-plus-自动填充组件 |
sharding_id | 分库id | mybatis-plus-自动填充组件 |
3、功能开发
在开始业务开发之前,我们首先看一下门店的UML图
StoreController:对AffixFace和StoreFace接口进行dubbo的RPC调用,为dubbo服务的消费者
AffixFace【公用功能】:附件上传dubbo接口,我们在创建门店时,需要上传品牌图片到图片中心
StoreFace:门店dubbo接口定义
StoreFaceImpl:门店dubbo接口定义实现,这里做VO和POJO的转换
IStoreService:门店的业务接口定义,为StoreFaceImpl提供核心业务逻辑的定义
StoreServiceImpl:门店的业务接口定义实现
3.1、StoreFace接口
StoreFace:门店dubbo接口定义:分页列表、创建门店、修改门店、删除门店、按ID查找门店、门店下拉框
package com.itheima.restkeeper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.itheima.restkeeper.req.StoreVo; import java.util.List; /** * @ClassName StoreFace.java * @Description 门店dubbo服务 */ public interface StoreFace { /** * @Description 品牌列表 * @param storeVo 查询条件 * @param pageNum 页码 * @param pageSize 每页条数 * @return Page<BrandVo> */ Page<StoreVo> findStoreVoPage(StoreVo storeVo, int pageNum, int pageSize); /** * @Description 创建门店 * @param storeVo 对象信息 * @return StoreVo */ StoreVo createStore(StoreVo storeVo); /** * @Description 修改门店 * @param storeVo 对象信息 * @return Boolean */ Boolean updateStore(StoreVo storeVo); /** * @Description 删除门店 * @param checkedIds 选择对象信息Id * @return Boolean */ Boolean deleteStore(String[] checkedIds); /** * @Description 查找门店 * @param storeId 选择对象信息Id * @return StoreVo */ StoreVo findStoreByStoreId(Long storeId); /*** * @description 查询门店下拉框 * @return: List<StoreVo> */ List<StoreVo> findStoreVoList(); }
3.2、StoreFaceImpl接口实现
StoreFaceImpl:门店dubbo接口定义实现,这里做VO和POJO的转换
package com.itheima.restkeeper.face; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.itheima.restkeeper.StoreFace; import com.itheima.restkeeper.pojo.Store; import com.itheima.restkeeper.req.StoreVo; import com.itheima.restkeeper.service.IStoreService; import com.itheima.restkeeper.utils.BeanConv; import com.itheima.restkeeper.utils.EmptyUtil; import lombok.extern.slf4j.Slf4j; import org.apache.dubbo.config.annotation.DubboService; import org.apache.dubbo.config.annotation.Method; import org.springframework.beans.factory.annotation.Autowired; import java.util.List; /** * @ClassName StoreFaceImpl.java * @Description 门店dubbbo服务实现 */ @Slf4j @DubboService(version = "${dubbo.application.version}",timeout = 5000, methods ={ @Method(name = "findStoreVoPage",retries = 2), @Method(name = "createStore",retries = 0), @Method(name = "updateStore",retries = 0), @Method(name = "deleteStore",retries = 0) }) public class StoreFaceImpl implements StoreFace { @Autowired IStoreService storeService; @Override public Page<StoreVo> findStoreVoPage(StoreVo storeVo, int pageNum, int pageSize) { //查询Page<Store>门店分页 Page<Store> page = storeService.findStoreVoPage(storeVo, pageNum, pageSize); //转化Page<Store>为Page<StoreVo> Page<StoreVo> pageVo = new Page<>(); BeanConv.toBean(page,pageVo); //转换List<Store>为 List<StoreVo> List<Store> storeList = page.getRecords(); List<StoreVo> storeVoList = BeanConv.toBeanList(storeList,StoreVo.class); pageVo.setRecords(storeVoList); //返回结果 return pageVo; } @Override public StoreVo createStore(StoreVo storeVo) { return BeanConv.toBean( storeService.createStore(storeVo), StoreVo.class); } @Override public Boolean updateStore(StoreVo storeVo) { return storeService.updateStore(storeVo); } @Override public Boolean deleteStore(String[] checkedIds) { return storeService.deleteStore(checkedIds); } @Override public StoreVo findStoreByStoreId(Long storeId) { return BeanConv.toBean(storeService.getById(storeId),StoreVo.class); } @Override public List<StoreVo> findStoreVoList() { return BeanConv.toBeanList(storeService.findStoreVoList(),StoreVo.class); } }
3.3、IStoreService业务接口
IStoreService:门店的业务接口定义,为StoreFaceImpl提供核心业务逻辑的定义
package com.itheima.restkeeper.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.itheima.restkeeper.basic.BasicPojo; import com.itheima.restkeeper.constant.SuperConstant; import com.itheima.restkeeper.pojo.Store; import com.itheima.restkeeper.mapper.StoreMapper; import com.itheima.restkeeper.req.StoreVo; import com.itheima.restkeeper.service.IStoreService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.itheima.restkeeper.utils.BeanConv; import com.itheima.restkeeper.utils.EmptyUtil; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * @Description:门店信息账号 服务实现类 */ @Service public class StoreServiceImpl extends ServiceImpl<StoreMapper, Store> implements IStoreService { @Override public Page<Store> findStoreVoPage(StoreVo storeVo, int pageNum, int pageSize) { //构建分页对象 Page<Store> page = new Page<>(pageNum,pageSize); QueryWrapper<Store> queryWrapper = new QueryWrapper<>(); //按门店名称查询 if (!EmptyUtil.isNullOrEmpty(storeVo.getStoreName())) { queryWrapper.lambda().likeRight(Store::getStoreName,storeVo.getStoreName()); } //按门店状态查询 if (!EmptyUtil.isNullOrEmpty(storeVo.getEnableFlag())) { queryWrapper.lambda().eq(Store::getEnableFlag,storeVo.getEnableFlag()); } //按创建时间降序 queryWrapper.lambda().orderByDesc(Store::getCreatedTime); //执行分页查询 return page(page, queryWrapper); } @Override public Store createStore(StoreVo storeVo) { //转换StoreVo为Store Store store = BeanConv.toBean(storeVo, Store.class); //执行保存 boolean flag = save(store); if (flag){ return store; } return null; } @Override public Boolean updateStore(StoreVo storeVo) { //转换StoreVo为Store Store store = BeanConv.toBean(storeVo, Store.class); //按ID执行修改 return updateById(store); } @Override public Boolean deleteStore(String[] checkedIds) { //转换数组为集合 List<String> ids = Arrays.asList(checkedIds); List<Long> idsLong = new ArrayList<>(); ids.forEach(n->{ idsLong.add(Long.valueOf(n)); }); //批量删除 return removeByIds(idsLong); } @Override public List<Store> findStoreVoList() { //构建查询条件 QueryWrapper<Store> queryWrapper = new QueryWrapper<>(); queryWrapper.lambda().eq(BasicPojo::getEnableFlag,SuperConstant.YES); //查询有效状态 return list(queryWrapper); } }
3.4、StoreServiceImpl接口实现
StoreServiceImpl:门店的业务接口定义实现
package com.itheima.restkeeper.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.itheima.restkeeper.basic.BasicPojo; import com.itheima.restkeeper.constant.SuperConstant; import com.itheima.restkeeper.pojo.Brand; import com.itheima.restkeeper.pojo.Store; import com.itheima.restkeeper.pojo.Store; import com.itheima.restkeeper.mapper.StoreMapper; import com.itheima.restkeeper.req.StoreVo; import com.itheima.restkeeper.service.IStoreService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.itheima.restkeeper.utils.BeanConv; import com.itheima.restkeeper.utils.EmptyUtil; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * @Description:门店信息账号 服务实现类 */ @Service public class StoreServiceImpl extends ServiceImpl<StoreMapper, Store> implements IStoreService { @Override public Page<Store> findStoreVoPage(StoreVo storeVo, int pageNum, int pageSize) { //分页条件构建 Page<Store> page = new Page<>(pageNum,pageSize); QueryWrapper<Store> queryWrapper = new QueryWrapper<>(); //按门店名称查询 if (!EmptyUtil.isNullOrEmpty(storeVo.getStoreName())) { queryWrapper.lambda().likeRight(Store::getStoreName,storeVo.getStoreName()); } //按门店状态查询 if (!EmptyUtil.isNullOrEmpty(storeVo.getEnableFlag())) { queryWrapper.lambda().eq(Store::getEnableFlag,storeVo.getEnableFlag()); } queryWrapper.lambda().orderByDesc(Store::getCreatedTime); return page(page, queryWrapper); } @Override public Store createStore(StoreVo storeVo) { Store store = BeanConv.toBean(storeVo, Store.class); boolean flag = save(store); if (flag){ return store; } return null; } @Override public Boolean updateStore(StoreVo storeVo) { Store store = BeanConv.toBean(storeVo, Store.class); return updateById(store); } @Override public Boolean deleteStore(String[] checkedIds) { List<String> ids = Arrays.asList(checkedIds); List<Long> idsLong = new ArrayList<>(); ids.forEach(n->{ idsLong.add(Long.valueOf(n)); }); return removeByIds(idsLong); } @Override public List<Store> findStoreVoList() { QueryWrapper<Store> queryWrapper = new QueryWrapper<>(); queryWrapper.lambda().eq(BasicPojo::getEnableFlag,SuperConstant.YES); return list(queryWrapper); } }
3.5、StoreController类
此类为服务的【消费者】,对AffixFace和StoreFace接口进行dubbo的RPC调用
package com.itheima.restkeeper.web; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.itheima.restkeeper.AffixFace; import com.itheima.restkeeper.StoreFace; import com.itheima.restkeeper.basic.ResponseWrap; import com.itheima.restkeeper.enums.StoreEnum; import com.itheima.restkeeper.exception.ProjectException; import com.itheima.restkeeper.req.StoreVo; import com.itheima.restkeeper.utils.EmptyUtil; import com.itheima.restkeeper.utils.ExceptionsUtil; import com.itheima.restkeeper.utils.ResponseWrapBuild; import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParams; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.apache.dubbo.config.annotation.DubboReference; import org.springframework.web.bind.annotation.*; import java.util.List; /** * @ClassName StoreController.java * @Description 门店Controller */ @RestController @RequestMapping("store") @Slf4j @Api(tags = "门店controller") public class StoreController { @DubboReference(version = "${dubbo.application.version}",check = false) StoreFace storeFace; @DubboReference(version = "${dubbo.application.version}",check = false) AffixFace affixFace; /** * @Description 门店列表 * @param storeVo 查询条件 * @return */ @PostMapping("page/{pageNum}/{pageSize}") @ApiOperation(value = "查询门店分页",notes = "查询门店分页") @ApiImplicitParams({ @ApiImplicitParam(name = "storeVo",value = "门店查询对象",required = true,dataType = "StoreVo"), @ApiImplicitParam(paramType = "path",name = "pageNum",value = "页码",dataType = "Integer"), @ApiImplicitParam(paramType = "path",name = "pageSize",value = "每页条数",dataType = "Integer") }) public ResponseWrap<Page<StoreVo>> findStoreVoPage( @RequestBody StoreVo storeVo, @PathVariable("pageNum") int pageNum, @PathVariable("pageSize") int pageSize) throws ProjectException { try { //分页查询 Page<StoreVo> storeVoPage = storeFace.findStoreVoPage(storeVo, pageNum, pageSize); //返回结果 return ResponseWrapBuild.build(StoreEnum.SUCCEED,storeVoPage); } catch (Exception e) { log.error("查询门店列表异常:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(StoreEnum.PAGE_FAIL); } } /** * @Description 添加门店 * @param storeVo 对象信息 * @return */ @PostMapping @ApiOperation(value = "添加门店",notes = "添加门店") @ApiImplicitParam(name = "storeVo",value = "门店对象",required = true,dataType = "StoreVo") ResponseWrap<StoreVo> createStore(@RequestBody StoreVo storeVo) throws ProjectException { try { //添加门店 StoreVo storeVoResult = storeFace.createStore(storeVo); //返回结果 return ResponseWrapBuild.build(StoreEnum.SUCCEED,storeVoResult); } catch (Exception e) { log.error("保存门店异常:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(StoreEnum.CREATE_FAIL); } } /** * @Description 修改门店 * @param storeVo 对象信息 * @return */ @PatchMapping @ApiOperation(value = "修改门店",notes = "修改门店") @ApiImplicitParam(name = "storeVo",value = "门店对象",required = true,dataType = "StoreVo") ResponseWrap<Boolean> updateStore(@RequestBody StoreVo storeVo) throws ProjectException { try { //修改门店 Boolean flag = storeFace.updateStore(storeVo); //返回结果 return ResponseWrapBuild.build(StoreEnum.SUCCEED,flag); } catch (Exception e) { log.error("保存门店异常:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(StoreEnum.UPDATE_FAIL); } } /** * @Description 删除门店 * @param storeVo 查询对象 * @return */ @DeleteMapping @ApiOperation(value = "删除门店",notes = "删除门店") @ApiImplicitParam(name = "storeVo",value = "门店查询对象",required = true,dataType = "StoreVo") ResponseWrap<Boolean> deleteStore(@RequestBody StoreVo storeVo ) throws ProjectException { try { //删除门店 String[] checkedIds = storeVo.getCheckedIds(); Boolean flag = storeFace.deleteStore(checkedIds); //返回结果 return ResponseWrapBuild.build(StoreEnum.SUCCEED,flag); } catch (Exception e) { log.error("删除门店异常:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(StoreEnum.DELETE_FAIL); } } /** * @Description 查找门店 * @param storeId 门店id * @return */ @GetMapping("{storeId}") @ApiOperation(value = "查找门店",notes = "查找门店") @ApiImplicitParam(paramType = "path",name = "storeId",value = "门店Id",dataType = "Long") ResponseWrap<StoreVo> findStoreByStoreId(@PathVariable("storeId") Long storeId) throws ProjectException { try { //按ID查询门店 StoreVo storeVo = storeFace.findStoreByStoreId(storeId); //返回结果 return ResponseWrapBuild.build(StoreEnum.SUCCEED,storeVo); } catch (Exception e) { log.error("查找门店所有门店异常:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(StoreEnum.SELECT_STORE_FAIL); } } /** * @Description 查找门店 * @return */ @GetMapping("list") @ApiOperation(value = "查找门店下拉列表",notes = "查找门店下拉列表") ResponseWrap<List<StoreVo>> findStoreVoList() throws ProjectException { try { //查询所有有效门店 List<StoreVo> list = storeFace.findStoreVoList(); //返回结果 return ResponseWrapBuild.build(StoreEnum.SUCCEED,list); } catch (Exception e) { log.error("查找门店所有门店异常:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(StoreEnum.SELECT_STORE_LIST_FAIL); } } @PostMapping("update-store-enableFlag") @ApiOperation(value = "修改门店状态",notes = "修改门店状态") ResponseWrap<Boolean> updateStoreEnableFlag(@RequestBody StoreVo storeVo) throws ProjectException { try { //修改门店状态 Boolean flag = storeFace.updateStore(storeVo); //返回结果 return ResponseWrapBuild.build(StoreEnum.SUCCEED,flag); } catch (Exception e) { log.error("修改门店状态:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(StoreEnum.UPDATE_FAIL); } } }
第五章 商家平台-用户管理
在梳理【商家平台-用户管理】前,我们先看下商家平台管理员账号的开通流程:商家申请入驻平台后,【商家运营平台】为商户开通管理员账号,开通后,商户管理可以登录系统,并且管理门店员账号功能
1、功能区拆解
红色区域:此处为查询条件功能区,筛选列表信息
功能 | 说明 |
---|---|
登录名 | 输入登录名,键盘按enter触发搜索列表 |
手机 | 输入手机,键盘按enter触发搜索列表 |
状态 | 下拉框,显示【启用、禁用】,change触发搜索列表 |
蓝色区域:员工账号维护功能
功能 | 说明 |
---|---|
添加 | 新增用户信息,包含头像上传,角色、打折、优惠比例设置等功能 |
修改 | 修改用户信息,包含头像重新上传,角色、打折、优惠比例设置择等功能 |
删除 | 删除用户信息==【真实删除】==,删除时,会有再次确认提示 |
禁用、启用 | 禁用启用账户 |
重置密码 | 对用户账号密码重置 |
2、数据库结构
员工表结构如下
CREATE TABLE `tab_user` ( `id` bigint(18) NOT NULL COMMENT '主键', `store_id` bigint(32) DEFAULT NULL COMMENT '门店Id', `enterprise_id` bigint(18) NOT NULL COMMENT '商户号', `username` varchar(36) DEFAULT NULL COMMENT '登录名称', `real_name` varchar(36) DEFAULT NULL COMMENT '真实姓名', `password` varchar(150) DEFAULT NULL COMMENT '密码', `sex` varchar(11) DEFAULT NULL COMMENT '性别', `mobil` varchar(36) DEFAULT NULL COMMENT '电话', `email` varchar(36) DEFAULT NULL COMMENT '邮箱', `discount_limit` decimal(10,2) DEFAULT NULL COMMENT '折扣上线', `reduce_limit` decimal(10,2) DEFAULT NULL COMMENT '减免金额上线', `duties` varchar(36) DEFAULT NULL COMMENT '职务', `sort_no` int(11) DEFAULT NULL COMMENT '排序', `enable_flag` varchar(18) DEFAULT NULL COMMENT '是否有效', `created_time` datetime DEFAULT NULL COMMENT '创建时间', `updated_time` datetime DEFAULT NULL COMMENT '创建时间', `sharding_id` bigint(18) DEFAULT NULL COMMENT '分库id', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='用户表';
自动填充:
下列字段,无需手动填充,系统会自动填充下列字段
字段 | 注释 | 填充方式 |
---|---|---|
id | 主键 | 雪花算法 |
enterprise_id | 商户号 | mybatis-plus-多租户拦截 |
created_time | 创建时间 | mybatis-plus-自动填充组件 |
updated_time | 修改时间 | mybatis-plus-自动填充组件 |
sharding_id | 分库id | mybatis-plus-自动填充组件 |
3、功能开发
商家平台的用户管理,其核心业务实现已在model-security-service模块中实现,这里我们只需要使用即可,在开始业务开发之前,我们首先看一下用户管理的UML图
UserController【model-shop-web】:对AffixFace和UserFace接口进行dubbo的RPC调用,为dubbo服务的消费者
AffixFace【公用功能】:附件上传dubbo接口,我们在创建用户时,需要上传用户头像到图片中心
UserFace【==model-shop-interface==】:用户dubbo接口定义
UserFaceImpl【==model-shop-user==】:用户dubbo接口定义实现,这里做VO和POJO的转换
IUserService【公用功能】:用户的业务接口定义,为UserFaceImpl提供核心业务逻辑的定义
UserServiceImpl【公用功能】:用户的业务接口定义实现
3.1、UserFace接口
UserFace:用户dubbo接口定义
package com.itheima.restkeeper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.itheima.restkeeper.req.UserVo; import java.util.List; /** * @Description:用户dubbo接口 */ public interface UserFace { /** * @param userVo 查询条件 * @param pageNum 当前页 * @param pageSize 每页条数 * @return Page<UserVo> * @Description 用户列表 */ Page<UserVo> findUserVoPage(UserVo userVo, int pageNum, int pageSize); /** * @param userVo 对象信息 * @return UserVo * @Description 创建用户 */ UserVo createUser(UserVo userVo); /** * @param userVo 对象信息 * @return Boolean * @Description 修改用户 */ Boolean updateUser(UserVo userVo); /** * @param checkedIds 选择对象信息Id * @return Boolean * @Description 删除用户 */ Boolean deleteUser(String[] checkedIds); /** * @param userId 选择对象信息Id * @return * @Description 查找用户 */ UserVo findUserByUserId(Long userId); /** * @return List<UserVo> * @Description 查找用户list */ List<UserVo> findUserVoList(); }
3.2、UserFaceImpl接口实现
UserFaceImpl:用户dubbo接口定义实现,这里做VO和POJO的转换
package com.itheima.restkeeper.face; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.itheima.restkeeper.UserFace; import com.itheima.restkeeper.pojo.Role; import com.itheima.restkeeper.pojo.User; import com.itheima.restkeeper.req.UserVo; import com.itheima.restkeeper.service.IUserAdapterService; import com.itheima.restkeeper.service.IUserService; import com.itheima.restkeeper.utils.BeanConv; import com.itheima.restkeeper.utils.EmptyUtil; import lombok.extern.slf4j.Slf4j; import org.apache.dubbo.config.annotation.DubboService; import org.apache.dubbo.config.annotation.Method; import org.springframework.beans.factory.annotation.Autowired; import java.util.ArrayList; import java.util.List; /** * @ClassName UserFaceImpl.java * @Description TODO */ @Slf4j @DubboService(version = "${dubbo.application.version}",timeout = 5000, methods ={ @Method(name = "findUserVoPage",retries = 2), @Method(name = "createUser",retries = 0), @Method(name = "updateUser",retries = 0), @Method(name = "deleteUser",retries = 0), @Method(name = "updateUserEnableFlag",retries = 0) }) public class UserFaceImpl implements UserFace { @Autowired IUserService userService; @Autowired IUserAdapterService userAdapterService; @Override public Page<UserVo> findUserVoPage(UserVo userVo, int pageNum, int pageSize) { //查询Page<User>用户分页 Page<User> page = userService.findUserVoPage(userVo, pageNum, pageSize); //转化Page<User>为Page<UserVo> Page<UserVo> pageVo = new Page<>(); BeanConv.toBean(page,pageVo); //转换List<User>为 List<UserVo> List<User> userList = page.getRecords(); List<UserVo> userVoList = BeanConv.toBeanList(userList,UserVo.class); //查询角色信息 if (!EmptyUtil.isNullOrEmpty(userList)){ userVoList.forEach(n->{ List<Role> roles = userAdapterService.findRoleByUserId(n.getId()); List<String> roleIdList = new ArrayList<>(); for (Role role : roles) { roleIdList.add(String.valueOf(role.getId())); } String[] roleIds = new String[roleIdList.size()]; roleIdList.toArray(roleIds); n.setHasRoleIds(roleIds); }); } //返回结果 pageVo.setRecords(userVoList); return pageVo; } @Override public UserVo createUser(UserVo userVo) { return BeanConv.toBean( userService.createUser(userVo),UserVo.class); } @Override public Boolean updateUser(UserVo userVo) { return userService.updateUser(userVo); } @Override public Boolean deleteUser(String[] checkedIds) { return userService.deleteUser(checkedIds); } @Override public UserVo findUserByUserId(Long userId) { return BeanConv.toBean(userService.getById(userId),UserVo.class); } @Override public List<UserVo> findUserVoList() { return BeanConv.toBeanList(userService.findUserVoList(),UserVo.class); } }
3.3、UserController类
UserController:对AffixFace和UserFace接口进行dubbo的RPC调用,为dubbo服务的消费者
package com.itheima.restkeeper.web; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.itheima.restkeeper.AffixFace; import com.itheima.restkeeper.UserFace; import com.itheima.restkeeper.basic.ResponseWrap; import com.itheima.restkeeper.enums.UserEnum; import com.itheima.restkeeper.exception.ProjectException; import com.itheima.restkeeper.req.AffixVo; import com.itheima.restkeeper.req.UserVo; import com.itheima.restkeeper.utils.EmptyUtil; import com.itheima.restkeeper.utils.ExceptionsUtil; import com.itheima.restkeeper.utils.ResponseWrapBuild; import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParams; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.apache.dubbo.config.annotation.DubboReference; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.web.bind.annotation.*; import java.util.List; import java.util.stream.Collectors; /** * @ClassName UserController.java * @Description 用户Controller */ @RestController @RequestMapping("user") @Slf4j @Api(tags = "用户controller") public class UserController { @DubboReference(version = "${dubbo.application.version}",check = false) UserFace userFace; @DubboReference(version = "${dubbo.application.version}",check = false) AffixFace affixFace; @Autowired BCryptPasswordEncoder bCryptPasswordEncoder; /** * @Description 用户列表 * @param userVo 查询条件 * @return */ @PostMapping("page/{pageNum}/{pageSize}") @ApiOperation(value = "查询用户分页",notes = "查询用户分页") @ApiImplicitParams({ @ApiImplicitParam(name = "userVo",value = "用户查询对象",required = true,dataType = "UserVo"), @ApiImplicitParam(paramType = "path",name = "pageNum",value = "页码",dataType = "Integer"), @ApiImplicitParam(paramType = "path",name = "pageSize",value = "每页条数",dataType = "Integer") }) public ResponseWrap<Page<UserVo>> findUserVoPage( @RequestBody UserVo userVo, @PathVariable("pageNum") int pageNum, @PathVariable("pageSize") int pageSize) throws ProjectException { try { //查询用户分页 Page<UserVo> userVoPage = userFace.findUserVoPage(userVo, pageNum, pageSize); //用户不为空,则处理用户头像附件 if (!EmptyUtil.isNullOrEmpty(userVoPage)&& !EmptyUtil.isNullOrEmpty(userVoPage.getRecords())){ List<UserVo> userVoList = userVoPage.getRecords(); userVoList.forEach(n->{ List<AffixVo> affixVoList = affixFace.findAffixVoByBusinessId(n.getId()); if (!EmptyUtil.isNullOrEmpty(affixVoList)){ n.setAffixVo(affixVoList.get(0)); } }); //回填用户头像附件 userVoPage.setRecords(userVoList); } return ResponseWrapBuild.build(UserEnum.SUCCEED,userVoPage); } catch (Exception e) { log.error("查询用户列表异常:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(UserEnum.PAGE_FAIL); } } /** * @Description 注册用户 * @param userVo 对象信息 * @return */ @PostMapping @ApiOperation(value = "注册用户",notes = "注册用户") @ApiImplicitParam(name = "userVo",value = "用户对象",required = true,dataType = "UserVo") ResponseWrap<UserVo> registerUser(@RequestBody UserVo userVo) throws ProjectException { try { String plainPassword = userVo.getPassword(); //明文转密文密码,必须要加{bcrypt}要不认证不通过 String password = "{bcrypt}"+bCryptPasswordEncoder.encode(plainPassword); userVo.setPassword(password); //注册用户 UserVo userVoResult = userFace.createUser(userVo); //绑定附件 if (!EmptyUtil.isNullOrEmpty(userVoResult)){ affixFace.bindBusinessId(AffixVo.builder() .businessId(userVoResult.getId()) .id(userVo.getAffixVo().getId()).build()); } //回填用户头像附加 userVoResult.setAffixVo(AffixVo.builder() .pathUrl(userVo.getAffixVo().getPathUrl()) .businessId(userVoResult.getId()) .id(userVo.getAffixVo().getId()).build()); //补全角色信息 userVoResult.setHasRoleIds(userVo.getHasRoleIds()); return ResponseWrapBuild.build(UserEnum.SUCCEED,userVoResult); } catch (Exception e) { log.error("保存用户异常:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(UserEnum.CREATE_FAIL); } } /** * @Description 修改用户 * @param userVo 对象信息 * @return */ @PatchMapping @ApiOperation(value = "修改用户",notes = "修改用户") @ApiImplicitParam(name = "userVo",value = "用户对象",required = true,dataType = "UserVo") ResponseWrap<Boolean> updateUser(@RequestBody UserVo userVo) throws ProjectException { try { //修改用户信息 Boolean flag = userFace.updateUser(userVo); //修改成功,修改用户头像附加 if (flag){ List<AffixVo> affixVoList = affixFace.findAffixVoByBusinessId(userVo.getId()); List<Long> affixIds = affixVoList.stream().map(AffixVo::getId).collect(Collectors.toList()); if (!affixIds.contains(userVo.getAffixVo().getId())){ //删除图片 flag = affixFace.deleteAffixVoByBusinessId(userVo.getId()); //绑定新图片 affixFace.bindBusinessId(AffixVo.builder() .businessId(userVo.getId()) .id(userVo.getAffixVo().getId()).build()); } } //返回结果 return ResponseWrapBuild.build(UserEnum.SUCCEED,flag); } catch (Exception e) { log.error("保存用户异常:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(UserEnum.UPDATE_FAIL); } } /** * @Description 删除用户 * @param userVo 查询对象 * @return */ @DeleteMapping @ApiOperation(value = "删除用户",notes = "删除用户") @ApiImplicitParam(name = "userVo",value = "用户查询对象",required = true,dataType = "UserVo") ResponseWrap<Boolean> deleteUser(@RequestBody UserVo userVo ) throws ProjectException { try { //删除选择用户 String[] checkedIds = userVo.getCheckedIds(); Boolean flag = userFace.deleteUser(checkedIds); //删除图片 for (String checkedId : checkedIds) { affixFace.deleteAffixVoByBusinessId(Long.valueOf(checkedId)); } //返回结果 return ResponseWrapBuild.build(UserEnum.SUCCEED,flag); } catch (Exception e) { log.error("删除用户异常:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(UserEnum.DELETE_FAIL); } } /** * @Description 查找用户 * @param userId 登录名 * @return */ @GetMapping("select-by-userId/{userId}") @ApiOperation(value = "查找用户",notes = "查找用户") @ApiImplicitParam(paramType = "path",name = "userId",value = "用户Id",example = "1",dataType = "Long") ResponseWrap<UserVo> findUserByUserId(@PathVariable("userId") Long userId) throws ProjectException { try { //查找用户信息 UserVo userVo = userFace.findUserByUserId(userId); //返回结果 return ResponseWrapBuild.build(UserEnum.SUCCEED,userVo); } catch (Exception e) { log.error("查找用户所有角色异常:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(UserEnum.SELECT_USER_FAIL); } } /** * @Description 查找用户list * @return */ @GetMapping("select-list") @ApiOperation(value = "查找用户list",notes = "查找用户list") ResponseWrap<List<UserVo>> findUserVoList() throws ProjectException { try { //查询所有有效的用户信息 List<UserVo> list = userFace.findUserVoList(); //返回结果 return ResponseWrapBuild.build(UserEnum.SUCCEED,list); } catch (Exception e) { log.error("查找用户list异常:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(UserEnum.SELECT_USER_LIST_FAIL); } } @PostMapping("update-user-enableFlag") @ApiOperation(value = "修改用户状态",notes = "修改用户状态") @ApiImplicitParam(name = "userVo",value = "用户查询对象",required = true,dataType = "UserVo") ResponseWrap<Boolean> updateUserEnableFlag(@RequestBody UserVo userVo) throws ProjectException { try { //修改用户状态 Boolean flag = userFace.updateUser(userVo); //返回结果 return ResponseWrapBuild.build(UserEnum.SUCCEED,flag); } catch (Exception e) { log.error("修改用户状态异常:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(UserEnum.UPDATE_FAIL); } } @PostMapping("rest-password") @ApiOperation(value = "重置用户密码",notes = "重置用户密码") @ApiImplicitParam(name = "userVo",value = "用户对象",required = true,dataType = "UserVo") ResponseWrap<Boolean> restPssword(@RequestBody UserVo userVo) throws ProjectException { //必须要加{bcrypt}要不认证不通过 String password = "{bcrypt}"+bCryptPasswordEncoder.encode("88488"); userVo.setPassword(password); try { //修改用户密码 Boolean flag = userFace.updateUser(userVo); //返回结果 return ResponseWrapBuild.build(UserEnum.SUCCEED,flag); } catch (Exception e) { log.error("重置用户密码:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(UserEnum.UPDATE_FAIL); } } }
学习目标
1、完成区域管理开发
2、完成桌台管理开发
3、完成分类管理开发
4、完成菜品管理开发
第一章 商家平台-区域管理
在商家用户建立品牌、门店、员工后,就需要对当前的门店划分区域,每个门店都可以维护自己的餐厅区域,==注意的桌台的数据归属我们采用多租户功能区实现这里不需要考虑【多租户】后面有专题去简介==
1、功能区拆解
红色区域:此处为查询条件功能区,筛选列表信息
功能 | 说明 |
---|---|
区域名称 | 输入区域名称,键盘按enter触发搜索列表 |
状态 | 下拉框,显示【启用、禁用】,change触发搜索列表 |
蓝色区域:区域维护功能
功能 | 说明 |
---|---|
添加 | 新增区域信息 |
修改 | 修改区域信息 |
删除 | 删除区域信息==【真实删除】==,删除时,会有再次确认提示 |
禁用、启用 | 禁用启用区域 |
2、数据库结构
CREATE TABLE `tab_table_area` ( `id` bigint(18) NOT NULL COMMENT '桌台区域id', `area_name` varchar(64) COLLATE utf8_bin NOT NULL COMMENT '区域名称', `created_time` datetime DEFAULT NULL COMMENT '创建时间', `updated_time` datetime DEFAULT NULL COMMENT '创建时间', `sharding_id` bigint(18) DEFAULT NULL COMMENT '分库id', `store_id` bigint(18) NOT NULL COMMENT '门店主键id', `enterprise_id` bigint(18) NOT NULL COMMENT '商户号', `sort_no` int(11) DEFAULT NULL COMMENT '排序', `enable_flag` varchar(10) CHARACTER SET utf8 NOT NULL COMMENT '是否有效', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='桌台区域';
自动填充:
下列字段,无需手动填充,系统会自动填充下列字段
字段 | 注释 | 填充方式 |
---|---|---|
id | 主键 | 雪花算法 |
enterprise_id | 商户号 | mybatis-plus-多租户拦截 |
created_time | 创建时间 | mybatis-plus-自动填充组件 |
updated_time | 修改时间 | mybatis-plus-自动填充组件 |
sharding_id | 分库id | mybatis-plus-自动填充组件 |
store_id | 门店id | mybatis-plus-多租户拦截 |
3、功能开发
在开始业务开发之前,我们首先看一下区域管理的UML图
TableAreaController:对TableAreaFace接口进行dubbo的RPC调用,为dubbo服务的消费者
TableAreaFace:区域管理dubbo接口定义
TableAreaFaceImpl:区域管理dubbo接口定义实现,这里做VO和POJO的转换
ITableAreaService:区域管理的业务接口定义,为TableAreaFaceImpl提供核心业务逻辑的定义
TableAreaServiceImpl:区域管理的业务接口定义实现
3.1、TableAreaFace接口
TableAreaFace:区域管理dubbo接口定义
package com.itheima.restkeeper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.itheima.restkeeper.req.TableAreaVo; import java.util.List; /** * @ClassName TableAreaFace.java * @Description 桌台区域dubbo服务 */ public interface TableAreaFace { /** * @param tableAreaVo 查询条件 * @param pageNum 当前页 * @param pageSize 每页大小 * @return Page<TableAreaVo> * @Description 区域列表 */ Page<TableAreaVo> findTableAreaVoPage(TableAreaVo tableAreaVo, int pageNum, int pageSize); /** * @param tableAreaVo 对象信息 * @return TableAreaVo * @Description 创建区域 */ TableAreaVo createTableArea(TableAreaVo tableAreaVo); /** * @param tableAreaVo 对象信息 * @return Boolean * @Description 修改区域 */ Boolean updateTableArea(TableAreaVo tableAreaVo); /** * @param checkedIds 选择对象信息Id * @return Boolean * @Description 删除区域 */ Boolean deleteTableArea(String[] checkedIds); /** * @param tableAreaId 选择对象信息Id * @return TableAreaVo * @Description 查找区域 */ TableAreaVo findTableAreaByTableAreaId(Long tableAreaId); /*** * @description 查询区域下拉框 * @return: List<TableAreaVo> */ List<TableAreaVo> findTableAreaVoList(); }
3.2、TableAreaFaceImpl接口实现
TableAreaFaceImpl:区域管理dubbo接口定义实现,这里做VO和POJO的转换
package com.itheima.restkeeper.face; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.itheima.restkeeper.TableAreaFace; import com.itheima.restkeeper.pojo.TableArea; import com.itheima.restkeeper.req.TableAreaVo; import com.itheima.restkeeper.service.ITableAreaService; import com.itheima.restkeeper.utils.BeanConv; import com.itheima.restkeeper.utils.EmptyUtil; import lombok.extern.slf4j.Slf4j; import org.apache.dubbo.config.annotation.DubboService; import org.apache.dubbo.config.annotation.Method; import org.springframework.beans.factory.annotation.Autowired; import java.util.List; /** * @ClassName TableAreaFaceImpl.java * @Description 桌台区域dubbo服务 */ @Slf4j @DubboService(version = "${dubbo.application.version}",timeout = 5000, methods ={ @Method(name = "findTableAreaVoPage",retries = 2), @Method(name = "createTableArea",retries = 0), @Method(name = "updateTableArea",retries = 0), @Method(name = "deleteTableArea",retries = 0) }) public class TableAreaFaceImpl implements TableAreaFace { @Autowired ITableAreaService tableAreaService; @Override public Page<TableAreaVo> findTableAreaVoPage(TableAreaVo tableAreaVo, int pageNum, int pageSize) { //查询Page<TableArea>区域分页 Page<TableArea> page = tableAreaService.findTableAreaVoPage(tableAreaVo, pageNum, pageSize); //转换Page<TableArea>为Page<TableAreaVo> Page<TableAreaVo> pageVo = new Page<>(); BeanConv.toBean(page,pageVo); //转换List<TableArea>为List<TableAreaVo> List<TableArea> tableAreaList = page.getRecords(); List<TableAreaVo> tableAreaVoList = BeanConv.toBeanList(tableAreaList,TableAreaVo.class); pageVo.setRecords(tableAreaVoList); //返回分页结果 return pageVo; } @Override public TableAreaVo createTableArea(TableAreaVo tableAreaVo) { return BeanConv.toBean(tableAreaService.createTableArea(tableAreaVo),TableAreaVo.class); } @Override public Boolean updateTableArea(TableAreaVo tableAreaVo) { return tableAreaService.updateTableArea(tableAreaVo); } @Override public Boolean deleteTableArea(String[] checkedIds) { return tableAreaService.deleteTableArea(checkedIds); } @Override public TableAreaVo findTableAreaByTableAreaId(Long tableAreaId) { return BeanConv.toBean(tableAreaService.getById(tableAreaId),TableAreaVo.class); } @Override public List<TableAreaVo> findTableAreaVoList() { return BeanConv.toBeanList(tableAreaService.findTableAreaVoList(),TableAreaVo.class); } }
3.3、ITableAreaService业务接口
ITableAreaService:区域管理的业务接口定义,为TableAreaFaceImpl提供核心业务逻辑的定义
package com.itheima.restkeeper.service; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.itheima.restkeeper.pojo.TableArea; import com.itheima.restkeeper.pojo.TableArea; import com.baomidou.mybatisplus.extension.service.IService; import com.itheima.restkeeper.req.TableAreaVo; import java.util.List; /** * @Description:桌台区域 服务类 */ public interface ITableAreaService extends IService<TableArea> { /** * @Description 区域列表 * @param tableAreaVo 查询条件 * @param pageNum 当前页 * @param pageSize 每页大小 * @return Page<TableArea> */ Page<TableArea> findTableAreaVoPage(TableAreaVo tableAreaVo, int pageNum, int pageSize); /** * @Description 创建区域 * @param tableAreaVo 对象信息 * @return TableArea */ TableArea createTableArea(TableAreaVo tableAreaVo); /** * @Description 修改区域 * @param tableAreaVo 对象信息 * @return Boolean */ Boolean updateTableArea(TableAreaVo tableAreaVo); /** * @Description 删除区域 * @param checkedIds 选择的区域ID * @return Boolean */ Boolean deleteTableArea(String[] checkedIds); /*** * @description 查询区域下拉框 * @return: List<TableArea> */ List<TableArea> findTableAreaVoList(); }
3.4、TableAreaServiceImpl接口实现
TableAreaServiceImpl:区域管理的业务接口定义实现
package com.itheima.restkeeper.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.itheima.restkeeper.basic.BasicPojo; import com.itheima.restkeeper.constant.SuperConstant; import com.itheima.restkeeper.mapper.TableAreaMapper; import com.itheima.restkeeper.pojo.TableArea; import com.itheima.restkeeper.req.TableAreaVo; import com.itheima.restkeeper.service.ITableAreaService; import com.itheima.restkeeper.utils.BeanConv; import com.itheima.restkeeper.utils.EmptyUtil; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * @Description:桌台区域 服务实现类 */ @Service public class TableAreaServiceImpl extends ServiceImpl<TableAreaMapper, TableArea> implements ITableAreaService { @Override public Page<TableArea> findTableAreaVoPage(TableAreaVo tableAreaVo, int pageNum, int pageSize) { //构建分页对象 Page<TableArea> page = new Page<>(pageNum,pageSize); QueryWrapper<TableArea> queryWrapper = new QueryWrapper<>(); //按区域名称查询 if (!EmptyUtil.isNullOrEmpty(tableAreaVo.getAreaName())) { queryWrapper.lambda().likeRight(TableArea::getAreaName,tableAreaVo.getAreaName()); } //按是否有效查询 if (!EmptyUtil.isNullOrEmpty(tableAreaVo.getEnableFlag())) { queryWrapper.lambda().eq(TableArea::getEnableFlag,tableAreaVo.getEnableFlag()); } //按sortNo升序排列 queryWrapper.lambda().orderByAsc(TableArea::getSortNo); //返回结果 return page(page, queryWrapper); } @Override public TableArea createTableArea(TableAreaVo tableAreaVo) { //转换TableAreaVo为TableArea TableArea tableArea = BeanConv.toBean(tableAreaVo, TableArea.class); //执行保存 boolean flag = save(tableArea); if (flag){ return tableArea; } return null; } @Override public Boolean updateTableArea(TableAreaVo tableAreaVo) { //转换TableAreaVo为TableArea TableArea tableArea = BeanConv.toBean(tableAreaVo, TableArea.class); //按ID执行修改 return updateById(tableArea); } @Override public Boolean deleteTableArea(String[] checkedIds) { //构建选中的List<Sring>集合 List<String> ids = Arrays.asList(checkedIds); List<Long> idsLong = new ArrayList<>(); ids.forEach(n->{ idsLong.add(Long.valueOf(n)); }); //批量删除 return removeByIds(idsLong); } @Override public List<TableArea> findTableAreaVoList() { //构建查询条件 QueryWrapper<TableArea> queryWrapper = new QueryWrapper<>(); queryWrapper.lambda().eq(BasicPojo::getEnableFlag, SuperConstant.YES); //执行list查询 return list(queryWrapper); } }
3.5、TableAreaController类
TableAreaController:对TableAreaFace接口进行dubbo的RPC调用,为dubbo服务的消费者
package com.itheima.restkeeper.web; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.itheima.restkeeper.TableAreaFace; import com.itheima.restkeeper.basic.ResponseWrap; import com.itheima.restkeeper.enums.TableAreaEnum; import com.itheima.restkeeper.exception.ProjectException; import com.itheima.restkeeper.req.TableAreaVo; import com.itheima.restkeeper.utils.EmptyUtil; import com.itheima.restkeeper.utils.ExceptionsUtil; import com.itheima.restkeeper.utils.ResponseWrapBuild; import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParams; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.apache.dubbo.config.annotation.DubboReference; import org.springframework.web.bind.annotation.*; import java.util.List; /** * @ClassName TableAreaController.java * @Description 区域Controller */ @RestController @RequestMapping("table-area") @Slf4j @Api(tags = "区域controller") public class TableAreaController { @DubboReference(version = "${dubbo.application.version}",check = false) TableAreaFace tableAreaFace; /** * @Description 区域列表 * @param tableAreaVo 查询条件 * @return */ @PostMapping("page/{pageNum}/{pageSize}") @ApiOperation(value = "查询区域分页",notes = "查询区域分页") @ApiImplicitParams({ @ApiImplicitParam(name = "tableAreaVo",value = "区域查询对象",dataType = "TableAreaVo"), @ApiImplicitParam(paramType = "path",name = "pageNum",value = "页码",dataType = "Integer"), @ApiImplicitParam(paramType = "path",name = "pageSize",value = "每页条数",dataType = "Integer") }) public ResponseWrap<Page<TableAreaVo>> findTableAreaVoPage( @RequestBody TableAreaVo tableAreaVo, @PathVariable("pageNum") int pageNum, @PathVariable("pageSize") int pageSize) throws ProjectException { try { //查询分页 Page<TableAreaVo> tableAreaVoPage = tableAreaFace .findTableAreaVoPage(tableAreaVo, pageNum, pageSize); //返回结果 return ResponseWrapBuild.build(TableAreaEnum.SUCCEED,tableAreaVoPage); } catch (Exception e) { log.error("查询区域列表异常:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(TableAreaEnum.PAGE_FAIL); } } /** * @Description 添加区域 * @param tableAreaVo 对象信息 * @return */ @PostMapping @ApiOperation(value = "添加区域",notes = "添加区域") @ApiImplicitParam(name = "tableAreaVo",value = "区域对象",required = true,dataType = "TableAreaVo") ResponseWrap<TableAreaVo> createTableArea(@RequestBody TableAreaVo tableAreaVo) throws ProjectException { try { //创建区域 TableAreaVo tableAreaVoResult = tableAreaFace.createTableArea(tableAreaVo); //返回结果 return ResponseWrapBuild.build(TableAreaEnum.SUCCEED,tableAreaVoResult); } catch (Exception e) { log.error("保存区域异常:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(TableAreaEnum.CREATE_FAIL); } } /** * @Description 修改区域 * @param tableAreaVo 对象信息 * @return */ @PatchMapping @ApiOperation(value = "修改区域",notes = "修改区域") @ApiImplicitParam(name = "tableAreaVo",value = "区域对象",required = true,dataType = "TableAreaVo") ResponseWrap<Boolean> updateTableArea(@RequestBody TableAreaVo tableAreaVo) throws ProjectException { try { //修改区域 Boolean flag = tableAreaFace.updateTableArea(tableAreaVo); //返回结果 return ResponseWrapBuild.build(TableAreaEnum.SUCCEED,flag); } catch (Exception e) { log.error("修改区域异常:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(TableAreaEnum.UPDATE_FAIL); } } /** * @Description 删除区域 * @param tableAreaVo 查询对象 * @return */ @DeleteMapping @ApiOperation(value ="删除区域",notes = "删除区域") @ApiImplicitParam(name="tableAreaVo",value = "区域查询对象",required = true,dataType = "TableAreaVo") ResponseWrap<Boolean> deleteTableArea(@RequestBody TableAreaVo tableAreaVo ) throws ProjectException { try { //获得选中的ids String[] checkedIds = tableAreaVo.getCheckedIds(); //删除区域 Boolean flag = tableAreaFace.deleteTableArea(checkedIds); //返回结果 return ResponseWrapBuild.build(TableAreaEnum.SUCCEED,flag); } catch (Exception e) { log.error("删除区域异常:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(TableAreaEnum.DELETE_FAIL); } } /** * @Description 查找区域 * @param tableAreaId 区域id * @return */ @GetMapping("{tableAreaId}") @ApiOperation(value = "查找区域",notes = "查找区域") @ApiImplicitParam(paramType = "path",name = "tableAreaId",value = "区域Id",dataType = "Long") ResponseWrap<TableAreaVo> findTableAreaByTableAreaId(@PathVariable("tableAreaId") Long tableAreaId) throws ProjectException { try { //查询区域 TableAreaVo tableAreaVo = tableAreaFace.findTableAreaByTableAreaId(tableAreaId); //返回结果 return ResponseWrapBuild.build(TableAreaEnum.SUCCEED,tableAreaVo); } catch (Exception e) { log.error("查找区域所有区域异常:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(TableAreaEnum.SELECT_AREA_FAIL); } } /** * @Description 查找区域 * @return */ @GetMapping("list") @ApiOperation(value = "查找区域下拉列表",notes = "查找区域下拉列表") ResponseWrap<List<TableAreaVo>> findTableAreaVoList() throws ProjectException { try { //查询有效区域列表 List<TableAreaVo> list = tableAreaFace.findTableAreaVoList(); //返回结果 return ResponseWrapBuild.build(TableAreaEnum.SUCCEED,list); } catch (Exception e) { log.error("查找区域所有区域异常:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(TableAreaEnum.SELECT_AREA_LIST_FAIL); } } @PostMapping("update-tableArea-enableFlag") @ApiOperation(value = "修改区域状态",notes = "修改区域状态") ResponseWrap<Boolean> updateTableAreaEnableFlag(@RequestBody TableAreaVo tableAreaVo) throws ProjectException { try { //修改区域状态 Boolean flag = tableAreaFace.updateTableArea(tableAreaVo); //返回结果 return ResponseWrapBuild.build(TableAreaEnum.SUCCEED,flag); } catch (Exception e) { log.error("修改区域状态:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(TableAreaEnum.UPDATE_FAIL); } } }
第二章 商家平台-桌台管理
商家建立区域后,需要为区域指定对于的桌台,桌台的就餐人数,以及各种状态【空闲、开桌、锁桌】等切换,==注意的桌台的数据归属我们采用多租户功能区实现这里不需要考虑【多租户】后面有专题去简介==
1、功能区拆解
红色区域:此处为查询条件功能区,筛选列表信息
功能 | 说明 |
---|---|
桌台名称 | 输入桌台名称,键盘按enter触发搜索列表 |
就餐人数 | 输入就餐人数,键盘按enter触发搜索列 |
桌台状体 | 下拉框,显示【空闲、开台、锁桌】,change触发搜索列表 |
状态 | 下拉框,显示【启用、禁用】,change触发搜索列表 |
蓝色区域:桌台维护功能
功能 | 说明 |
---|---|
添加 | 新增桌台信息 |
修改 | 修改桌台信息 |
删除 | 删除桌台信息==【真实删除】==,删除时,会有再次确认提示 |
禁用、启用 | 禁用启用桌台 |
空闲、锁台 | 切换桌台状态 |
2、数据库结构
CREATE TABLE `tab_table` ( `id` bigint(18) NOT NULL COMMENT '桌台id', `area_id` bigint(18) DEFAULT NULL COMMENT '区域ID', `table_name` varchar(64) COLLATE utf8_bin NOT NULL COMMENT '桌台名称', `table_seat_number` int(11) NOT NULL COMMENT '桌台座位数目', `table_status` varchar(32) COLLATE utf8_bin NOT NULL COMMENT 'FREE:空闲 USER:开桌 lOCK 锁桌', `created_time` datetime DEFAULT NULL COMMENT '创建时间', `updated_time` datetime DEFAULT NULL COMMENT '创建时间', `sharding_id` bigint(18) DEFAULT NULL COMMENT '分库id', `store_id` bigint(18) NOT NULL COMMENT '门店主键id', `enterprise_id` bigint(18) NOT NULL COMMENT '商户号', `sort_no` int(11) DEFAULT NULL COMMENT '排序', `enable_flag` varchar(10) CHARACTER SET utf8 NOT NULL COMMENT '是否有效', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='桌台';
自动填充:
下列字段,无需手动填充,系统会自动填充下列字段
字段 | 注释 | 填充方式 |
---|---|---|
id | 主键 | 雪花算法 |
enterprise_id | 商户号 | mybatis-plus-多租户拦截 |
created_time | 创建时间 | mybatis-plus-自动填充组件 |
updated_time | 修改时间 | mybatis-plus-自动填充组件 |
sharding_id | 分库id | mybatis-plus-自动填充组件 |
store_id | 门店id | mybatis-plus-多租户拦截 |
3、功能开发
TableController:对TableFace接口进行dubbo的RPC调用,为dubbo服务的消费者
TableFace:桌台管理dubbo接口定义
TableFaceImpl:桌台管理dubbo接口定义实现,这里做VO和POJO的转换
ITableService:桌台管理的业务接口定义,为TableFaceImpl提供核心业务逻辑的定义
TableServiceImpl:桌台管理的业务接口定义实现
3.1、TableFace接口
桌台管理dubbo接口定义
package com.itheima.restkeeper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.itheima.restkeeper.req.TableVo; import java.util.List; /** * @Description 桌台dubbo接口服务 */ public interface TableFace { /** * @param tableVo 查询条件 * @param pageNum 当前页 * @param pageSize 每页大小 * @return * @Description 桌台列表 */ Page<TableVo> findTableVoPage(TableVo tableVo, int pageNum, int pageSize); /** * @param tableVo 对象信息 * @return TableVo * @Description 创建桌台 */ TableVo createTable(TableVo tableVo); /** * @param tableVo 对象信息 * @return Boolean * @Description 修改桌台 */ Boolean updateTable(TableVo tableVo); /** * @param checkedIds 选择对象信息Id * @return Boolean * @Description 删除桌台 */ Boolean deleteTable(String[] checkedIds); /** * @param tableId 选择对象信息Id * @return TableVo * @Description 查找桌台 */ TableVo findTableByTableId(Long tableId); /*** * @description 查询桌台下拉框 * @return: List<TableVo> */ List<TableVo> findTableVoList(); }
3.2、TableFaceImpl接口实现
桌台管理dubbo接口定义实现,这里做VO和POJO的转换
package com.itheima.restkeeper.face; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.itheima.restkeeper.TableFace; import com.itheima.restkeeper.pojo.Table; import com.itheima.restkeeper.req.TableVo; import com.itheima.restkeeper.service.ITableService; import com.itheima.restkeeper.utils.BeanConv; import com.itheima.restkeeper.utils.EmptyUtil; import io.seata.spring.annotation.GlobalTransactional; import lombok.extern.slf4j.Slf4j; import org.apache.dubbo.config.annotation.DubboService; import org.apache.dubbo.config.annotation.Method; import org.springframework.beans.factory.annotation.Autowired; import java.util.List; /** * @Description 桌台dubbo接口实现 */ @Slf4j @DubboService(version = "${dubbo.application.version}",timeout = 5000, methods ={ @Method(name = "findTableVoPage",retries = 2), @Method(name = "createTable",retries = 0), @Method(name = "updateTable",retries = 0), @Method(name = "deleteTable",retries = 0) }) public class TableFaceImpl implements TableFace { @Autowired ITableService tableService; @Override public Page<TableVo> findTableVoPage(TableVo tableVo, int pageNum, int pageSize) { Page<Table> page = tableService.findTableVoPage(tableVo, pageNum, pageSize); Page<TableVo> pageVo = new Page<>(); BeanConv.toBean(page,pageVo); //结果集转换 List<Table> tableList = page.getRecords(); List<TableVo> tableVoList = BeanConv.toBeanList(tableList,TableVo.class); pageVo.setRecords(tableVoList); return pageVo; } @Override public TableVo createTable(TableVo tableVo) { return BeanConv.toBean( tableService.createTable(tableVo), TableVo.class); } @Override @GlobalTransactional public Boolean updateTable(TableVo tableVo) { return tableService.updateTable(tableVo); } @Override public Boolean deleteTable(String[] checkedIds) { return tableService.deleteTable(checkedIds); } @Override public TableVo findTableByTableId(Long tableId) { Table table = tableService.getById(tableId); if (!EmptyUtil.isNullOrEmpty(table)){ return BeanConv.toBean(table,TableVo.class); } return null; } @Override public List<TableVo> findTableVoList() { return BeanConv.toBeanList(tableService.findTableVoList(),TableVo.class); } }
3.3、ITableService业务接口
桌台管理的业务接口定义,为TableFaceImpl提供核心业务逻辑的定义
package com.itheima.restkeeper.service; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.IService; import com.itheima.restkeeper.pojo.Table; import com.itheima.restkeeper.req.TableVo; import java.util.List; /** * @Description:桌台服务类 */ public interface ITableService extends IService<Table> { /** * @param tableVo 查询条件 * @param pageNum 当前页 * @param pageSize 每页大小 * @return Page<Table> * @Description 门店列表 */ Page<Table> findTableVoPage(TableVo tableVo, int pageNum, int pageSize); /** * @param tableVo 对象信息 * @return Table * @Description 创建门店 */ Table createTable(TableVo tableVo); /** * @param tableVo 对象信息 * @return Boolean * @Description 修改门店 */ Boolean updateTable(TableVo tableVo); /** * @param checkedIds 选择的门店ID * @return Boolean * @Description 删除门店 */ Boolean deleteTable(String[] checkedIds); /*** * @description 查询门店下拉框 * @return: List<Table> */ List<Table> findTableVoList(); /*** * @description 当桌台处于空闲状态,进行开桌 * @param tableVo * @return */ Boolean openTable(TableVo tableVo); }
3.4、TableServiceImpl业务接口实现
桌台管理的业务接口定义实现
package com.itheima.restkeeper.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.itheima.restkeeper.basic.BasicPojo; import com.itheima.restkeeper.constant.SuperConstant; import com.itheima.restkeeper.pojo.Table; import com.itheima.restkeeper.mapper.TableMapper; import com.itheima.restkeeper.req.TableVo; import com.itheima.restkeeper.service.ITableService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.itheima.restkeeper.utils.BeanConv; import com.itheima.restkeeper.utils.EmptyUtil; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * @Description:桌台 服务实现类 */ @Service public class TableServiceImpl extends ServiceImpl<TableMapper, Table> implements ITableService { @Override public Page<Table> findTableVoPage(TableVo tableVo,int pageNum,int pageSize) { //构建分页对象 Page<Table> page = new Page<>(pageNum,pageSize); QueryWrapper<Table> queryWrapper = new QueryWrapper<>(); //按桌台名称查询 if (!EmptyUtil.isNullOrEmpty(tableVo.getTableName())) { queryWrapper.lambda().likeRight(Table::getTableName,tableVo.getTableName()); } //按就餐人数查询 if (!EmptyUtil.isNullOrEmpty(tableVo.getTableSeatNumber())) { queryWrapper.lambda().eq(Table::getTableSeatNumber,tableVo.getTableSeatNumber()); } //按桌台使用状态查询 if (!EmptyUtil.isNullOrEmpty(tableVo.getTableStatus())) { queryWrapper.lambda().eq(Table::getTableStatus,tableVo.getTableStatus()); } //按桌台有效性查询 if (!EmptyUtil.isNullOrEmpty(tableVo.getEnableFlag())) { queryWrapper.lambda().eq(Table::getEnableFlag,tableVo.getEnableFlag()); } //按sortNo升序排列 queryWrapper.lambda().orderByAsc(Table::getSortNo); //返回分页结果 return page(page,queryWrapper); } @Override public Table createTable(TableVo tableVo) { //转换TableVo为Table Table table = BeanConv.toBean(tableVo, Table.class); //执行保存 boolean flag = save(table); if (flag){ return table; } return null; } @Override public Boolean updateTable(TableVo tableVo) { //转换TableVo为Table Table table = BeanConv.toBean(tableVo, Table.class); //执行按ID进行修改 return updateById(table); } @Override public Boolean deleteTable(String[] checkedIds) { //构建选择ids的List<String>集合 List<String> ids = Arrays.asList(checkedIds); List<Long> idsLong = new ArrayList<>(); ids.forEach(n->{ idsLong.add(Long.valueOf(n)); }); //执行批量移除 return removeByIds(idsLong); } @Override public List<Table> findTableVoList() { //构建查询条件:有效状态 QueryWrapper<Table> queryWrapper = new QueryWrapper<>(); queryWrapper.lambda().eq(BasicPojo::getEnableFlag, SuperConstant.YES); //执行list查询 return list(queryWrapper); } @Override public Boolean openTable(TableVo tableVo) { //构建条件:SuperConstant.FREE LambdaQueryWrapper<Table> lambdaQueryWrapper = new LambdaQueryWrapper(); lambdaQueryWrapper .eq(Table::getTableStatus,SuperConstant.FREE).eq(Table::getId,tableVo.getId()); //执行update条件查询 return update(BeanConv.toBean(tableVo,Table.class),lambdaQueryWrapper); } }
3.5、TableController类
对TableFace接口进行dubbo的RPC调用,为dubbo服务的消费者
package com.itheima.restkeeper.web; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.itheima.restkeeper.TableFace; import com.itheima.restkeeper.basic.ResponseWrap; import com.itheima.restkeeper.enums.TableEnum; import com.itheima.restkeeper.exception.ProjectException; import com.itheima.restkeeper.req.TableVo; import com.itheima.restkeeper.utils.EmptyUtil; import com.itheima.restkeeper.utils.ExceptionsUtil; import com.itheima.restkeeper.utils.ResponseWrapBuild; import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParams; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.apache.dubbo.config.annotation.DubboReference; import org.springframework.web.bind.annotation.*; import java.util.List; /** * @ClassName TableController.java * @Description 桌台Controller */ @RestController @RequestMapping("table") @Slf4j @Api(tags = "桌台controller") public class TableController { @DubboReference(version = "${dubbo.application.version}",check = false) TableFace tableFace; /** * @Description 桌台列表 * @param tableVo 查询条件 * @return */ @PostMapping("page/{pageNum}/{pageSize}") @ApiOperation(value = "查询桌台list",notes = "查询桌台list") @ApiImplicitParams({ @ApiImplicitParam(name = "tableVo",value = "桌台查询对象",dataType = "TableVo"), @ApiImplicitParam(paramType = "path",name = "pageNum",value = "页码",dataType = "Integer"), @ApiImplicitParam(paramType = "path",name = "pageSize",value = "每页条数",dataType = "Integer") }) public ResponseWrap<Page<TableVo>> findTableVoPage( @RequestBody TableVo tableVo, @PathVariable("pageNum") int pageNum, @PathVariable("pageSize") int pageSize) throws ProjectException { try { //分页查询桌台信息 Page<TableVo> tableVoList = tableFace.findTableVoPage(tableVo,pageNum,pageSize); //返回查询结果 return ResponseWrapBuild.build(TableEnum.SUCCEED,tableVoList); } catch (Exception e) { log.error("查询桌台列表异常:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(TableEnum.SELECT_TABLE_LIST_FAIL); } } /** * @Description 添加桌台 * @param tableVo 对象信息 * @return */ @PostMapping @ApiOperation(value = "添加桌台",notes = "添加桌台") @ApiImplicitParam(name = "tableVo",value = "桌台对象",required = true,dataType = "TableVo") ResponseWrap<TableVo> createTable(@RequestBody TableVo tableVo) throws ProjectException { try { //添加桌台 TableVo tableVoResult = tableFace.createTable(tableVo); //返回结果 return ResponseWrapBuild.build(TableEnum.SUCCEED,tableVoResult); } catch (Exception e) { log.error("保存桌台异常:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(TableEnum.CREATE_FAIL); } } /** * @Description 修改桌台 * @param tableVo 对象信息 * @return */ @PatchMapping @ApiOperation(value = "修改桌台",notes = "修改桌台") @ApiImplicitParam(name = "tableVo",value = "桌台对象",required = true,dataType = "TableVo") ResponseWrap<Boolean> updateTable(@RequestBody TableVo tableVo) throws ProjectException { try { //修改桌台 Boolean flag = tableFace.updateTable(tableVo); //返回结果 return ResponseWrapBuild.build(TableEnum.SUCCEED,flag); } catch (Exception e) { log.error("保存桌台异常:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(TableEnum.UPDATE_FAIL); } } /** * @Description 查找桌台 * @param tableId 桌台id * @return */ @GetMapping("{tableId}") @ApiOperation(value = "查找桌台",notes = "查找桌台") @ApiImplicitParam(paramType = "path",name = "tableId",value = "桌台Id",dataType = "Long") ResponseWrap<TableVo> findTableByTableId(@PathVariable("tableId") Long tableId) throws ProjectException { try { //查找桌台 TableVo tableVo = tableFace.findTableByTableId(tableId); //返回结果 return ResponseWrapBuild.build(TableEnum.SUCCEED,tableVo); } catch (Exception e) { log.error("查找桌台所有桌台异常:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(TableEnum.SELECT_TABLE_FAIL); } } /** * @Description 删除桌台 * @param tableVo 查询对象 * @return */ @DeleteMapping @ApiOperation(value = "删除桌台",notes = "删除桌台") @ApiImplicitParam(name = "tableVo",value = "桌台查询对象",required = true,dataType = "TableVo") ResponseWrap<Boolean> deleteTable(@RequestBody TableVo tableVo ) throws ProjectException { try { //选择的ids String[] checkedIds = tableVo.getCheckedIds(); //按ids删除桌台 Boolean flag = tableFace.deleteTable(checkedIds); //返回结果 return ResponseWrapBuild.build(TableEnum.SUCCEED,flag); } catch (Exception e) { log.error("删除桌台异常:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(TableEnum.DELETE_FAIL); } } /** * @Description 查找桌台下拉框集合 * @return */ @GetMapping("list") @ApiOperation(value = "查找桌台列表",notes = "查找桌台列表") ResponseWrap<List<TableVo>> findTableVoList() throws ProjectException { try { //查询集合 List<TableVo> list = tableFace.findTableVoList(); //返回结果 return ResponseWrapBuild.build(TableEnum.SUCCEED,list); } catch (Exception e) { log.error("查找桌台所有桌台异常:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(TableEnum.SELECT_TABLE_LIST_FAIL); } } @PostMapping("update-table-enableFlag") @ApiOperation(value = "修改桌台有效状态",notes = "修改桌台有效状态") ResponseWrap<Boolean> updateTableEnableFlag(@RequestBody TableVo tableVo) throws ProjectException { try { //修改桌台有效状态 Boolean flag = tableFace.updateTable(tableVo); //返回结果 return ResponseWrapBuild.build(TableEnum.SUCCEED,flag); } catch (Exception e) { log.error("修改桌台有效状态:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(TableEnum.UPDATE_FAIL); } } @PostMapping("update-table-tableStatus") @ApiOperation(value = "修改桌台状态",notes = "修改桌台状态") ResponseWrap<Boolean> updateTableStatus(@RequestBody TableVo tableVo) throws ProjectException { try { //修改桌台状态 Boolean flag = tableFace.updateTable(tableVo); //返回结果 return ResponseWrapBuild.build(TableEnum.SUCCEED,flag); } catch (Exception e) { log.error("修改桌台有效状态:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(TableEnum.UPDATE_FAIL); } } }
第三章 商家平台-分类管理
菜品的分类主要是为菜品进行分类,在点餐的app端用户可以根据菜品分类去筛选菜品,帮助用户快速找到锁需要的菜品
==注意的分类的数据归属我们采用多租户功能区实现这里不需要考虑【多租户】后面有专题去简介==
1、功能区拆解
红色区域:此处为查询条件功能区,筛选列表信息
功能 | 说明 |
---|---|
分类名称 | 输入分类名称,键盘按enter触发搜索列表 |
状态 | 下拉框,显示【启用、禁用】,change触发搜索列表 |
蓝色区域:菜品分类维护功能
功能 | 说明 |
---|---|
添加 | 新增分类信息 |
修改 | 修改分类信息 |
删除 | 删除分类信息==【真实删除】==,删除时,会有再次确认提示 |
禁用、启用 | 禁用启用分类 |
在添加或修改菜品分类时候,我们会从数字字典中获得分类类型【菜品、套餐】,用于区分是单个菜品还是套餐
2、数据库结构
CREATE TABLE `tab_category` ( `id` bigint(18) NOT NULL COMMENT '分类id', `category_type` varchar(11) COLLATE utf8_bin DEFAULT NULL COMMENT '类型 菜品:DISH 套餐:MEAL', `category_name` varchar(64) COLLATE utf8_bin NOT NULL COMMENT '分类名称', `enable_flag` varchar(18) CHARACTER SET utf8 DEFAULT NULL COMMENT '是否有效', `created_time` datetime DEFAULT NULL COMMENT '创建时间', `updated_time` datetime DEFAULT NULL COMMENT '创建时间', `sharding_id` bigint(18) DEFAULT NULL COMMENT '分库id', `store_id` bigint(18) NOT NULL COMMENT '门店主键id', `enterprise_id` bigint(18) NOT NULL COMMENT '商户号', `sort_no` int(11) DEFAULT NULL COMMENT '排序', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='菜品及套餐分类';
自动填充:
下列字段,无需手动填充,系统会自动填充下列字段
字段 | 注释 | 填充方式 |
---|---|---|
id | 主键 | 雪花算法 |
enterprise_id | 商户号 | mybatis-plus-多租户拦截 |
created_time | 创建时间 | mybatis-plus-自动填充组件 |
updated_time | 修改时间 | mybatis-plus-自动填充组件 |
sharding_id | 分库id | mybatis-plus-自动填充组件 |
store_id | 门店id | mybatis-plus-多租户拦截 |
3、功能开发
CategoryController:对CategoryFace接口进行dubbo的RPC调用,为dubbo服务的消费者
CategoryFace:菜品分类管理dubbo接口定义
CategoryFaceImpl:菜品分类管理dubbo接口定义实现,这里做VO和POJO的转换
ICategoryService:菜品分类管理的业务接口定义,为CategoryFaceImpl提供核心业务逻辑的定义
CategoryServiceImpl:菜品分类管理的业务接口定义实现
3.1、CategoryFace接口
品分类管理dubbo接口定义
package com.itheima.restkeeper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.itheima.restkeeper.req.CategoryVo; import java.util.List; /** * @ClassName CategoryFace.java * @Description 菜品分类 */ public interface CategoryFace { /** * @Description 分类列表 * @param categoryVo 查询条件 * @param pageNum 当前页 * @param pageSize 每页条数 * @return Page<CategoryVo> */ Page<CategoryVo> findCategoryVoPage(CategoryVo categoryVo, int pageNum, int pageSize); /** * @Description 创建分类 * @param categoryVo 对象信息 * @return CategoryVo */ CategoryVo createCategory(CategoryVo categoryVo); /** * @Description 修改分类 * @param categoryVo 对象信息 * @return Boolean */ Boolean updateCategory(CategoryVo categoryVo); /** * @Description 删除分类 * @param checkedIds 选择对象信息Id * @return Boolean */ Boolean deleteCategory(String[] checkedIds); /** * @Description 查找分类 * @param categoryId 选择对象信息Id * @return CategoryVo */ CategoryVo findCategoryByCategoryId(Long categoryId); /*** * @description 查询分类下拉框 * @return: List<CategoryVo> */ List<CategoryVo> findCategoryVoList(); }
3.2、CategoryFaceImpl接口实现
菜品分类管理dubbo接口定义实现,这里做VO和POJO的转换
package com.itheima.restkeeper.face; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.itheima.restkeeper.CategoryFace; import com.itheima.restkeeper.pojo.Category; import com.itheima.restkeeper.req.CategoryVo; import com.itheima.restkeeper.service.ICategoryService; import com.itheima.restkeeper.utils.BeanConv; import com.itheima.restkeeper.utils.EmptyUtil; import lombok.extern.slf4j.Slf4j; import org.apache.dubbo.config.annotation.DubboService; import org.apache.dubbo.config.annotation.Method; import org.springframework.beans.factory.annotation.Autowired; import java.util.List; /** * @ClassName CategoryFaceImpl.java * @Description 菜品分类dubbo服务接口实现 */ @Slf4j @DubboService(version = "${dubbo.application.version}",timeout = 5000, methods ={ @Method(name = "findCategoryVoPage",retries = 2), @Method(name = "createCategory",retries = 0), @Method(name = "updateCategory",retries = 0), @Method(name = "deleteCategory",retries = 0) }) public class CategoryFaceImpl implements CategoryFace { @Autowired ICategoryService categoryService; @Override public Page<CategoryVo> findCategoryVoPage(CategoryVo categoryVo, int pageNum, int pageSize) { //查询Page<Category>分类分页 Page<Category> page = categoryService.findCategoryVoPage(categoryVo, pageNum, pageSize); //转换Page<Category>为Page<CategoryVo> Page<CategoryVo> pageVo = new Page<>(); BeanConv.toBean(page,pageVo); //转换List<Category>为List<CategoryVo> List<Category> categoryList = page.getRecords(); List<CategoryVo> categoryVoList = BeanConv.toBeanList(categoryList,CategoryVo.class); pageVo.setRecords(categoryVoList); //返回分页结果 return pageVo; } @Override public CategoryVo createCategory(CategoryVo categoryVo) { return BeanConv.toBean( categoryService.createCategory(categoryVo), CategoryVo.class); } @Override public Boolean updateCategory(CategoryVo categoryVo) { return categoryService.updateCategory(categoryVo); } @Override public Boolean deleteCategory(String[] checkedIds) { return categoryService.deleteCategory(checkedIds); } @Override public CategoryVo findCategoryByCategoryId(Long categoryId) { return BeanConv.toBean(categoryService.getById(categoryId),CategoryVo.class); } @Override public List<CategoryVo> findCategoryVoList() { return BeanConv.toBeanList(categoryService.findCategoryVoList(),CategoryVo.class); } }
3.3、ICategoryService业务接口
菜品分类管理的业务接口定义,为CategoryFaceImpl提供核心业务逻辑的定义
package com.itheima.restkeeper.service; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.IService; import com.itheima.restkeeper.pojo.Category; import com.itheima.restkeeper.req.CategoryVo; import java.util.List; /** * @Description:菜品及套餐分类 服务类 */ public interface ICategoryService extends IService<Category> { /** * @Description 分类列表 * @param categoryVo 查询条件 * @param pageNum 当前页 * @param pageSize 当前页 * @return Page<Category> */ Page<Category> findCategoryVoPage(CategoryVo categoryVo, int pageNum, int pageSize); /** * @Description 创建分类 * @param categoryVo 对象信息 * @return Category */ Category createCategory(CategoryVo categoryVo); /** * @Description 修改分类 * @param categoryVo 对象信息 * @return Boolean */ Boolean updateCategory(CategoryVo categoryVo); /** * @Description 删除分类 * @param checkedIds 选择的分类ID * @return Boolean */ Boolean deleteCategory(String[] checkedIds); /*** * @description 查询分类下拉框 * @return: List<Category> */ List<Category> findCategoryVoList(); /*** * @description 查询当前门店的分类 * @param storeId * @return List<Category> */ List<Category> findCategoryVoByStoreId(Long storeId); }
3.4、CategoryServiceImpl业务接口实现
菜品分类管理的业务接口定义实现
package com.itheima.restkeeper.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.itheima.restkeeper.basic.BasicPojo; import com.itheima.restkeeper.constant.SuperConstant; import com.itheima.restkeeper.pojo.Category; import com.itheima.restkeeper.mapper.CategoryMapper; import com.itheima.restkeeper.req.CategoryVo; import com.itheima.restkeeper.service.ICategoryService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.itheima.restkeeper.utils.BeanConv; import com.itheima.restkeeper.utils.EmptyUtil; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * @Description:菜品分类业务服务实现类 */ @Service public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements ICategoryService { @Override public Page<Category> findCategoryVoPage(CategoryVo categoryVo, int pageNum, int pageSize) { //构建Page<Category>分页对象 Page<Category> page = new Page<>(pageNum,pageSize); QueryWrapper<Category> queryWrapper = new QueryWrapper<>(); //按分类类型查询 if (!EmptyUtil.isNullOrEmpty(categoryVo.getCategoryType())) { queryWrapper.lambda().eq(Category::getCategoryType,categoryVo.getCategoryType()); } //按分类名称查询 if (!EmptyUtil.isNullOrEmpty(categoryVo.getCategoryName())) { queryWrapper.lambda().likeRight(Category::getCategoryName,categoryVo.getCategoryName()); } //按分类有效性查询 if (!EmptyUtil.isNullOrEmpty(categoryVo.getEnableFlag())) { queryWrapper.lambda().eq(Category::getEnableFlag,categoryVo.getEnableFlag()); } //按sortNo升序排列 queryWrapper.lambda().orderByAsc(Category::getSortNo); //执行page查询返回结果 return page(page, queryWrapper); } @Override public Category createCategory(CategoryVo categoryVo) { //转换CategoryVo为Category Category category = BeanConv.toBean(categoryVo, Category.class); //执行保存 boolean flag = save(category); if (flag){ return category; } return null; } @Override public Boolean updateCategory(CategoryVo categoryVo) { //转换CategoryVo为Category Category category = BeanConv.toBean(categoryVo, Category.class); //执行updateById修改 return updateById(category); } @Override public Boolean deleteCategory(String[] checkedIds) { //构建选中ids的List<String> List<String> ids = Arrays.asList(checkedIds); List<Long> idsLong = new ArrayList<>(); ids.forEach(n->{ idsLong.add(Long.valueOf(n)); }); //执行removeByIds批量移除 return removeByIds(idsLong); } @Override public List<Category> findCategoryVoList() { //构建查询条件:SuperConstant.YES QueryWrapper<Category> queryWrapper = new QueryWrapper<>(); queryWrapper.lambda().eq(BasicPojo::getEnableFlag, SuperConstant.YES); //执行list查询 return list(queryWrapper); } @Override public List<Category> findCategoryVoByStoreId(Long storeId) { //构建查询条件:SuperConstant.YES QueryWrapper<Category> queryWrapper = new QueryWrapper<>(); queryWrapper.lambda().eq(Category::getStoreId,storeId) .eq(BasicPojo::getEnableFlag, SuperConstant.YES); //执行list查询 return list(queryWrapper); } }
3.5、CategoryController类
对TableFace接口进行dubbo的RPC调用,为dubbo服务的消费者
package com.itheima.restkeeper.web; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.itheima.restkeeper.CategoryFace; import com.itheima.restkeeper.basic.ResponseWrap; import com.itheima.restkeeper.enums.CategoryEnum; import com.itheima.restkeeper.exception.ProjectException; import com.itheima.restkeeper.req.CategoryVo; import com.itheima.restkeeper.utils.EmptyUtil; import com.itheima.restkeeper.utils.ExceptionsUtil; import com.itheima.restkeeper.utils.ResponseWrapBuild; import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParams; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.apache.dubbo.config.annotation.DubboReference; import org.springframework.web.bind.annotation.*; import java.util.List; /** * @ClassName CategoryController.java * @Description 分类Controller */ @RestController @RequestMapping("category") @Slf4j @Api(tags = "分类controller") public class CategoryController { @DubboReference(version = "${dubbo.application.version}",check = false) CategoryFace categoryFace; /** * @Description 分类列表 * @param categoryVo 查询条件 * @return */ @PostMapping("page/{pageNum}/{pageSize}") @ApiOperation(value = "查询分类分页",notes = "查询分类分页") @ApiImplicitParams({ @ApiImplicitParam(name="categoryVo",value="分类查询对象",required=false,dataType = "CategoryVo"), @ApiImplicitParam(paramType = "path",name = "pageNum",value = "页码",dataType = "Integer"), @ApiImplicitParam(paramType = "path",name = "pageSize",value = "每页条数",dataType = "Integer") }) public ResponseWrap<Page<CategoryVo>> findCategoryVoPage( @RequestBody CategoryVo categoryVo, @PathVariable("pageNum") int pageNum, @PathVariable("pageSize") int pageSize) throws ProjectException { try { //执行分页查询 Page<CategoryVo> categoryVoPage = categoryFace .findCategoryVoPage(categoryVo, pageNum, pageSize); //返回结果 return ResponseWrapBuild.build(CategoryEnum.SUCCEED,categoryVoPage); } catch (Exception e) { log.error("查询分类列表异常:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(CategoryEnum.PAGE_FAIL); } } /** * @Description 添加分类 * @param categoryVo 对象信息 * @return */ @PostMapping @ApiOperation(value = "添加分类",notes = "添加分类") @ApiImplicitParam(name = "categoryVo",value = "分类对象",required = true,dataType = "CategoryVo") ResponseWrap<CategoryVo> createCategory(@RequestBody CategoryVo categoryVo) throws ProjectException { try { //保存分类 CategoryVo categoryVoResult = categoryFace.createCategory(categoryVo); //返回结果 return ResponseWrapBuild.build(CategoryEnum.SUCCEED,categoryVoResult); } catch (Exception e) { log.error("保存分类异常:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(CategoryEnum.CREATE_FAIL); } } /** * @Description 修改分类 * @param categoryVo 对象信息 * @return */ @PatchMapping @ApiOperation(value = "修改分类",notes = "修改分类") @ApiImplicitParam(name = "categoryVo",value = "分类对象",required = true,dataType = "CategoryVo") ResponseWrap<Boolean> updateCategory(@RequestBody CategoryVo categoryVo) throws ProjectException { try { //修改分类 Boolean flag = categoryFace.updateCategory(categoryVo); //返回结果 return ResponseWrapBuild.build(CategoryEnum.SUCCEED,flag); } catch (Exception e) { log.error("保存分类异常:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(CategoryEnum.UPDATE_FAIL); } } /** * @Description 删除分类 * @param categoryVo 查询对象 * @return */ @DeleteMapping @ApiOperation(value = "删除分类",notes = "删除分类") @ApiImplicitParam(name = "categoryVo",value = "分类查询对象",required = true,dataType = "CategoryVo") ResponseWrap<Boolean> deleteCategory(@RequestBody CategoryVo categoryVo ) throws ProjectException { try { //选中的分类ids String[] checkedIds = categoryVo.getCheckedIds(); //执行删除 Boolean flag = categoryFace.deleteCategory(checkedIds); //返回结果 return ResponseWrapBuild.build(CategoryEnum.SUCCEED,flag); } catch (Exception e) { log.error("删除分类异常:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(CategoryEnum.DELETE_FAIL); } } /** * @Description 查找分类 * @param categoryId 分类id * @return */ @GetMapping("{categoryId}") @ApiOperation(value = "查找分类",notes = "查找分类") @ApiImplicitParam(paramType = "path",name = "categoryId",value = "分类Id",dataType = "Long") ResponseWrap<CategoryVo> findCategoryByCategoryId(@PathVariable("categoryId") Long categoryId) throws ProjectException { try { //查询分类信息 CategoryVo categoryVo = categoryFace.findCategoryByCategoryId(categoryId); //返回结果 return ResponseWrapBuild.build(CategoryEnum.SUCCEED,categoryVo); } catch (Exception e) { log.error("查找分类所有分类异常:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(CategoryEnum.SELECT_CATEGORY_FAIL); } } /** * @Description 查找分类下拉列表 * @return */ @GetMapping("list") @ApiOperation(value = "查找分类下拉列表",notes = "查找分下拉类列表") ResponseWrap<List<CategoryVo>> findCategoryVoList() throws ProjectException { try { //查询所有有效分类 List<CategoryVo> list = categoryFace.findCategoryVoList(); //返回结构 return ResponseWrapBuild.build(CategoryEnum.SUCCEED,list); } catch (Exception e) { log.error("查找分类所有分类异常:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(CategoryEnum.SELECT_CATEGORY_LIST_FAIL); } } @PostMapping("update-category-enableFlag") @ApiOperation(value = "修改分类状态",notes = "修改分类状态") ResponseWrap<Boolean> updateCategoryEnableFlag(@RequestBody CategoryVo categoryVo) throws ProjectException { try { //修改分类状态 Boolean flag = categoryFace.updateCategory(categoryVo); //返回结果 return ResponseWrapBuild.build(CategoryEnum.SUCCEED,flag); } catch (Exception e) { log.error("修改分类状态:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(CategoryEnum.UPDATE_FAIL); } } }
第四章 商家平台-菜品管理
门店管理进行菜品管理的功能,包括对菜品的添加、修改、删除、禁用、启用、起售、停售、查看详情等功能,==注意的分类的数据归属我们采用多租户功能区实现这里不需要考虑【多租户】后面有专题去简介==
1、功能区拆解
红色区域:此处为查询条件功能区,筛选列表信息
功能 | 说明 |
---|---|
编码 | 输入菜品编码【简码】,键盘按enter触发搜索列表 |
菜品名称 | 输入菜品名称,键盘按enter触发搜索列表 |
分类 | 下拉框,显示菜品分类【数字字典】,change触发搜索列表 |
状态 | 下拉框,显示【启用、禁用】,change触发搜索列表 |
蓝色区域:菜品维护功能
功能 | 说明 |
---|---|
添加 | 新增菜品信息 |
修改 | 修改菜品信息 |
删除 | 删除菜品信息==【真实删除】==,删除时,会有再次确认提示 |
详情 | 右侧弹出菜品详细信息 |
禁用、启用 | 禁用启用菜品 |
起售、停售 | 起售、停售菜品 |
2、数据库结构
CREATE TABLE `tab_dish` ( `id` bigint(18) NOT NULL COMMENT '菜品Id', `dish_name` varchar(64) COLLATE utf8_bin NOT NULL COMMENT '菜品名称', `dish_number` int(9) DEFAULT NULL COMMENT '菜品数量', `category_id` varchar(64) COLLATE utf8_bin NOT NULL COMMENT '菜品分类id', `price` decimal(12,4) NOT NULL COMMENT '菜品价格', `reduce_price` decimal(12,4) NOT NULL COMMENT '菜品优惠价格', `code` varchar(64) COLLATE utf8_bin NOT NULL COMMENT '商品码', `description` varchar(400) COLLATE utf8_bin DEFAULT NULL COMMENT '描述信息', `dish_status` varchar(11) COLLATE utf8_bin NOT NULL COMMENT 'YES 停售 NO 起售', `created_time` datetime DEFAULT NULL COMMENT '创建时间', `updated_time` datetime DEFAULT NULL COMMENT '创建时间', `sharding_id` bigint(18) DEFAULT NULL COMMENT '分库id', `store_id` bigint(18) NOT NULL COMMENT '门店主键id', `enterprise_id` bigint(18) NOT NULL COMMENT '商户号', `sort_no` int(11) DEFAULT NULL COMMENT '排序', `enable_flag` varchar(10) CHARACTER SET utf8 NOT NULL COMMENT '是否有效', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='菜品管理';
CREATE TABLE `tab_dish_flavor` ( `id` bigint(18) NOT NULL COMMENT '主键Id', `dish_id` bigint(18) DEFAULT NULL COMMENT '菜品id', `data_key` varchar(255) CHARACTER SET utf8 NOT NULL COMMENT '数据字典KEY【数字字典统一管理】', `created_time` datetime DEFAULT NULL COMMENT '创建时间', `updated_time` datetime DEFAULT NULL COMMENT '创建时间', `sharding_id` bigint(18) DEFAULT NULL COMMENT '分库id', `enable_flag` varchar(10) CHARACTER SET utf8 NOT NULL COMMENT '是否有效', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4COMMENT='菜品口味';
自动填充:
下列字段,无需手动填充,系统会自动填充下列字段
字段 | 注释 | 填充方式 |
---|---|---|
id | 主键 | 雪花算法 |
enterprise_id | 商户号 | mybatis-plus-多租户拦截 |
created_time | 创建时间 | mybatis-plus-自动填充组件 |
updated_time | 修改时间 | mybatis-plus-自动填充组件 |
sharding_id | 分库id | mybatis-plus-自动填充组件 |
store_id | 门店id | mybatis-plus-多租户拦截 |
3、功能开发
DishController:对DishFace接口进行dubbo的RPC调用,为dubbo服务的消费者
DishFace:菜品管理dubbo接口定义
DishFaceImpl:菜品管理dubbo接口定义实现,这里做VO和POJO的转换
IDishService:菜品管理的业务接口定义,为DishFaceImpl提供核心业务逻辑的定义
DishServiceImpl:菜品管理的业务接口定义实现
3.1、DishFace接口
菜品管理dubbo接口定义
package com.itheima.restkeeper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.itheima.restkeeper.exception.ProjectException; import com.itheima.restkeeper.req.DishVo; /** * @ClassName DishFace.java * @Description 菜品接口 */ public interface DishFace { /** * @Description 菜品列表 * @param dishVo 查询条件 * @param pageNum 当前页 * @param pageSize 每页条数 * @return Page<DishVo> */ Page<DishVo> findDishVoPage(DishVo dishVo, int pageNum, int pageSize); /** * @Description 创建菜品 * @param dishVo 对象信息 * @return DishVo */ DishVo createDish(DishVo dishVo); /** * @Description 修改菜品 * @param dishVo 对象信息 * @return Boolean */ Boolean updateDish(DishVo dishVo) throws ProjectException; /** * @Description 删除菜品 * @param checkedIds 选择对象信息Id * @return Boolean */ Boolean deleteDish(String[] checkedIds); /** * @Description 查找菜品 * @param dishId 选择对象信息Id * @return DishVo */ DishVo findDishByDishId(Long dishId); }
3.2、DishFaceImpl接口实现
菜品管理dubbo接口定义实现,这里做VO和POJO的转换
package com.itheima.restkeeper.face; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.itheima.restkeeper.DishFace; import com.itheima.restkeeper.constant.AppletCacheConstant; import com.itheima.restkeeper.enums.DishEnum; import com.itheima.restkeeper.exception.ProjectException; import com.itheima.restkeeper.pojo.Dish; import com.itheima.restkeeper.pojo.DishFlavor; import com.itheima.restkeeper.req.DishVo; import com.itheima.restkeeper.service.IDishFlavorService; import com.itheima.restkeeper.service.IDishService; import com.itheima.restkeeper.utils.BeanConv; import com.itheima.restkeeper.utils.EmptyUtil; import lombok.extern.slf4j.Slf4j; import org.apache.dubbo.config.annotation.DubboService; import org.apache.dubbo.config.annotation.Method; import org.redisson.api.RAtomicLong; import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import org.springframework.beans.factory.annotation.Autowired; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; /** * @ClassName DishFaceImpl.java * @Description 菜品接口实现 */ @Slf4j @DubboService(version = "${dubbo.application.version}",timeout = 5000, methods ={ @Method(name = "findDishVoPage",retries = 2), @Method(name = "createDish",retries = 0), @Method(name = "updateDish",retries = 0), @Method(name = "deleteDish",retries = 0) }) public class DishFaceImpl implements DishFace { @Autowired IDishService dishService; @Autowired IDishFlavorService dishFlavorService; @Override public Page<DishVo> findDishVoPage(DishVo dishVo, int pageNum, int pageSize) { //查询Page<Dish>分页对象 Page<Dish> page = dishService.findDishVoPage(dishVo, pageNum, pageSize); //转换Page<Dish>为Page<DishVo> Page<DishVo> pageVo = new Page<>(); BeanConv.toBean(page,pageVo); //转换List<Dish>为List<DishVo> List<Dish> dishList = page.getRecords(); List<DishVo> dishVoList = BeanConv.toBeanList(dishList,DishVo.class); //为每个菜品指定拥有的口味 if (!EmptyUtil.isNullOrEmpty(dishVoList)){ dishVoList.forEach(n->{ List<DishFlavor> dishFlavors = dishFlavorService.findDishFlavorByDishId(n.getId()); List<String> dishFavorList = new ArrayList<>(); for (DishFlavor dishFlavor : dishFlavors) { dishFavorList.add(String.valueOf(dishFlavor.getDataKey())); } String[] dishFlavorDataKey = new String[dishFavorList.size()]; dishFavorList.toArray(dishFlavorDataKey); n.setHasDishFlavor(dishFlavorDataKey); }); } pageVo.setRecords(dishVoList); //返回结果 return pageVo; } @Override public DishVo createDish(DishVo dishVo) { //创建菜品 DishVo dishVoResult = BeanConv.toBean(dishService.createDish(dishVo), DishVo.class); //指定可有信息 dishVoResult.setHasDishFlavor(dishVo.getHasDishFlavor()); return dishVoResult; } @Override public Boolean updateDish(DishVo dishVo) throws ProjectException { return dishService.updateDish(dishVo); } @Override public Boolean deleteDish(String[] checkedIds) { return dishService.deleteDish(checkedIds); } @Override public DishVo findDishByDishId(Long dishId) { return BeanConv.toBean(dishService.getById(dishId),DishVo.class); } }
3.3、IDishService业务接口
菜品管理的业务接口定义,为DishFaceImpl提供核心业务逻辑的定义
package com.itheima.restkeeper.service; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.itheima.restkeeper.pojo.Dish; import com.itheima.restkeeper.pojo.Dish; import com.baomidou.mybatisplus.extension.service.IService; import com.itheima.restkeeper.req.DishVo; import java.util.List; /** * @Description:菜品管理 服务类 */ public interface IDishService extends IService<Dish> { /** * @Description 菜品列表 * @param dishVo 查询条件 * @param pageNum 当前页 * @param pageSize 每页条数 * @return Page<Dish> */ Page<Dish> findDishVoPage(DishVo dishVo, int pageNum, int pageSize); /** * @Description 创建菜品 * @param dishVo 对象信息 * @return Dish */ Dish createDish(DishVo dishVo); /** * @Description 修改菜品 * @param dishVo 对象信息 * @return Boolean */ Boolean updateDish(DishVo dishVo); /** * @Description 删除菜品 * @param checkedIds 选择的菜品ID * @return Boolean */ Boolean deleteDish(String[] checkedIds); /*** * @description 查询分类下所有菜品 * @param categoryId * @return List<Dish> */ List<Dish> findDishVoByCategoryId(Long categoryId); /*** * @description 查询店铺下所有起售且有效菜品 * @param storeId * @return List<Dish> */ List<Dish> findDishVoByStoreId(Long storeId); /*** * @description 增减菜品库存数量 * @param step 增减步长 * @return Boolean */ Boolean updateDishNumber(Long step, Long dishId); /*** * @description 查询所有有效的菜品信息 * @return */ List<Dish> findDishVos(); }
3.4、DishServiceImpl业务接口实现
菜品管理的业务接口定义实现
package com.itheima.restkeeper.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.google.common.collect.Lists; import com.itheima.restkeeper.constant.SuperConstant; import com.itheima.restkeeper.pojo.Dish; import com.itheima.restkeeper.mapper.DishMapper; import com.itheima.restkeeper.pojo.DishFlavor; import com.itheima.restkeeper.req.DishVo; import com.itheima.restkeeper.service.IDishFlavorService; import com.itheima.restkeeper.service.IDishService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.itheima.restkeeper.utils.BeanConv; import com.itheima.restkeeper.utils.EmptyUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * @Description:菜品管理 服务实现类 */ @Service public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements IDishService { @Autowired IDishFlavorService dishFlavorService; @Autowired DishMapper dishMapper; @Override public Page<Dish> findDishVoPage(DishVo dishVo, int pageNum, int pageSize) { //构建Page<Dish>分页对象 Page<Dish> page = new Page<>(pageNum,pageSize); QueryWrapper<Dish> queryWrapper = new QueryWrapper<>(); //按分类查询 if (!EmptyUtil.isNullOrEmpty(dishVo.getCategoryId())) { queryWrapper.lambda().eq(Dish::getCategoryId,dishVo.getCategoryId()); } //按菜品名称查询 if (!EmptyUtil.isNullOrEmpty(dishVo.getDishName())) { queryWrapper.lambda().likeRight(Dish::getDishName,dishVo.getDishName()); } //按简码查询 if (!EmptyUtil.isNullOrEmpty(dishVo.getCode())) { queryWrapper.lambda().likeRight(Dish::getCode,dishVo.getCode()); } //按有效性查询 if (!EmptyUtil.isNullOrEmpty(dishVo.getEnableFlag())) { queryWrapper.lambda().eq(Dish::getEnableFlag,dishVo.getEnableFlag()); } //按sortNo升序 queryWrapper.lambda().orderByAsc(Dish::getSortNo); //执行page返回结果 return page(page, queryWrapper); } @Override @Transactional public Dish createDish(DishVo dishVo) { //转换DishVo为Dish Dish dish = BeanConv.toBean(dishVo, Dish.class); //执行保存 boolean flag = save(dish); //保存菜品和口味中间表信息 if (flag){ List<DishFlavor> list = Lists.newArrayList(); List<String> dataKeys = Arrays.asList(dishVo.getHasDishFlavor()); dataKeys.forEach(n->{ DishFlavor dishFlavor = DishFlavor.builder() .dishId(dish.getId()) .dataKey(n) .build(); list.add(dishFlavor); }); flag = dishFlavorService.saveBatch(list); } if (flag){ return dish; } return null; } /** * @Description 菜品拥有的口味 */ private List<String> dishHasDishFlavor(Long dishId){ QueryWrapper<DishFlavor> queryWrapper = new QueryWrapper<>(); queryWrapper.lambda().eq(DishFlavor::getDishId,dishId); List<DishFlavor> list = dishFlavorService.list(queryWrapper); ArrayList<String> listDataKeys = Lists.newArrayList(); if (!EmptyUtil.isNullOrEmpty(list)){ list.forEach(n->{ listDataKeys.add(String.valueOf(n.getDataKey())); }); } return listDataKeys; } @Override public Boolean updateDish(DishVo dishVo) { Dish dish = BeanConv.toBean(dishVo, Dish.class); boolean flag = updateById(dish); if (flag){ //删除以往有的口味 QueryWrapper<DishFlavor> queryWrapper = new QueryWrapper<>(); queryWrapper.lambda().eq(DishFlavor::getDishId,dish.getId()); dishFlavorService.remove(queryWrapper); } //添加新口味 List<DishFlavor> list = Lists.newArrayList(); List<String> newDishHasDishFlavors = Arrays.asList(dishVo.getHasDishFlavor()); newDishHasDishFlavors.forEach(n->{ DishFlavor dishFlavors = DishFlavor.builder() .dishId(dish.getId()) .dataKey(n) .build(); list.add(dishFlavors); }); flag = dishFlavorService.saveBatch(list); return flag; } @Override public Boolean deleteDish(String[] checkedIds) { //构建选择ids的List<String> List<String> ids = Arrays.asList(checkedIds); List<Long> idsLong = new ArrayList<>(); ids.forEach(n->{ idsLong.add(Long.valueOf(n)); }); //批量移除菜品 boolean flag = removeByIds(idsLong); //批量移除菜品口味 if (flag){ QueryWrapper<DishFlavor> queryWrapper = new QueryWrapper<>(); queryWrapper.lambda().in(DishFlavor::getDishId,idsLong); flag = dishFlavorService.remove(queryWrapper); } return flag; } @Override public List<Dish> findDishVoByCategoryId(Long categoryId) { //构建查询条件:菜品起售,菜品有效 LambdaQueryWrapper<Dish> lambdaQueryWrapper = new LambdaQueryWrapper<>(); lambdaQueryWrapper.eq(Dish::getCategoryId,categoryId) .eq(Dish::getEnableFlag,SuperConstant.YES) .eq(Dish::getDishStatus,SuperConstant.YES); //执行list查询 return list(lambdaQueryWrapper); } @Override public List<Dish> findDishVoByStoreId(Long storeId) { //构建查询条件:店铺ID,菜品起售,菜品有效 LambdaQueryWrapper<Dish> lambdaQueryWrapper = new LambdaQueryWrapper<>(); lambdaQueryWrapper.eq(Dish::getStoreId,storeId) .eq(Dish::getEnableFlag,SuperConstant.YES) .eq(Dish::getDishStatus,SuperConstant.YES); //执行list查询 return list(lambdaQueryWrapper); } @Override public Boolean updateDishNumber(Long step,Long dishId) { //修改菜品数量 Integer row = dishMapper.updateDishNumber(step,dishId); return row==1 ? true:false; } @Override public List<Dish> findDishVos() { QueryWrapper<Dish> queryWrapper = new QueryWrapper<>(); queryWrapper.lambda().eq(Dish::getEnableFlag,SuperConstant.YES) .eq(Dish::getDishStatus,SuperConstant.YES); return list(queryWrapper); } }
3.5、DishController类
对DishFace接口进行dubbo的RPC调用,为dubbo服务的消费者
package com.itheima.restkeeper.web; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.itheima.restkeeper.AffixFace; import com.itheima.restkeeper.DishFace; import com.itheima.restkeeper.basic.ResponseWrap; import com.itheima.restkeeper.enums.DishEnum; import com.itheima.restkeeper.exception.ProjectException; import com.itheima.restkeeper.req.AffixVo; import com.itheima.restkeeper.req.DishVo; import com.itheima.restkeeper.utils.EmptyUtil; import com.itheima.restkeeper.utils.ExceptionsUtil; import com.itheima.restkeeper.utils.ResponseWrapBuild; import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParams; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.apache.dubbo.config.annotation.DubboReference; import org.springframework.web.bind.annotation.*; import java.util.List; import java.util.stream.Collectors; /** * @ClassName DishController.java * @Description 菜品Controller */ @RestController @RequestMapping("dish") @Slf4j @Api(tags = "菜品controller") public class DishController { @DubboReference(version = "${dubbo.application.version}",check = false) DishFace dishFace; @DubboReference(version = "${dubbo.application.version}",check = false) AffixFace affixFace; /** * @Description 菜品列表 * @param dishVo 查询条件 * @return */ @PostMapping("page/{pageNum}/{pageSize}") @ApiOperation(value = "查询菜品分页",notes = "查询菜品分页") @ApiImplicitParams({ @ApiImplicitParam(name = "dishVo",value = "菜品查询对象",dataType = "DishVo"), @ApiImplicitParam(paramType = "path",name = "pageNum",value = "页码",dataType = "Integer"), @ApiImplicitParam(paramType = "path",name = "pageSize",value = "每页条数",dataType = "Integer") }) public ResponseWrap<Page<DishVo>> findDishVoPage( @RequestBody DishVo dishVo, @PathVariable("pageNum") int pageNum, @PathVariable("pageSize") int pageSize) throws ProjectException { try { //查询菜品分页 Page<DishVo> dishVoPage = dishFace.findDishVoPage(dishVo, pageNum, pageSize); //菜品不为空,查询菜品对于的图片 if (!EmptyUtil.isNullOrEmpty(dishVoPage) &&!EmptyUtil.isNullOrEmpty(dishVoPage.getRecords())){ List<DishVo> dishVoList = dishVoPage.getRecords(); dishVoList.forEach(n->{ List<AffixVo> affixVoList = affixFace.findAffixVoByBusinessId(n.getId()); if (!EmptyUtil.isNullOrEmpty(affixVoList)){ n.setAffixVo(affixVoList.get(0)); } }); dishVoPage.setRecords(dishVoList); } //返回结果 return ResponseWrapBuild.build(DishEnum.SUCCEED,dishVoPage); } catch (Exception e) { log.error("查询菜品列表异常:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(DishEnum.PAGE_FAIL); } } /** * @Description 添加菜品 * @param dishVo 对象信息 * @return */ @PostMapping @ApiOperation(value = "添加菜品",notes = "添加菜品") @ApiImplicitParam(name = "dishVo",value = "菜品对象",required = true,dataType = "DishVo") ResponseWrap<DishVo> createDish(@RequestBody DishVo dishVo) throws ProjectException { try { //添加菜品 DishVo dishVoResult = dishFace.createDish(dishVo); //绑定菜品图片附件 if (!EmptyUtil.isNullOrEmpty(dishVoResult)){ affixFace.bindBusinessId(AffixVo.builder() .businessId(dishVoResult.getId()) .id(dishVo.getAffixVo().getId()) .build()); } //构建菜品返回记录中的图片信息 dishVoResult.setAffixVo(AffixVo.builder() .pathUrl(dishVo.getAffixVo().getPathUrl()) .businessId(dishVoResult.getId()) .id(dishVo.getAffixVo().getId()).build()); //返回结果 return ResponseWrapBuild.build(DishEnum.SUCCEED,dishVoResult); } catch (Exception e) { log.error("保存菜品异常:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(DishEnum.CREATE_FAIL); } } /** * @Description 修改菜品 * @param dishVo 对象信息 * @return */ @PatchMapping @ApiOperation(value = "修改菜品",notes = "修改菜品") @ApiImplicitParam(name = "dishVo",value = "菜品对象",required = true,dataType = "DishVo") ResponseWrap<Boolean> updateDish(@RequestBody DishVo dishVo) throws ProjectException { try { //修改菜品信息 Boolean flag = dishFace.updateDish(dishVo); //修改菜品对于的图片信息 if (flag){ List<AffixVo> affixVoList = affixFace.findAffixVoByBusinessId(dishVo.getId()); List<Long> affixIds = affixVoList.stream() .map(AffixVo::getId).collect(Collectors.toList()); if (!affixIds.contains(dishVo.getAffixVo().getId())){ //删除图片 flag = affixFace.deleteAffixVoByBusinessId(dishVo.getId()); //绑定新图片 affixFace.bindBusinessId(AffixVo.builder() .businessId(dishVo.getId()) .id(dishVo.getAffixVo().getId()) .build()); } } //返回结果 return ResponseWrapBuild.build(DishEnum.SUCCEED,flag); } catch (Exception e) { log.error("保存菜品异常:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(DishEnum.UPDATE_FAIL); } } /** * @Description 删除菜品 * @param dishVo 查询对象 * @return */ @DeleteMapping @ApiOperation(value = "删除菜品",notes = "删除菜品") @ApiImplicitParam(name = "dishVo",value = "菜品查询对象",required = true,dataType = "DishVo") ResponseWrap<Boolean> deleteDish(@RequestBody DishVo dishVo ) throws ProjectException { try { //选择ids String[] checkedIds = dishVo.getCheckedIds(); //删除菜品信息 Boolean flag = dishFace.deleteDish(checkedIds); //删除菜品图片 for (String checkedId : checkedIds) { affixFace.deleteAffixVoByBusinessId(Long.valueOf(checkedId)); } return ResponseWrapBuild.build(DishEnum.SUCCEED,flag); } catch (Exception e) { log.error("删除菜品异常:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(DishEnum.DELETE_FAIL); } } /** * @Description 查找菜品 * @param dishId 菜品id * @return */ @GetMapping("{dishId}") @ApiOperation(value = "查找菜品",notes = "查找菜品") @ApiImplicitParam(paramType = "path",name = "dishId",value = "菜品Id",dataType = "Long") ResponseWrap<DishVo> findDishByDishId(@PathVariable("dishId") Long dishId) throws ProjectException { try { //执行菜品信息查询 DishVo dishVo = dishFace.findDishByDishId(dishId); //返回结果 return ResponseWrapBuild.build(DishEnum.SUCCEED,dishVo); } catch (Exception e) { log.error("查找菜品所有菜品异常:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(DishEnum.SELECT_DISH_FAIL); } } @PostMapping("update-dish-enableFlag") @ApiOperation(value = "修改菜品有效状态",notes = "修改菜品有效状态") ResponseWrap<Boolean> updateDishEnableFlag(@RequestBody DishVo dishVo) throws ProjectException { try { //修改菜品有效状态 Boolean flag = dishFace.updateDish(dishVo); //返回结果 return ResponseWrapBuild.build(DishEnum.SUCCEED,flag); } catch (Exception e) { log.error("修改菜品有效状态:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(DishEnum.UPDATE_FAIL); } } @PostMapping("update-dish-dishStatus") @ApiOperation(value = "修改菜品状态",notes = "修改菜品状态") ResponseWrap<Boolean> updateDishDishStatus(@RequestBody DishVo dishVo) throws ProjectException { try { //修改菜品状态 Boolean flag = dishFace.updateDish(dishVo); //返回结果 return ResponseWrapBuild.build(DishEnum.SUCCEED,flag); } catch (Exception e) { log.error("修改菜品有效状态:{}", ExceptionsUtil.getStackTraceAsString(e)); throw new ProjectException(DishEnum.UPDATE_FAIL); } } }