学习目标
1、掌握mybatis-plus代码生成器的使用
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此文件的作用主要是定义关于数据库及生成策略定义
#数据库地址
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简介
协议方式==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服务接口的业务逻辑
注意:当产生跨服务接口调用,例如一个dubbo接口需要多个dubbo接口来支持,我们会放到face层进行服务调用然后业务组装,如果牵涉分布式事务问题,我们会采用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
cloud:
#表示要订阅服务的服务名,可以配置'*',代表订阅所有服务,不推荐使用。若需订阅多应用,使用 "," 分割。
subscribed-services: model-basic-producer,model-trading-producer
registry:
#注册中心
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-shop-applet,model-shop-user
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);
4、商家平台启动
启动运营平台后,再启动上述模块,然后启动restkeeper-vue-shop模块,
注意检查hosts的配置 :127.0.0.1 ppsk.shop.eehp.cn
访问路径:ppsk.shop.eehp.cn:8080
账号:pp@qq.com 密码 pass
启动时增加内存限制,防止内存不够:-Xmx128M -XX:MetaspaceSize=128M -XX:MaxMetaspaceSize=128M
第三章 商家平台-品牌管理
餐掌柜是一个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:对BrandFace接口进行dubbo的RPC调用,为dubbo服务的消费者
AffixFace【公用功能】:附件上传dubbo接口,我们在创建品牌时,需要上传品牌图片到图片中心
BrandFace:品牌dubbo接口定义对AffixFace进行dubbo的RPC调用
BrandFaceImpl:品牌dubbo接口定义实现,这里做VO和POJO的转换
IBrandService:品牌的业务接口定义,为BrandFaceImpl提供核心业务逻辑的定义
BrandServiceImpl:品牌的业务接口定义实现
3.1、BrandFace接口
在开发中,我们以往都是先写controller层业务1口,因为我们一致提倡的是面向接口开发,而这里,我们应该先定义dubbo服务的接口,只有统一接口在做微服务调用时才能保证逻辑的清晰
package com.itheima.restkeeper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.itheima.restkeeper.exception.ProjectException;
import com.itheima.restkeeper.req.BrandVo;
import java.util.List;
/**
* @ClassName BrandFace.java
* @Description 品牌dubbo服务定义
*/
public interface BrandFace {
/**
* @Description 品牌列表
* @param brandVo 查询条件
* @param pageNum 页码
* @param pageSize 每页条数
* @return Page<BrandVo>
*/
Page<BrandVo> findBrandVoPage(BrandVo brandVo,
int pageNum,
int pageSize)throws ProjectException;
/**
* @Description 创建品牌
* @param brandVo 对象信息
* @return BrandVo
*/
BrandVo createBrand(BrandVo brandVo)throws ProjectException;
/**
* @Description 修改品牌
* @param brandVo 对象信息
* @return Boolean
*/
Boolean updateBrand(BrandVo brandVo)throws ProjectException;
/**
* @Description 删除品牌
* @param checkedIds 选择中对象Ids
* @return Boolean
*/
Boolean deleteBrand(String[] checkedIds)throws ProjectException;
/**
* @Description 查找品牌
* @param brandId 选择对象信息Id
* @return BrandVo
*/
BrandVo findBrandByBrandId(Long brandId)throws ProjectException;
/***
* @description 查询品牌下拉框
* @return: List<BrandVo>
*/
List<BrandVo> findBrandVoList()throws ProjectException;
}
3.2、BrandFaceImpl接口实现
BrandFace接口定义实现,注意这里我们对VO和POJO的转换都在此类中进行处理,下面我们看下具体实现:
package com.itheima.restkeeper.face;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.itheima.restkeeper.AffixFace;
import com.itheima.restkeeper.BrandFace;
import com.itheima.restkeeper.enums.BrandEnum;
import com.itheima.restkeeper.exception.ProjectException;
import com.itheima.restkeeper.pojo.Brand;
import com.itheima.restkeeper.req.AffixVo;
import com.itheima.restkeeper.req.BrandVo;
import com.itheima.restkeeper.service.IBrandService;
import com.itheima.restkeeper.utils.BeanConv;
import com.itheima.restkeeper.utils.EmptyUtil;
import com.itheima.restkeeper.utils.ExceptionsUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
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;
import java.util.stream.Collectors;
/**
* @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)
})
@Slf4j
public class BrandFaceImpl implements BrandFace {
@Autowired
IBrandService brandService;
@DubboReference(version = "${dubbo.application.version}",check = false)
AffixFace affixFace;
@Override
public Page<BrandVo> findBrandVoPage(BrandVo brandVo,
int pageNum,
int pageSize) throws ProjectException{
try {
//查询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);
//处理附件
if (!EmptyUtil.isNullOrEmpty(pageVo)&&!EmptyUtil.isNullOrEmpty(brandVoList)){
brandVoList.forEach(n->{
List<AffixVo> affixVoList = affixFace.findAffixVoByBusinessId(n.getId());
if (!EmptyUtil.isNullOrEmpty(affixVoList)){
n.setAffixVo(affixVoList.get(0));
}
});
}
//指定带有附件消息的brandVoList到pageVo中
pageVo.setRecords(brandVoList);
//返回结果
return pageVo;
} catch (Exception e) {
log.error("查询品牌列表异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(BrandEnum.PAGE_FAIL);
}
}
@Override
public BrandVo createBrand(BrandVo brandVo) throws ProjectException{
try {
//保存品牌
BrandVo brandVoResult = BeanConv.toBean(brandService.createBrand(brandVo), BrandVo.class);
//绑定附件
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 brandVoResult;
} catch (Exception e) {
log.error("保存品牌异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(BrandEnum.CREATE_FAIL);
}
}
@Override
public Boolean updateBrand(BrandVo brandVo)throws ProjectException {
try {
//修改品牌
Boolean flag = brandService.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 flag;
} catch (Exception e) {
log.error("修改品牌列表异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(BrandEnum.UPDATE_FAIL);
}
}
@Override
public Boolean deleteBrand(String[] checkedIds) throws ProjectException{
try {
//删除品牌
Boolean flag = brandService.deleteBrand(checkedIds);
//删除图片,是不是可以debug进去看看?
for (String checkedId : checkedIds) {
affixFace.deleteAffixVoByBusinessId(Long.valueOf(checkedId));
}
return flag ;
} catch (Exception e) {
log.error("删除品牌列表异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(BrandEnum.DELETE_FAIL);
}
}
@Override
public BrandVo findBrandByBrandId(Long brandId)throws ProjectException {
try {
//查找品牌消息
Brand brand = brandService.getById(brandId);
return BeanConv.toBean(brand,BrandVo.class);
} catch (Exception e) {
log.error("查找品牌所有品牌异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(BrandEnum.SELECT_BRAND_FAIL);
}
}
@Override
public List<BrandVo> findBrandVoList()throws ProjectException {
try {
//查找品牌列表,用到哪里?要不要注意状态?
return BeanConv.toBeanList(brandService.findBrandVoList(),BrandVo.class);
} catch (Exception e) {
log.error("查询品牌列表异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(BrandEnum.PAGE_FAIL);
}
}
}
3.3、IBrandService业务接口
品牌的业务接口定义,为BrandFaceImpl提供核心业务逻辑的定义,此接口继承了IService接口,IService里面有很多基础的方法可以直接使用
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
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.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.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);
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类
此类为服务的【消费者】,对BrandFace接口进行dubbo的RPC调用
package com.itheima.restkeeper.web;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.itheima.restkeeper.BrandFace;
import com.itheima.restkeeper.basic.ResponseWrap;
import com.itheima.restkeeper.enums.BrandEnum;
import com.itheima.restkeeper.req.BrandVo;
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 BrandController.java
* @Description 品牌Controller
*/
@RestController
@RequestMapping("brand")
@Slf4j
@Api(tags = "品牌controller")
public class BrandController {
@DubboReference(version = "${dubbo.application.version}",check = false)
BrandFace brandFace;
/**
* @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) {
Page<BrandVo> brandVoPage = brandFace.findBrandVoPage(brandVo, pageNum, pageSize);
return ResponseWrapBuild.build(BrandEnum.SUCCEED,brandVoPage);
}
/**
* @Description 查询品牌下拉框
* @return
*/
@GetMapping("list")
@ApiOperation(value = "查询品牌下拉框",notes = "查询品牌下拉框")
public ResponseWrap<List<BrandVo>> findBrandVoList() {
List<BrandVo> brandVoPage = brandFace.findBrandVoList();
return ResponseWrapBuild.build(BrandEnum.SUCCEED,brandVoPage);
}
/**
* @Description 添加品牌
* @param brandVo 对象信息
* @return
*/
@PostMapping
@ApiOperation(value = "添加品牌",notes = "添加品牌")
@ApiImplicitParam(name = "brandVo",value = "品牌对象",required = true,dataType = "BrandVo")
ResponseWrap<BrandVo> createBrand(@RequestBody BrandVo brandVo) {
BrandVo brandVoResult = brandFace.createBrand(brandVo);
return ResponseWrapBuild.build(BrandEnum.SUCCEED,brandVoResult);
}
/**
* @Description 修改品牌
* @param brandVo 对象信息
* @return
*/
@PatchMapping
@ApiOperation(value = "修改品牌",notes = "修改品牌")
@ApiImplicitParam(name = "brandVo",value = "品牌对象",required = true,dataType = "BrandVo")
ResponseWrap<Boolean> updateBrand(@RequestBody BrandVo brandVo) {
Boolean flag = brandFace.updateBrand(brandVo);
return ResponseWrapBuild.build(BrandEnum.SUCCEED,flag);
}
/**
* @Description 删除品牌
* @param brandVo 查询对象
* @return
*/
@DeleteMapping
@ApiOperation(value = "删除品牌",notes = "删除品牌")
@ApiImplicitParam(name = "brandVo",value = "品牌查询对象",required = true,dataType = "BrandVo")
ResponseWrap<Boolean> deleteBrand(@RequestBody BrandVo brandVo ) {
//获得所有选中品牌IDS
String[] checkedIds = brandVo.getCheckedIds();
Boolean flag = brandFace.deleteBrand(checkedIds);
return ResponseWrapBuild.build(BrandEnum.SUCCEED,flag);
}
/**
* @Description 查找品牌
* @param brandId 登录名
* @return
*/
@GetMapping("{brandId}")
@ApiOperation(value = "查找品牌",notes = "查找品牌")
@ApiImplicitParam(paramType = "path",name = "brandId",value = "品牌Id",dataType = "Long")
ResponseWrap<BrandVo> findBrandByBrandId(@PathVariable("brandId") Long brandId) {
BrandVo brandVo = brandFace.findBrandByBrandId(brandId);
return ResponseWrapBuild.build(BrandEnum.SUCCEED,brandVo);
}
@PostMapping("update-brand-enableFlag")
@ApiOperation(value = "修改品牌状态",notes = "修改品牌状态")
@ApiImplicitParam(name = "brandVo",value = "品牌查询对象",required = true,dataType = "BrandVo")
ResponseWrap<Boolean> updateBrandEnableFlag(@RequestBody BrandVo brandVo) {
Boolean flag = brandFace.updateBrand(brandVo);
return ResponseWrapBuild.build(BrandEnum.SUCCEED,flag);
}
}
第四章 商家平台-门店管理
上一章节中,我们开发了品牌管理,为每个入住的商家提供品牌维护的功能,从下面的图中我们知道,品牌下面有对应的门店,下面我们来开发商家平台-门店管理
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:对StoreFace接口进行dubbo的RPC调用,为dubbo服务的消费者
AffixFace【公用功能】:附件上传dubbo接口,我们在创建门店时,需要上传品牌图片到图片中心
StoreFace:门店dubbo接口定义对AffixFace进行dubbo的RPC调用
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.exception.ProjectException;
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)throws ProjectException;
/**
* @Description 创建门店
* @param storeVo 对象信息
* @return StoreVo
*/
StoreVo createStore(StoreVo storeVo)throws ProjectException;
/**
* @Description 修改门店
* @param storeVo 对象信息
* @return Boolean
*/
Boolean updateStore(StoreVo storeVo)throws ProjectException;
/**
* @Description 删除门店
* @param checkedIds 选择对象信息Id
* @return Boolean
*/
Boolean deleteStore(String[] checkedIds)throws ProjectException;
/**
* @Description 查找门店
* @param storeId 选择对象信息Id
* @return StoreVo
*/
StoreVo findStoreByStoreId(Long storeId)throws ProjectException;
/***
* @description 查询门店下拉框
* @return: List<StoreVo>
*/
List<StoreVo> findStoreVoList()throws ProjectException;
}
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.enums.StoreEnum;
import com.itheima.restkeeper.exception.ProjectException;
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 com.itheima.restkeeper.utils.ExceptionsUtil;
import com.itheima.restkeeper.utils.ResponseWrapBuild;
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) throws ProjectException{
try {
//构建分页对象
Page<Store> page = storeService.findStoreVoPage(storeVo, pageNum, pageSize);
Page<StoreVo> pageVo = new Page<>();
BeanConv.toBean(page,pageVo);
//结果集转换
List<Store> storeList = page.getRecords();
List<StoreVo> storeVoList = BeanConv.toBeanList(storeList,StoreVo.class);
pageVo.setRecords(storeVoList);
return pageVo;
} catch (Exception e) {
log.error("查询门店列表异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(StoreEnum.PAGE_FAIL);
}
}
@Override
public StoreVo createStore(StoreVo storeVo) throws ProjectException{
try {
//创建门店
return BeanConv.toBean( storeService.createStore(storeVo), StoreVo.class);
} catch (Exception e) {
log.error("保存门店异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(StoreEnum.CREATE_FAIL);
}
}
@Override
public Boolean updateStore(StoreVo storeVo)throws ProjectException {
try {
//修改门店
return storeService.updateStore(storeVo);
} catch (Exception e) {
log.error("保存门店异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(StoreEnum.UPDATE_FAIL);
}
}
@Override
public Boolean deleteStore(String[] checkedIds) throws ProjectException{
try {
//删除门店
return storeService.deleteStore(checkedIds);
} catch (Exception e) {
log.error("删除门店异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(StoreEnum.DELETE_FAIL);
}
}
@Override
public StoreVo findStoreByStoreId(Long storeId)throws ProjectException {
try {
//查询门店
Store store = storeService.getById(storeId);
if (!EmptyUtil.isNullOrEmpty(store)){
return BeanConv.toBean(store,StoreVo.class);
}
return null;
} catch (Exception e) {
log.error("查找门店所有门店异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(StoreEnum.SELECT_STORE_FAIL);
}
}
@Override
public List<StoreVo> findStoreVoList()throws ProjectException {
try {
//查询门店列表,用在哪里?需要注意状态吗?
return BeanConv.toBeanList(storeService.findStoreVoList(),StoreVo.class);
} catch (Exception e) {
log.error("查找门店所有门店异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(StoreEnum.SELECT_STORE_LIST_FAIL);
}
}
}
3.3、IStoreService业务接口
IStoreService:门店的业务接口定义,为StoreFaceImpl提供核心业务逻辑的定义
package com.itheima.restkeeper.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.itheima.restkeeper.pojo.Store;
import com.itheima.restkeeper.pojo.Store;
import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.restkeeper.req.StoreVo;
import java.util.List;
/**
* @Description:门店信息账号 服务类
*/
public interface IStoreService extends IService<Store> {
/**
* @Description 品牌列表
* @param storeVo 查询条件
* @param pageNum 页码
* @param pageSize 每页条数
* @return Page<BrandVo>
*/
Page<Store> findStoreVoPage(StoreVo storeVo, int pageNum, int pageSize);
/**
* @Description 创建门店
* @param storeVo 对象信息
* @return Store
*/
Store createStore(StoreVo storeVo);
/**
* @Description 修改门店
* @param storeVo 对象信息
* @return Boolean
*/
Boolean updateStore(StoreVo storeVo);
/**
* @Description 删除门店
* @param checkedIds 选择的门店ID
* @return Boolean
*/
Boolean deleteStore(String[] checkedIds);
/***
* @description 查询门店下拉框
* @return: List<Store>
*/
List<Store> findStoreVoList();
}
3.4、StoreServiceImpl接口实现
StoreServiceImpl:门店的业务接口定义实现
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);
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;
/**
* @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) {
Page<StoreVo> storeVoPage = storeFace.findStoreVoPage(storeVo, pageNum, pageSize);
return ResponseWrapBuild.build(StoreEnum.SUCCEED,storeVoPage);
}
/**
* @Description 添加门店
* @param storeVo 对象信息
* @return
*/
@PostMapping
@ApiOperation(value = "添加门店",notes = "添加门店")
@ApiImplicitParam(name = "storeVo",value = "门店对象",required = true,dataType = "StoreVo")
ResponseWrap<StoreVo> createStore(@RequestBody StoreVo storeVo) {
StoreVo storeVoResult = storeFace.createStore(storeVo);
return ResponseWrapBuild.build(StoreEnum.SUCCEED,storeVoResult);
}
/**
* @Description 修改门店
* @param storeVo 对象信息
* @return
*/
@PatchMapping
@ApiOperation(value = "修改门店",notes = "修改门店")
@ApiImplicitParam(name = "storeVo",value = "门店对象",required = true,dataType = "StoreVo")
ResponseWrap<Boolean> updateStore(@RequestBody StoreVo storeVo) {
Boolean flag = storeFace.updateStore(storeVo);
return ResponseWrapBuild.build(StoreEnum.SUCCEED,flag);
}
/**
* @Description 删除门店
* @param storeVo 查询对象
* @return
*/
@DeleteMapping
@ApiOperation(value = "删除门店",notes = "删除门店")
@ApiImplicitParam(name = "storeVo",value = "门店查询对象",required = true,dataType = "StoreVo")
ResponseWrap<Boolean> deleteStore(@RequestBody StoreVo storeVo ) {
//获得所有选择的门店IDS
String[] checkedIds = storeVo.getCheckedIds();
Boolean flag = storeFace.deleteStore(checkedIds);
return ResponseWrapBuild.build(StoreEnum.SUCCEED,flag);
}
/**
* @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) {
StoreVo storeVo = storeFace.findStoreByStoreId(storeId);
return ResponseWrapBuild.build(StoreEnum.SUCCEED,storeVo);
}
/**
* @Description 查找门店
* @return
*/
@GetMapping("list")
@ApiOperation(value = "查找门店列表",notes = "查找门店列表")
ResponseWrap<List<StoreVo>> findStoreVoList() {
List<StoreVo> list = storeFace.findStoreVoList();
return ResponseWrapBuild.build(StoreEnum.SUCCEED,list);
}
/**
* @Description 修改门店状态
* @return
*/
@PostMapping("update-store-enableFlag")
@ApiOperation(value = "修改门店状态",notes = "修改门店状态")
ResponseWrap<Boolean> updateStoreEnableFlag(@RequestBody StoreVo storeVo) {
Boolean flag = storeFace.updateStore(storeVo);
return ResponseWrapBuild.build(StoreEnum.SUCCEED,flag);
}
}
第五章 商家平台-用户管理
在梳理【商家平台-用户管理】前,我们先看下商家平台管理员账号的开通流程:商家申请入驻平台后,【商家运营平台】为商户开通管理员账号,开通后,商户管理可以登录系统,并且管理门店员账号功能
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】:对UserFace接口进行dubbo的RPC调用,为dubbo服务的消费者
AffixFace【公用功能】:附件上传dubbo接口,我们在创建用户时,需要上传用户头像到图片中心
UserFace【**model-shop-interface**】:用户dubbo接口定义和AffixFace接口进行dubbo的RPC调用,为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.exception.ProjectException;
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)throws ProjectException;
/**
* @param userVo 对象信息
* @return UserVo
* @Description 创建用户
*/
UserVo createUser(UserVo userVo)throws ProjectException;
/**
* @param userVo 对象信息
* @return Boolean
* @Description 修改用户
*/
Boolean updateUser(UserVo userVo)throws ProjectException;
/**
* @param checkedIds 选择对象信息Id
* @return Boolean
* @Description 删除用户
*/
Boolean deleteUser(String[] checkedIds)throws ProjectException;
/**
* @param userId 选择对象信息Id
* @return
* @Description 查找用户
*/
UserVo findUserByUserId(Long userId)throws ProjectException;
/**
* @return List<UserVo>
* @Description 查找用户list
*/
List<UserVo> findUserVoList()throws ProjectException;
}
3.2、UserFaceImpl接口实现
UserFaceImpl:用户dubbo接口定义实现,这里做VO和POJO的转换
package com.itheima.restkeeper.face;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.itheima.restkeeper.AffixFace;
import com.itheima.restkeeper.UserFace;
import com.itheima.restkeeper.enums.UserEnum;
import com.itheima.restkeeper.exception.ProjectException;
import com.itheima.restkeeper.pojo.Role;
import com.itheima.restkeeper.pojo.User;
import com.itheima.restkeeper.req.AffixVo;
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 com.itheima.restkeeper.utils.ExceptionsUtil;
import com.itheima.restkeeper.utils.ResponseWrapBuild;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
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;
import java.util.stream.Collectors;
/**
* @ClassName UserFaceImpl.java
* @Description 用户dubbo服务实现
*/
@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)
})
public class UserFaceImpl implements UserFace {
@DubboReference(version = "${dubbo.application.version}",check = false)
AffixFace affixFace;
@Autowired
IUserService userService;
@Autowired
IUserAdapterService userAdapterService;
@Override
public Page<UserVo> findUserVoPage(UserVo userVo,
int pageNum,
int pageSize)throws ProjectException {
try {
//查询用户分页
Page<User> page = userService.findUserVoPage(userVo, pageNum, pageSize);
Page<UserVo> pageVo = new Page<>();
BeanConv.toBean(page,pageVo);
//结果集转换
List<User> userList = page.getRecords();
List<UserVo> userVoList = BeanConv.toBeanList(userList,UserVo.class);
if (!EmptyUtil.isNullOrEmpty(userList)){
userVoList.forEach(n->{
//处理附件
List<AffixVo> affixVoList = affixFace.findAffixVoByBusinessId(n.getId());
if (!EmptyUtil.isNullOrEmpty(affixVoList)){
n.setAffixVo(affixVoList.get(0));
}
//处理角色
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;
} catch (Exception e) {
log.error("查询用户列表异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(UserEnum.PAGE_FAIL);
}
}
@Override
public UserVo createUser(UserVo userVo)throws ProjectException {
try {
//保存用户
UserVo userVoResult = BeanConv.toBean(userService.createUser(userVo), UserVo.class);
//绑定附件
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 userVoResult;
} catch (Exception e) {
log.error("保存用户异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(UserEnum.CREATE_FAIL);
}
}
@Override
public Boolean updateUser(UserVo userVo) throws ProjectException{
try {
//修改用户
Boolean flag = userService.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 flag;
} catch (Exception e) {
log.error("保存用户异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(UserEnum.UPDATE_FAIL);
}
}
@Override
public Boolean deleteUser(String[] checkedIds)throws ProjectException {
try {
//删除用户
boolean flag = userService.deleteUser(checkedIds);
if (flag){
//删除图片
for (String checkedId : checkedIds) {
affixFace.deleteAffixVoByBusinessId(Long.valueOf(checkedId));
}
}
return flag;
} catch (Exception e) {
log.error("删除用户异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(UserEnum.DELETE_FAIL);
}
}
@Override
public UserVo findUserByUserId(Long userId)throws ProjectException {
try {
//查找用户
User user = userService.getById(userId);
UserVo userVo = BeanConv.toBean(user, UserVo.class);
if (!EmptyUtil.isNullOrEmpty(user)){
//处理附件
List<AffixVo> affixVoList = affixFace.findAffixVoByBusinessId(user.getId());
if (!EmptyUtil.isNullOrEmpty(affixVoList)){
userVo.setAffixVo(affixVoList.get(0));
}
return userVo;
}
return null;
} catch (Exception e) {
log.error("查找用户所有角色异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(UserEnum.SELECT_USER_FAIL);
}
}
@Override
public List<UserVo> findUserVoList()throws ProjectException {
try {
//查找用户下拉框
return BeanConv.toBeanList(userService.findUserVoList(),UserVo.class);
} catch (Exception e) {
log.error("查找用户list异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(UserEnum.SELECT_USER_LIST_FAIL);
}
}
}
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;
@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 {
Page<UserVo> userVoPage = userFace.findUserVoPage(userVo, pageNum, pageSize);
return ResponseWrapBuild.build(UserEnum.SUCCEED,userVoPage);
}
/**
* @Description 注册用户
* @param userVo 对象信息
* @return
*/
@PostMapping
@ApiOperation(value = "注册用户",notes = "注册用户")
@ApiImplicitParam(name = "userVo",value = "用户对象",required = true,dataType = "UserVo")
ResponseWrap<UserVo> registerUser(@RequestBody UserVo userVo) throws ProjectException {
String plainPassword = userVo.getPassword();
//必须要加{bcrypt}要不认证不通过
String password = "{bcrypt}"+bCryptPasswordEncoder.encode(plainPassword);
userVo.setPassword(password);
return ResponseWrapBuild.build(UserEnum.SUCCEED,userFace.createUser(userVo));
}
/**
* @Description 修改用户
* @param userVo 对象信息
* @return
*/
@PatchMapping
@ApiOperation(value = "修改用户",notes = "修改用户")
@ApiImplicitParam(name = "userVo",value = "用户对象",required = true,dataType = "UserVo")
ResponseWrap<Boolean> updateUser(@RequestBody UserVo userVo) throws ProjectException {
Boolean flag = userFace.updateUser(userVo);
return ResponseWrapBuild.build(UserEnum.SUCCEED,flag);
}
/**
* @Description 删除用户
* @param userVo 查询对象
* @return
*/
@DeleteMapping
@ApiOperation(value = "删除用户",notes = "删除用户")
@ApiImplicitParam(name = "userVo",value = "用户查询对象",required = true,dataType = "UserVo")
ResponseWrap<Boolean> deleteUser(@RequestBody UserVo userVo ) throws ProjectException {
//获得所有选中的用户IDS
String[] checkedIds = userVo.getCheckedIds();
Boolean flag = userFace.deleteUser(checkedIds);
return ResponseWrapBuild.build(UserEnum.SUCCEED,flag);
}
/**
* @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 {
UserVo userVo = userFace.findUserByUserId(userId);
return ResponseWrapBuild.build(UserEnum.SUCCEED,userVo);
}
/**
* @Description 查找用户list
* @return
*/
@GetMapping("select-list")
@ApiOperation(value = "查找用户list",notes = "查找用户list")
ResponseWrap<List<UserVo>> findUserVoList() throws ProjectException {
List<UserVo> list = userFace.findUserVoList();
return ResponseWrapBuild.build(UserEnum.SUCCEED,list);
}
@PostMapping("update-user-enableFlag")
@ApiOperation(value = "修改用户状态",notes = "修改用户状态")
@ApiImplicitParam(name = "userVo",value = "用户查询对象",required = true,dataType = "UserVo")
ResponseWrap<Boolean> updateUserEnableFlag(@RequestBody UserVo userVo) throws ProjectException {
Boolean flag = userFace.updateUser(userVo);
return ResponseWrapBuild.build(UserEnum.SUCCEED,flag);
}
@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);
Boolean flag = userFace.updateUser(userVo);
return ResponseWrapBuild.build(UserEnum.SUCCEED,flag);
}
}
Spring validation:
详细参看:https://blog.csdn.net/ilove_story/article/details/109260847
引入相关依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
约束性注解(简单)说明
@AssertFalse 可以为null,如果不为null的话必须为false
@AssertTrue 可以为null,如果不为null的话必须为true
@DecimalMax 设置不能超过最大值
@DecimalMin 设置不能超过最小值
@Digits 设置必须是数字且数字整数的位数和小数的位数必须在指定范围内
@Future 日期必须在当前日期的未来
@Past 日期必须在当前日期的过去
@Max 最大不得超过此最大值
@Min 最大不得小于此最小值
@NotNull 不能为null,可以是空
@Null 必须为null
@Pattern 必须满足指定的正则表达式
@Size 集合、数组、map等的size()值必须在指定范围内
@Email 必须是email格式
@Length 长度必须在指定范围内
@NotBlank 字符串不能为null,字符串trim()后也不能等于“”
@NotEmpty 不能为null,集合、数组、map等size()不能为0;字符串trim()后可以等于“”
@Range 值必须在指定范围内
@URL 必须是一个URL
@Validated的使用时机
@Validated的使用位置较多(可详见源码),但其主流的使用位置却是以下两种:
1、在Controller层中,放在模型参数对象前。
当Controller层中参数是一个对象模型时,只有将@Validated直接放在该模型前,该模型内部的字段才会被
校验(如果有对该模型的字段进行约束的话)。
2、在Controller层中,放在类上。
当一些约束是直接出现在Controller层中的参数前时,只有将@Validated放在类上时,参数前的约束才会生效。
自定义异常
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.ExceptionsUtil;
import com.itheima.restkeeper.utils.ResponseWrapBuild;
import lombok.extern.log4j.Log4j2;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.rpc.RpcException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolationException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* @ClassName BaseController.java
* @Description 基础的controller
* ControllerAdvice:对controller层的增强,其他的controller则不需要继承,也会被拦截处理
*/
@ControllerAdvice
@Slf4j
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 if (ex instanceof RpcException){
responseWrap = ResponseWrapBuild.build(BasicEnum.DUBBO_FAIL, null);
//参数校验异常
}else if (ex instanceof MethodArgumentNotValidException){
MethodArgumentNotValidException manvExceptionex = (MethodArgumentNotValidException) ex;
log.warn("@Validated约束在参数模型前,校验该模型的字段时发生异常:{} ", manvExceptionex.getMessage());
responseWrap = ResponseWrapBuild.build(BasicEnum.VALID_EXCEPTION, manvExceptionex.getMessage());
}else {
//系统异常
responseWrap = ResponseWrapBuild.build(BasicEnum.SYSYTEM_FAIL, null);
log.error("系统异常:{}",ExceptionsUtil.getStackTraceAsString(ex));
}
//编码防止中文问题
response.setContentType("application/json;charset =utf-8");
response.getWriter().write(JSON.toJSONString(responseWrap));
}
}
课堂讨论
1、mybatis的执行原理你还记得吗?说说你知道的mybatis的常用功能和标签
2、说说你对dubbo的理解,我们还需要掌握哪些关键组件及属性
整体架构:[https://dubbo.apache.org/zh/docsv2.7/dev/design/](https://dubbo.apache.org/zh/docsv2.7/dev/design/)
@DubboService属性: [https://dubbo.apache.org/zh/docsv2.7/user/references/xml/dubbo-service/](https://dubbo.apache.org/zh/docsv2.7/user/references/xml/dubbo-service/)
@DubboReference属性: [https://dubbo.apache.org/zh/docsv2.7/user/references/xml/dubbo-reference/](https://dubbo.apache.org/zh/docsv2.7/user/references/xml/dubbo-reference/)
协议类型:[https://dubbo.apache.org/zh/docsv2.7/user/references/protocol/](https://dubbo.apache.org/zh/docsv2.7/user/references/protocol/)
集群容错:[https://dubbo.apache.org/zh/docsv2.7/user/examples/fault-tolerent-strategy/](https://dubbo.apache.org/zh/docsv2.7/user/examples/fault-tolerent-strategy/)
负载均衡:[https://dubbo.apache.org/zh/docsv2.7/user/examples/loadbalance/](https://dubbo.apache.org/zh/docsv2.7/user/examples/loadbalance/)
分组聚合:[https://dubbo.apache.org/zh/docsv2.7/user/examples/group-merger/](https://dubbo.apache.org/zh/docsv2.7/user/examples/group-merger/)
上下文信息:[https://dubbo.apache.org/zh/docsv2.7/user/examples/context/](https://dubbo.apache.org/zh/docsv2.7/user/examples/context/)
隐式传参:[https://dubbo.apache.org/zh/docsv2.7/user/examples/attachment/](https://dubbo.apache.org/zh/docsv2.7/user/examples/attachment/)
3、springcloud-alibaba-dubbo在餐掌柜中的链路如何调用的?
课后任务
1、手绘:餐掌柜业务调用链路
2、完成当天课堂讨论,同步到git【☆☆☆☆☆】
3、完成2-5道sql练习【☆☆☆☆】
4、运营平台采用怎样的maven分层构建,每个模块有什么作用?
5、图片上传在项目中是如何使用的,图片数据表结构如何?
6、为什么在商家的用户管理中不用写UserService接口?这么做有什么好处?