学习目标
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() {@Overridepublic void initMap() {// to do nothing}};// 如果模板引擎是 freemarkerString 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=trueentity.ftl.path=/templates/entity.javamapper=falsemapper.ftl.path=/templates/mapper.javaservice=falseservice.ftl.path=/templates/service.javaserviceImp=falseserviceImp.ftl.path=/templates/serviceImpl.javacontroller=falsecontroller.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 {@Testpublic 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-producermain:allow-bean-definition-overriding: truecloud:#nacos添加中心nacos:discovery:server-addr: 192.168.112.77:8848namespace: publicgroup: SEATA_GROUPdubbo:#dubbo服务版本application:version: 1.0.0logger: slf4j#dubbo接口扫描路径scan:base-packages: com.itheima.dubbo#dubbo服务添加registry:address: spring-cloud://192.168.112.77#dubbo服务协议类型及端口,线程数【这里是默认配置】protocol:name: dubboport: 28080threads: 200accesslog: 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 {@Overridepublic 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-webmain:allow-bean-definition-overriding: true#nacos添加中心cloud:nacos:discovery:server-addr: 192.168.112.77:8848namespace: publicgroup: SEATA_GROUP#dubbo消费端配置dubbo:application:version: 1.0.0logger: slf4jcloud:#表示要订阅服务的服务名,可以配置'*',代表订阅所有服务,不推荐使用。若需订阅多应用,使用 "," 分割。subscribed-services: dubbo-producerscan:#扫描路径base-packages: com.itheima.dubbo.webregistry:address: spring-cloud://192.168.112.77#dubbo服务协议类型及端口,线程数【这里是默认配置】protocol:name: dubboport: 28081threads: 200accesslog: 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*/@RestControllerpublic 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: slf4jscan:#扫描路径base-packages: com.itheima.restkeepercloud:#表示要订阅服务的服务名,可以配置'*',代表订阅所有服务,不推荐使用。若需订阅多应用,使用 "," 分割。subscribed-services: model-basic-producer,model-trading-producerregistry:#注册中心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: slf4jcloud:#表示要订阅服务的服务名,可以配置'*',代表订阅所有服务,不推荐使用。若需订阅多应用,使用 "," 分割。subscribed-services: model-shop-producer,model-shop-applet,model-shop-userscan:#扫描路径base-packages: com.itheima.restkeeper.webregistry:#添加中心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 式。注意:不支持 KotlinLambdaQueryChainWrapper<T> lambdaQuery();// 示例:query().eq("column", value).one();lambdaQuery().eq(Entity::getId, value).list();
update
// 链式更改 普通UpdateChainWrapper<T> update();// 链式更改 lambda 式。注意:不支持 KotlinLambdaUpdateChainWrapper<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)})@Slf4jpublic class BrandFaceImpl implements BrandFace {@AutowiredIBrandService brandService;@DubboReference(version = "${dubbo.application.version}",check = false)AffixFace affixFace;@Overridepublic 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);}}@Overridepublic 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);}}@Overridepublic 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);}}@Overridepublic 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);}}@Overridepublic 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);}}@Overridepublic 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:品牌管理 服务实现类*/@Servicepublic class BrandServiceImpl extends ServiceImpl<BrandMapper, Brand> implements IBrandService {@Overridepublic 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);}@Overridepublic Brand createBrand(BrandVo brandVo) {//转换BrandVo为BrandBrand brand = BeanConv.toBean(brandVo, Brand.class);boolean flag = save(brand);if (flag){return brand;}return null;}@Overridepublic Boolean updateBrand(BrandVo brandVo) {//转换BrandVo为BrandBrand brand = BeanConv.toBean(brandVo, Brand.class);return updateById(brand);}@Overridepublic 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);}@Overridepublic 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 ) {//获得所有选中品牌IDSString[] 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 {@AutowiredIStoreService storeService;@Overridepublic 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);}}@Overridepublic 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);}}@Overridepublic 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);}}@Overridepublic 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);}}@Overridepublic 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);}}@Overridepublic 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:门店信息账号 服务实现类*/@Servicepublic class StoreServiceImpl extends ServiceImpl<StoreMapper, Store> implements IStoreService {@Overridepublic 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);}@Overridepublic Store createStore(StoreVo storeVo) {//转换storeVo为StoreStore store = BeanConv.toBean(storeVo, Store.class);boolean flag = save(store);if (flag){return store;}return null;}@Overridepublic Boolean updateStore(StoreVo storeVo) {//转换storeVo为StoreStore store = BeanConv.toBean(storeVo, Store.class);return updateById(store);}@Overridepublic 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);}@Overridepublic 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 ) {//获得所有选择的门店IDSString[] 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;@AutowiredIUserService userService;@AutowiredIUserAdapterService userAdapterService;@Overridepublic 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);}}@Overridepublic 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);}}@Overridepublic 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);}}@Overridepublic 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);}}@Overridepublic 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);}}@Overridepublic 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;@AutowiredBCryptPasswordEncoder 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 {//获得所有选中的用户IDSString[] 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@Slf4jpublic class BaseController {//表示当请求发生异常时,被ExceptionHandler注释的方法会去处理@ExceptionHandlerpublic 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接口?这么做有什么好处?
