接口文档:https://easydoc.net/doc/75716633/ZUqEdvA4/LnjzZHPj

一、分类维护

04:商品服务之分类维护 - 图1

  1. DROP TABLE IF EXISTS `pms_category`;
  2. CREATE TABLE `pms_category` (
  3. `cat_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '分类id',
  4. `name` char(50) DEFAULT NULL COMMENT '分类名称',
  5. `parent_cid` bigint(20) DEFAULT NULL COMMENT '父分类id',
  6. `cat_level` int(11) DEFAULT NULL COMMENT '层级',
  7. `show_status` tinyint(4) DEFAULT NULL COMMENT '是否显示[0-不显示,1显示]',
  8. `sort` int(11) DEFAULT NULL COMMENT '排序',
  9. `icon` char(255) DEFAULT NULL COMMENT '图标地址',
  10. `product_unit` char(50) DEFAULT NULL COMMENT '计量单位',
  11. `product_count` int(11) DEFAULT NULL COMMENT '商品数量',
  12. PRIMARY KEY (`cat_id`)
  13. ) ENGINE=InnoDB AUTO_INCREMENT=1433 DEFAULT CHARSET=utf8mb4 COMMENT='商品三级分类';

image.png

1、递归查询

image.png

1)CategoryEntity实体类

  1. @Data
  2. @TableName("pms_category")
  3. public class CategoryEntity implements Serializable {
  4. private static final long serialVersionUID = 1L;
  5. /**
  6. * 分类id
  7. */
  8. @TableId
  9. private Long catId;
  10. /**
  11. * 分类名称
  12. */
  13. private String name;
  14. /**
  15. * 父分类id
  16. */
  17. private Long parentCid;
  18. /**
  19. * 层级
  20. */
  21. private Integer catLevel;
  22. /**
  23. * 是否显示[0-不显示,1显示]
  24. * 若与配置文件yml默认规则不一致,可以手动设置显示:value = "1",不显示:delval = "0"
  25. */
  26. @TableLogic(value = "1",delval = "0")
  27. private Integer showStatus;
  28. /**
  29. * 排序
  30. */
  31. private Integer sort;
  32. /**
  33. * 图标地址
  34. */
  35. private String icon;
  36. /**
  37. * 计量单位
  38. */
  39. private String productUnit;
  40. /**
  41. * 商品数量
  42. */
  43. private Integer productCount;
  44. /**
  45. * 子分类属性
  46. * @TableField(exist = false):表示在数据库中不存在
  47. * @JsonInclude(JsonInclude.Include.NON_EMPTY):表示这个数据不为空时才返回
  48. */
  49. @JsonInclude(JsonInclude.Include.NON_EMPTY)
  50. @TableField(exist = false)
  51. private List<CategoryEntity> children;
  52. }

image.png

2)controller层

  1. /**
  2. * 查出所有分类以及子分类,以树形结构组装起来
  3. */
  4. @RequestMapping("/list/tree")
  5. public R list(){
  6. List<CategoryEntity> entities = categoryService.listWithTree();
  7. return R.ok().put("data", entities);
  8. }

3)service及实现类

  1. public interface CategoryService extends IService<CategoryEntity> {
  2. List<CategoryEntity> listWithTree();
  3. }
  1. /**
  2. * 查出所有分类以及子分类,以树形结构组装起来
  3. * @return
  4. */
  5. @Override
  6. public List<CategoryEntity> listWithTree() {
  7. // 1、查出所有分类
  8. List<CategoryEntity> entityList = baseMapper.selectList(null);
  9. // 2、组装成父子的树形结构
  10. // 2.1、找到所有的一级分类,filter筛选所有的一级分类,map设置一级分类的子菜单
  11. List<CategoryEntity> level1Menus = entityList.stream().filter(categoryEntity ->
  12. categoryEntity.getParentCid() == 0
  13. ).map(menu -> {
  14. // 递归查找所有菜单的子菜单
  15. menu.setChildren(getChildrens(menu, entityList));
  16. return menu;
  17. }).sorted((menu1, menu2) -> {
  18. return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
  19. }).collect(Collectors.toList());
  20. return level1Menus;
  21. }
  22. /**
  23. * 递归查找所有菜单的子菜单
  24. */
  25. private List<CategoryEntity> getChildrens(CategoryEntity root, List<CategoryEntity> entityList){
  26. List<CategoryEntity> children = entityList.stream().filter(categoryEntity -> {
  27. // 1、过滤子菜单
  28. return categoryEntity.getParentCid() == root.getCatId();
  29. }).map(menu -> {
  30. // 2、设置子菜单
  31. menu.setChildren(getChildrens(menu, entityList));
  32. return menu;
  33. }).sorted((menu1, menu2) -> {
  34. // 3、菜单的排序
  35. // 防止sort为空时,报空指针异常
  36. return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
  37. }).collect(Collectors.toList());
  38. return children;
  39. }

4)前端实现

image.png
image.png

2、配置网关路由与路径重写

前端请求:localhost:8080/renren-fast/product/category/list/tree,报错404找不到。此时访问登录页验证码不显示。
image.png
前端访问端口设置:
image.png
原因分析:前端请求8080端口,但数据在10000端口(商品服务)上
1)方法一:修改vue项目里的全局配置static/config/index.js,此时只能访问商品服务,若需要访问其他服务,需要再次修改端口号

  1. window.SITE_CONFIG['baseUrl'] = 'http://localhost:10000';

2)方法2:搭建网关,让网关路由到10000端口,即将前端所有请求都发送到网关,网关通过路径匹配,然后到nacos里找到管理后台的微服务,就可以找到对应的端口号,这样我们就无需管理端口,统一交给网关管理端口接口
修改vue项目里的全局配置static/config/index.js

  1. // 意思是说本vue项目中要请求的资源url都发给88/api,那么我们就让网关端口为88,然后匹配到/api请求即可
  2. // 网关可以通过过滤器处理url后指定给某个微服务
  3. // renren-fast服务必须注册到了nacos中
  4. window.SITE_CONFIG['baseUrl'] = 'http://localhost:88/api';

image.png

  1. # gateway微服务的配置
  2. spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
  3. spring.application.name=gulimall-gateway
  4. server.port=88

1)解决验证码不显示的问题:

让网关将所有请求发送到renren-fast服务,首先需要网关发现renren-fast服务,那么需要将renren-fast服务注册到nacos注册中心(gulimall-common有nacos依赖),这样所有请求经过网关88端口时,就会将请求转发到renren-fast的8080端口

  1. <dependency>
  2. <groupId>com.atguigu.gulimall</groupId>
  3. <artifactId>gulimall-common</artifactId>
  4. <version>0.0.1-SNAPSHOT</version>
  5. </dependency>
  1. spring:
  2. application:
  3. name: renren-fast #服务名称。把renren-fast服务注册到nacos
  4. cloud:
  5. nacos:
  6. discovery:
  7. server-addr: localhost:8848 #nacos地址

然后在fast启动类上加上注解@EnableDiscoveryClient,重启服务

  1. @EnableDiscoveryClient // 开启服务注册发现功能
  2. @SpringBootApplication
  3. public class RenrenApplication {
  4. public static void main(String[] args) {
  5. SpringApplication.run(RenrenApplication.class, args);
  6. }
  7. }

如果gson依赖报错,就导入google的gson依赖

  1. <dependency>
  2. <groupId>com.google.code.gson</groupId>
  3. <artifactId>gson</artifactId>
  4. <version>2.8.5</version>
  5. </dependency>

然后在nacos的服务列表里看到了renren-fast
image.png
所有请求到达gateway服务,然后在配置文件application.yml中配置路由规则

  1. spring:
  2. cloud:
  3. gateway:
  4. routes:
  5. # 路由地址存在先后顺序,需要把精确的uri放到前面,模糊的uri放到后面
  6. - id: test_route
  7. uri: https://www.baidu.com
  8. predicates:
  9. - Query=url,baidu
  10. - id: admin_route
  11. uri: lb://renren-fast # 负载均衡(lb)到指定的服务(renren-fast)
  12. predicates: # 路由规则
  13. - Path=/api/** # 默认前端项目发起的所有请求都路由到admin_route。即localhost:88/api
  14. filters: # 路径重写,把/api/* 转换成 /renren-fast/*
  15. - RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment}

2)网关路径重写

前端请求网关服务,路由到了renren-fast,然后去nacos里找renren-fast服务。
找到后拼接成了http://renren-fast:8080/api/captcha.jpg
image.png
但是正确的是localhost:8080/renren-fast/captcha.jpg
image.png
所以要利用网关带的路径重写,参考https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#the-rewritepath-gatewayfilter-factory
在网关里把api换成renren-fast即可

  1. - id: admin_route
  2. uri: lb://renren-fast
  3. predicates:
  4. - Path=/api/**
  5. filters: # 路径重写,把/api/* 转换成 /renren-fast/*
  6. - RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment}

登录还是报错:出现了跨域的问题,就是说前端访问的是8001端口,却要跳转到网关的88端口,为了安全性,浏览器会拒绝跨域请求
image.png
从8001访问88,引发CORS跨域请求,浏览器会拒绝跨域请求。具体来说当前页面是8001端口,但是要跳转88端口,这是不可以的(post请求json可以)

3、网关统一配置跨域

问题描述:已拦截跨源请求:同源策略禁止8001端口页面读取位于 http://localhost:88/api/sys/login 的远程资源。(原因:CORS 头缺少 ‘Access-Control-Allow-Origin’)。
问题分析:这是一种跨域问题。访问的域名或端口和原来请求的域名端口一旦不同,请求就会被限制

跨域:指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对javascript施加的安全限制

同源策略:是指协议,域名,端口都要相同,其中有一个不同都会产生跨域;
image.png

跨域流程:
这个跨域请求的实现是通过预检请求实现的,先发送一个OPSTIONS探路,收到响应允许跨域后再发送真实请求。
image.png
什么意思呢?跨域是要请求的、新的端口那个服务器限制的,不是浏览器限制的。
image.png
资料:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS

解决跨域方式一:使用nginx部署为同一域

image.png
将前端项目和gateway服务都部署到nginx上。所有请求都先到nginx,静态请求都发送给前端项目,动态请求都发送给网关服务,网关在转发到对应的微服务。所以从头到尾都是访问的nginx地址。然后配置好反向代理,将前端和网关配置为同一个域即可

解决跨域方式二:配置当次请求允许跨域

浏览器会先发一个预检请求,去问服务器能不能跨域。让服务器告诉预检请求可以跨域即可。给预检请求一个响应。添加如下响应头:

  1. Access-Control-Allow-Origin:支持哪些来源的请求跨域
  2. Access-Control-Allow-Methods:支持哪些方法跨域
  3. Access-Control-Allow-Credentials:跨域请求默认不包含cookie,设置为true可以包含cookie
  4. Access-Control-Expose-Headers:跨域请求暴露的字段
  5. CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:
  6. Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma。如
  7. 果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。
  8. Access-Control-Max-Age:表明该响应的有效时间为多少秒。在有效时间内,浏览器无须为同一请求
  9. 再次发起预检请求。请注意,浏览器自身维护了一个最大有效时间,如果该首部字段的值超过了最大有效时间,
  10. 将不会生效。

参考:https://blog.csdn.net/qq_38128179/article/details/84956552

解决方法:在网关中定义GulimallCorsConfiguration类,该类用来做过滤所有请求,允许所有的请求跨域

  1. @Configuration
  2. public class GulimallCorsConfiguration {
  3. @Bean // 加入到容器中
  4. public CorsWebFilter corsWebFilter(){
  5. // 跨域的配置信息
  6. UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
  7. // 配置跨域
  8. CorsConfiguration corsConfiguration = new CorsConfiguration();
  9. corsConfiguration.addAllowedHeader("*"); // 跨域请求暴露的字段
  10. corsConfiguration.addAllowedMethod("*"); // 支持哪些方法跨域
  11. corsConfiguration.addAllowedOrigin("*"); // 支持哪些来源的请求跨域
  12. corsConfiguration.setAllowCredentials(true); // 跨域请求默认不包含cookie,设置为true可以包含 cookie
  13. // 跨域配置,任何路径都要
  14. source.registerCorsConfiguration("/**",corsConfiguration);
  15. return new CorsWebFilter(source);
  16. }
  17. }

再次访问:http://localhost:8001/#/login,预检请求已成功返回跨域设置
image.png
但正式请求仍报错,提示不允许有多个 ‘Access-Control-Allow-Origin’。即不允许多次跨域
image.png

为了解决这个问题,需要修改renren-fast服务,注释掉“io.renren.config.CorsConfig”类。然后再次进行访问。
image.png

在显示商品系统/分类信息的时候,出现了404异常,请求的http://localhost:88/api/product/category/list/tree不存在
image.png
需要在网关配置product服务
image.png
在nacos中新建命名空间,用命名空间隔离项目(可以在其中新建gulimall-product.yml)
在product项目中新建bootstrap.properties

  1. spring.application.name=gulimall-product
  2. spring.cloud.nacos.config.server-addr=127.0.0.1:8848
  3. spring.cloud.nacos.config.namespace=e6cd36a8-81a2-4df2-bfbc-f0524fa17664

将product服务注册到nacos中:在启动类上加上@EnableDiscoveryClient
image.png

访问localhost:88/api/product/category/list/tree,报错:invalid token,非法令牌,后台管理系统中没有登录,所以没有带令牌
image.png
原因:先匹配的先路由,fast和product路由重叠,fast要求登录
修正:在路由规则的顺序上,将精确的路由规则放置到模糊的路由规则的前面,否则的话,精确的路由规则将不会被匹配到,类似于异常体系中try catch子句中异常的处理顺序。
image.png
http://localhost:88/api/product/category/list/tree 正常
访问http://localhost:8001/#/product-category ,正常
原因是:先访问网关88,网关路径重写后访问nacos8848,nacos找到服务

4、逻辑删除

菜单删除前提:没有子菜单、没有被其他菜单引用

1)controller层

  1. /**
  2. * 商品三级分类
  3. */
  4. @RestController
  5. @RequestMapping("product/category")
  6. public class CategoryController {
  7. @Autowired
  8. private CategoryService categoryService;
  9. /**
  10. * 删除
  11. * @RequestBody:获取请求体,必须发送POST请求
  12. * SpringMVC自动将请求体的数据(json),转为对应的对象
  13. */
  14. @RequestMapping("/delete")
  15. // @RequiresPermissions("product:category:delete")
  16. public R delete(@RequestBody Long[] catIds){
  17. // categoryService.removeByIds(Arrays.asList(catIds));
  18. if(catIds == null || catIds.length == 0){
  19. return R.ok().put("message", "参数不能为空");
  20. }
  21. categoryService.removeMenuByIds(Arrays.asList(catIds));
  22. return R.ok();
  23. }
  24. }

2)service以及实现类

  1. public interface CategoryService extends IService<CategoryEntity> {
  2. void removeMenuByIds(List<Long> asList);
  3. }
  1. @Service("categoryService")
  2. public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {
  3. /**
  4. * 删除
  5. */
  6. @Override
  7. public void removeMenuByIds(List<Long> asList) {
  8. //TODO 1、检查当前删除的菜单,是否被别的地方引用
  9. baseMapper.deleteBatchIds(asList);
  10. }
  11. }

3)逻辑删除配置


配置全局的逻辑删除规则(可以省略)
image.png
配置逻辑删除的组件Bean(mybatis3以上版本可以省略)
* 给Bean加上逻辑删除注解@TableLogic
image.png
image.png

5、新增分类

  1. /**
  2. * 商品三级分类
  3. */
  4. @RestController
  5. @RequestMapping("product/category")
  6. public class CategoryController {
  7. @Autowired
  8. private CategoryService categoryService;
  9. /**
  10. * 保存
  11. */
  12. @RequestMapping("/save")
  13. //@RequiresPermissions("product:category:save")
  14. public R save(@RequestBody CategoryEntity category){
  15. categoryService.save(category);
  16. return R.ok();
  17. }
  18. }

6、修改分类

  1. /**
  2. * 商品三级分类
  3. */
  4. @RestController
  5. @RequestMapping("product/category")
  6. public class CategoryController {
  7. @Autowired
  8. private CategoryService categoryService;
  9. /**
  10. * 修改
  11. */
  12. @RequestMapping("/update")
  13. //@RequiresPermissions("product:category:update")
  14. public R update(@RequestBody CategoryEntity category){
  15. categoryService.update(category);
  16. return R.ok();
  17. }
  18. }

7、修改排序

  1. /**
  2. * 商品三级分类
  3. */
  4. @RestController
  5. @RequestMapping("product/category")
  6. public class CategoryController {
  7. @Autowired
  8. private CategoryService categoryService;
  9. /**
  10. * 修改排序
  11. */
  12. @RequestMapping("/update/sort")
  13. //@RequiresPermissions("product:category:update")
  14. public R updateSort(@RequestBody CategoryEntity[] category){
  15. categoryService.updateBatchById(Arrays.asList(category));
  16. return R.ok();
  17. }
  18. }

二、Object 划分

  1. PO(persistant object) 持久对象

PO 就是对应数据库中某个表中的一条记录,多个记录可以用 PO 的集合。 PO 中应该不包含任何对数据库的操作。 —比如:BrandEntity

  1. DO(Domain Object)领域对象

就是从现实世界中抽象出来的有形或无形的业务实体。

  1. TO(Transfer Object) ,数据传输对象

不同的应用程序之间传输的对象 — 微服务调用之间的数据

  1. DTO(Data Transfer Object)数据传输对象

这个概念来源于 J2EE 的设计模式,原来的目的是为了 EJB 的分布式应用提供粗粒度的数据实体,以减少分布式调用的次数,从而提高分布式调用的性能和降低网络负载,但在这里,泛指用于展示层与服务层之间的数据传输对象。

  1. VO(value object) 值对象

通常用于业务层之间的数据传递,和 PO 一样也是仅仅包含数据而已。但应是抽象出的业务对象 , 可以和表对应 , 也可以不 , 这根据业务的需要 。用 new 关键字创建,由GC 回收的。
View object:视图对象;
接受页面传递来的数据,封装对象
将业务处理完成的对象,封装成页面要用的数据

  1. BO(business object) 业务对象

从业务模型的角度看 , 见 UML 元件领域模型中的领域对象。封装业务逻辑的 java 对象 , 通过调用 DAO 方法 , 结合 PO,VO 进行业务操作。business object: 业务对象 主要作用是把业务逻辑封装为一个对象。这个对象可以包括一个或多个其它的对象。 比如一个简历,有教育经历、工作经历、社会关系等等。 我们可以把教育经历对应一个 PO ,工作经历对应一个 PO ,社会关系对应一个 PO 。 建立一个对应简历的 BO 对象处理简历,每个 BO 包含这些 PO 。 这样处理业务逻辑时,我们就可以针对 BO 去处理。

7. POJO(plain ordinary java object) 简单无规则 java 对象
传统意义的 java 对象。就是说在一些 Object/Relation Mapping 工具中,能够做到维护数据库表记录的 persisent object 完全是一个符合 Java Bean 规范的纯 Java 对象,没有增加别的属性和方法。我的理解就是最基本的 java Bean ,只有属性字段及 setter 和 getter方法!。
POJO 是 DO/DTO/BO/VO 的统称。

  1. DAO(data access object) 数据访问对象

是一个 sun 的一个标准 j2ee 设计模式, 这个模式中有个接口就是 DAO ,它负持久层的操作。为业务层提供接口。此对象用于访问数据库。通常和 PO 结合使用, DAO 中包含了各种数据库的操作方法。通过它的方法 , 结合 PO 对数据库进行相关的操作。夹在业务逻辑与数据库资源中间。配合 VO, 提供数据库的 CRUD 操作.

4、接口文档地址
https://easydoc.xyz/#/s/78237135