跳至主要內容

四、前后端联调

apzs...大约 245 分钟

四、前后端联调

4.1、商品服务-API-三级分类

4.1.1、商品服务-API-三级分类后端

1、先执行sql

image-20220426164247687
image-20220426164247687

2、修改com.atguigu.gulimall.product.controller.CategoryController的list方法

image-20220426164551505
image-20220426164551505

改为

 /**
     * 查询分类几子分类,以树状形式组装
     */
    @RequestMapping("/list")
        public R list(){
        List<CategoryEntity> list = categoryService.listWithTree();

        return R.ok().put("data", list);
    }
image-20220426164901518
image-20220426164901518

3、实现CategoryServicelistWithTree方法

1、CategoryService接口添加方法
List<CategoryEntity> listWithTree();
image-20220426165019336
image-20220426165019336
2、先查询所有
@Override
public List<CategoryEntity> listWithTree() {
    //baseMapper就是ServiceImpl<CategoryDao, CategoryEntity>中的CategoryDao
    //查询所有分类
    List<CategoryEntity> list = baseMapper.selectList(null);

    return list;
}
image-20220426170219018
image-20220426170219018
3、测试

http://localhost:10000/product/category/list/tree

image-20220426170316381
image-20220426170316381
4、实体添加属性

com.atguigu.gulimall.product.entity.CategoryEntity类添加children字段

children字段为其下一级分类(共有三级分类)

image-20220426172202666
image-20220426172202666
5、修改Service层代码
    @Override
    public List<CategoryEntity> listWithTree() {
        //baseMapper就是ServiceImpl<CategoryDao, CategoryEntity>中的CategoryDao
        //查询所有分类
        List<CategoryEntity> list = baseMapper.selectList(null);
        List<CategoryEntity> topCategory = list.stream()
                //查出一级分类
                .filter(categoryEntity -> categoryEntity.getParentCid() == 0)
                //映射方法,改变对象结构
                .map((menu)->{
                    menu.setChildren(getAllChildren(menu,list));
                    return menu;
                })
                //根据sort字段排序
                .sorted(Comparator.comparingInt((menu)->menu.getSort()!=null?menu.getSort():0))
                //搜集
                .collect(Collectors.toList());

        return topCategory;
    }

    /**
     * 从list集合中获得当前菜单的子菜单
     * @param root 当前菜单
     * @param list  菜单集合
     * @return
     */
    private List<CategoryEntity> getAllChildren(CategoryEntity root, List<CategoryEntity> list) {
        List<CategoryEntity> collect = list.stream()
                .filter(categoryEntity -> root.getCatId().equals(categoryEntity.getParentCid()))
                //
                .map((menu)->{
                    //递归求解其子菜单
                     menu.setChildren(getAllChildren(menu,list));
                     return menu;
                })
                //根据sort字段排序
                .sorted(Comparator.comparingInt((menu)->menu.getSort()!=null?menu.getSort():0))
                .collect(Collectors.toList());
        return collect;

    }
image-20220426205730443
6、查看结果

可以看到数据已经按树状显示,并且按sort字段排序**😄**

image-20220426205415943
image-20220426205415943
//1.可以通过Collection 系列集合提供的stream()或parallelStream()
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();

//2.通过Arrays中的静态方法stream() 获收数组流
String[] ints = {"a", "b", "c", "d"};
Stream<String> stream2 = Arrays.stream(ints);

//3.通过Stream类中的静态方法of()
Stream<String> stream3 = Stream.of("a", "b", "c", "d");

//4.迭代方式创建无限流
//10、12、14、16、18、20、22、24、26、28 ······(从10开始,下一个是 10+2=12,下一个是12+2······)
Stream<Integer> stream4 = Stream.iterate(10, (x) -> x + 2);
//10、12、14、16、18
stream4.limit(5).forEach(System.out::print);
System.out.println();

//5.生成方式创建无限流
//没有种子,每一次生成的元素与上一次生成的元素没有关系,生成的元素为double类型
// 1.2 1.2 1.2 1.2 1.2
Stream.generate(() -> 1.2).limit(5).forEach(System.out::println);
Stream<Double> stream5 = Stream.generate(Math::random);
//0.3522966301192748
//0.20867372930661876
//0.06987341089850951
//0.10069902801339281
//0.3395435668418123
stream5.limit(5).forEach(System.out::println);

4.1.2、商品服务-API-三级分类前端

1、先启动项目

1、启动后端

启动 renren-fastio.renren.RenrenApplication

image-20220426213132389
image-20220426213132389
2、启动前端
npm run dev
image-20220426213208523
image-20220426213208523

2、跨域报错😠

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

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

image-20220721201231225

跨域流程:

非简单请求(PUT、DELETE) 等,需要先发送预检请求

image-20220721201441177

参考网址: https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS

解决跨域-(一)使用nginx部署为同一域

image-20220721201658286

解决跨域-(二)配置当次请求允许跨域

添加响应头

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

http://localhost:8001/#/login

可以

跨域报错
跨域报错
2、解决跨域问题

1、方式一:

Spring Boot 中如何解决跨域问题:参考链接open in new window ?

跨域可以在前端通过 JSONP 来解决,但是 JSONP 只可以发送 GET 请求,无法发送其他类型的请求,在 RESTful 风格的应用中,就显得非常鸡肋,因此我们推荐在后端通过 (CORS,Cross-origin resource sharing) 来解决跨域问题。这种解决方案并非 Spring Boot 特有的,在传统的 SSM 框架中,就可以通过 CORS 来解决跨域问题,只不过之前我们是在 XML 文件中配置 CORS ,现在可以通过实现WebMvcConfigurer接口然后重写addCorsMappings方法解决跨域问题。

renren-fast模块的io.renren.config.CorsConfig类的addCorsMappings方法注释打开

这个本来是开着的,由于我使用的是资料提供的代码,所有这里先取消注释,后来这个是要注释掉的

image-20220426213642581
image-20220426213642581

2、添加过滤器

项目中前后端分离部署,所以需要解决跨域的问题。 我们使用cookie存放用户登录的信息,在spring拦截器进行权限控制,当权限不符合时,直接返回给用户固定的json结果。 当用户登录以后,正常使用;当用户退出登录状态时或者token过期时,由于拦截器和跨域的顺序有问题,出现了跨域的现象。 我们知道一个http请求,先走filter,到达servlet后才进行拦截器的处理,如果我们把cors放在filter里,就可以优先于权限拦截器执行。

package io.renren.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

/**
 * springboot解决跨域问题
 */
@Configuration
public class CorsConfig2 {

    @Bean
    public CorsFilter corsFilter() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedOrigin("*");
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("*");
        corsConfiguration.setAllowCredentials(true);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", corsConfiguration);
        return new CorsFilter(source);
    }
}
image-20220426221916301
image-20220426221916301

3、添加一级菜单

添加商品系统为一级菜单

image-20220426223335101
image-20220426223335101

4、数据库中查看

可以看到商品系统已经添加进来了

image-20220426223429705
image-20220426223429705

5、添加二级菜单

添加分类维护为商品系统的下一级菜单

image-20220426224339948
image-20220426224339948

6、分析路由规则

1、菜单URL与访问URL对应关系

可以发现当菜单URL为product/category时,访问的url为product-category

image-20220426230159499
image-20220426230159499
image-20220426230258838
image-20220426230258838
2、访问URL与路径的关系

可以发现当访问的URL为sys-role时,文件路径为src/views/modules/sys/role.vue

因此分类维护product-category的文件路径src/views/modules/product/category.vue

image-20220426230951488
image-20220426230951488
image-20220426230916453
image-20220426230916453

7、创建目录和vue文件

src/views/modules/目录中创建product目录,在product目录下创建category.vue文件

category.vue中输入vue然后回车,生成模板

随便在div写点东西,然后运行项目

image-20220427155235060
image-20220427155235060

8、发现ES Link注释报错(项目能运行)

这个ES Link我禁用了还报错😠,但是项目能运行

这个ES Link真是阴魂不散

image-20220427154646562
image-20220427154646562
image-20220427155049406
image-20220427155049406
方法一

eslintrc.js里面的extends: 'standard',注释掉就不报错了

image-20220427160029935
image-20220427160029935
方法二

删掉build\webpack.base.conf.js里面的createLintingRule()

image-20220427160549017
image-20220427160549017
方法三

删掉build\webpack.base.conf.js里面reateLintingRule()方法里面的代码

image-20220503140831432
image-20220503140831432
方法四

删掉config\index.js里面的useEslint: true,

image-20220503141016797
image-20220503141016797

📌后来我又使用的是renren-fast-vue,放弃了资料提供的做好的代码

报了一个VS Code的错

could not use PowerShell to find Visual Studio 2017 or newer, try re-running with '--loglevel silly' for more details
无法使用Power Shell查找Visual Studio 2017或更新,请尝试使用“ -loglevel Silly”重新运行,以获取更多详细信息
You need to install the latest version of Visual Studio        
find VS including the "Desktop development with C++" workload.
您需要安装最新版本的Visual Studio查找VS,包括“带有C ++的桌面开发”工作负载。
image-20220426234340229
image-20220426234340229

修改一下msvs_version版本就行了

npm config set msvs_version 2019

9、使用tree树形组件

src\views\modules\product\category.vue文件内使用element-ui中的Tree 树形组件 => 组件 | Elementopen in new window

点击查看category.vue完整代码

image-20220429123519980
image-20220429123519980

封装的发送ajax请求方法在src\utils\httpRequest.js文件内,get请求可以复制src\views\modules\sys\role.vue里面的

image-20220429123839285
image-20220429123839285

10、运行项目

1、端口访问错误

发现请求的url为 http://localhost:8080/renren-fast/product/category/list/tree

而正确的url为 http://localhost:10000/product/category/list/tree

访问的是8080端口下的renren-fast,而想访问的是10000端口

image-20220427203758880
image-20220427203758880
2、修改端口

ctrl+shift+F 全局搜索localhost:8080/renren-fast

发现其定义在static\config\index.js里的window.SITE_CONFIG['baseUrl']字段

image-20220427205415040
image-20220427205415040

由于要向多个模块发请求,所以可以指定访问网关,网关再路由到其他模块

http://localhost:88

ps:后面又修改为 http://localhost:88/api

image-20220427205600163
image-20220427205600163
3、刷新页面

刷新页面发现验证码也给网关发请求了,这是因为刚刚配置了basUrl,所有请求都发给网关

刷新页面
刷新页面

11、修改网关配置

先让网关都转给renren-fast模块

1、renren-fast模块注册到注册中心

1、依赖gulimall-common模块

gulimall-common模块配置的有nacos,依赖gulimall-common模块后,点击刷新

<dependency>
   <groupId>com.atguigu.gulimall</groupId>
   <artifactId>gulimall-common</artifactId>
   <version>0.0.1-SNAPSHOT</version>
</dependency>
image-20220427210524719
image-20220427210524719

由于gulimall-common模块依赖了配置中心,"renren-fast"模块暂时没配,所以可以先排除掉配置中心

这样的话运行可以不报错(不排除也能运行,不过会报错)

<dependency>
   <groupId>com.atguigu.gulimall</groupId>
   <artifactId>gulimall-common</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <exclusions>
      <exclusion>
         <groupId>com.alibaba.cloud</groupId>
         <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
      </exclusion>
   </exclusions>
</dependency>
2、修改配置文件

配置应用名和注册中心地址

spring:  
    application:
      name: renren-fast
    cloud:
      nacos:
        discovery:
          server-addr: 127.0.0.1:8848
image-20220427210950685
image-20220427210950685
3、启动类添加服务发现注解
1、添加注解
@EnableDiscoveryClient
image-20220427211241374
image-20220427211241374
2、提示Gson不存在
image-20220427211712823
image-20220427211712823
3、添加gson依赖

添加gson依赖后,重启模块

<dependency>
   <groupId>com.google.code.gson</groupId>
   <artifactId>gson</artifactId>
   <version>2.8.5</version>
</dependency>
image-20220427211829461
image-20220427211829461
4、配置中心报错

由于在gulimall-common配置了配置中心,而项目没有配置配置中心地址和端口

因此项目启动时,会报配置中心相关的错误,这里先不用管

image-20220428160014549
image-20220428160014549
4、查看注册中心

启动nacos,在浏览器输入: http://localhost:8848/nacos/

用户名和密码都为nacos

可以发现已经注册到nacos上了

image-20220427212615394
image-20220427212615394
5、修改网关配置
- id: admin_route
  uri: lb://renren-fast   #loadbalanced 负载均衡
  predicates:
    - Path=/api/**		  #Path请求路径,请求路径前面加一个/api,**表示任意请求
image-20220427213105152
image-20220427213105152

前端的index.js的baseUrl也由

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

修改为

window.SITE_CONFIG['baseUrl'] = 'http://localhost:88/api';

参考文档:https://docs.spring.io/spring-cloud-gateway/docs/2.2.9.RELEASE/reference/html/#the-cookie-route-predicate-factory

image-20220427214715052
image-20220427214715052
image-20220427215853495
image-20220427215853495
6、重启项目

重启项目发现还是访问不了

访问不了的原因是

前端访问了 http://localhost:88/api/captcha.jpg

通过网关,断言匹配到了 /api/**

所以网关路由到了renren-fast模块,即找到了 http://localhost:8080

然后将 http://localhost:8080 /api/captcha.jpg组成了 http://localhost:8080/api/captcha.jpg

而正确的路径为 http://localhost:8080/renren-fast/captcha.jpg

image-20220427215308179
image-20220427215308179
7、路径重写

将将请求由 http://localhost:88/api/captcha.jpg 变为 http://localhost:8080/renren-fast/captcha.jpg

然后重启项目

filters:
  #路径重写,将请求由 http://localhost:88/api/captcha.jpg 变为 http://localhost:8080/renren-fast/captcha.jpg
  - RewritePath=/api/(?<segment>/?.*),/renren-fast/$\{segment}
image-20220428162416500
image-20220428162416500
image-20220427220727452
image-20220427220727452

12、验证码已经显示出来了

image-20220427221353624
image-20220427221353624

13、跨域请求

点击登录发现没反应,看一下控制台显示跨域请求

Access to XMLHttpRequest at 'http://localhost:88/api/sys/login' from origin 'http://localhost:8001' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
image-20220427224950491
image-20220427224950491

查看Network可以看到有CORS error

image-20220427225621837
image-20220427225621837

点击第二个login,发现请求方式为OPTION,表示这个请求为域检请求

image-20220427225801035
image-20220427225801035

14、后端配置允许跨域请求

由于前端指定访问网关,网关再路由到其他模块,所有可以再网关模块配置跨域请求,这样别的模块就不用配置跨域请求了

package com.atguigu.gulimall.gateway.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;

/**
 * @author 无名氏
 * @date 2022/4/27
 * @Description: 跨域请求过滤器
 */
@Configuration
public class GulimallCorsConfiguration {

    @Bean
    public CorsWebFilter corsWebFilter(){
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        //添加允许的请求头
        corsConfiguration.addAllowedHeader("*");
        //添加允许的请求方式
        corsConfiguration.addAllowedMethod("*");
        //添加允许的请求来源
        corsConfiguration.addAllowedOrigin("*");
        //是否允许携带cookie进行跨域
        //设为false会丢失cookie信息
        corsConfiguration.setAllowCredentials(true);

        //CorsWebFilter需要传入CorsConfigurationSource接口类型的参数
        //UrlBasedCorsConfigurationSource是CorsConfigurationSource接口的实现类
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        // "/**"表示任意路径
        source.registerCorsConfiguration("/**",corsConfiguration);
        return new CorsWebFilter(source);
    }
}
image-20220427230019471
image-20220427230019471

15、刷新前端页面

重启gulimall-gateway模块,刷新前端页面

The 'Access-Control-Allow-Origin' header contains multiple values 'http://localhost:8001, http://localhost:8001', but only one is allowed.
'Access-Control-Allow-Origin'头包含多个值,但只有一个被允许
image-20220427231312786
image-20220427231312786

NetWork也可以看到包含了两个

image-20220427231548647
image-20220427231548647

16、取消renren-fast项目的跨域配置

包含两个值的原因是gulimall-gateway配置了跨域

而renren-fast项目默认也配置了跨域

因此取消的renren-fast项目的跨域配置就行了

注释到这里

image-20220427232200865
image-20220427232200865

17、重新刷新前端页面

重启renren-fast项目后,重新刷新前端页面,点击登录后,发现已经登录成功了

image-20220427232353711
image-20220427232353711

4.1.3、显示商品服务下的分类维护

1、分类维护页面

点击商品系统下的分类维护,发现没有数据,打开控制台,发现给网关发送了请求,但请求失败

请求路径为: http://localhost:88/api/product/category/list/tree

正确的请求为: http://localhost:10000/product/category/list/tree

网关由于没有配置gulimall-product,因此默认路由到了renren-fast

即: http://localhost:8080/renren-fast/category/list/tree

image-20220428160942870
image-20220428160942870

2、网关添加配置

在网关的配置文件中添加路由到gulumall-product模块的配置

(product_routefilters写错了,少写了个s😨)

- id: product_route
  uri: lb://gulimall-product
  predicates:
    - Path=/api/product/**
  filter:  #这里应该为`filters`,少写了一个`s`
    #http://localhost:88/api/product/category/list/tree 变为http://localhost:10000/product/category/list/tree
    - RewritePath=/api/(?<segment>/?.*),/$\{segment}
image-20220428163318103
image-20220428163318103

3、新建product命名空间

1、新建product命名空间,作为gilimall-product项目在配置中心的命名空间
image-20220428164008683
image-20220428164008683
2、复制product的命名空间ID
image-20220428164334571
image-20220428164334571

4、配置配置中心

gulimall-product模块的resource目录下新建bootstrap.properties文件,并配置 应用名、配置中心地址、命名空间等

namespace写刚刚复制的product的命名空间ID

spring.application.name=gulimall-product

spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.namespace=d6d03bd1-5815-4fa1-8caf-93b09462fd45
image-20220428165002877
image-20220428165002877

5、配置注册中心

gulimall-product模块的application.yml配置文件中配置注册中心地址

spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
image-20220428165402783
image-20220428165402783

6、开启服务发现

gulimall-product模块添加@EnableDiscoveryClient注解,来开启服务发现

image-20220428165643767
image-20220428165643767

7、查看是否添加到注册中心

重新启动gulimall-product项目,可以看到gulimall-product已经添加到注册中心上了

image-20220428170006762
image-20220428170006762

8、查看前端页面

1、第二个tree的请求出错

发现第二个tree的请求出错(第一个OPTIONS请求方式的域检请求请求成功)

image-20220428170834876
image-20220428170834876
2、令牌无效

http://localhost:88/api/product/category/list/tree

直接访问会出现invalid token:令牌无效,非法的令牌

image-20220428171048454
image-20220428171048454
3、直接访问正常

http://localhost:10000/product/category/list/tree

image-20220428173622969
image-20220428173622969
4、查看匹配的路由

gulimall-gateway模块的application.yml配置文件中修改日志级别

root表示所有包

logging:
  level:
    root: debug

重新运行项目

image-20220428180356028
image-20220428180356028

9、调整路由顺序

令牌无效的原因是product_route没有生效,先被admin_route拦截了

调整一下product_routeadmin_route的路由顺序

(product_routefilters写错了,少写了个s😨)

image-20220428171421729
image-20220428171421729

10、发现错误

1、请求报错
image-20220428173247272
image-20220428173247272
2、直接访问正常
image-20220428173746039
image-20220428173746039
3、修改日志级别

gulimall-gateway模块的application.yml配置文件中修改日志级别

root表示所有包

logging:
  level:
    root: debug
image-20220428173856379
image-20220428173856379
4、查看错误

的确匹配到了product_route但是请求路径变成了 http://192.168.19.1:10000/api/product/category/list/tree

而正确的路径应该为: http://192.168.19.1:10000/product/category/list/tree

没有这个/api,应该是路径重写没有生效

image-20220428173111604
image-20220428173111604

这里的ip为192.168.19.1是因为它用的是VMnet8的ip

cmd输入ipconfig命令可以查看所有ip

ipconfig
image-20220428182149959
image-20220428182149959
5、少写了个s😨

filters写成了filter,少写了个s😨

改过来就行了

image-20220428182642830
image-20220428182642830

11、刷新页面

已经不报错,并且数据也获取到了

image-20220428182729802
image-20220428182729802

控制台也输出数据了,数据在data.data

image-20220428183017017
image-20220428183017017

12、获得数据

使用console.log(data)返回的数据在data.data

console.log(data)
image-20220428184002713
image-20220428184002713

因此使用console.log(data.data.data)即为数据

image-20220428184312865
image-20220428184312865

这样写可以少写一个.data,由console.log(data.data.data)改为console.log(data.data)

then(({data})=>{
   console.log(data.data)
});
image-20220428184442887
image-20220428184442887

把数据赋给data

image-20220428185149029
image-20220428185149029

13、结构出来了

结构出来了但是没有数据

image-20220428184923869
image-20220428184923869

14、修改属性名字段

默认的属性名为label,要改为name

data也改为menu,共3处

image-20220428185623465
image-20220428185623465

15、显示树形结构

image-20220428185858698
image-20220428185858698

16、显示添加和删除

参数说明类型默认值
data展示数据array
node-key每个树节点用来作为唯一标识的属性,整棵树应该是唯一的String
props配置选项object
expand-on-click-node是否在点击节点的时候展开或者收缩节点, 默认值为 true,如果为 false,则只有点箭头图标的时候才会展开或者收缩节点。booleantrue
show-checkbox节点是否可被选择booleanfalse

以下代码均在category.vue文件中修改

1、显示appenddelete

el-tree标签内添加 AppendDelete

<span class="custom-tree-node" slot-scope="{ node, data }">
  <span>{{ node.label }}</span>
  <span>
    <el-button type="text" size="mini" @click="() => append(data)">
      Append
    </el-button>
    <el-button type="text" size="mini" @click="() => remove(node, data)">
      Delete
    </el-button>
  </span>
</span>
2、添加appendremove方法
append(data) {
  console.log("append方法中的data参数:");
  console.log(data);
},
remove(node, data) {
  console.log("remove方法中的node参数:");
  console.log(node);
  console.log("remove方法中的data参数:");
  console.log(data);
},
3、修改添加和删除按钮显示规则

当不是叶子节点时才显示Append

在内容为Append对应的el-button标签内添加属性v-if="data.catLevel<=2",使当前节点深度少于2时才显示

v-if="data.catLevel<=2"

当没有子节点时才显示Delete

在内容为Delete对应的el-button标签内添加属性v-if="data.children.length==0",使当前节点没有子节点时才显示

v-if="data.children.length==0"
4、el-tree标签内添加属性

:expand-on-click-node="false"使用户只有点击箭头图标的时候才会展开或者收缩节点。

:expand-on-click-node="false"
5、显示复选框

el-tree标签内添加show-checkbox属性

show-checkbox
6、添加节点标识

由于catId为唯一id,不会重复,所以把catId作为节点标识

el-tree标签添加属性node-key="data.catId",把catId作为节点标识,加快渲染效率

应该为node-key="catId",后面发现写错了,可以看到点击“摄影摄像”,下方直接打印的"catId"即为想要设置的值,并不在data下

image-20220429205157370
image-20220429205157370

17、完整代码

点击查看完整代码

4.1.4、分类维护实现添加和删除功能

1、添加测试数据

Duplicate entry '1432' for key 'Primary ': 主键重复

Duplicate: 重复,复制

我都没用,它说我主键重复😨

image-20220429090628243
image-20220429090628243

不写cat_id直接添加数据,发现cat_id已经到1433了,应该是导入的sql语句已经用过1432了

(这里是navicat没显示完,1432已经被使用了,死坑😈)

image-20220429091322757
image-20220429091322757

汉字乱码是因为cmd编码为gbk,而mysql设置的编码为utf-8,这里不用管它

image-20220429110456709
image-20220429110456709

2、测试删除方法

image-20220429092432729
image-20220429092432729
POST http://localhost:10000/product/category/delete
Content-Type: application/json

[1433]
image-20220429091751970
image-20220429091751970

可以看到已经成功了("msg"为"success")

刷新一下表,已经没有测试数据了

image-20220429092613049
image-20220429092613049

3、删除功能Controller

categoryService.removeByIds(Arrays.asList(catIds));这一行应该删掉,这里写错了

/**
 * 删除
 * @RequestBody ;获取请求体,必须发送POST请求
 * SpringMVC自动将请求体的数据(json) ,转为对应的对象
 */
@RequestMapping("/delete")
    public R delete(@RequestBody Long[] catIds){
    categoryService.removeByIds(Arrays.asList(catIds));  //这一行应该删掉

    //检查当前删除的菜单是否被别的地方引用
    //categoryService.removeByIds(Arrays.asList(catIds));

    //批量删除
    categoryService.removeMenuByIds(Arrays.asList(catIds));


    return R.ok();
}
image-20220429100222561
image-20220429100222561

4、删除功能Service接口

在Controller的调用该方法的地方使用alt+enter快捷键可以快速转到CategoryService接口,并生成该方法

image-20220429093531976
image-20220429093531976

5、实现批量删除方法

在接口类的左侧点击下箭头可以迅速跳转到实现类,使用alt+enter快捷将添加未实现的方法

先需要检查,由于不知道什么业务会引用菜单,因此先加个//TODO做个标记(由于生成的时候里面有很多//TODO所以待办事项看起来有很多)

(当然也可以使用IDEA中的Favorites,这个感觉很好用)

然后再使用逻辑删除来批量删除数据

@Override
public void removeMenuByIds(List<Long> asList) {
    //TODO 检查当前删除的菜单是否被别的地方引用

    //逻辑删除(show_status作为标志位,置为0表示删除)
    baseMapper.deleteBatchIds(asList);
}
image-20220429095942397
image-20220429095942397

6、配置逻辑删除

1、添加配置

配置com.baomidou.mybatisplus.core.config.GlobalConfig$DbConfig

(这个配置正好和需求是反的)

mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: flag # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
image-20220429101346965
image-20220429101346965
2、添加注解

com.atguigu.gulimall.product.entity.CategoryEntity类的showStatus字段添加注解

  • value逻辑未删除除的值
  • delval 逻辑被删除的值
@TableLogic(value = "1",delval = "0")
image-20220429101838137
image-20220429101838137
3、调整日志级别

gulimall-product\src\main\resources\application.yml里面添加配置,调成debug级别

logging:
  level:
    com.atguigu.gulimall: debug

图的gulimall写错了,后来没打印sql语句才发现的

image-20220429102750359
image-20220429102750359

7、测试

1、添加测试数据
image-20220429102907478
image-20220429102907478
2、删除cat_id为1434的数据

重新运行项目,点击运行按钮,发送删除的请求

可以看到已经删除成功了

POST http://localhost:10000/product/category/delete
Content-Type: application/json

[1434]
image-20220429103123471
image-20220429103123471
3、检查是否逻辑删除
1、刷新后,发现直接删除了
image-20220429103338298
image-20220429103338298
2、删除方法

删除CategoryController类的这一行

image-20220429103415229
image-20220429103415229
3、重新测试

重启项目,重新测试发现没有打印sql语句,这个日志配置写错了一个字母😨

logging:
  level:
    com.atguigu.gulimall: debug
image-20220429104837069
image-20220429104837069
4、发现还是直接删除了

需要点击右下角的下一页,显示后面的数据

image-20220429105019814
image-20220429105019814
5、打印的sql语句显示的是逻辑删除
image-20220429105123536
image-20220429105123536
6、使用cmd连接mysql

可以看到其实是逻辑删除,数据还在,而且show_status已经标记为0了(坑爹navicat不一页显示全数据😈)

汉字乱码是因为cmd编码为gbk,而mysql设置的编码为utf-8,这里不用管它

image-20220429105941270
image-20220429105941270
7、右下角可以翻页
image-20220429130754284
image-20220429130754284
8、点击设置可以更改每页显示的行数
image-20220429131007804
image-20220429131007804

4.1.5、分类维护前端发送删除请求

1、封装请求的工具类

封装的发送ajax请求方法在src\utils\httpRequest.js文件内

image-20220429122946232
image-20220429122946232

2、post请求模块

post请求可以复制src\views\modules\sys\role.vue里面的

image-20220429124108364
image-20220429124108364

3、新建用户片段

1、选择用户片段
image-20220429124726677
image-20220429124726677
2、点击以前生成的模板

如果跳过前面的前端部分,在这里可以点击新建全局代码片段文件...,然后复制全部代码模板

image-20220429124827693
image-20220429124827693
3、添加getpost请求模板

在后面添加添加getpost请求模板,输入"httpget"和"httppost"即可出现相应代码片段

get请求模板

"http-get 请求": {
	"prefix": "httpget",
	"body": [
		"this.\\$http({",
		"url: this.\\$http.adornUrl(''),",
		"method: 'get',",
		"params: this.\\$http.adornParams({})",
		"}).then(({data}) => {",
		"})"
	],
	"description": "httpGET 请求"
}

post请求模板

"http-post 请求": {
	"prefix": "httppost",
	"body": [
		"this.\\$http({",
		"url: this.\\$http.adornUrl(''),",
		"method: 'post',",
		"data: this.\\$http.adornData(data, false)",
		"}).then(({ data }) => { });"
	],
	"description": "httpPOST 请求"
}

点击查看完整代码模板

image-20220429124905747
image-20220429124905747

4、编写删除代码

remove(node, data) {
  var ids = [data.catId];
  this.$http({
  url: this.$http.adornUrl('/product/category/delete'),
  method: 'post',
  data: this.$http.adornData(ids, false)
  }).then(({ data }) => {
    console.log("删除成功...")
    //重新发送请求,更新数据
    this.getMenus();
    });
},
image-20220429193841630
image-20220429193841630

5、添加测试数据

image-20220429193328389
image-20220429193328389

6、点击删除

image-20220429194221023
image-20220429194221023

可以看到,已经显示删除成功了

image-20220429194316686
image-20220429194316686

7、查看结果

可以看到测试数据5show_status字段已经为0了(已经逻辑删除了)

image-20220429194442816
image-20220429194442816

8、体验优化

1、添加删除提示框

以下代码为模板 组件 | Elementopen in new window

this.$confirm("此操作将永久删除该文件, 是否继续?", "提示", {
  confirmButtonText: "确定",
  cancelButtonText: "取消",
  type: "warning",
})
  .then(() => {
    this.$message({
      type: "success",
      message: "删除成功!",
    });
  })
  .catch(() => {
    this.$message({
      type: "info",
      message: "已取消删除",
    });
  });

添加删除提示框

this.$confirm(`是否删除【${data.name}】菜单`, "提示", {
  confirmButtonText: "确定",
  cancelButtonText: "取消",
  type: "warning",
})
  .then(() => {
    var ids = [data.catId];
    this.$http({
      url: this.$http.adornUrl("/product/category/delete"),
      method: "post",
      data: this.$http.adornData(ids, false),
    }).then(({ datas }) => {
      this.$message({
        type: "success",
        message: `${data.name}】删除成功`,
      });
      //重新发送请求,更新数据
      this.getMenus();
    });
  })
  .catch(() => {
    this.$message({
      type: "info",
      message: "已取消删除",
    });
  });
2、删除后默认展开其父节点

default-expanded-keys: 默认展开的节点的 key 的数组

el-tree标签添加属性:default-expanded-keys="expandedKey"

data的中return语句里添加 expandedKey: [],

remove方法添加 this.expandedKey = [node.parent.data.catId];

1、发现并没有展开

测试数据5show_status字段重新置为1后再删除,发现并没有展开

image-20220429203815538
image-20220429203815538
2、修改node-key

node-key="data.catId"改为node-key="catId"

image-20220429204100137
image-20220429204100137

可以看到点击“摄影摄像”,下方直接打印的"catId"即为想要设置的值,并不在data下

image-20220429205411898
image-20220429205411898

9、完整代码

点击查看完整代码

4.1.6、分类维护新增功能

1、前端发送新增请求

1、添加页面

template标签的div里面添加页面

<el-dialog title="提示" :visible.sync="dialogVisible" width="30%">
  <el-form :model="category">
    <el-form-item label="分类名称">
      <el-input v-model="category.name" autocomplete="off"></el-input>
    </el-form-item>
  </el-form>
  <span slot="footer" class="dialog-footer">
    <el-button @click="dialogVisible = false">取 消</el-button>
    <el-button type="primary" @click="addCatagory">确 定</el-button>
  </span>
</el-dialog>
2、添加数据字段

datareturn语句中添加 category: { name: "" },

3、添加提交方法
addCatagory(){
  this.dialogVisible = false;
  console.log("catagory中的数据:");
  console.log(this.category);
}
4、点击append显示对话框

append方法中添加this.dialogVisible = true;

5、测试

点击"append"按钮,输入分类名称,点击确定,可以发现category中的数据有name: 'hhh'

image-20220429213147531
image-20220429213147531
6、修改category数据

而后端需要的由以下五个数据

image-20220429213643518
image-20220429213643518

修改data数据下retrun里面的category

category: { name: "", parentCid: 0, catLevel: 0, showStatus: 1, sort: 0 },
7、修改append方法

append方法中添加以下代码

//<点击append按钮的节点>为<要添加的节点>的父节点
this.category.parentCid = data.catId;
//<要添加的节点>的父节点的层级为<点击append按钮的节点>+1
//data.catLevel*1 可以将 String类型的 data.catLevel 转为 int 类型
this.category.catLevel = data.catLevel * 1 + 1;
8、测试category携带的参数

可以看到这些数据都带上了

image-20220429215054133
image-20220429215054133
9、向后端发送新增请求

addCatagory方法内添加向后端发送新增请求的代码(输入httppost会生成用户代码片段)

addCatagory() {
  this.dialogVisible = false;

  this.$http({
    url: this.$http.adornUrl("/product/category/save"),
    method: "post",
    data: this.$http.adornData(this.category, false),
  }).then(({ data }) => {
    this.$message({
      type: "success",
      message: `添加成功`,
    });
    //重新获取数据
    this.getMenus();
    //默认展开的菜单节点
    this.expandedKey = [this.category.parentCid];
  });
},
10、添加菜单

点击"append"按钮,输入分类名称,点击确定,可以发现已经有数据了

image-20220429221202449
image-20220429221202449
11、完整代码

点击查看完整代码

4.1.7、分类维护增删查改

1、添加修改按钮

删除按钮下添加修改按钮

<el-button type="text" size="mini" @click="() => edit(data)">
  Edit
</el-button>

2、添加数据

添加数据字段,用来标识当前对话框是添加还是删除

//对话框的类型(添加或删除)
dialogType: "",
//对话框的标题(添加分类或删除分类)
dialogTitle: "",

3、添加catagory数据字段

添加 catId: null,字段

category: {
  name: "",
  parentCid: 0,
  catLevel: 0,
  showStatus: 1,
  sort: 0,
  catId: null,
},

4、编写edit方法

edit(data) {
  console.log("修改按钮...", data);

  this.category.name = data.name;
  this.category.catId = data.catId;
  this.dialogType = "edit";
  this.dialogTitle = "修改分类";
  //打开添加或修改的对话框
  this.dialogVisible = true;
},

5、修改append方法

append(data) {
  console.log("append方法中的data参数:", data);

  this.dialogType = "append";
  this.dialogTitle = "添加分类";
  this.dialogVisible = true;
  //<点击append按钮的节点>为<要添加的节点>的父节点
  this.category.parentCid = data.catId;
  //<要添加的节点>的父节点的层级为<点击append按钮的节点>+1
  //data.catLevel*1 可以将 String类型的 data.catLevel 转为 int 类型
  this.category.catLevel = data.catLevel * 1 + 1;
},

6、修改确定按钮绑定的事件方法

修改确定按钮点击事件调用的方法为submitData

 <el-button type="primary" @click="submitData">确 定</el-button>

7、添加submitData方法

submitData() {
  if (this.dialogType == "append") {
    this.addCatagory();
  } else if (this.dialogType == "edit") {
    this.editCatagory();
  }
},

8、打印修改请求数据

editCatagory() {
  //关闭对话框
  this.dialogVisible = false;
  console.log("修改提交的数据:", this.category);
},
image-20220430180933340
image-20220430180933340

9、对话框内添加标签

el-dialog标签内添加el-form-item标签

<el-form-item label="图标">
  <el-input v-model="category.icon" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="计量单位">
  <el-input v-model="category.productUnit" autocomplete="off" ></el-input>
</el-form-item>

10、发送修改请求

这里的代码写的有问题(后面测试发现数据库已经修改数据了,但是前端没有显示,刷新后才能显示)

editCatagory() {
  console.log("修改提交的数据:", this.category);
  var {catId,name,icon,productUnit} = this.category;
  this.$http({
  url: this.$http.adornUrl('/product/category/update'),
  method: 'post',
  data: this.$http.adornData({catId,name,icon,productUnit}, false)
  }).then(({ data }) => { });
  //关闭对话框
    this.dialogVisible = false;
    //重新获取数据
    this.getMenus();
    //默认展开的菜单节点
    this.expandedKey = [this.category.parentCid];
},

应该写为

editCatagory() {
  console.log("修改提交的数据:", this.category);
  var { catId, name, icon, productUnit } = this.category;
  this.$http({
    url: this.$http.adornUrl("/product/category/update"),
    method: "post",
    data: this.$http.adornData({ catId, name, icon, productUnit }, false),
  }).then(({ data }) => {
    this.$message({
      type: "success",
      message: `添加成功`,
    });
    //关闭对话框
    this.dialogVisible = false;
    //重新获取数据
    this.getMenus();
    //默认展开的菜单节点
    this.expandedKey = [this.category.parentCid];
  });
},

11、修改edit方法,动态获取值

edit(data) {
  console.log("修改按钮...", data);

  this.category.catId = data.catId;
  this.dialogType = "edit";
  this.dialogTitle = "修改分类";
  //打开添加或修改的对话框
  this.dialogVisible = true;
  this.$http({
    url: this.$http.adornUrl(
      `/product/category/info/${this.category.catId}`
    ),
    method: "get",
  }).then(({ data }) => {
    console.log("修改按钮调用后端回显的数据:", data);
    this.category.name = data.data.name;
    this.category.icon = data.data.icon;
    this.category.productUnit = data.data.productUnit;
    //更新父菜单id,发送修改请求后,可以展开父节点
    this.category.parentCid = data.data.parentCid;
  });
},

12、修改append方法

append(data) {
  console.log("append方法中的data参数:", data);
  //清空category数据
  this.clearCategory();
  this.dialogType = "append";
  this.dialogTitle = "添加分类";
  this.dialogVisible = true;
  //<点击append按钮的节点>为<要添加的节点>的父节点
  this.category.parentCid = data.catId;
  //<要添加的节点>的父节点的层级为<点击append按钮的节点>+1
  //data.catLevel*1 可以将 String类型的 data.catLevel 转为 int 类型
  this.category.catLevel = data.catLevel * 1 + 1;
},
clearCategory(){
  this.category ={
    name: "",
    parentCid: 0,
    catLevel: 0,
    showStatus: 1,
    sort: 0,
    icon: "",
    productUnit: "",
    catId: null,
  }
},

13、修改addCatagory方法

addCatagory() {
  console.log("添加提交的数据:", this.category);

  var { name, parentCid, catLevel, showStatus, sort, icon ,productUnit} = this.category;
  this.$http({
    url: this.$http.adornUrl("/product/category/save"),
    method: "post",
    data: this.$http.adornData({ name, parentCid, catLevel, showStatus, sort, icon ,productUnit}, false),
  }).then(({ data }) => {
    this.$message({
      type: "success",
      message: `添加成功`,
    });
    //关闭对话框
    this.dialogVisible = false;
    //重新获取数据
    this.getMenus();
    //默认展开的菜单节点
    this.expandedKey = [this.category.parentCid];
  });
},

14、优化体验,防止误关对话框

1、添加close-on-click-modal属性

el-dialog标签内添加close-on-click-modal属性

使用户只有在点击"×"、"取消"、"确定"才会关闭对话框,防止误点到其他地方导致关闭了对话框

参数说明类型可选值默认值
close-on-click-modal是否可以通过点击 modal 关闭 Dialogbooleantrue
close-on-click-modal="false"
2、控制台显示需要Boolean类型,而给出的是"String"类型
image-20220430204554937
image-20220430204554937
3、使用v-bind属性
:close-on-click-modal="false"
image-20220430204921080
image-20220430204921080
4、控制台不报错了
image-20220430205044369
image-20220430205044369

15、修改后前端不更新数据

修改分类名称后,发现数据库已经修改数据了,但是前端没有显示,刷新后才能显示

16、修改editCatagory方法

editCatagory() {
  console.log("修改提交的数据:", this.category);
  var { catId, name, icon, productUnit } = this.category;
  this.$http({
    url: this.$http.adornUrl("/product/category/update"),
    method: "post",
    data: this.$http.adornData({ catId, name, icon, productUnit }, false),
  }).then(({ data }) => {
    this.$message({
      type: "success",
      message: `添加成功`,
    });
    //关闭对话框
    this.dialogVisible = false;
    //重新获取数据
    this.getMenus();
    //默认展开的菜单节点
    this.expandedKey = [this.category.parentCid];
  });
},

17、完整代码

点击查看完整代码

4.1.8、添加拖拽节点功能

1、添加拖拽功能

参考文档: 组件 | Elementopen in new window

参数说明类型默认值
draggable是否开启拖拽节点功能booleanfalse
allow-drop拖拽时判定目标节点能否被放置。type 参数有三种情况:'prev'、'inner' 和 'next',分别表示放置在目标节点前、插入至目标节点和放置在目标节点后Function(draggingNode, dropNode, type)

el-tree标签添加属性 draggable :allow-drop="allowDrop"

添加数据字段maxLength: 0, 表示拖拽节点及其子节点在整颗树的最大深度

并添加allowDrop方法,判断是否可以拖动

/**
 * 判断该节点是否可以拖拽(节点最大深度不能大于3)
 * @param draggingNode  被拖拽的节点
 * @param dropNode      目标节点
 * @param type  放置在目标节点前:'prev'、插入至目标节点:'inner'、 放置在目标节点后:'next'
 * @return
 */
allowDrop(draggingNode, dropNode, type) {
  console.log("拖拽节点:", draggingNode, dropNode, type);
  //最大深度初始化为该该节点的深度,表示没有子节点
  this.maxLength = draggingNode.data.catLevel;
  this.countNodeLevel(draggingNode.data);
  console.log("拖拽节点后的menu:", this.menu);
  console.log(this.maxLength);
  return true;
},

这里的整棵树是指:被拖动节点和目标节点 及其子节点 和 其所有父节点 组成的树

编写拖动的节点及其子节点在整颗大树下的最深深度(这里的代码有问题)

countNodeLevel(data) {
  var children = data.children;
  if (children != null && children.length > 0) {
    for (let i = 0; i < children.length; i++) {
      if (children[i].catLevel > this.maxLength) {
        children[i].catLevel = this.maxLength;
      }
      //递归查找其子树的子树的最大深度
      this.countNodeLevel(children[i]);
    }
  }
},

2、测试数据1

1、拖拽前
image-20220430233700326
image-20220430233700326
image-20220430233559727
image-20220430233559727
2、拖拽后

(不用管控制台里的next,跟这个没有影响,只是我选的一组数据不是inner而已)

image-20220430234003283
image-20220430234003283

可以发现catLevel改变了

image-20220430234200253
image-20220430234200253

3、测试数据2

1、拖动前
image-20220430235635855
image-20220430235635855
image-20220430235733676
image-20220430235733676
image-20220430235821830
image-20220430235821830
2、拖动后

(不用管控制台里的next,跟这个没有影响,只是我选的一组数据不是inner而已)

image-20220501000013931
image-20220501000013931
image-20220501000137695
image-20220501000137695
image-20220501000306998
image-20220501000306998
3、menu中的数据
image-20220501000725560
image-20220501000725560
image-20220501000839585
image-20220501000839585
image-20220501000923470
image-20220501000923470

可见menu中的数据已经改变了

被拖拽节点的子节点的catLevel都被改为了被拖拽节点catLevel

4、修改countNodeLevel方法

countNodeLevel(data) {
  var children = data.children;
  if (children != null && children.length > 0) {
    for (let i = 0; i < children.length; i++) {
      if (children[i].catLevel > this.maxLength) {
        this.maxLength = children[i].catLevel;
      }
      //递归查找其子树的子树的最大深度
      this.countNodeLevel(children[i]);
    }
  }
},
1、测试数据1

改动后,节点信息显示正常,并且打印了正确的节点深度

image-20220501154304218
image-20220501154304218
image-20220501154457199
image-20220501154457199
image-20220501154606221
image-20220501154606221
2、测试数据2
image-20220501154951313
image-20220501154951313

还是能显示正确的深度

image-20220501155115738
image-20220501155115738
3、测试数据3
image-20220501155748334
image-20220501155748334
image-20220501160239808
image-20220501160239808

经测试可以看到,最长深度的计算没有问题了

5、修改allowDrop方法

这里的整棵树是指:被拖动节点和目标节点 及其子节点 和 其所有父节点 组成的树

/**
 * 判断该节点是否可以拖拽(节点最大深度不能大于3)
 * @param draggingNode  被拖拽的节点
 * @param dropNode      目标节点
 * @param type  放置在目标节点前:'prev'、插入至目标节点:'inner'、 放置在目标节点后:'next'
 * @return
 */
allowDrop(draggingNode, dropNode, type) {
  console.log("拖拽节点:", draggingNode, dropNode, type);
  //最大深度初始化为该该节点的深度,表示没有子节点
  this.maxLength = draggingNode.data.catLevel;
  this.countNodeLevel(draggingNode.data);
  console.log("拖拽节点后的menu:", this.menu);
  console.log(this.maxLength);
  //拖拽节点及其子节点组成的树的最大深度
  let deep = this.maxLength - draggingNode.data.catLevel + 1;
  if(type=="inner"){
    //类型为 inner(表示在目标节点的内部)
    //拖拽后整颗树的最大深度=(目标节点的深度 + 拖拽节点及其子节点组成的树的最大深度)
    return (dropNode.data.catLevel + deep) <= 3;
  }else{
      //类型为 prev 或 next (表示在目标节点的上面或下面,与目标节点同级)
      //拖拽后整颗树的最大深度=(目标节点的父节点的深度 + 拖拽节点及其子节点组成的树的最大深度)
    return (dropNode.parent.level + deep) <= 3;
  }
},

6、完整代码

点击查看完整代码

4.1.9、添加拖拽节点回调函数

事件名称说明回调参数
node-drop拖拽成功完成时触发的事件共四个参数,依次为:被拖拽节点对应的 Node、结束拖拽时最后进入的节点、被拖拽节点的放置位置(before、after、inner)、event

1、el-tree添加属性

el-tree添加@node-drop="handleDrop"属性

2、添加回调方法

handleDrop(draggingNode, dropNode, dropType, ev) {
  console.log("tree drop: ", dropNode.label, dropType);
},

3、查看回调参数的数据

数据和拖动节点的数据差不多

image-20220501233323689
image-20220501233323689

4、编写拖拽成功后触发的事件

/**
 * 拖拽成功后的回调函数,用来更新节点的信息
 * @param draggingNode  被拖拽节点对应的 Node (draggingNode里的数据不会更新)
 * @param dropNode      结束拖拽时最后进入的节点 
 * (dropNode里面的childNodes,parent等会动态更新(dropNode里面的data为数据库获取到的,不会更新))
 * @param type  被拖拽节点的放置位置(before、after、inner)
 * 放置在目标节点前:'prev'、插入至目标节点:'inner'、 放置在目标节点后:'next'
 * @return
 */
handleDrop(draggingNode, dropNode, dropType, ev) {
  console.log("拖拽成功的回调参数: ", draggingNode, dropNode, dropType);
  //当前节点的父节点id
  let pCid = 0;
  //拖动节点后,被拖动节点的新的父节点的所有子节点数组
  let sliblings = null;

  //1、找到父节点id和父节点的子节点
  if (dropType == "inner") {
    //类型为 inner(表示在目标节点的内部)
    //dropNode.data表示的是数据库获取到的data数据
    pCid = dropNode.data.catId;
    //dropNode里的数据(除了.data)表示的是element-ui动态更新的数据
    //这里的dropNode.childNodes是拖动成功后,已经更新的节点的子节点信息
    //拖拽到内部,则dropNode即为其父节点
    sliblings = dropNode.childNodes;
  } else {
    //类型为 prev 或 next (表示在目标节点的上面或下面,与目标节点同级)
    //拖拽到上面或下面,则 目标节点的父节点即为拖拽节点的父节点
    //如果托拖拽到一级菜单(level=1)dropNode.parent.data装的是一级菜单集合(和dropNode.parent.childNodes数据一样),获取到的catId为undefined
    //如果为undefined赋值为0
    pCid = dropNode.parent.data.catId || 0;
    //这里的dropNode.parent为element-ui提供的,是拖拽后的更新过的数据(draggingNode里的数据不会跟新)
    //拖拽到上面或下面,则 目标节点的父节点子节点即为被拖动节点的新的父节点的所有子节点
    sliblings = dropNode.parent.childNodes;
  }

  //2、将子节点更新的数据放到updateNodes中
  this.updateNodes = [];
  for (let i = 0; i < sliblings.length; i++) {
    //如果是正在托拽的节点,需要更新其父id
    if (sliblings[i].data.catId == draggingNode.data.catId) {
      let catLevel = draggingNode.data.catLevel;
      //当前节点的层级发生变化就更新子节点层级
      if (sliblings[i].level != draggingNode.data.catLevel) {
        //其实这个catLevel始终都是等于sliblings[i].level,即更新后的level
        catLevel = sliblings[i].level;
        //更新子节点层级
        this.updateChildNodeLevel(sliblings[i]);
      }
      this.updateNodes.push({catId: sliblings[i].data.catId,sort: i,parentCid: pCid,catLevel: catLevel,
      });
    } else {
      this.updateNodes.push({ catId: sliblings[i].data.catId, sort: i });
    }
  }

  //3、把数据发送给后端
  console.log("拖拽节点成功后,发送给后端的数据:", this.updateNodes);
},
//拖动节点后,更新其子节点的层级
updateChildNodeLevel(node) {
  if (node.childNodes.length > 0) {
    for (let i = 0; i < node.childNodes.length; i++) {
      let catId = node.childNodes[i].data.catId;
      let catLevel = node.childNodes[i].level;
      this.updateNodes.push({ catId: catId, catLevel: catLevel });
      //递归更新子节点层级
      this.updateChildNodeLevel(node.childNodes[i]);
    }
  }
},

5、后端添加批量修改功能

/**
 * 修改节点的树形结构
 */
@RequestMapping("/update/sort")
public R updateSort(@RequestBody CategoryEntity[] category){
    categoryService.updateBatchById(Arrays.asList(category));

    return R.ok();
}

6、handleDrop方法里添加代码

在第3步那里添加向后端发送请求的代码

this.$http({
  url: this.$http.adornUrl("/product/category/update/sort"),
  method: "post",
  data: this.$http.adornData(this.updateNodes, false),
}).then(({ data }) => {
  this.$message({
    type: "success",
    message: `菜单结构修改成功`,
  });
  //重新获取数据
  this.getMenus();
  //默认展开的菜单节点
  this.expandedKey = [pCid];
  this.updateNodes = [];
});

7、测试是否成功修改

修改代码结构后,刷新页面,发现树的结构已正确显示

image-20220502154324423
image-20220502154324423
image-20220502154013655
image-20220502154013655

4.1.10、多次拖拽后一次提交

多次拖拽后一次提交有可能出现其他的用户修改后,当前用户没有及时更新,当前用户覆盖了其他用户数据,但当前用户并不知情

(不能像修改节点信息那样,点击edit向后端发送请求,获取最新数据)

参数说明类型默认值
draggable是否开启拖拽节点功能booleanfalse

1、添加开关

参考element-uiSwitch开关里的文字描述组件 | Elementopen in new window

<el-switch v-model="draggable" active-text="开启拖拽" inactive-text="关闭拖拽">

2、修改el-treedraggable属性

动态绑定draggable的值来确定是否可以拖拽

:draggable="draggable"

3、添加数据字段

默认设置为不可拖拽

//是否可以拖拽
draggable: false,

4、添加批量保存按钮

<el-button v-if="draggable" @click="batchSave">批量保存</el-button>

5、添加batchSave方法

删除handleDrop方法里向后端发送请求的代码,把向后端发送请求的代码写到batchSave

由于batchSave里面没有pCid,因此需要在data里添加pCid : 0,

handleDrop方法里,计算完pCid把它赋给this.pCid

batchSave() {
  this.$http({
    url: this.$http.adornUrl("/product/category/update/sort"),
    method: "post",
    data: this.$http.adornData(this.updateNodes, false),
  }).then(({ data }) => {
    this.$message({
      type: "success",
      message: `菜单结构修改成功`,
    });
    //重新获取数据
    this.getMenus();
    //默认展开的菜单节点
    this.expandedKey = [this.pCid];
    this.updateNodes = [];
    //重新赋给pCid默认值
    //这一步可以可以省略,但前端使用变量后重新赋初值是一个良好的习惯
    this.pCid = 0;
  });
},

6、使用动态更新的层级

由于加入了批量提交功能,当节点拖拽多次后才提交给后端

因此如果获取data里的节点的层级节点的父节点id会导致获取到了没有更新的错误数据

(而以前获取data里的数据后立即向后端发送请求,重新获取新的已经更新过的数据,因此不会发生错误)

(其实不更新节点的层级也可以,因为错误的节点层级只用来计算拖动节点的局部树的深度,push的都是动态更新的层级,

而且恰巧被拖动节点的局部树都是使用未跟新的层级,因此计算的深度也是正确的结果,由于这些节点的层级并不用来做其他用途,

其实使用旧的层级来确定是否允许拖动并没有问题,但return dropNode.parent.level + deep <= 3;必须使用动态更新的层级,

因为假设一个节点的层级由2变为了1,其可以在里面再放2层,如果使用未更新的层级则会显示不可以放,但其实是可以的,

return dropNode.data.catLevel + deep <= 3;不用改,因为目标节点的层级并不会改变,只有被拖拽节点及其子节点层级会改变

总结:被拖动节点的局部树的层级并不影响其深度,可以不用修改,当拖拽到prevnext后,目标节点的层级并不会改变不用修改,但return dropNode.parent.level + deep <= 3;必须使用动态更新的层级)

(可以使用不动态更新的层级是建立在被拖动节点其子节点的结构(主要是深度)没有改变的情况下,类型为prevnext其父节点有可能是已经被拖拽的节点、类型为inner目标节点也有可能为已经被拖拽过的节点。其层级可能已经改变,因此也必须使用动态更新的数据,被拖动节点的子节点也需要使用动态更新的子节点,层级也需要使用动态更新的层级)

例子:

  1. 一个节点的层级由2变为了1,其可以在里面再放2层,如果使用未更新的层级则会显示不可以放,但其实是可以的
  2. 一个节点的层级由3变为了2,其可以在里面再放1层,如果使用未更新的层级则会显示不可以放,但其实是可以的
  3. 一个节点的层级由2变为了1,把一个深度为2的节点放到这个节点的子节点的prevnext,如果使用未更新的层级则会显示不可以放,但其实是可以的

所以应使用element-ui提供的动态更新的层级父节点id

(以前的代码使用的就是element-ui提供的动态更新的父节点id,因此不用修改了)

修改allowDrop方法里的代码,使用被拖拽节点的element-ui提供的动态更新的层级

被拖拽节点如果被再次拖拽draggingNode.leveldraggingNode.data.catLevel有可能会不一样

把这段代码

this.maxLength = draggingNode.data.catLevel;

修改改为

this.maxLength = draggingNode.level;

把这段代码

let deep = this.maxLength - draggingNode.data.catLevel + 1;

修改改为

let deep = this.maxLength - draggingNode.level + 1;

allowDrop方法里的这一行不用修改,因为目标节点的层级并不会改变,只有被拖拽节点及其子节点层级会改变

把这段代码

return dropNode.data.catLevel + deep <= 3;

可以必须修改为~~(也可以不修改)~~

return dropNode.level + deep <= 3;

子节点也使用动态更新的层级,参数不传被拖拽节点的data字段,而传被拖拽节点本身

动态获取其子节点和子节点的层级

把这段代码

this.countNodeLevel(draggingNode.data);

修改该为

this.countNodeLevel(draggingNode);

把这段代码

countNodeLevel(data) {
  var children = data.children;
  if (children != null && children.length > 0) {
    for (let i = 0; i < children.length; i++) {
      if (children[i].catLevel > this.maxLength) {
        this.maxLength = children[i].catLevel;
      }
      //递归查找其子树的子树的最大深度
      this.countNodeLevel(children[i]);
    }
  }
},

修改该为

countNodeLevel(node) {
  var children = node.childNodes;
  if (children != null && children.length > 0) {
    for (let i = 0; i < children.length; i++) {
      if (children[i].level > this.maxLength) {
        this.maxLength = children[i].level;
      }
      //递归查找其子树的子树的最大深度
      this.countNodeLevel(children[i]);
    }
  }
},

7、测试

开拖拽后,连续拖拽以下两次,发现允许拖拽,证明代码没问题

初始结构

image-20220502172234731
image-20220502172234731

2.1拖拽到一级菜单

image-20220502182545906
image-20220502182545906

3.1拖拽到2.1下,发现是可以的,如果没有使用动态更新的数据,会显示不可以拖拽

image-20220502192350288
image-20220502192350288

8、完整代码

1、不使用动态更新的层级之前的错误代码

点击查看完整代码

2、使用动态更新的层级之后的正确的代码

点击查看完整代码

4.1.11、优化体验

1、提交后展开多个节点

拖拽节点并批量保存成功后展开的节点只有一个,即展开的是最后一次拖拽的父节点

因此可以将pCid: 0,改为pCid: [],

this.pCid = pCid;改为this.pCid.push(pCid);

this.pCid = 0;改为this.pCid = [];

这样每拖拽节点成功后,都能保存其父节点id,批量保存后可以显示所有拖拽节点成功的节点的父节点id

(但没拖拽成功但展开的节点在批量保存后不能再次展开)

2、以前展开的节点,提交后依然展开

事件名称说明回调参数
node-expand节点被展开时触发的事件共三个参数,依次为:传递给 data 属性的数组中该节点所对应的对象、节点对应的 Node、节点组件本身
node-collapse节点被关闭时触发的事件共三个参数,依次为:传递给 data 属性的数组中该节点所对应的对象、节点对应的 Node、节点组件本身

el-tree添加属性

@node-expand="nodeExpand"
@node-collapse="nodeCollapse"

添加方法:(现在 this.pCid表示的是提交拖拽结果时需要展开的节点)

//节点被展开时触发的事件
nodeExpand(data, node, ele) {
  console.log("节点被展开时触发的事件:",data, node, ele);
  this.pCid.push(data.catId)
},
//节点被关闭时触发的事件
nodeCollapse(data, node, ele) {
  console.log("节点被关闭时触发的事件",data, node, ele);
    this.pCid.pop(data.catId)
},

同理,可以使编辑、修改、添加,只要是重新获取数据(调用this.getMenus();)导致展开的节点被折叠的都可以做相似的操作

也可以另辟蹊径:当展开节点时就把它加入到默认展开的节点的 key 的数组,当折叠节点时就把它从默认展开的节点的 key 的数组里删除

这样重新获取数据后,以前展开的节点都会被展开

1、使用该方法前

点击查看使用该方法前的代码

2、使用该方法后

点击查看使用该方法后的代码

3、后端报错

当开启拖拽节点后,不拖拽节点,直接批量保存后页面没反应

打开控制台报500的错误,证明是后端的错误

image-20220502230717102
image-20220502230717102

打开后端发现,实体列表不能为空

image-20220502230934587
image-20220502230934587

后端加个判断就行了

/**
 * 修改节点的树形结构
 */
@RequestMapping("/update/sort")
public R updateSort(@RequestBody CategoryEntity[] category){
    if (category!=null && category.length>0) {
        categoryService.updateBatchById(Arrays.asList(category));
    }
    return R.ok();
}
image-20220502231359263
image-20220502231359263

重启项目后,再次不拖拽节点,直接批量保存不报错了

image-20220502231603498
image-20220502231603498

3、开启拖拽功能后,不能添加、修改、删除节点

appendremoveedit方法开头都加上如下类似代码(只需修改一下message)

if(this.draggable){
  this.$message({
    type: 'warning',
    message: '开启拖拽后不可以删除菜单'
  });
  return;
}

4、批量提交后关闭拖拽功能

batchSave最后添加如下代码

//关闭拖拽功能
 this.draggable = false;

5、完整代码

点击查看完整代码

4.1.12、批量删除

1、添加按钮

参考element-uiButton按钮里的基本用法中的危险按钮组件 | Elementopen in new window

在批量保存右边添加批量删除按钮(代码写在批量保存下面,el-tree上面)

<el-button type="danger" @click="batchDelete">批量删除</el-button>

2、添加el-tree属性

el-tree标签里添加ref="menuTree"属性,标记这个组件的名字

ref="menuTree"

3、添加批量删除方法

方法名说明参数
getCheckedNodes若节点可被选择(即 show-checkboxtrue),则返回目前被选中的节点所组成的数组(leafOnly, includeHalfChecked) 接收两个 boolean 类型的参数,1. 是否只是叶子节点,默认值为 false 2. 是否包含半选节点,默认值为 false
getCheckedKeys若节点可被选择(即 show-checkboxtrue),则返回目前被选中的节点的 key 所组成的数组(leafOnly) 接收一个 boolean 类型的参数,若为 true 则仅返回被选中的叶子节点的 keys,默认值为 false

this.$refs会获取当前vue文件所有组件

this.$refs.menuTree获取当前vue文件所有标签中ref属性为menuTreecomponents{}里面为menuTree的组件

然后再调用该组件的方法

//批量删除
batchDelete() {
  let catIds = this.$refs.menuTree.getCheckedKeys();
  console.log("批量删除的id:", JSON.parse(JSON.stringify(catIds)));
  let names = this.$refs.menuTree
    .getCheckedNodes()
    .map((node) => node.name);
  console.log("批量删除的名字:", JSON.parse(JSON.stringify(names)));
  this.$confirm(`是否删除【${names}】这些菜单`, "提示", {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    type: "warning",
  })
    .then(() => {
      this.$http({
        url: this.$http.adornUrl("/product/category/delete"),
        method: "post",
        data: this.$http.adornData(catIds, false),
      }).then(({ datas }) => {
        this.$message({
          type: "success",
          message: `批量删除成功`,
        });
        //重新发送请求,更新数据
        this.getMenus();
      });
    })
    .catch(() => {
      this.$message({
        type: "info",
        message: "已取消批量删除",
      });
    });
},

3种方式可以获取到选中节点的id

let catIds = this.$refs.menuTree.getCheckedKeys();
let catIds = this.$refs.menuTree.getCheckedNodes().map(node=>node.catId);
let catIds = [];
this.$refs.menuTree.getCheckedNodes().forEach(node => { catIds.push(node.catId)});

4、体验优化

当没有选中节点的时候,不出现批量删除按钮

1、添加数据字段

data里添加字段,用于判断是否显示批量删除按钮

 canBatchDeletion: false,
2、添加v-if属性
<el-button v-if="canBatchDeletion" type="danger" @click="batchDelete">批量删除</el-button>
3、添加事件
事件名称说明回调参数
check-change节点选中状态发生变化时的回调共三个参数,依次为:传递给 data 属性的数组中该节点所对应的对象、节点本身是否被选中、节点的子树中是否有被选中的节点

el-tree标签内添加属性

@check-change="nodeCheckChange"
4、编写方法
nodeCheckChange(data, checked, indeterminate){
  let catIds = this.$refs.menuTree.getCheckedKeys();
  console.log(catIds)
  //如果catIds长度为0就不显示批量删除按钮
  this.canBatchDeletion = !(catIds.length==0);
}
5、发现页面有抖动现象
4.1.7.6.4.5
4.1.7.6.4.5

把这一行加个div设个高度就可以解决

<div style="height: 50px; line-height: 50px">
  <el-switch
    v-model="draggable"
    active-text="开启拖拽"
    inactive-text="关闭拖拽"
  >
  </el-switch>
  <!-- draggable为true时,开启了拖拽功能,批量保存才显示 -->
  <el-button v-if="draggable" @click="batchSave">批量保存</el-button>
  <el-button v-if="canBatchDeletion" type="danger" @click="batchDelete">批量删除</el-button>
</div>
4.1.7.6.4.5.2
4.1.7.6.4.5.2
6、点击批量删除后,隐藏该按钮

batchDelete方法里面 重新发送请求,更新数据this.getMenus();的这行下面添加这行代码

//不显示批量删除按钮
this.canBatchDeletion = false;
7、完整代码

点击查看完整代码

4.2、商品服务-API-品牌管理

4.2.1、添加品牌管理前端页面

1、复制brand.vue文件

复制以前逆向生成的gulimall-product项目的brand.vue文件(在\main\resources\src\views\modules\product文件夹下)

📌如果已经删除了可以查看1.10.10节的笔记,重新逆向生成gulimall-product项目

image-20220503114854616
image-20220503114854616

2、复制到product文件夹下

复制到前端项目的renren-fast-vue\src\views\modules\product文件夹下

image-20220503115203222
image-20220503115203222
📌快速打开product文件夹

选中product-->右键-->在文件资源管理器中显示

image-20220503120339993
image-20220503120339993

3、删除[0-不显示;1-显示]

image-20220503120956488
image-20220503120956488

4、报了个错

This relative module was not found: 未找到此相关模块

image-20220503163746900
image-20220503163746900

就是找不到brand-add-or-update,把brand-add-or-update.vue文件也复制进去就行了

image-20220503170253394
image-20220503170253394

5、添加品牌管理路由

image-20220503165715370
image-20220503165715370

6、已成功显示

image-20220503170705172
image-20220503170705172

4.2.2、为显示状态所在的列添加按钮

1、添加按钮

参考element-uiTable表格的自定义列模板:组件 | Elementopen in new window

通过 Scoped slot 可以获取到 row, column, $index 和 store(table 内部的状态管理)的数据

<template slot-scope="scope">
	<i class="el-icon-time"></i>
	<span style="margin-left: 10px">{{ scope.row.date }}</span>
</template>

参考element-uiSwitch开关里的基本用法组件 | Elementopen in new window

<el-switch
  v-model="value"
  active-color="#13ce66"
  inactive-color="#ff4949">
</el-switch>

因此在label为显示状态的列里面,添加如下模板

scope.row获取当前行数据,scope.row.showStatus获取当前行数据中键为showStatus的值

<template slot-scope="scope">
  <el-switch
    v-model="scope.row.showStatus"
    active-color="#13ce66"
    inactive-color="#ff4949"
  ></el-switch>
</template>
image-20220503173256805
image-20220503173256805

效果就出来了

image-20220503173852450
image-20220503173852450

2、新建批量删除按钮不显示

由于使用了v-ifisAuth('product:brand:save')true(没有权限)时,就不显示按钮

image-20220503175051236
image-20220503175051236

src\utils\index.jsisAuth方法的这一行删掉

image-20220503174636295
image-20220503174636295

直接返回true就行了

image-20220503174745909
image-20220503174745909

可以看到按钮已经显示了

image-20220503174810653
image-20220503174810653

3、修改对话框

点击新建按钮的弹出框写在brand-add-or-update.vue文件里,在这里引入的

image-20220503175515419
image-20220503175515419

删掉这些,还是使用按钮的方式

image-20220503175845685
image-20220503175845685

v-model绑定的数据还是和删除掉的el-input标签双向绑定的数据一样,是dataForm.showStatus不变

<template slot-scope="scope">
  <el-switch
    v-model="dataForm.showStatus"
    active-color="#13ce66"
    inactive-color="#ff4949"
  ></el-switch>
</template>
image-20220503180026375
image-20220503180026375

点击新建后弹出的显示状态已经是按钮了

image-20220503180641548
image-20220503180641548

4、修改表单宽度

表单的宽度有点窄

image-20220503180824164
image-20220503180824164

这里改为140px

image-20220503180933818
image-20220503180933818

这样就好看多了

image-20220503181014800
image-20220503181014800

5、绑定监听事件

事件名称说明回调参数
changeswitch 状态发生变化时的回调函数新状态的值
1、为按钮添加监听事件
<template slot-scope="scope">
  <el-switch
    v-model="scope.row.showStatus"
    active-color="#13ce66"
    inactive-color="#ff4949"
    @change="updateBrandStatus"
  ></el-switch>
</template>
image-20220504155713996
image-20220504155713996
2、编写回调方法
//显示状态按钮的回调函数
updateBrandStatus(status){
    console.log("显示状态:"+status);
},
3、只显示truefalse

只显示truefalse,并不能确定是哪个按钮的事件

image-20220504160833252
image-20220504160833252
4、修改调用方法的参数
<template slot-scope="scope">
  <el-switch
    v-model="scope.row.showStatus"
    active-color="#13ce66"
    inactive-color="#ff4949"
    @change="updateBrandStatus(scope.row)"
  ></el-switch>
</template>
5、修改回调方法
//显示状态按钮的回调函数
updateBrandStatus(data){
    console.log("显示状态:",data);
}

可以看到,已经可以获取显示状态对应按钮那一行的信息和按钮状态了

image-20220504162444824
image-20220504162444824
6、测试后端接口

点击BrandControllerupdate方法左侧的小图标,进入IDEA自带的HTTP Client调试工具进行测试

###
POST http://localhost:10000/product/brand/update
Content-Type: application/json

{"brandId": 1,"logo": "xxx"}
image-20220504163757244
image-20220504163757244

可以看到logo列已更新

image-20220504164033831
image-20220504164033831

7、修改updateBrandStatus方法

//显示状态按钮的回调函数
updateBrandStatus(data){
    console.log("显示状态:",data);
    let {brandId,showStatus} = data;
    this.$http({
    url: this.$http.adornUrl('/product/brand/update'),
    method: 'post',
    data: this.$http.adornData({brandId,showStatus}, false)
    }).then(({ data }) => {
      this.$message({
        type: "success",
        message: "状态更新成功"
      })
      });
}
8、控制台报错
image-20220504165445351
image-20220504165445351

查看请求体的内容

image-20220504165604410
image-20220504165604410

后端也报了警告

2022-05-04 16:55:41.980  WARN 18564 --- [io-10000-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize instance of `java.lang.Integer` out of VALUE_TRUE token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.lang.Integer` out of VALUE_TRUE token
 at [Source: (PushbackInputStream); line: 1, column: 27] (through reference chain: com.atguigu.gulimall.product.entity.BrandEntity["showStatus"])]

不能把showStatus的值转为java.lang.Integer类型

image-20220504165704201
image-20220504165704201

数据库是使用10来表示显示不显示,而不是truefalse

image-20220504170055609
image-20220504170055609
9、修改showStatus值的类型

方法一:

参数说明类型默认值
active-valueswitch 打开时的值boolean / string / numbertrue
nactive-valueswitch 关闭时的值boolean / string / numberfalse

指定switch 打开或关闭的值

<template slot-scope="scope">
  <el-switch
    v-model="scope.row.showStatus"
    active-color="#13ce66"
    inactive-color="#ff4949"
    @change="updateBrandStatus(scope.row)"
    :active-value="1"
    :inactive-value="0"
  ></el-switch>
</template>

方法二:

三元运算符判断(这种方法不行

//显示状态按钮的回调函数
updateBrandStatus(data){
    console.log("显示状态:",data);
    let {brandId,showStatus} = data;
    this.$http({
    url: this.$http.adornUrl('/product/brand/update'),
    method: 'post',
    data: this.$http.adornData({brandId,showStatus:showStatus?1:0}, false)
    }).then(({ data }) => {
      this.$message({
        type: "success",
        message: "状态更新成功"
      })
      });
}

这种方法不可以初始化正确的开关状态(默认使用truefalse来表示开关状态,由于这种方法返回10,所以只能默认显示关闭状态)

1、先让显示状态开关打开
image-20220504182321591
image-20220504182321591
2、当刷新页面后,显示状态还是关闭
image-20220504182757184
image-20220504182757184
3、但数据库其实已更新为1了
image-20220504183048856
image-20220504183048856
10、查看效果

使用方法一刷新页面后,显示状态依然可以正确展示,但方法二不行

image-20220504171102995
image-20220504171102995

同理修改brand-add-or-update.vue文件里el-switch标签里的这两个属性

<template slot-scope="scope">
  <el-switch
    v-model="dataForm.showStatus"
    active-color="#13ce66"
    inactive-color="#ff4949"
    :active-value="1"
    :inactive-value="0"
  ></el-switch>
</template>

4.2.3、对象存储OOS

对象存储服务(Object Storage Service,OSS)是一种海量、安全、低成本、高可靠的云存储 服务,适合存放任意类型的文件。容量和处理能力弹性扩展,多种存储类型供选择,全面优化存储成本。

常用的文件存储方式:

image-20220504231711548
image-20220504231711548

本项目采用阿里云对象存储服务

阿里云对象存储-普通上传方式

image-20220721202146252

阿里云对象存储-服务端签名后直传

image-20220721202225102

1、实名认证

1、登陆后,点击控制台
image-20220504212358467
image-20220504212358467
2、点击实名认证按钮

鼠标经过小人头像,在弹出的框中点击的实名认证,按步骤操作即可

image-20220504212450484
image-20220504212450484

2、开启OSS对象存储服务

1、点击左上角的三条横杠
image-20220504212845267
image-20220504212845267
2、点击对象存储OSS
image-20220504212952128
image-20220504212952128
3、点击立即开通
image-20220504213041621
image-20220504213041621
4、勾选协议,然后点击立即开通
image-20220504213212922
image-20220504213212922
5、点击管理控制台
image-20220504213312344
image-20220504213312344
6、查看帮助文档
image-20220504223019548
image-20220504223019548

3、创建Bucket

参考链接:https://help.aliyun.com/document_detail/31947.html

中文英文说明
存储空间Bucket存储空间是您用于存储对象(Object)的容器,所有的对象都必须隶属于某个存储空间。
对象/文件Object对象是 OSS 存储数据的基本单元,也被称为OSS的文件。对象由元信息(Object Meta)、用户数据(Data)和文件名(Key)组成。对象由存储空间内部唯一的Key来标识。
地域Region地域表示 OSS 的数据中心所在物理位置。您可以根据费用、请求来源等综合选择数据存储的地域。详情请查看OSS已经开通的Regionopen in new window
访问域名EndpointEndpoint 表示OSS对外服务的访问域名。OSS以HTTP RESTful API的形式对外提供服务,当访问不同地域的时候,需要不同的域名。通过内网和外网访问同一个地域所需要的域名也是不同的。具体的内容请参见各个Region对应的Endpointopen in new window
访问密钥AccessKeyAccessKey,简称 AK,指的是访问身份验证中用到的AccessKeyId 和AccessKeySecret。OSS通过使用AccessKeyId 和AccessKeySecret对称加密的方法来验证某个请求的发送者身份。AccessKeyId用于标识用户,AccessKeySecret是用户用于加密签名字符串和OSS用来验证签名字符串的密钥,其中AccessKeySecret 必须保密。
image-20220504223548831
image-20220504223548831
1、点击创建Bucket
image-20220504223758657
image-20220504223758657
2、创建Bucket

随便输个Bucket名称

image-20220504225120652
image-20220504225120652

🚀读写权限的对话框中要选择"继续修改"

image-20220504225234319
image-20220504225234319
3、点击上传文件
image-20220504225642860
image-20220504225642860

4、使用Java代码上传

1、上传策略

用户先向应用服务器获取上传策略,应用服务器根据服务器内部存储的阿里云的账号和密码生成一个防伪签名,防伪签名包括授权令牌、阿里云哪个地址、哪个位值等信息,前端带着防伪签名和要上传的文件交给阿里云,阿里云可以验证这些签名是否正确。

image-20220504230801395
image-20220504230801395
2、查看文档

参考文档:https://help.aliyun.com/document_detail/195870.html

image-20220504232317468
image-20220504232317468
image-20220504232339802
image-20220504232339802
3、操作步骤

参考文档:https://help.aliyun.com/document_detail/32006.html

1、添加依赖

gulimall-product模块内添加阿里云的oss依赖

<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>3.5.0</version>
</dependency>
image-20220504233153801
image-20220504233153801
2、添加测试方法
/**
 * 对象存储OSS测试
 * @throws FileNotFoundException
 */
@Test
public void testUpload() throws FileNotFoundException {
   // Endpoint以杭州为例,其它Region请按实际情况填写。
   String endpoint = "oss-cn-beijing.aliyuncs.com";
   // 云账号AccessKey有所有API访问权限,建议遵循阿里云安全最佳实践,创建并使用RAM子账号进行API访问或日常运维,请登录 https://ram.console.aliyun.com 创建。
   String accessKeyId = "LTAl4FwfjSycd1APnuG9bjj";
   String accessKeySecret = "O6xaxyiWfSlitcOkSuK27ju4hXT5HI";

   // 创建OSSClient实例。
   OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

   // 上传文件流。
   InputStream inputStream = new FileInputStream("C:\\1.png");

   ossClient.putObject("gulimall-hello", "1.png", inputStream);

   // 关闭OSSClient。
   ossClient.shutdown();
   System.out.println("上传成功。。。");
}
3、修改endpoint

点击概览,查看Endpoint

image-20220504235015158
image-20220504235015158
4、添加AccessKey

1、点击AccessKey管理

image-20220504235453599
image-20220504235453599

2、点击开始使用子用户AccessKey

image-20220504235549106
image-20220504235549106

3、点击创建用户

image-20220504235623910
image-20220504235623910

4、创建用户

image-20220505000109142
image-20220505000109142

5、复制AccessKey IDAccessKey Secret

AccessKey IDAccessKey Secret只会出现一次,关掉这个页面就再也看不到了,因此测试成功之前不要关闭该页面

image-20220505001844577
image-20220505001844577

6、为这个AccessKey添加权限

image-20220505000806248
image-20220505000806248

7、选择管理对象存储服务(OSS)权限

image-20220505001118061
image-20220505001118061

8、点击确定

image-20220505001200826
image-20220505001200826

9、粘贴AccessKey IDAccessKey Secret

AccessKey IDAccessKey Secret粘贴到accessKeyIdaccessKeySecret

5、修改inputStream里面的文件的位置

点击文件-->右键-->属性-->安全-->复制对象名称

把复制的文件路径粘贴到FileInputStream里面

image-20220505161036130
image-20220505161036130
6、修改putObject方法的参数
ossClient.putObject("gulimall-clouds", "1.png", inputStream);

第一个参数为创建Bucket时的名称

第二个为上传的文件起一个文件名,可以通过阿里云提供的域名+该文件名访问这个图片

第三个不用改,为刚才图片的IO流

image-20220505161839620
image-20220505161839620
7、测试

可以看到,测试已经通过

image-20220505162104781
image-20220505162104781

阿里云上已经看到图片了

复制这个URL即可直接访问

image-20220505162407468
image-20220505162407468

5、OSS整合Spring Boot

参考文档:aliyun-spring-boot/README-zh.md at master · alibaba/aliyun-spring-boot (github.com)open in new window

(先删掉刚才gulimall-product模块的aliyun-sdk-oss依赖和测试方法)

1、修改 pom.xml 文件

修改gulimall-common模块 pom.xml 文件,引入 aliyun-oss-spring-boot-starter

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alicloud-oss</artifactId>
</dependency>
image-20220505165703522
image-20220505165703522

最新的参考文档artifactId已经改了

使用最新版的会报Cannot resolve com.alibaba.cloud:aliyun-oss-spring-boot-starter:unknown这个错误

image-20220505165730515
image-20220505165730515
2、在配置文件中配置OSS

gulimall-product模块中的配置文件中配置OSS

使用自己的access-keysecret-key,配置自己地域的endpoint

spring:
  cloud:
    alicloud:
      access-key: LTAl4FwfjSycd1APnuG9bjj
      secret-key: O6xaxyiWfSlitcOkSuK27ju4hXT5HI
      oss:
        endpoint: oss-cn-beijing.aliyuncs.com
image-20220505170749948
image-20220505170749948
3、添加测试方法
@Autowired
OSSClient ossClient;

@Test
public void testUpload2() throws FileNotFoundException {

   // 上传文件流。
   InputStream inputStream = new FileInputStream("C:\\2.png");

   ossClient.putObject("gulimall-anonymous", "2.png", inputStream);

   // 关闭OSSClient。
   ossClient.shutdown();
   System.out.println("上传成功。。。");
}

这里的报错不用管,这个是IDEA没有识别出来

使用@Resource注解不报红,@Resource注解写在字段上按名称注入,@Autowired注解写在字段上按类型注入

putObject方法的第一个参数要更改成自己的Bucket名

image-20220505172414935
image-20220505172414935
4、测试

重启gulimall-common模块和gulimall-product模块,运行测试方法

image-20220505171924124
image-20220505171924124

4.2.4、新建gulimall-third-party模块

(**先删掉刚才gulimall-common模块 pom.xml里面的spring-cloud-starter-alicloud-oss依赖、删掉gulimall-product模块中在配置文件中配置的OSS、删掉测试方法 **)

1、新建模块

1、新建gulimall-third-party模块
com.atguigu.gulimall
gulimall-third-party
第三方服务
com.atguigu.gulimall.thirdparty
image-20220505173619537
image-20220505173619537
2、选择Web下的Spring Web
image-20220505173727336
image-20220505173727336
3、选择Spring Cloud Routing里的OpenFeign

右边可以查看选择的依赖

image-20220505173733736
image-20220505173733736
4、修改pom文件

为了和老师使用的配置一样,我使用了老师的pom文件(这样可以少很多错误)

点击查看完整pom文件

5、2.2.1.RELEASE没有找到

依赖文件: Central Repository: org/springframework/boot/spring-boot-parent/2.2.1.RELEASE (maven.org)open in new window

刷新后报错,说org.springframework.boot:spring-boot-starter-parent:2.2.1.RELEASE没有发现

Project 'org.springframework.boot:spring-boot-starter-parent:2.2.1.RELEASE' not found
Project 'org.springframework.boot:spring-boot-starter-parent:2.2.1.RELEASE' not found
Dependency 'org.springframework.cloud:spring-cloud-dependencies:Hoxton.RC1' not found
Plugin 'org.springframework.boot:spring-boot-maven-plugin:' not found
Plugin 'org.springframework.boot:spring-boot-maven-plugin:' not found
image-20220505204534787
image-20220505204534787

可以看到2.2.1.RELEASE2.1.8.RELEASE的目录结构一样,所以应该不是maven的问题,应该是IDEA的问题

image-20220505212548886
image-20220505212548886

可以点击File-->Invalidate Caches / Restar 删除原来的缓存和索引,等待Idea重新构建缓存和索引

现在2.2.1.RELEASE已经不报错了

image-20220505214331561
image-20220505214331561

spring-boot-maven-plugin这里报错

image-20220505214448720
image-20220505214448720

可以发现其实已经下载了,应该又是IDEA的问题,但我重新Invalidate Caches / Restar 还是报错😥

image-20220505220808846
image-20220505220808846

最后我删掉这个spring-boot-maven-plugin插件就好了

image-20220505221657720
image-20220505221657720
6、修改测试类

由于不同spring-boot-starter-parent版本的测试类不一样,所以修改一下测试类

package com.atguigu.gulimall.thirdparty;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class GulimallThirdPartyApplicationTests {

   @Test
   void contextLoads() {


   }

}

测试成功

image-20220505222624423
image-20220505222624423

如果测试类ossClent报错,说没定义"ossClent"类路径资源

是因为没删gulimall-common模块 pom.xml里面的spring-cloud-starter-alicloud-oss依赖,删掉就行了或者先排除掉(后面还是要删的)

<dependency>
   <groupId>com.atguigu.gulimall</groupId>
   <artifactId>gulimall-common</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <exclusions>
      <exclusion>
         <groupId>com.alibaba.cloud</groupId>
         <artifactId>spring-cloud-starter-alicloud-oss</artifactId>
      </exclusion>
   </exclusions>
</dependency>
image-20220505223841319
image-20220505223841319

同理,如果renren-fast报这个错也可以删掉这个依赖(后面还是要删的)

如果还不行可以使用相同的spring-boot-starter-parent版本

还是不行的话,可以先备份项目,删掉备份的.deal文件,重新导入备份的看看报不报错(这个操作比较微危险,一定要备份)

2、导入依赖

gulimall-third-party模块依赖gulimall-common模块,点击查看完整配置

需要排除mybatis-plus依赖,该模块不操作数据库,用不到

<dependency>
   <groupId>com.atguigu.gulimall</groupId>
   <artifactId>gulimall-common</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <exclusions>
      <exclusion>
         <groupId>com.baomidou</groupId>
         <artifactId>mybatis-plus-boot-starter</artifactId>
      </exclusion>
   </exclusions>
</dependency>

dependencyManagement标签里添加依赖

<dependency>
   <groupId>com.alibaba.cloud</groupId>
   <artifactId>spring-cloud-alibaba-dependencies</artifactId>
   <version>2.1.0.RELEASE</version>
   <type>pom</type>
   <scope>import</scope>
</dependency>

gulimall-third-party模块用来处理第三方服务,不用放在gulimall-common公共模块里

gulimall-third-party模块添加依赖,删掉gulimall-common的这个依赖

<dependency>
   <groupId>com.alibaba.cloud</groupId>
   <artifactId>spring-cloud-starter-alicloud-oss</artifactId>
</dependency>

3、注册到注册中心

1、新建命名空间
image-20220505225655873
image-20220505225655873
2、添加配置

点击“配置管理”下的"配置列表",点击"third-party",然后点击**+**

image-20220505230915820
image-20220505230915820

新建配置

access-keysecret-key以及endpointbucket输入自己的

spring.cloud.alicloud.oss.bucket为自己写的配置,不是官方有的配置

spring:
  cloud:
    alicloud:
      access-key: LTAl4FwfjSycd1APnuG9bjj
      secret-key: O6xaxyiWfSlitcOkSuK27ju4hXT5HI
      oss:
        endpoint: oss-cn-beijing.aliyuncs.com
        bucket: gulimall-anonymous
image-20220505231655295
image-20220505231655295
3、配置配置文件
1、复制唯一标识
image-20220505233148868
image-20220505233148868
2、配置配置中心

新建"bootstrap.properties"文件,添加配置

namespace填刚刚复制的

spring.application.name=gulimall-third-party
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.namespace=dd540e1b-ffd6-43e2-b9af-065130d391ec

#配置多配置文件
#数据集id
spring.cloud.nacos.config.ext-config[0].data-id=oss.yml
#数据分组
spring.cloud.nacos.config.ext-config[0].group=DEFAULT_GROUP
#动态刷新
spring.cloud.nacos.config.ext-config[0].refresh=true
image-20220505233837829
image-20220505233837829
3、配置注册中心

在"application.yml"中添加配置

spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
  application:
    name: gulimall-third-party
server:
  port: 30000
image-20220505234015676
image-20220505234015676
4、添加@EnableDiscoveryClient注解

gulimall-third-party模块的启动类上添加@EnableDiscoveryClient注解

image-20220505232327742
image-20220505232327742
5、添加测试方法
@Autowired
OSSClient ossClient;

@Test
public void testUpload2() throws FileNotFoundException {

   // 上传文件流。
   InputStream inputStream = new FileInputStream("C:\\2.png");

   ossClient.putObject("gulimall-anonymous", "2222.png", inputStream);

   // 关闭OSSClient。
   ossClient.shutdown();
   System.out.println("上传成功。。。");
}
6、测试是否能上传

可以看到运行成功了

代码报红不用管,那是IDEA没有检测到,其实可以注入进去的

使用@Resource注解不报红,@Resource注解写在字段上按名称注入,@Autowired注解写在字段上按类型注入

image-20220505235720081
image-20220505235720081

已经上传成功了

image-20220506081810670
image-20220506081810670
7、修改配置

access-keysecret-key以及endpointbucket输入自己的

在开发阶段,可以在"application.yml"中修改配置以方便调试,项目完成后,再上传到配置中心

spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    alicloud:
      access-key: LTAl4FwfjSycd1APnuG9bjj
      secret-key: O6xaxyiWfSlitcOkSuK27ju4hXT5HI
      oss:
        endpoint: oss-cn-beijing.aliyuncs.com
        bucket: gulimall-anonymous

  application:
    name: gulimall-third-party

server:
  port: 30000

4、编写方法

1、添加接口

com.atguigu.gulimall.thirdparty.controller目录下新建OssController类,编写获取密钥的工具方法

package com.atguigu.gulimall.thirdparty.controller;

import com.aliyun.oss.OSSClient;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.MatchMode;
import com.aliyun.oss.model.PolicyConditions;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.time.LocalDate;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @author 无名氏
 * @date 2022/5/6
 * @Description:
 */
@RestController
//@RefreshScope 自动刷新配置(使用最新的配置中心配置)
public class OssController {


    @Value("${spring.cloud.alicloud.oss.endpoint}")
    private String endpoint;

    @Value("${spring.cloud.alicloud.oss.bucket}")
    private String bucket;

    @Value("${spring.cloud.alicloud.access-key}")
    private String accessId;

    //@Autowired(required = false)
    @Resource
    private OSSClient ossClient;


    @RequestMapping("/oss/policy")
    public Map<String, String> policy() {
        //https://gulimall-anonymous.oss-cn-beijing.aliyuncs.com/1.png
        String host = "https://" + bucket + "." + this.endpoint; // host的格式为 bucketname.endpoint
        // callbackUrl为 上传回调服务器的URL,请将下面的IP和Port配置为您自己的真实信息。
//        String callbackUrl = "http://88.88.88.88:8888";
        String format = LocalDate.now().toString();
        String dir = format + "/"; // 用户上传文件时指定的前缀。

        Map<String, String> respMap = null;
        try {
            long expireTime = 30;
            long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
            Date expiration = new Date(expireEndTime);
            PolicyConditions policyConds = new PolicyConditions();
            policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
            policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);

            String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
            byte[] binaryData = postPolicy.getBytes("utf-8");
            String encodedPolicy = BinaryUtil.toBase64String(binaryData);
            String postSignature = ossClient.calculatePostSignature(postPolicy);

            respMap = new LinkedHashMap<String, String>();
            respMap.put("accessid", accessId);
            respMap.put("policy", encodedPolicy);
            respMap.put("signature", postSignature);
            respMap.put("dir", dir);
            respMap.put("host", host);
            respMap.put("expire", String.valueOf(expireEndTime / 1000));
            // respMap.put("expire", formatISO8601Date(expiration));
        } catch (Exception e) {
            // Assert.fail(e.getMessage());
            System.out.println(e.getMessage());
            e.printStackTrace();
        }
        return respMap;
    }
}
image-20220506102103657
image-20220506102103657
2、浏览器测试接口

浏览器输入 http://localhost:30000/oss/policy

已经正确访问了

image-20220506102230145
image-20220506102230145
3、添加配置

gulimall-gateway模块添加配置(一定要写在admin_route前面)

- id: third_party_route
  uri: lb://gulimall-third-party
  predicates:
    - Path=/api/product/**
  filters:
    #http://localhost:88/api/thirdparty/oss/policy 变为 http://localhost:30000/oss/policy
    - RewritePath=/api/thirdparty/(?<segment>/?.*),/$\{segment}
image-20220506104808034
image-20220506104808034
4、测试是否可以通过网关访问
1、访问失败了
image-20220506105012117
image-20220506105012117
2、查看日志

没有这个日志的是因为日志级别没有调成debug,调整日志级别就行了

logging:
  level:
    root: debug

可以发现匹配到admin_route

这是因为我- Path=/api/product/**这里写错了,应该写成- Path=/api/thirdparty/**

因为写错了,所以没匹配的,使用了默认的admin_route(所有前面没匹配到的,只要是path是以/api/开头的都会匹配到admin_route)

image-20220506104658815
image-20220506104658815
3、修改配置
- id: third_party_route
  uri: lb://gulimall-third-party
  predicates:
    - Path=/api/thirdparty/**
  filters:
    #http://localhost:88/api/thirdparty/oss/policy 变为 http://localhost:30000/oss/policy
    - RewritePath=/api/thirdparty/(?<segment>/?.*),/$\{segment}
image-20220506110148531
image-20220506110148531
4、重新测试

已经访问成功了

image-20220506110317459
image-20220506110317459

4.2.5、编写图片上传前端代码

1、复制文件

复制1.分布式基础(全栈开发篇)\资料源码.zip\docs\代码\前端目录下upload文件

image-20220506111426347
image-20220506111426347

2、修改Bucket域名

1、复制Bucket外网访问域名
image-20220506201245636
image-20220506201245636
2、粘贴到singleUpload.vue

粘贴到src\components\upload\singleUpload.vue里的el-upload标签,action属性里面

注意:前面有"http://"

image-20220506201710959
image-20220506201710959
3、粘贴到multiUpload.vue

粘贴到src\components\upload\multiUpload.vue里的el-upload标签,action属性里面

注意:前面有"http://"

image-20220506201943305
image-20220506201943305
附录
1、multiUpload.vue文件

点击查看完整代码

2、policy.js文件
import http from '@/utils/httpRequest.js'
export function policy() {
   return  new Promise((resolve,reject)=>{
        http({
            url: http.adornUrl("/thirdparty/oss/policy"),
            method: "get",
            params: http.adornParams({})
        }).then(({ data }) => {
            resolve(data);
        })
    });
}
3、singleUpload.vue文件

点击查看完整代码

3、使用Upload上传组件

element-uiUpload 上传组件:组件 | Elementopen in new window

上传组件已经封装在刚刚复制到upload里面了

4、上传图片报错

1、查看请求
image-20220506203350269
image-20220506203350269
2、查看匹配到的路由
image-20220506203432683
image-20220506203432683
3、查看对应路由

发现我gulimall-third-party没启动😰

image-20220506203844785
image-20220506203844785
4、启动gulimall-third-party
image-20220506204230648
image-20220506204230648
5、已经上传成功了
image-20220506204119458
image-20220506204119458
6、跨域问题

如果还是上传不了,可以看看是不是报403CROS

如果是报403CROS可以在阿里云OSS里面配置跨域规则

1、点击跨域设置

进入你的Bucket的控制台,点击**"权限管理**"-->"跨域设置"

image-20220506205320072
image-20220506205320072
2、点击跨域设置里的设置
image-20220506205451008
image-20220506205451008
3、创建规则

点击创建规则,输入规则就可以了

image-20220506211944153
image-20220506211944153

5、修改签名方法

1、获取数据的路径

可以看到路径为响应里的data字段

image-20220506210529130
image-20220506210529130
2、查看请求响应的数据

可以看到响应的数据直接返回了,没有封装在data中

image-20220506210632683
image-20220506210632683
3、修改返回类型

修改com.atguigu.gulimall.thirdparty.controller下的OssController类的policy方法返回类型

然后重启项目

image-20220506211124975
image-20220506211124975
4、跨域错误
1、查看控制台

发现有跨域错误

image-20220506211741596
image-20220506211741596
2、点击概要
image-20220506212310759
image-20220506212310759
3、跨域访问

往下滑,找到跨域访问

image-20220506212332731
image-20220506212332731
4、创建规则
image-20220506212515400
image-20220506212515400
5、再次上传

再次上传,图片已经回显出来了

image-20220506212726224
image-20220506212726224
5、修改路径
1、拼路径的时候多了一条杠
image-20220506213014421
image-20220506213014421
2、删除"/"

把这个删掉就行了

image-20220506213238514
image-20220506213238514
3、重新上传

图片已经在日期对应的文件夹下了

image-20220506213419593
image-20220506213419593

4.2.6、前端表单校验

1、修改按钮激活值

<template slot-scope="scope">
  <el-switch
    v-model="dataForm.showStatus"
    active-color="#13ce66"
    inactive-color="#ff4949"
    :active-value="1"
    :inactive-value="0"
  ></el-switch>
</template>
image-20220506214954428
image-20220506214954428

2、新增商品

image-20220506215355124
image-20220506215355124

3、修改品牌logo地址

1、品牌logo地址为文字
image-20220506215446629
image-20220506215446629
2、自定义列模板

Table表格自定义列模板:组件 | Elementopen in new window

通过 Scoped slot 可以获取到 row, column, $index 和 store(table 内部的状态管理)的数据

<template slot-scope="scope">
  <i class="el-icon-time"></i>
  <span style="margin-left: 10px">{{ scope.row.date }}</span>
</template>

Image图片里的基础用法中的contain:组件 | Elementopen in new window

<el-image
  style="width: 100px; height: 100px"
  :src="url"
  :fit="contain"></el-image>

因此修改src\views\modules\product\brand.vue文件里的品牌logo地址列为

<el-table-column
  prop="logo"
  header-align="center"
  align="center"
  label="品牌logo地址"
>
  <template slot-scope="scope">
    <el-image
      style="width: 100px; height: 80px"
      :src="scope.row.logo"
      :fit="contain"
    ></el-image>
  </template>
</el-table-column>
3、查看页面
1、图片显示不出来

查看报错可以看到,<el-image>组件没有正确注入进来

<el-image> - did you register the component correctly:您是否正确注册了<el-image>组件
image-20220506223735304
image-20220506223735304
2、查看引入的element-ui依赖

src\main.js文件中可以看到,element-ui引入在src/element-ui

@符号位自定义的,指向src目录

image-20220506225556450
image-20220506225556450

src\element-ui\index.js文件中搜索"Image",可以看到没有这个组件

image-20220506225431970
image-20220506225431970
3、添加组件

修改importVue.use里面的内容

element-ui组件:组件 | Elementopen in new window

点击查看默认配置

4、没有发现这些依赖
These dependencies were not found: 未找到这些依赖项

删掉这些依赖就行了

点击查看src\element-ui\index.js完整代码

image-20220506223507564
image-20220506223507564
5、fit未定义
 Property or method "fit" is not defined on the instance but referenced during render
 属性或方法“fit”未在实例上定义,但在渲染期间引用
image-20220506230513442
image-20220506230513442

实例用的":"为动态绑定,由于data中没设content,所以删掉这个":"就行了,不使用动态绑定

image-20220506231648179
image-20220506231648179
6、查看图片

可以看到图片已经出来了,不过是显示的问题

image-20220506231408833
image-20220506231408833

查看显示位置

image-20220506232005751
image-20220506232005751

最后还是用了原生的img标签

image-20220506233108057
image-20220506233108057

图片成功显示

image-20220506233245174
image-20220506233245174

4、自定义校验规则

Form 表单中的自定义校验规则:组件 | Elementopen in new window

1、修改校验规则

修改firstLettersort的校验规则

firstLetter: [
  {
    validator: (rule, value, callback) => {
      if (value === "" || value ==null) {
        callback(new Error("首字母必须填写"));
      } else if (!/^[a-zA-Z]$/.test(value)) {
        callback(new Error("首字母必须为单个的英文大写或小写"));
      } else {
        callback();
      }
    },
    trigger: "blur",
  },
],
sort: [
  {
    validator: (rule, value, callback) => {
      if (value === "" || value==null) {
        callback(new Error("排序字段必须填写"));
      } else if ((!Number.isInteger(value) || value<0)) {
        callback(new Error("排序字段必须为一个大于0的整数"));
      } else {
        callback();
      }
    },
    trigger: "blur",
  },
],
image-20220506235834283
image-20220506235834283
2、修改数据字段

修改showStatus字段默认为1(显示)

修改sort字段默认为0

image-20220506235221089
image-20220506235221089
3、sort标识为数组

v-moudel添加.number标识该数据为数字

image-20220506235509229
image-20220506235509229

4.2.7、后端校验

1、添加依赖

新版本需要在pom.xml文件里添加依赖(老版本不需要)

引入

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>2.0.1.Final</version>
</dependency>

2、添加校验规则注解

在实体类里需要校验的字段上添加校验注解

需要导入javax.validation.constraints包下的校验规则,在constraints上按ctrl键并点击即可查看所有校验注解

image-20220507091854747
image-20220507091854747

3、标识需要校验

在controller层需要校验的参数对象的左边添加@Valid注解

image-20220507092700948
image-20220507092700948

4、测试

重启gulimall-product项目后进行测试

url: http://localhost:10000/product/brand/save

image-20220507093733971
image-20220507093733971

状态为400,消息为不能为空,校验失败的对象为brandEntity,字段为name,失败的原因是name的值为空串

image-20220507094322011
image-20220507094322011

5、自定义显示格式

1、添加错误消息提示
image-20220507095020241
image-20220507095020241
2、修改返回样式

给校验的bean后紧跟一个org.springframework.validation包下的BindingResult,就可以获取到校验的结果

  @RequestMapping("/save")
      public R save(@Valid @RequestBody BrandEntity brand, BindingResult result){
      if (result.hasErrors()){
          Map<String, String> map = new HashMap<>();
          result.getFieldErrors().forEach((item)->{
              //错误消息
              String message = item.getDefaultMessage();
              //bean的字段名
              String name = item.getField();
              map.put(name,message);
          });
          return R.error(400,"提交的数据不合法").put("data",map);
      }
	  brandService.save(brand);

      return R.ok();
  }
image-20220507100430365
image-20220507100430365

6、重新测试

重启gulimall-product项目后重新进行测试

可以看到已经按照想要的格式显示了

image-20220507102736630
image-20220507102736630

7、添加其他校验规则

package com.atguigu.gulimall.product.entity;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;

import java.io.Serializable;
import lombok.Data;
import org.hibernate.validator.constraints.URL;

import javax.validation.constraints.*;

/**
 * 品牌
 *
 * @author 无名氏
 * @email [email protected]
 * @date 2022-04-17 18:19:58
 */
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
   private static final long serialVersionUID = 1L;

   /**
    * 品牌id
    */
   @TableId
   private Long brandId;
   /**
    * 品牌名
    * @NotBlank: 不能为空,必须包含一个非空白字符
    */
   @NotBlank(message = "品牌名不能为空")
   private String name;
   /**
    * 品牌logo地址
    * @NotEmpty :The annotated element must not be {@code null} nor empty
    *              可以接收 CharSequence、Collection、Map、Array 类型
    * @URL :必须为一个合法的url地址
    */
   @NotEmpty
   @URL(message = "Logo必须为一个合法的url地址")
   private String logo;
   /**
    * 介绍
    */
   private String descript;
   /**
    * 显示状态[0-不显示;1-显示]
    */
   private Integer showStatus;
   /**
    * 检索首字母
    */
   @NotEmpty
   @Pattern(regexp = "^[a-zA-Z]$",message = "首字母必须为单个的大写或小写英文字母")
   private String firstLetter;
   /**
    * 排序
    * @NotNull : The annotated element must not be {@code null}.
    *        * Accepts any type.
    */
   @NotNull
   @Min(value = 0,message = "排序字段必须为一个大于0的整数")
   private Integer sort;

}

8、再次测试

重启gulimall-product项目后重新进行测试

1、提交错误请求
1、为空判断
image-20220507103142585
image-20220507103142585
2、错误值判断

name值为一个空格可以判断,但sort为浮点型判断不出来

{"name": "1","logo":"http://www.example.org/1.jpg","firstLetter":"q","sort":1.1}
image-20220507104541200
image-20220507104541200
3、测试sort为浮点型

当其他值合法,只有sort不为int类型时,尽然校验通过了

image-20220507105030110
image-20220507105030110

sort直接被转成int了😰

image-20220507105222663
image-20220507105222663
4、读取请求体信息

sort确实为1.1

@RequestMapping("/save")
public R save(HttpServletRequest request) throws IOException {
    System.out.println(request.getParameter("sort"));
    request.getParameterMap().forEach((k, v) -> System.out.println(k + " : " + v));

    StringBuffer stringBuffer = new StringBuffer();
    BufferedReader reader = request.getReader();
    String temp;
    while ((temp = reader.readLine()) != null) {
        stringBuffer.append(temp);
    }
    System.out.println(stringBuffer);

    brandService.save(brand);

    return R.ok();
}
image-20220507120100556
image-20220507120100556

不要加上@RequestBody BrandEntity brand,如果加上这些参数spring会读取request输入流

而request的输入流只允许被读取一次

@RequestMapping("/save")
public R save(HttpServletRequest request, @Valid @RequestBody BrandEntity brand, BindingResult result) throws IOException {
    request.getParameterMap().forEach((k, v) -> System.out.println("111" + k + " : " + v));

    StringBuffer stringBuffer = new StringBuffer();
    BufferedReader reader = request.getReader();
    String temp;
    while ((temp = reader.readLine()) != null) {
        stringBuffer.append(temp);
    }
    System.out.println(stringBuffer);

    if (result.hasErrors()) {
        Map<String, String> map = new HashMap<>();
        result.getFieldErrors().forEach((item) -> {
            //错误消息
            String message = item.getDefaultMessage();
            //bean的字段名
            String name = item.getField();
            map.put(name, message);
        });
        return R.error(400, "提交的数据不合法").put("data", map);
    }
    
    brandService.save(brand);

    return R.ok();
}
image-20220507122011532
image-20220507122011532
5、不允许强转

设置json反序列化不允许float强转成int

spring:
  jackson:
    deserialization:
      ACCEPT_FLOAT_AS_INT: false
image-20220507144358777
image-20220507144358777

可以看到已经不允许强转了

Cannot coerce a floating-point value ('1.1') into Integer:无法将浮点值 ('1.1') 强制转换为整数

image-20220507144324665
image-20220507144324665
2、提交正确请求
{"name": "1","logo":"http://www.example.org/1.jpg","firstLetter":"q","sort":0}
image-20220507103754722
image-20220507103754722

已经显示出来了

image-20220507103937140
image-20220507103937140

9、批量捕获异常

1、编写异常捕获类

com.atguigu.gulimall.product下新建exception文件夹

com.atguigu.gulimall.product.exception下新建 GulimallExceptionControllerAdvice类自定义捕获异常

package com.atguigu.gulimall.product.exception;

import com.atguigu.common.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
 * @author 无名氏
 * @date 2022/5/7
 * @Description: 自定义异常捕获类
 *
 * @RestControllerAdvice =  @ControllerAdvice + @ResponseBody
 */

@Slf4j  //lombok日志
@RestControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")
public class GulimallExceptionControllerAdvice {

    /**
     * 捕获异常
     * @ExceptionHandler: 捕获异常的类型
     * @param e 该异常类
     * @return  返回前端的结果
     */
    @ExceptionHandler(value = Exception.class)
    public R handleValidException(Exception e){
        log.error("数据校验出现问题:{},异常类型:{}",e.getMessage(),e.getClass());
        return R.error();
    }
}
image-20220507153231608
image-20220507153231608
2、测试upload方法

重启gulimall-product项目后测试

http://localhost:10000/product/brand/update

{"id":1,"name": " ","logo":"www.example.org/1.jpg","firstLetter":"qq","sort":-1}
1、未知错误

可以看到显示的是未知错误

image-20220507151321841
image-20220507151321841
2、控制台查看异常类

异常类为:org.springframework.web.bind.MethodArgumentNotValidException

image-20220507153339775
image-20220507153339775
3、修改捕获异常类型

修改 GulimallExceptionControllerAdvice类的handleValidException方法

@ExceptionHandler(value = MethodArgumentNotValidException.class)
public R handleValidException(MethodArgumentNotValidException e){
    log.error("数据校验出现问题:{},异常类型:{}",e.getMessage(),e.getClass());
    Map<String, String> errMap = new HashMap<>();
    e.getBindingResult().getFieldErrors().forEach((item)->{
        //实体类的字段名和对应的错误消息
        errMap.put(item.getField(),item.getDefaultMessage());
    });

    return R.error(400,"数据校验失败").put("data",errMap);
}
image-20220507152807842
image-20220507152807842

4、测试upload方法

测试upload方法,可以看到按想要的格式显示出来了

image-20220507152532267
image-20220507152532267

10、捕获所有类型异常

GulimallExceptionControllerAdvice类中添加方法

所有前面没有被匹配的异常都会执行这个方法

@ExceptionHandler(value = Throwable.class)
public R handleException(Throwable throwable){
    return R.error();
}
image-20220507154355873
image-20220507154355873

11、错误类型枚举

1、新建枚举类

gulimall-common模块的com.atguigu.common.exception文件夹下新建BizCodeException枚举类

定义可能的错误信息

package com.atguigu.common.exception;

/**
 * @author 无名氏
 * @date 2022/5/7
 * @Description:
 * 错误码和错误信息定义类
 * 1. 错误码定义规则为 5 为数字
 * 2. 前两位表示业务场景,最后三位表示错误码。例如:100001。10:通用 001:系统未知异常
 * 3. 维护错误码后需要维护错误描述,将他们定义为枚举形式
 * 错误码列表:
 * 10: 通用
 * 001:参数格式校验
 * 11: 商品
 * 12: 订单
 * 13: 购物车
 * 14: 物流
 */
public enum BizCodeException {
    /**
     * 系统未知异常
     */
    UNKNOW_EXCEPTION(10000,"系统未知异常"),
    /**
     * 参数格式校验失败
     */
    VALID_EXCEPTION(10001,"参数格式校验失败");

    private int code;
    private String msg;

    BizCodeException(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public int getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }
}
image-20220507155929408
image-20220507155929408
2、R类添加方法

gulimall-common模块的com.atguigu.common.utils文件夹下的R类里添加方法

public static R error(BizCodeException bizCodeException){
   return error(bizCodeException.getCode(),bizCodeException.getMsg());
}
image-20220507161305656
image-20220507161305656
3、修改返回参数

修改gulimall-product模块com.atguigu.gulimall.product.exception包下的

GulimallExceptionControllerAdvice类的方法返回参数

image-20220507161815782
image-20220507161815782

12、分组校验

默认没有指定分组的校验注解@NotBlank,在分组校验情况@Validated({AddGroup.class})下不生效,只会在@Validated生效;

默认指定分组的校验注解@NotBlank(groups = AddGroup.class),在@Validated下不生效,只会在分组校验@Validated({AddGroup.class})下生效

可以看到groups的参数为接口数组

image-20220507175045352
image-20220507175045352
1、新建接口

gulimall-common模块的com.atguigu.common包下新建valid文件夹

gulimall-common模块的com.atguigu.common.valid包下创建AddGroupUpdateGroup接口

这些接口只做标识,不用书写任何方法和字段

image-20220507175647905
image-20220507175647905
image-20220507175702832
image-20220507175702832
2、添加分组

id添加校验注解,使用groups属性进行分组,传入的类只做一个标识

@Null(message = "添加不能指定品牌id",groups = {AddGroup.class})
@NotNull(message = "修改必须指定品牌id",groups = {UpdateGroup.class})
image-20220507182831588
image-20220507182831588
3、按组校验

使用org.springframework.validation.annotation包下的Validated注解,指定分组的类

image-20220507183447890
image-20220507183447890
4、测试

重启gulimall-product模块

{"brandId":1,"name": " ","logo":"www.example.org/1.jpg","firstLetter":"qq","sort":-1}

可以看到brandId字段已经分组校验了,其他没有分组的字段在使用Validated注解进行分组校验的情况下不生效

image-20220507193841877
image-20220507193841877
image-20220507193934405
image-20220507193934405
5、其他字段添加分组校验

修改gulimall-product模块下的com.atguigu.gulimall.product.entity.BrandEntity

package com.atguigu.gulimall.product.entity;

import com.atguigu.common.valid.AddGroup;
import com.atguigu.common.valid.UpdateGroup;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;

import java.io.Serializable;
import lombok.Data;
import org.hibernate.validator.constraints.URL;

import javax.validation.constraints.*;

/**
 * 品牌
 *
 * @author 无名氏
 * @email [email protected]
 * @date 2022-04-17 18:19:58
 */
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
   private static final long serialVersionUID = 1L;

   /**
    * 品牌id
    * UpdateGroup不能指定品牌
    * AddGroup必须指定品牌id
    */
   @Null(message = "添加不能指定品牌id",groups = {AddGroup.class})
   @NotNull(message = "修改必须指定品牌id",groups = {UpdateGroup.class})
   @TableId
   private Long brandId;
   /**
    * 品牌名
    * @NotBlank: 不能为空,必须包含一个非空白字符
    */
   @NotBlank(message = "品牌名不能为空",groups = AddGroup.class)
   private String name;
   /**
    * 品牌logo地址
    * @NotEmpty :The annotated element must not be {@code null} nor empty
    *              可以接收 CharSequence、Collection、Map、Array 类型
    * @URL :必须为一个合法的url地址
    *
    * 添加品牌时logo不能为空,并且需要是一个URL
    * 修改可以为空,但如果不为空则必须为一个URL
    */
   @NotEmpty(groups = AddGroup.class)
   @URL(message = "Logo必须为一个合法的url地址",groups = {AddGroup.class,UpdateGroup.class})
   private String logo;
   /**
    * 介绍
    */
   private String descript;
   /**
    * 显示状态[0-不显示;1-显示]
    */
   private Integer showStatus;
   /**
    * 检索首字母
    */
   @NotEmpty(groups = AddGroup.class)
   @Pattern(regexp = "^[a-zA-Z]$",message = "首字母必须为单个的大写或小写英文字母",groups = {AddGroup.class,UpdateGroup.class})
   private String firstLetter;
   /**
    * 排序
    * @NotNull : The annotated element must not be {@code null}.
    *        * Accepts any type.
    */
   @NotNull(groups = AddGroup.class)
   @Min(value = 0,message = "排序字段必须为一个大于0的整数",groups = {AddGroup.class,UpdateGroup.class})
   private Integer sort;



}

13、自定义校验注解

需求:showStatus只能为0或1

Integer和Long类型不能使用正则

/**
 * 显示状态[0-不显示;1-显示]
 * @Pattern不能用在Integer和Long上
 * No validator could be found for constraint 'javax.validation.constraints.Pattern' validating type 'java.lang.Integer'
 * @Pattern 注解可以标注在 CharSequence 和 null 上
 */
//@Pattern(regexp = "^[01]$",message = "显示状态异常",groups = AddGroup.class) 不能使用该注解来校验
private Integer showStatus;

不过可以使用以下三个注解来完成功能

@NotNull(groups = AddGroup.class)
@Max(value = 1,groups = {AddGroup.class,UpdateGroup.class})
@Min(value = 0,groups = {AddGroup.class,UpdateGroup.class})
private Integer showStatus;

如果有更复杂的需求,比方说只能为[1,4,7]中的一个,这些注解就不能实现这个需求了

因此可以自定义注解

1、编写自定义注解

gulimall-common模块的com.atguigu.common.valid包下创建ListValue注解

jsr303规范中,校验注解必须添加这三个属性

package com.atguigu.common.valid;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * @author 无名氏
 * @date 2022/5/7
 * @Description: 只能为列表内的值
 */
//生成文档
@Documented
//使用哪个校验器进行校验(如果不指定,需要在初始化的时候指定)
@Constraint(validatedBy = { })
//注解可以标注在哪个位置
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
//校验时机,在运行时获取
@Retention(RUNTIME)
public @interface ListValue {
    /**
     * 校验出错以后,错误信息去哪取
     * 默认使用com.atguigu.common.valid.ListValue.message属性去
     * org/hibernate/validator/ValidationMessages.properties配置文件中去取
     *
     * 一般为 (注解全类名 + .message)
     * @return
     */
    String message() default "{com.atguigu.common.valid.ListValue.message}";
    /**
     * 支持分组校验
     * @return
     */
    Class<?>[] groups() default { };
    /**
     * 自定义负载信息
     * @return
     */
    Class<? extends Payload>[] payload() default { };
    /**
     * 可以为哪些值
     * @return
     */
    int[] vals() default { };
}
image-20220507212345254
image-20220507212345254
2、添加坐标

gulimall-common模块的pom.xml文件中添加validation坐标,点击刷新,然后在ListValue注解类中导包就不报错

<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>2.0.1.Final</version>
</dependency>
image-20220507215105090
image-20220507215105090
3、创建配置文件

jsr303规范中,在ValidationMessages.properties文件中获取message中键对应的值作为消息

1、搜索ValidationMessages.properties

双击shift键,在弹出的框中搜索ValidationMessages.properties即可看到

image-20220507212629762
image-20220507212629762
2、新建配置文件

gulimall-common模块的resources目录下新建ValidationMessages.properties配置文件

com.atguigu.common.valid.ListValue.message = "必须提交指定的值"

这里写错了,测试的时候发现它把两个双引号也显示出来了(别人写的也没有双引号),其实为

com.atguigu.common.valid.ListValue.message = 必须提交指定的值

注意编码为"UTF-8"

image-20220507213536293
image-20220507213536293
3、设置字符编码

如果编码不为"UTF-8"可以设置字符编码

image-20220507214152772
image-20220507214152772
4、自定义校验规则

校验类必须实现ConstraintValidator接口

image-20220507215413611
image-20220507215413611

ConstraintValidator接口有两个泛型,第一个泛型用来指定注解,第二个泛型用来指定需要校验的数据类型

image-20220507215539055
image-20220507215539055

com.atguigu.common.valid包下新建ListValueConstraintValidator类,实现ConstraintValidator接口

package com.atguigu.common.valid;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
import java.util.Set;

/**
 * @author 无名氏
 * @date 2022/5/7
 * @Description:
 */
public class ListValueConstraintValidator implements ConstraintValidator<ListValue, Integer> {

    private Set<Integer> set = new HashSet<>();

    /**
     * 初始化信息
     * 可以获取注解的详细信息
     * @param constraintAnnotation
     */
    @Override
    public void initialize(ListValue constraintAnnotation) {
        //@ListValue注解中 vals 属性的值
        //值必须为这几个中的一个
        int[] vals = constraintAnnotation.vals();
        for (int val : vals) {
            set.add(val);
        }
    }

    /**
     * 判断是否校验成功
     * @param value
     * @param context
     * @return
     */
    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext context) {

        return set.contains(value);
    }
}
image-20220507222417417
image-20220507222417417
5、指定校验器

可以指定多个校验器,能够根据泛型自动匹配正确的校验器

image-20220507222719730
image-20220507222719730
6、使用该注解

gulimall-product模块的com.atguigu.gulimall.product.entity.BrandEntity类的showStatus字段上使用自定义注解

@NotNull(groups = AddGroup.class)
@ListValue(vals = {0,1},groups = {AddGroup.class,UpdateGroup.class})
image-20220507225522195
image-20220507225522195
7、测试

重启gulimall-product模块

1、多了两个双引号
image-20220507223836326
image-20220507223836326
2、去掉双引号

去掉双引号后重启gulimall-product模块

image-20220507223924267
image-20220507223924267
3、重新测试
image-20220507224020853
image-20220507224020853

4.2.8、校验注解总结

1 Maven依赖

<!--第一种方式导入校验依赖-->
<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>2.0.1.Final</version>
</dependency>
<!--第二种方式导入校验依赖-->
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
</dependency>

2 值校验

2.1 @Null注解

被注解的元素必须为null

@Null(message = "必须为null")
private String username;
2.2 @NotNull注解

被注解的元素必须不为null

@NotNull(message = "必须不为null")
private String username;
2.3 @NotBlank注解

验证注解的元素值不为空(不为null、去除首位空格后长度为0) ,并且类型为String。

@NotBlank(message = "必须不为空")
private String username;
2.4 @NotEmpty注解

验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0) ,并且类型为String。

@NotEmpty(message = "必须不为null且不为空")
private String username;
2.5 @AssertTrue注解

被注解的元素必须为true,并且类型为boolean。

@AssertTrue(message = "必须为true")
private boolean status;
2.6 @AssertFalse注解

被注解的元素必须为false,并且类型为boolean。

@AssertFalse(message = "必须为false")
private boolean status;

3 范围校验

3.1 @Min注解

被注解的元素其值必须大于等于最小值,并且类型为int,long,float,double。

@Min(value = 18, message = "必须大于等于18")
private int age;
3.2 @Max注解

被注解的元素其值必须小于等于最小值,并且类型为int,long,float,double。

@Max(value = 18, message = "必须小于等于18")
private int age;
3.3 @DecimalMin注解

验证注解的元素值大于等于@DecimalMin指定的value值,并且类型为BigDecimal。

@DecimalMin(value = "150", message = "必须大于等于150")
private BigDecimal height;
3.4 @DecimalMax注解

验证注解的元素值小于等于@DecimalMax指定的value值 ,并且类型为BigDecimal。

@DecimalMax(value = "300", message = "必须大于等于300")
private BigDecimal height;
3.5 @Range注解

验证注解的元素值在最小值和最大值之间,并且类型为BigDecimal,BigInteger,CharSequence,byte,short,int,long。

@Range(max = 80, min = 18, message = "必须大于等于18且小于等于80")
private int age;
3.6 @Past注解

被注解的元素必须为过去的一个时间,并且类型为java.util.Date。

@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Past(message = "必须为过去的时间")
private Date createDate;
3.7 @Future注解

被注解的元素必须为未来的一个时间,并且类型为java.util.Date。

@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Future(message = "必须为未来的时间")
private Date createDate;

4 长度校验

4.1 @Size注解

被注解的元素的长度必须在指定范围内,并且类型为String,Array,List,Map。

@Size(max = 11, min = 7, message = "长度必须大于等于7或小于等于11")
private String mobile;
4.2 @Length注解

验证注解的元素值长度在min和max区间内 ,并且类型为String。

@Length(max = 11, min = 7, message = "长度必须大于等于7或小于等于11")
private String mobile;

5 格式校验

5.1 @Digits注解

验证注解的元素值的整数位数和小数位数上限 ,并且类型为float,double,BigDecimal。

@Digits(integer=3,fraction = 2,message = "整数位上限为3位,小数位上限为2位")
private BigDecimal height;
5.2 @Pattern注解

被注解的元素必须符合指定的正则表达式,并且类型为String。

@Pattern(regexp = "\\d{11}",message = "必须为数字,并且长度为11")
private String mobile;
5.3 @Email注解

验证注解的元素值是Email,也可以通过regexp和flag指定自定义的email格式,类型为String。

 @Email(message = "必须是邮箱")
private String email;

4.3、商品服务-API-属性分组

4.3.1、前端组件导入

1、导入数据

1、执行sql语句

打开1.分布式基础(全栈开发篇)\资料源码.zip\docs\代码\sql文件夹下的sys_menus.sql,全选复制

点击gulimall_admin然后右键选择命令行界面 ,粘贴到里面,回车执行sql语句

(不要点击运行SQL文件,这样会有中文乱码问题)

image-20220508090404565
image-20220508090404565
2、查看结果

url: http://localhost:8001/#/product-brand

刷新前端项目,可以看到这些系统已经显示出来了

image-20220508091348831
image-20220508091348831

2、新建文件

1、新建category.vue文件

src\views\modules文件夹里新建common文件夹,用来存储公共组件

src\views\modules文件夹下的common文件夹里新建category.vue文件,输入vue生成模板

image-20220508092805251
image-20220508092805251
2、新建attrgroup.vue文件
1、查看位置
image-20220508092955297
image-20220508092955297
2、新建attrgroup.vue文件

src\views\modules文件夹下的product文件夹里新建attrgroup.vue文件,输入vue生成模板

image-20220508095359481
image-20220508095359481

3、添加代码

1、添加category.vue代码

修改src\views\modules\common\category.vue文件,点击查看category.vue完整代码

2、添加attrgroup.vue代码

element-ui里面的Layout 布局下的分栏间隔: 组件 | Elementopen in new window

引入组件步骤:

  1. import Category from "../common/category.vue"; 把组件导进来
  2. components: { Category:Category }, 指明需要的组件
  3. <category></category> 使用该标签

修改src\views\modules\product\attrgroup.vue文件,点击查看attrgroup.vue完整内容

3、结构已经出来了
image-20220508100902403
image-20220508100902403
4、添加表格

<div>标签里面的内容替换掉"表格"

然后把其他除<template>标签里的内容与attrgroup.vue里面的内容合并

点击查看attrgroup.vue完整代码

image-20220508101443870
image-20220508101443870
5、引入attrgroup-add-or-update.vue文件
image-20220508103547839
image-20220508103547839
6、效果
image-20220508104040518
image-20220508104040518

4、完整代码

1、category.vue

点击查看category.vue完整代码

2、attrgroup.vue

点击查看attrgroup.vue完整代码

4.3.2、父子组件交互

1、发送消息

1、绑定事件

Tree树形控件中的Events: 组件 | Elementopen in new window

事件名称说明回调参数
node-click节点被点击时的回调共三个参数,依次为:传递给 data 属性的数组中该节点所对应的对象、节点对应的 Node、节点组件本身。

src\views\modules\common\category.vue文件的el-tree标签内添加属性

@node-click="nodeClick"
2、添加方法

src\views\modules\common\category.vue文件的methods里面添加方法

nodeClick(data,node,component){
  console.log("子组件的category里的节点被点击:",data,node,component);
  //向父组件发送事件
  //第一个参数为事件的名称,后面的都为发送的数据
  this.$emit("tree-node-click",data,node,component);
},

2、接收事件

1、添加事件

src\views\modules\product\attrgroup.vue文件中的category标签里添加属性

格式: @事件名称=”父组件方法“

@tree-node-click="treeNodeClick"
image-20220508111126728
image-20220508111126728
2、添加方法

src\views\modules\product\attrgroup.vue文件中的methods里面添加方法

//参数为子组件传递的参数
treeNodeClick(data,node,component){
  console.log("父组件attrgroup感知到子组件category的节点被点击:",data,node,component);
  console.log("被点击的节点名:",data.name)
},

3、查看效果

点击"数码相机",可以看到父组件attrgroup已经感知到子组件category的节点被点击了

image-20220508112504211
image-20220508112504211

4.3.3、获取分类属性分组

1、后端

1、更改list方法

修改gulimall-product模块下com.atguigu.gulimall.product.service.impl.AttrGroupServiceImpl类的list方法

@RequestMapping("/list/{catelogId}")
    public R list(@RequestParam Map<String, Object> params,@PathVariable("catelogId") Long catelogId){
    //PageUtils page = attrGroupService.queryPage(params);
    PageUtils page = attrGroupService.queryPage(params,catelogId);
    return R.ok().put("page", page);
}

alt+enter在提示里面选第二个Create method

image-20220508165717823
image-20220508165717823
2、添加抽象方法

com.atguigu.gulimall.product.service包下的AttrGroupService接口添加抽象方法

PageUtils queryPage(Map<String, Object> params, Long catelogId);
3、添加实现类

com.atguigu.gulimall.product.service.impl包下的AttrGroupServiceImpl类添加实现方法

@Override
public PageUtils queryPage(Map<String, Object> params, Long catelogId) {
    if (catelogId == 0){
        return this.queryPage(params);
    }else {
        String key = (String) params.get("key");
        //select * from attr_group where catelogId = ? and ( attr_group_id = key or attr_group_name like %key%)
        LambdaQueryWrapper<AttrGroupEntity> queryWrapper = new LambdaQueryWrapper<AttrGroupEntity>()
                .eq(AttrGroupEntity::getCatelogId,catelogId);
        if (key != null && key.length() > 0){
            queryWrapper.and((obj)->{
                obj.eq(AttrGroupEntity::getAttrGroupId,key).or().like(AttrGroupEntity::getAttrGroupName,key);
            });
        }
        IPage<AttrGroupEntity> page = this.page(new Query<AttrGroupEntity>().getPage(params),queryWrapper);
        return new PageUtils(page);
    }
}
4、测试

使用Postman测试

url: http://localhost:88/api/product/attrgroup/list/1?page=1&key=aa

选择get方法

image-20220508165944731
image-20220508165944731

可以看到已正确打印sql语句

image-20220508165149621
image-20220508165149621

2、前端

1、修改treeNodeClick方法

修改src\views\modules\product\attrgroup.vuetreeNodeClic方法,为第三级时查询该catelogId的信息

//参数为子组件传递的参数
treeNodeClick(data,node,component){
  console.log("父组件attrgroup感知到子组件category的节点被点击:",data,node,component);
  console.log("被点击的节点名:",data.name)
  //为第三级时查询该catelogId的信息
  if(node.level==3){
    this.catelogId = data.catId;
    this.getDataList();
  }
},
2、添加catelogId字段

data里的return里添加catelogId数据字段

catelogId : 0,
3、修改url

修改getDataList方法里面传递的url

url: this.$http.adornUrl(`/product/attrgroup/list/${this.catelogId}`),

3、测试

1、查询全部

点击 手机-->手机通讯-->手机 ,所有数据都查询出来了

image-20220508173023284
image-20220508173023284
2、根据id查

输入1点击查询

image-20220508173218019
image-20220508173218019
3、根据组名查

输入基本点击查询

image-20220508173329517
image-20220508173329517

4.3.4、属性分组新增功能

1、添加级联选择器

切换到src\views\modules\product\attrgroup-add-or-update.vue文件

label="所属分类id"el-form-item 标签内,删除el-input标签

<el-input v-model="dataForm.catelogId" placeholder="所属分类id"></el-input>

添加Cascader级联选择器

<el-cascader
  v-model="dataForm.catelogId"
  :options="categorys"
  :props="props"
></el-cascader>

2、添加属性

data里面的return里添加属性

props: {
  value: 'catId',
  label: 'name',
  children: 'children'
},
categorys: [],

3、添加方法

添加getCategorys方法,获取分类数据

getCategorys() {
  this.$http({
    url: this.$http.adornUrl("/product/category/list/tree"),
    method: "get",
  }).then(({ data }) => {
    console.log(data.data);
    this.categorys = data.data;
  });
},

4、调用方法

在创建完成(可以访问当前 this 实例)的时候调用getCategorys方法,获取分类数据

created方法写在与“data"和”methods“同级的位置

created() {
  this.getCategorys();
},
image-20220508200114324
image-20220508200114324

5、测试

1、控制台报错
Invalid prop: type check failed for prop "value". Expected Array, got String
无效的属性:属性“value”的类型检查失败。 预期数组,得到字符串。
image-20220508201217559
image-20220508201217559

src\views\modules\product下的attrgroup-add-or-update.vue里的

catelogId: "",

改成

catelogId: [],

就行了,现在已经不报错了

image-20220509210341350
image-20220509210341350
2、查看数据,但多出一级

数据已经显示出来了,但是在点击第三级分类时,又出来了一级

image-20220508200249772
image-20220508200249772
image-20220508202500776
image-20220508202500776
image-20220508202714332
image-20220508202714332
3、后端添加注解

gulimall-product模块的com.atguigu.gulimall.product.entity.CategoryEntity文件里的children字段上添加注解

@JsonInclude(JsonInclude.Include.NON_EMPTY)
image-20220508203003106
image-20220508203003106
4、重新测试

显示已经成功了,

image-20220509211116839
image-20220509211116839

也没有children为空的字段了

image-20220509211356536
image-20220509211356536
image-20220509211717999
image-20220509211717999
5、提交的catelogId为数组

提交的catelogId为数组,而想要的catelogId只是最后一级(不一定是第三级)分类的id

image-20220508204402893
image-20220508204402893
6、修改提交的数据

src\views\modules\product下的attrgroup-add-or-update.vue里的

catelogId: [],

改为

catelogIds: [],

并添加catelogId字段,此时的catelogId为要提交的最后一级(不一定是第三级)分类的id

catelogId: 0,

el-cascader标签内的属性

v-model="dataForm.catelogId"

改为

v-model="dataForm.catelogIds"

使其继续绑定以前的catelogId


dataFormSubmit方法里data字段里的

catelogId: this.dataForm.catelogId,

改成

catelogId: this.dataForm.catelogIds[this.dataForm.catelogIds.length-1],

使其绑定最后一级(不一定是第三级)分类的id

7、继续测试
image-20220509213100721
image-20220509213100721

提交后,页面自动刷新,获取最新数据

image-20220509213118005
image-20220509213118005

📌这个刷新的功能就是通过刚才父子组件交换实现的

image-20220509213451960
image-20220509213451960
image-20220509213506714
image-20220509213506714
image-20220509213521855
image-20220509213521855
8、所属分类id不在一行显示

src\views\modules\product\attrgroup-add-or-update.vue文件里的el-form标签里修改属性

label-width="100px"
image-20220509214406434
image-20220509214406434

这样所属分类id就显示在一行了

image-20220509214549516
image-20220509214549516

6、完整代码

1、common下的category.vue文件

点击查看src\views\modules\common\category.vue文件完整代码

2、attrgroup.vue文件

点击查看src\views\modules\product\attrgroup.vue文件完整代码

3、attrgroup-add-or-update.vue文件

点击查看src\views\modules\product\attrgroup-add-or-update.vue文件完整代码

4.3.5、回显所属分类id

当点击修改按钮后

image-20220509222434388
image-20220509222434388

所属分类id没有回显

image-20220509222509610
image-20220509222509610

1、查看点击修改按钮调用的方法

查看src\views\modules\product\attrgroup.vue文件的addOrUpdateHandle方法,分析点击修改按钮后所属分类id没有回显的原因


this.$nextTick():当要显示的组件(上面这个对话框)完全渲染后,再调用里面的lambda表达式

this.$refs会获取当前vue文件所有组件

this.$refs.addOrUpdate获取当前vue文件所有标签中ref属性为addOrUpdatecomponents{}里面为addOrUpdate的组件

然后再调用该组件的方法

// 新增 / 修改
addOrUpdateHandle(id) {
  //打开对话框
  this.addOrUpdateVisible = true;
  //当要显示的组件(上面这个对话框)完全渲染后,再调用一个方法
  this.$nextTick(() => {
    this.$refs.addOrUpdate.init(id);
  });
},

可以看到调用了addOrUpdate组件的init方法

当前vue文件引了同文件夹下的attrgroup-add-or-update.vue文件

并把它命名为addOrUpdate,并使用这个名字添加到该vue文件的组件里

因此其调用了attrgroup-add-or-update.vue文件的init方法,并传入了attrGroupId

image-20220509223330783
image-20220509223330783

2、查看init方法

src\views\modules\product\attrgroup-add-or-update.vue文件init方法得到的catelogId只是一个值(最后一级分类的id)

image-20220509224447118
image-20220509224447118

所属分类id需要的参数是完整分类id(最后一级分类的所有父分类id+最后一级分类的id)

因此可以添加返回的数据

3、修改前端代码

src\views\modules\product\attrgroup-add-or-update.vueinit方法里添加这一行

//最后一级分类的所有父分类id+最后一级分类id
this.dataForm.catelogPath = data.attrGroup.catelogPath;

按"ctrl+H"快捷键调出替换,把"catelogIds"替换成"catelogPath",点击替换所有

image-20220509225211670
image-20220509225211670

4、修改后端代码

1、修改AttrGroupControllerinfo方法

修改gulimall-product模块的com.atguigu.gulimall.product.controller.AttrGroupController类的info方法

注入categoryService

@Autowired
private CategoryService categoryService;

调用其getCatelogPath方法,根据最后一级分类的id查出完整分类id(最后一级分类的所有父分类id+最后一级分类id)

并将查处的结果赋给AttrGroupEntitycatelogPath字段

@RequestMapping("/info/{attrGroupId}")
public R info(@PathVariable("attrGroupId") Long attrGroupId) {
    AttrGroupEntity attrGroup = attrGroupService.getById(attrGroupId);

    Long catelogId = attrGroup.getCatelogId();
    //根据最后一级分类的id查出完整分类id(最后一级分类的所有父分类id+最后一级分类id)
    Long[] catelogPath = categoryService.findCatelogPath(catelogId);
    //赋给AttrGroupEntity的catelogPath字段
    attrGroup.setCatelogPath(catelogPath);

    return R.ok().put("attrGroup", attrGroup);
}
image-20220509233808743
image-20220509233808743
2、AttrGroupEntity类添加catelogPath字段
/**
 * 此字段在数据库中不存在
 */
@TableField(exist = false)
private Long[] catelogPath;
image-20220509231524255
image-20220509231524255
3、添加getCatelogPath抽象方法

由于使用了lombok插件,添加了lombok依赖,并添加了@Data注解,所以会在编译时给字段自动添加getset方法,

因此attrGroup.setCatelogPath(catelogPath);不报错了

鼠标悬停在getCatelogPath上(或者使用alt+enter快捷键),点击Create method,创建方法

image-20220509233935848
image-20220509233935848

添加抽象方法

/**
 * 根据最后一级分类的id查出完整分类id(最后一级分类的所有父分类id+最后一级分类id)
 * @return
 */
Long[] findCatelogPath(Long catelogId);

然后点击1 related problem或接口类左边的I和向下箭头的那个按钮或使用ctrl+alt+B快捷键转到其实现类

image-20220509234029398
image-20220509234029398

使用alt+enter快捷键,点击"Implement methods",实现未实现的方法

image-20220509234117877
image-20220509234117877
4、实现getCatelogPath抽象方法

com.atguigu.gulimall.product.service.impl.CategoryServiceImpl类里添加方法

@Override
public Long[] findCatelogPath(Long catelogId) {
    List<Long> paths = new ArrayList<>();

    List<Long> parentPath = findParentPath(catelogId,paths);
    //先加入节点id后再递归求解其父分类,所有求出的完整路径是反的,需要转置一下
    Collections.reverse(parentPath);
    return (Long[]) parentPath.toArray();
}

/**
 * 例如:[413,50,5]
 * 根据最后一级分类的id递归求解完整分类id(最后一级分类的所有父分类id+最后一级分类id)
 * @param catelogId 当前分类id
 * @param paths 分类id数组
 * @return  完整分类id
 */
private List<Long> findParentPath(Long catelogId, List<Long> paths) {
    paths.add(catelogId);
    CategoryEntity categoryEntity = this.getById(catelogId);
    Long parentCid = categoryEntity.getParentCid();
    if (parentCid!=0){
        findParentPath(parentCid,paths);
    }
    return paths;
}
5、单元测试

test\javacom.atguigu.gulimall.product.GulimallProductApplicationTests类里添加方法,点击方法左侧的运行按钮

📌如果报空指针,说明你gulimall_pms数据库的pms_category表里面不存在cat_id为你写的那个id

@Autowired
CategoryService categoryService;

@Test
public void test(){
   Long[] catelogPath = categoryService.findCatelogPath(413L);
   System.out.println(Arrays.toString(catelogPath));
}
[Ljava.lang.Object; cannot be cast to [Ljava.lang.Long;
Object[]类型不能强转成Long[]类型
image-20220510113030426
image-20220510113030426
image-20220510114911801
image-20220510114911801
6、修改findCatelogPath方法

修改com.atguigu.gulimall.product.service.impl.CategoryServiceImpl类的findCatelogPath方法

return (Long[]) parentPath.toArray();

改成

return parentPath.toArray(new Long[parentPath.size()]);

测试成功,但又提示Call to 'toArray()' with pre-sized array argument 'new Long[parentPath.size()]'

image-20220510115355135
image-20220510115355135

可以改成

return parentPath.toArray(new Long[0]);

这样写测试也没有问题

image-20220510115533565
image-20220510115533565

📌其实findParentPath方法的返回值有点多余

📌findCatelogPath方法也可以直接返回List<Long>类型,返回到前端的内容Long[]List<Long>都是一样的

5、测试

重启gulimall-product项目后测试

1、点击修改按钮

点击测试数据修改按钮,可以看到所属分类id已正确回显

image-20220510123323627
image-20220510123323627
2、点击新增按钮

点击新增按钮,发现所属分类id没有删除

image-20220510123145766
image-20220510123145766
3、添加closed回调

element-uiDialog对话框中的事件:组件 | Elementopen in new window

事件名称说明
closedDialog 关闭动画结束时的回调

src\views\modules\product\attrgroup-add-or-update.vueel-dialog标签添加属性

 @closed="dialogClose"

并再method里面添加方法,清空catelogPath

dialogClose(){
  this.dataForm.catelogPath = [];
},
4、查看是否清除

刷新页面,先点击修改,再点击新增可以看到已经没有上次修改回显执行后留下的所属分类id的信息了

image-20220510125315522
image-20220510125315522
5、可搜索级联选择器

修改src\views\modules\product\attrgroup-add-or-update.vue里的el-cascader标签

elememt-uiCascader 级联选择器里面的可搜索组件 | Elementopen in new window

添加filterable就变成可搜索的了,添加placeholder属性可以给予提示

<el-cascader
  v-model="dataForm.catelogPath"
  :options="categorys"
  :props="props"
  placeholder="试试搜索:手机"
  filterable
></el-cascader>

输入手机后,下面可以提供选择

image-20220510130049823
image-20220510130049823

4.4、商品服务-API-品牌管理

4.4.1、完善品牌管理

1、重新执行sql

重新打开1.分布式基础(全栈开发篇)\资料源码.zip\docs\代码\sql下的pms_catelog.sql文件,复制内容

点击gulimall_pms数据库,右键选择命令行界面,粘贴刚刚复制的内容,点击回车,执行sql语句

(不要点击运行SQL文件,这样会有中文乱码问题)

image-20220510145629313
image-20220510145629313

2、添加分页插件

1、总条数错误

url:http://localhost:8001/#/product-brand

image-20220510150440811
image-20220510150440811
2、添加分页插件

gulimall-product模块的com.atguigu.gulimall.product文件夹下新建config文件夹

config文件夹下新建MyBatisConfig

package com.atguigu.gulimall.product.config;

import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;

/**
 * @author 无名氏
 * @date 2022/5/10
 * @Description:
 * @EnableTransactionManagement :开启事务功能
 */
@Configuration
@EnableTransactionManagement
@MapperScan("com.atguigu.gulimall.product.dao")
public class MyBatisConfig {

    /**
     * 引入分页插件
     * @return
     */
    @Bean
    public PaginationInterceptor paginationInterceptor(){
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        //设置请求的页面大于最大页后操作,true调回到首页,false 继续请求 默认false
        paginationInterceptor.setOverflow(false);
        //设置最大单页限制数量,默认500条,-1 不受限制
        paginationInterceptor.setLimit(1000);

        return paginationInterceptor;
    }
}
image-20220510151731315
image-20220510151731315
3、查看条数

重启gulimall-product模块,刷新前端页面,可以看到总条数显示正确

image-20220510151901745
image-20220510151901745

3、修改查询逻辑

1、模糊查询失败

根据品牌id查询

image-20220510154018496
image-20220510154018496

根据品牌名模糊查询

image-20220510154130509
image-20220510154130509
2、根据品牌id品牌名查询

修改gulimall-product模块里com.atguigu.gulimall.product.service.impl.BrandServiceImpl类下的queryPage方法

@Override
public PageUtils queryPage(Map<String, Object> params) {
    String key = (String) params.get("key");
    LambdaQueryWrapper<BrandEntity> lambdaQueryWrapper = new LambdaQueryWrapper<BrandEntity>();
    if (key!=null && key.length()>0){
        lambdaQueryWrapper.eq(BrandEntity::getBrandId,key)
                .or().like(BrandEntity::getName,key);
    }
    IPage<BrandEntity> page = this.page(
            new Query<BrandEntity>().getPage(params),
           lambdaQueryWrapper
    );

    return new PageUtils(page);
}
image-20220510154241194
image-20220510154241194
3、测试

重新运行gulimall-product模块,进行测试

根据品牌id查询

image-20220510153551322
image-20220510153551322

根据品牌名模糊查询

image-20220510153617854
image-20220510153617854

都没有什么问题

4、添加数据

1、删除品牌管理数据
image-20220510201428448
image-20220510201428448

可以在数据库执行以下命令,清空gulimall_pms.pms_brand表数据,并让主键重新从1开始

truncate gulimall_pms.pms_brand;

也可以选中表-->右键-->截断表

image-20220511151930445
image-20220511151930445
2、添加有用数据

图片在1.分布式基础(全栈开发篇)\资料源码.zip\docs\pics文件夹里面

image-20220510223618376
image-20220510223618376

添加华为品牌

image-20220510223418633
image-20220510223418633

添加小米品牌

image-20220510223456458
image-20220510223456458

添加oppo品牌

image-20220510223906193
image-20220510223906193

添加Apple品牌

image-20220510224046126
image-20220510224046126

所有品牌

image-20220510224352810
image-20220510224352810
3、复制文件

📌最好先提交到远程仓库复制前端项目再操作,最少也要复制renren-fast-vue\src\views\modules下的commonproduct文件夹

  1. 复制1.分布式基础(全栈开发篇)\资料源码.zip\docs\代码\前端\modules里的commonproduct文件夹
  2. 点击src下的"views",然后右键
  3. 选择在文件资源管理器中显示
  4. 进入到view下的modules里面
  5. 右键,选择粘贴
  6. 选择替换目标中的文件
image-20220510201443183
image-20220510201443183

4.4.2、根据品牌id查询品牌和三级分类的关联关系

【属性分组-规格参数-销售属性-三级分类】关联关系

image-20220721202638445

SPU-SKU-属性

image-20220721202802806

SPU-SKU-属性表

image-20220721202830773

1、添加catelogList方法

gulimall-product模块的com.atguigu.gulimall.product.controller.CategoryBrandRelationController类里添加方法

/**
 * 列表
 * @GetMapping = @RequestMapping(method = RequestMethod.GET)
 */
@GetMapping("/catelog/list")
public R catelogList(@RequestParam(value = "brandId") Long brandId){
    List<CategoryBrandRelationEntity> data = categoryBrandRelationService.listByBrandId(brandId);

    return R.ok().put("data", data);
}
image-20220510213324100
image-20220510213324100

2、添加listByBrandId抽象方法

com.atguigu.gulimall.product.service.CategoryBrandRelationService接口里添加listByBrandId抽象方法

List<CategoryBrandRelationEntity> listByBrandId(Long brandId);
image-20220510213901065
image-20220510213901065

3、实现listByBrandId抽象方法

com.atguigu.gulimall.product.service.impl.CategoryBrandRelationServiceImpl类里

实现CategoryBrandRelationService接口未实现的listByBrandId方法

@Override
public List<CategoryBrandRelationEntity> listByBrandId(Long brandId) {
    LambdaQueryWrapper<CategoryBrandRelationEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    return this.list(lambdaQueryWrapper.eq(CategoryBrandRelationEntity::getBrandId, brandId));
}
image-20220510214006492
image-20220510214006492

4.4.3、保存品牌和分类完整信息

1、修改save方法

gulimall-product模块的com.atguigu.gulimall.product.controller.CategoryBrandRelationController类里

修改save方法,保存其brand_id、catelog_id、brand_name、catelog_name

传来的数据只有brand_id、catelog_id,调用categoryBrandRelationServicesaveDetail查出brand_namecatelog_name

并将完整的brand_id、catelog_id、brand_name、catelog_name保存到数据库

@RequestMapping("/save")
    public R save(@RequestBody CategoryBrandRelationEntity categoryBrandRelation){
    // 保存 brand_id、catelog_id、brand_name、catelog_name
    categoryBrandRelationService.saveDetail(categoryBrandRelation);

    return R.ok();
}
image-20220510215925645
image-20220510215925645

2、添加saveDetail抽象方法

com.atguigu.gulimall.product.service.CategoryBrandRelationService接口里添加saveDetail抽象方法

void saveDetail(CategoryBrandRelationEntity categoryBrandRelation);
image-20220510215959515
image-20220510215959515

3、实现saveDetail抽象方法

com.atguigu.gulimall.product.service.impl.CategoryBrandRelationServiceImpl类里

实现CategoryBrandRelationService接口未实现的saveDetail方法

@Autowired
BrandDao brandDao;
@Autowired
CategoryDao categoryDao;
    
/**
 *原本的save方法只能保存 brand_id 和 catelog_id 的关联关系,不能保存 brand_name 和 catelog_name
 * 保存 brand_id、catelog_id、brand_name、catelog_name
 * @param categoryBrandRelation
 */
@Override
public void saveDetail(CategoryBrandRelationEntity categoryBrandRelation) {
    Long brandId = categoryBrandRelation.getBrandId();
    Long catelogId = categoryBrandRelation.getCatelogId();

    //根据brandId 查询 brandName
    BrandEntity brandEntity = brandDao.selectById(brandId);
    //根据 categoryId 查询 categoryName
    CategoryEntity categoryEntity = categoryDao.selectById(catelogId);

    categoryBrandRelation.setBrandName(brandEntity.getName());
    categoryBrandRelation.setCatelogName(categoryEntity.getName());

    this.save(categoryBrandRelation);
}

这里报红不用管,使用MyBatis时dao接口没有实现类,接口是没有办法创建实例的,因此也就无法注入到ioc容器

IDEA检测到没有注入到ioc容器,所有就报红了

image-20220510220108353
image-20220510220108353

4、解决IDEA报红

按住ctrl键,点击BrandDao进入BrandDao

image-20220510222434101
image-20220510222434101

添加@Repository注解,显式的把BrandDao添加到ioc容器

image-20220510222612590
image-20220510222612590

添加@Repository注解,显式的把CategoryDao添加到ioc容器

image-20220510222727987
image-20220510222727987

已经不报红了(其实不修改也行)

image-20220510223032990
image-20220510223032990

5、测试

重启gulimall-product模块,刷新前端页面

1、添加关联分类
3
3
2、查看回显数据

URL: http://localhost:88/api/product/categorybrandrelation/catelog/list?t=1652197270780&brandId=1

回显正常,证明catelogList方法和save方法正确

image-20220510234520024
image-20220510234520024
3、查看数据库

可以看到已经brand_id、catelog_id、brand_name、catelog_name全部添加到数据库了,所有save方法正确

image-20220510233840276
image-20220510233840276

4.4.4、保证数据一致性

当修改brand_namecatelog_name时,不仅要更新pms_categorypms_brand

同时也要更新pms_category_brand_relation表,用来保证冗余字段的一致性

1、修改update方法

修改gulimall-product模块下的com.atguigu.gulimall.product.controller.BrandController类的update方法

@RequestMapping("/update")
public R update(@Validated(UpdateGroup.class) @RequestBody BrandEntity brand) {
    brandService.updateDetail(brand);

    return R.ok();
}
image-20220510235115019
image-20220510235115019

2、添加updateDetail抽象方法

com.atguigu.gulimall.product.service.BrandService接口里添加updateDetail方法

void updateDetail(BrandEntity brand);
image-20220510235219096
image-20220510235219096

3、实现updateDetail抽象方法

com.atguigu.gulimall.product.service.impl.BrandServiceImpl类里实现updateDetail抽象方法

@Autowired
CategoryBrandRelationService categoryBrandRelationService;

@Override
public void updateDetail(BrandEntity brand) {
    //保证冗余字段的数据一致
    this.updateById(brand);
    if (StringUtils.hasLength(brand.getName())){
        //同步更新其他关联表中的数据
        categoryBrandRelationService.updateBrand(brand);
        
        //TODO 更新其他关联
    }
}
image-20220511000527982
image-20220511000527982

4、添加updateBrand抽象方法

com.atguigu.gulimall.product.service.CategoryBrandRelationService接口里添加updateBrand抽象方法

void updateBrand(BrandEntity brand);
image-20220511000607317
image-20220511000607317

5、实现updateBrand抽象方法

com.atguigu.gulimall.product.service.impl.CategoryBrandRelationServiceImpl类里实现updateBrand抽象方法

@Override
public void updateBrand(BrandEntity brand) {
    CategoryBrandRelationEntity categoryBrandRelationEntity = new CategoryBrandRelationEntity();
    categoryBrandRelationEntity.setBrandId(brand.getBrandId());
    categoryBrandRelationEntity.setBrandName(brand.getName());

    LambdaUpdateWrapper<CategoryBrandRelationEntity> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
    lambdaUpdateWrapper.eq(CategoryBrandRelationEntity::getBrandId,brand.getBrandId());
    this.update(categoryBrandRelationEntity,lambdaUpdateWrapper);
}
image-20220511001114986
image-20220511001114986

6、测试

1、重启项目前

可以看到当修改品牌名后,关联关系表并没有更新,因此没有正确回显

GIF 2022-5-11 0-14-16
GIF 2022-5-11 0-14-16
image-20220511001742563
image-20220511001742563
image-20220511001759165
image-20220511001759165
2、重启项目后

华为1改为华为,重启gulimall-product项目后,再次测试

可以看到当修改品牌名后,关联关系表已经更新,并且正确回显

(成功后要恢复为原始数据)

GIF 2022-5-11 0-20-17
GIF 2022-5-11 0-20-17
image-20220511002213445
image-20220511002213445
image-20220511002226070
image-20220511002226070

4.4.5、保证Category数据一致性

1、修改Categoryupdate方法

gulimall-product模块的com.atguigu.gulimall.product.controller.CategoryController类里修改update方法

@RequestMapping("/update")
    public R update(@RequestBody CategoryEntity category){
    //级联更新
    categoryService.updateCascade(category);

    return R.ok();
}
image-20220511002457753
image-20220511002457753

2、添加updateCascade抽象方法

gulimall-product模块的com.atguigu.gulimall.product.service.CategoryService类里添加updateCascade抽象方法

void updateCascade(CategoryEntity category);
image-20220511002632061
image-20220511002632061

3、实现updateCascade抽象方法

com.atguigu.gulimall.product.service.impl.CategoryServiceImpl类里实现updateCascade抽象方法

@Autowired
CategoryBrandRelationService categoryBrandRelationService;

/**
 * 级联更新所有的数据
 * @param category
 */
@Override
public void updateCascade(CategoryEntity category) {
    this.updateById(category);
    categoryBrandRelationService.updateCategory(category);
}
image-20220511003343110
image-20220511003343110

4、添加updateCategory抽象方法

com.atguigu.gulimall.product.service.CategoryBrandRelationService接口里添加updateCategory抽象方法

void updateCategory(CategoryEntity category);
image-20220511003421670
image-20220511003421670

5、实现updateCategory抽象方法

com.atguigu.gulimall.product.service.impl.CategoryBrandRelationServiceImpl类里实现updateCategory抽象方法

@Override
public void updateCategory(CategoryEntity category) {
   this.baseMapper.updateCategory(category);
}
image-20220511113803364
image-20220511113803364

6、添加updateCategory抽象方法

com.atguigu.gulimall.product.dao.CategoryBrandRelationDao类添加updateCategory抽象方法

void updateCategory(CategoryEntity category);

鼠标悬浮在updateCategory上或使用alt+enter快捷键点击Generater statement,如果没有这个选项说明没有安装MyBatis插件

没有小鸟图标也可以证明没有安装MyBatis插件

image-20220511123714784
image-20220511123714784
🚀Mybatis传递多个参数的4种方式
方法1:顺序传参法
public User selectUser(String name, int deptId);

<select id="selectUser" resultMap="UserResultMap">
    select * from user
    where user_name = #{0} and dept_id = #{1}
</select>
123456

#{}里面的数字代表你传入参数的顺序。

这种方法不建议使用,sql层表达不直观,且一旦顺序调整容易出错。

方法2:@Param注解传参法
public User selectUser(@Param("userName") String name, int @Param("deptId") deptId);

<select id="selectUser" resultMap="UserResultMap">
    select * from user
    where user_name = #{userName} and dept_id = #{deptId}
</select>
123456

#{}里面的名称对应的是注解 @Param括号里面修饰的名称。 这种方法在参数不多的情况还是比较直观的,推荐使用。

方法3:Map传参法
public User selectUser(Map<String, Object> params);

<select id="selectUser" parameterType="java.util.Map" resultMap="UserResultMap">
    select * from user
    where user_name = #{userName} and dept_id = #{deptId}
</select>
123456

#{}里面的名称对应的是 Map里面的key名称。

这种方法适合传递多个参数,且参数易变能灵活传递的情况。

方法4:Java Bean传参法
public User selectUser(User params);

<select id="selectUser" parameterType="com.test.User" resultMap="UserResultMap">
    select * from user
    where user_name = #{userName} and dept_id = #{deptId}
</select>

#{}里面的名称对应的是 User类里面的成员属性。

这种方法很直观,但需要建一个实体类,扩展不容易,需要加属性,看情况使用。

🚀添加MyBatis插件

点击File-->Settings...-->Plugins-->搜索MyBatisX-->点击install

image-20220511145711068
image-20220511145711068

7、添加updateCategory的sql语句

src/main/resources/mapper/product/CategoryBrandRelationDao.xml中添加updateCategory的sql语句

<update id="updateCategory" parameterType="categoryEntity">
    update gulimall_pms.pms_category_brand_relation
    set catelog_name=#{name}
    where catelog_id=#{catId}
</update>
image-20220511124930457
image-20220511124930457

8、为实体类起别名

src/main/resources/application.yml里添加配置,为com.atguigu.gulimall.product.entity包下的实体类起别名

mybatis-plus:
  #起别名
  type-aliases-package: com.atguigu.gulimall.product.entity
image-20220511125048450
image-20220511125048450

9、单元测试

src/test/java/com/atguigu/gulimall/product/GulimallProductApplicationTests.java里添加测试方法

使用MyBatis时dao接口没有实现类,接口是没有办法创建实例的,因此也就无法注入到ioc容器

IDEA检测到没有注入到ioc容器,所有就报红了

可以按ctrl键并点击CategoryBrandRelationDao,在该类上添加@Repository注解,显式的把BrandDao添加到ioc容器,就不报红了

@Autowired
CategoryBrandRelationDao categoryBrandRelationDao;
@Test
public void testCategoryBrandRelationDao(){
   CategoryEntity categoryEntity = new CategoryEntity();
   categoryEntity.setCatId(225L);
   categoryEntity.setName("手机2");
   categoryBrandRelationDao.updateCategory(categoryEntity);
}
image-20220511151116797
image-20220511151116797

数据库已经更新

image-20220511125255539
image-20220511125255539

10、前端测试

把数据在改回来(把brand_name华为1的修改为华为、把catelog_name手机2的修改为手机)

然后重启gulimall-product项目,刷新前端页面进行测试,可以看到冗余字段已经更新了

(成功后要恢复为原始数据)

GIF 2022-5-11 15-30-12
GIF 2022-5-11 15-30-12

4.4.6、开启事务功能

1、开启事务功能

com.atguigu.gulimall.product.config.MyBatisConfig配置类里开启事务功能(已经开启过了)

@EnableTransactionManagement
image-20220511004119519
image-20220511004119519

2、updateCascade方法上添加事务注解

com.atguigu.gulimall.product.service.impl.CategoryServiceImpl类的updateCascade方法上添加@Transactional注解

image-20220511004035837
image-20220511004035837

3、updateDetail方法上添加事务注解

com.atguigu.gulimall.product.service.impl.BrandServiceImpl类的updateDetail方法上添加@Transactional注解

image-20220511004000692
image-20220511004000692

4.5、商品服务-API-平台属性

4.5.1、规格参数新增与VO

1、修改queryPage方法

1、查询全部时有查询条件

url: http://localhost:88/api/product/attrgroup/list/0?t=1652341878270&page=1&limit=10&key=%E4%B8%BB

以前写的只有catelogId != 0时才带查询条件,因此输入查询条件,然后点击查询全部,并没有根据条件查询全部

image-20220512160738015
image-20220512160738015
2、以前写的只有catelogId != 0时才带查询条件
@Override
public PageUtils queryPage(Map<String, Object> params, Long catelogId) {
    if (catelogId == 0){
        return this.queryPage(params);
    }else {
        String key = (String) params.get("key");
        //select * from attr_group where catelogId = ? and ( attr_group_id = key or attr_group_name like %key%)
        LambdaQueryWrapper<AttrGroupEntity> queryWrapper = new LambdaQueryWrapper<AttrGroupEntity>()
                .eq(AttrGroupEntity::getCatelogId,catelogId);
        if (key != null && key.length() > 0){
            queryWrapper.and((obj)->{
                obj.eq(AttrGroupEntity::getAttrGroupId,key).or().like(AttrGroupEntity::getAttrGroupName,key);
            });
        }
        IPage<AttrGroupEntity> page = this.page(new Query<AttrGroupEntity>().getPage(params),queryWrapper);
        return new PageUtils(page);
    }
}
image-20220512155755111
image-20220512155755111
3、修改queryPage方法

修改gulimall-product模块的com.atguigu.gulimall.product.service.impl.AttrGroupServiceImpl里的queryPage方法,

使得不管catelogId是否等于0,都带上查询条件

@Override
public PageUtils queryPage(Map<String, Object> params, Long catelogId) {
    String key = (String) params.get("key");

    LambdaQueryWrapper<AttrGroupEntity> queryWrapper = new LambdaQueryWrapper<AttrGroupEntity>();

    if (key != null && key.length() > 0) {
        queryWrapper.and(obj -> {
            obj.eq(AttrGroupEntity::getAttrGroupId, key).or().like(AttrGroupEntity::getAttrGroupName, key);
        });
    }


    if (catelogId == 0) {
        //select * from attr_group where ( attr_group_id = key or attr_group_name like %key%)
        IPage<AttrGroupEntity> page = this.page(new Query<AttrGroupEntity>().getPage(params), queryWrapper);
        return new PageUtils(page);
    } else {
        //select * from attr_group where ( attr_group_id = key or attr_group_name like %key%) and catelogId = ?
        queryWrapper.eq(AttrGroupEntity::getCatelogId, catelogId);
        IPage<AttrGroupEntity> page = this.page(new Query<AttrGroupEntity>().getPage(params), queryWrapper);
        return new PageUtils(page);
    }
}
SELECT attr_group_id,icon,catelog_id,sort,descript,attr_group_name FROM pms_attr_group 
WHERE (( (attr_group_id = ? OR attr_group_name LIKE ?) ) AND catelog_id = ?) LIMIT ?,? 
image-20220512000235143
image-20220512000235143
4、错误写法
@Override
public PageUtils queryPage(Map<String, Object> params, Long catelogId) {
    String key = (String) params.get("key");

    LambdaQueryWrapper<AttrGroupEntity> queryWrapper = new LambdaQueryWrapper<AttrGroupEntity>();

    if (key != null && key.length() > 0) {
        queryWrapper.eq(AttrGroupEntity::getAttrGroupId, key).or().like(AttrGroupEntity::getAttrGroupName, key);
    }


    if (catelogId == 0) {
        //select * from attr_group where ( attr_group_id = key or attr_group_name like %key%)
        IPage<AttrGroupEntity> page = this.page(new Query<AttrGroupEntity>().getPage(params), queryWrapper);
        return new PageUtils(page);
    } else {
        //select * from attr_group where ( attr_group_id = key or attr_group_name like %key%) and catelogId = ?
        queryWrapper.and(obj->obj.eq(AttrGroupEntity::getCatelogId, catelogId));
        IPage<AttrGroupEntity> page = this.page(new Query<AttrGroupEntity>().getPage(params), queryWrapper);
        return new PageUtils(page);
    }
}

这样写的queryWrapper.and(obj->obj.eq(AttrGroupEntity::getCatelogId, catelogId));使用and就没意义了

SELECT attr_group_id,icon,catelog_id,sort,descript,attr_group_name FROM pms_attr_group WHERE (attr_group_id = ? OR attr_group_name LIKE ? AND ( (catelog_id = ?) )) LIMIT ?,? 
image-20220512000844124
image-20220512000844124
5、测试

重启gulimall-product模块,然后刷新前端页面

重新输入查询条件,然后点击查询全部,已经根据条件查询全部了

image-20220512161159756
image-20220512161159756

2、测试新增属性

1、基础属性

当点击商品系统下的平台属性下的规格参数时,会获取基础属性(规格参数,如机身颜色等)的列表

image-20220512161328451
image-20220512161328451
2、新建属性(规格参数)
image-20220512162502623
image-20220512162502623
3、保存成功了
image-20220512162518487
image-20220512162518487
4、但是只是基本保存

只是基本保存,只是保存了是哪个分类的,并没有保存是哪个分组

image-20220512162814832
image-20220512162814832

分类属性保存进来了

image-20220512163039683
image-20220512163039683

但是关联关系没保存

image-20220512163053494
image-20220512163053494

3、修改方法

1、添加AttrVo

由于在实体类里添加字段,然后再标注@TableField(exist = false)注解,告诉MyBatis该字段不存在是很不规范的,

所以可以使用Vo对象,Vo是用来封装请求和响应数据的,不建议继承实体类,因遵循多实现,少继承的原则

AttrVo类里面跟数据库相关的注解就不需要了

在``gulimall-product模块的com.atguigu.gulimall.product包下新建vo`文件夹

com.atguigu.gulimall.product.vo包下新建AttrVo

package com.atguigu.gulimall.product.vo;

import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;

/**
 * @author 无名氏
 * @date 2022/5/12
 * @Description:
 */
@Data
public class AttrVo {
    /**
     * 属性id
     */
    private Long attrId;
    /**
     * 属性名
     */
    private String attrName;
    /**
     * 是否需要检索[0-不需要,1-需要]
     */
    private Integer searchType;
    /**
     * 属性图标
     */
    private String icon;
    /**
     * 可选值列表[用逗号分隔]
     */
    private String valueSelect;
    /**
     * 属性类型[0-销售属性,1-基本属性,2-既是销售属性又是基本属性]
     */
    private Integer attrType;
    /**
     * 启用状态[0 - 禁用,1 - 启用]
     */
    private Long enable;
    /**
     * 所属分类
     */
    private Long catelogId;
    /**
     * 快速展示【是否展示在介绍上;0-否 1-是】,在sku中仍然可以调整
     */
    private Integer showDesc;

    private Long attrGroupId;
}
image-20220512164302169
image-20220512164302169

扩展:

Object 划分

1.PO(persistant object) 持久对象

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

2.DO(Domain Object)领域对象

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

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

不同的应用程序之间传输的对象

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

这个概念来源于 J2EE 的设计模式,原来的目的是为了 EJB 的分布式应用提供粗粒度的数据实体,以减少分布式调用的次数,

从而提高分布式调用的性能和降低网络负载,但在这里,泛指用于展示层与服务层之间的数据传输对象。

5.VO(value object) 值对象

通常用于业务层之间的数据传递,和 PO 一样也是仅仅包含数据而已。但应是抽象出的业务对象 , 可以和表对应 , 也可以不 ,

这根据业务的需要 。用 new 关键字创建,由GC 回收的。 View object:视图对象; 接受页面传递来的数据,封装对象 将业务处理完成的对象,封装成页面要用的数据

6.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 的统称。

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

是一个 sun 的一个标准 j2ee 设计模式, 这个模式中有个接口就是 DAO ,它负持久层的操作。为业务层提供接口。

此对象用于访问数据库。通常和 PO 结合使用, DAO 中包含了各种数据库的操作方法。通过它的方法 ,

结合 PO 对数据库进行相关的操作。夹在业务逻辑与数据库资源中间。配合 VO, 提供数据库的 CRUD 操作

2、修改save方法

修改gulimall-product模块的com.atguigu.gulimall.product.controller.AttrController类的save方法

@RequestMapping("/save")
public R save(@RequestBody AttrVo attr) {
    attrService.saveAttr(attr);

    return R.ok();
}
image-20220512165118105
image-20220512165118105
3、添加saveAttr抽象方法

gulimall-product模块的com.atguigu.gulimall.product.service.AttrService接口里添加saveAttr抽象方法

void saveAttr(AttrVo attr);
image-20220512165526385
image-20220512165526385
4、实现saveAttr抽象方法

gulimall-product模块的com.atguigu.gulimall.product.service.impl.AttrServiceImpl类里实现saveAttr抽象方法

@Override
public void saveAttr(AttrVo attr) {
    AttrEntity attrEntity = new AttrEntity();
    //将attr中的数据复制到attrEntity对应的字段里
    BeanUtils.copyProperties(attr,attrEntity);
    //1、保存基本数据
    this.save(attrEntity);
    //2、保存关联关系
    AttrAttrgroupRelationEntity attrAttrgroupRelationEntity = new AttrAttrgroupRelationEntity();
    attrAttrgroupRelationEntity.setAttrGroupId(attr.getAttrGroupId());
    //调用this.save(attrEntity);方法后,会将数据库生成的attrId封装到AttrEntity里面
    attrAttrgroupRelationEntity.setAttrId(attrEntity.getAttrId());
    attrAttrgroupRelationDao.insert(attrAttrgroupRelationEntity);
}
image-20220512171158059
image-20220512171158059
5、测试

重启gulimall-product模块,重新添加规格参数

image-20220512171458466
image-20220512171458466

关联关系已经成功保存了

image-20220512171744173
image-20220512171744173

4.5.2、规格参数列表显示

1、添加查询规格参数列表功能

1、查看请求

当点击规格参数里的手机\手机通讯\手机后,会发送这个请求

http://localhost:88/api/product/attr/base/list/225?t=1652347219117&page=1&limit=10&key=

image-20220512172045858
image-20220512172045858

接口文档:05、获取分类规格参数 - 谷粒商城 - 易文档 (easydoc.net)open in new window

image-20220518194247523
image-20220518194247523
2、添加baseAttrList方法

gulimall-product模块的com.atguigu.gulimall.product.controller.AttrController类里添加baseAttrList方法

这里的代码有问题,应该为@GetMapping("/base/list/{categoryId}"),多写了个$

@GetMapping("/base/list/${categoryId}")
public R baseAttrList(@RequestParam Map<String, Object> params,@PathVariable("categoryId") Long categoryId){
    PageUtils page = attrService.queryBaseAttrPage(params,categoryId);
    return R.ok().put("page", page);
}
image-20220512173043820
image-20220512173043820
3、添加queryBaseAttrPage抽象方法

gulimall-product模块的com.atguigu.gulimall.product.service.AttrService接口里添加queryBaseAttrPage抽象方法

PageUtils queryBaseAttrPage(Map<String, Object> params, Long categoryId);
image-20220512173145318
image-20220512173145318
4、实现queryBaseAttrPage抽象方法

gulimall-product模块的com.atguigu.gulimall.product.service.impl.AttrServiceImpl类里实现queryBaseAttrPage抽象方法

@Override
public PageUtils queryBaseAttrPage(Map<String, Object> params, Long categoryId) {
    LambdaQueryWrapper<AttrEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    if (categoryId != 0) {
        lambdaQueryWrapper.eq(AttrEntity::getCatelogId, categoryId);
    }

    String key = (String) params.get("key");
    if (StringUtils.hasLength(key)){
        lambdaQueryWrapper.and((obj)->{
           obj.eq(AttrEntity::getAttrId,key).or().like(AttrEntity::getAttrName,key);
        });
    }
    IPage<AttrEntity> page = this.page(new Query<AttrEntity>().getPage(params), lambdaQueryWrapper);
    return new PageUtils(page);
}
image-20220512174710768
image-20220512174710768
5、测试

重启gulimall-product模块,刷新前端界面

报错了

Could not resolve placeholder 'categoryId' in value "/base/list/${categoryId}"
image-20220512175657245
image-20220512175657245

修改代码,删掉@GetMapping("/base/list/{categoryId}")里面多写的$

@GetMapping("/base/list/{categoryId}")
public R baseAttrList(@RequestParam Map<String, Object> params,@PathVariable("categoryId") Long categoryId){
    PageUtils page = attrService.queryBaseAttrPage(params,categoryId);
    return R.ok().put("page", page);
}
image-20220512231037337
image-20220512231037337

重新启动gulimall-product模块,刷新前端界面

已经显示出来了,但是所属分类所属分组没有显示

image-20220512231242187
image-20220512231242187

2、显示所属分类和所属分组

1、新建AttrRespVo

gulimall-product模块下的com.atguigu.gulimall.product.vo包内新建AttrRespVo类,继承AttrVo

这里的categoryName是错的,应该为catelogName

package com.atguigu.gulimall.product.vo;

import lombok.Data;

/**
 * @author 无名氏
 * @date 2022/5/12
 * @Description:
 */
@Data
public class AttrRespVo extends AttrVo{

    /**
     * 所属分类名   /手机/数码/手机
     */

    private String categoryName;
    /**
     * 所属分组名  主机
     */
    private String groupName;
}
image-20220513000203162
image-20220513000203162
2、修改queryBaseAttrPage方法

gulimall-product模块的com.atguigu.gulimall.product.service.impl.AttrServiceImpl类里修改queryBaseAttrPage方法

在大型项目中,连表查询很危险,做笛卡儿积会使数据量非常大,因此也不推荐使用外键,使用service来处理表之间的关系

streatmap那,IDEA提示建议使用peek来代替mapjava.util.Stream.peek()主要用于支持调试。如果流管道不包含终端操作,则不会使用任何元素,并且根本不会调用peek()操作。所以最好不要使用peek

@Autowired
CategoryDao categoryDao;
@Autowired
AttrGroupDao attrGroupDao;

@Override
public PageUtils queryBaseAttrPage(Map<String, Object> params, Long categoryId) {
    LambdaQueryWrapper<AttrEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    if (categoryId != 0) {
        //如果有categoryId,则查该categoryId的数据
        lambdaQueryWrapper.eq(AttrEntity::getCatelogId, categoryId);
    }

    String key = (String) params.get("key");
    if (StringUtils.hasLength(key)) {
        lambdaQueryWrapper.and((obj) -> {
            //如果有查询条件,则判断该条件是否 与attrId相等 或 名字包含该条件
            obj.eq(AttrEntity::getAttrId, key).or().like(AttrEntity::getAttrName, key);
        });
    }
    IPage<AttrEntity> page = this.page(new Query<AttrEntity>().getPage(params), lambdaQueryWrapper);

    //查询categoryName字段和groupName字段
    List<AttrEntity> list = page.getRecords();
    List<AttrRespVo> respVos = list.stream().map(attrEntity -> {
        AttrRespVo attrRespVo = new AttrRespVo();
        BeanUtils.copyProperties(attrEntity, attrRespVo);
        //根据attrId到attr和attrgroup的中间表查询 attrgroupId
        LambdaQueryWrapper<AttrAttrgroupRelationEntity> attrAttrgroupRelationQueryWrapper = new LambdaQueryWrapper<>();
        attrAttrgroupRelationQueryWrapper.eq(AttrAttrgroupRelationEntity::getAttrId, attrEntity.getAttrId());
        //根据attrgroupId查询中间表的该行数据,并封装到对象
        AttrAttrgroupRelationEntity attrAttrgroupRelationEntity = attrAttrgroupRelationDao.selectOne(attrAttrgroupRelationQueryWrapper);
        if (attrAttrgroupRelationEntity != null) {
            //如果查到attrgroupId,则根据attrgroupId查询attrgroupName,并添加到attrRespVo中
            AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(attrAttrgroupRelationEntity.getAttrGroupId());
            attrRespVo.setGroupName(attrGroupEntity.getAttrGroupName());
        }

        LambdaQueryWrapper<CategoryEntity> categoryQueryWrapper = new LambdaQueryWrapper<>();
        categoryQueryWrapper.eq(CategoryEntity::getCatId, attrEntity.getCatelogId());
        //根据catelogId查询该行数据,并封装到对象
        CategoryEntity categoryEntity = categoryDao.selectOne(categoryQueryWrapper);
        if (categoryEntity != null) {
            attrRespVo.setCategoryName(categoryEntity.getName());
        }
        return attrRespVo;
    }).collect(Collectors.toList());

    PageUtils pageUtils = new PageUtils(page);
    //重新给数据
    pageUtils.setList(respVos);
    return pageUtils;
}
3、测试

重启gulimall-product模块,刷新前端页面

1、所属分类没有数据,所属分组有数据
image-20220513102104224
image-20220513102104224
2、查看所属分类绑定的名字

可以看到绑定的名字为catelogName,而后端传过来的为categoryName

image-20220513102522427
image-20220513102522427

为什么是这个文件呢?

可以查看就是这个方法发送的请求

image-20220513102715896
image-20220513102715896
3、修改字段名称

修改gulimall-product模块com.atguigu.gulimall.product.vo.AttrRespVo类的字段

categoryName改为catelogName

image-20220513102854084
image-20220513102854084

修改gulimall-product模块的com.atguigu.gulimall.product.service.impl.AttrServiceImpl类的queryBaseAttrPage方法

attrRespVo.setCategoryName(categoryEntity.getName());

改为

attrRespVo.setCatelogName(categoryEntity.getName());
image-20220513102940156
image-20220513102940156
4、重新测试

重启gulimall-product模块,刷新前端页面,已成功显示所属分类

image-20220513103829286
image-20220513103829286

4.5.3、规格参数修改回显

1、查看需要回显的数据

点击修改按钮,可以看到还需要会先拿属性分类所属分组,而且我点的是修改,这个表单却显示的是修改

image-20220513104507146
image-20220513104507146

2、填加catelogPath字段

gulimall-product模块的com.atguigu.gulimall.product.vo.AttrRespVo类里添加catelogPath字段

private Long[] catelogPath;
image-20220513104838259
image-20220513104838259

3、修改info方法

gulimall-product模块里,修改com.atguigu.gulimall.product.controller.AttrController类的info方法

@RequestMapping("/info/{attrId}")
public R info(@PathVariable("attrId") Long attrId) {
    //AttrEntity attr = attrService.getById(attrId);
    AttrRespVo respVo = attrService.getAttrInfo(attrId);
    return R.ok().put("attr", respVo
    );
}
image-20220513105158303
image-20220513105158303

4、添加getAttrInfo抽象方法

gulimall-product模块的com.atguigu.gulimall.product.service.AttrService接口里添加getAttrInfo抽象方法

AttrRespVo getAttrInfo(Long attrId);
image-20220513105520512
image-20220513105520512

5、实现getAttrInfo抽象方法

gulimall-product模块的com.atguigu.gulimall.product.service.impl.AttrServiceImpl类里实现getAttrInfo抽象方法

@Override
public AttrRespVo getAttrInfo(Long attrId) {
    AttrRespVo attrRespVo = new AttrRespVo();
    //根据attrId到attr表中查该行数据
    AttrEntity attrEntity = this.getById(attrId);
    BeanUtils.copyProperties(attrEntity, attrRespVo);

    LambdaQueryWrapper<AttrAttrgroupRelationEntity> attrAttrgroupRelationQueryWrapper = new LambdaQueryWrapper<>();
    attrAttrgroupRelationQueryWrapper.eq(AttrAttrgroupRelationEntity::getAttrId, attrId);
    //根据attrId到attrAttrgroupRelation关联关系表里查attrGroupId
    AttrAttrgroupRelationEntity attrAttrgroupRelationEntity = attrAttrgroupRelationDao.selectOne(attrAttrgroupRelationQueryWrapper);
    if (attrAttrgroupRelationEntity != null) {
        attrRespVo.setAttrGroupId(attrAttrgroupRelationEntity.getAttrGroupId());
        //根据attrGroupId到attrGroup表里查groupName
        AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(attrAttrgroupRelationEntity.getAttrGroupId());
        if (attrGroupEntity != null) {
            attrRespVo.setGroupName(attrGroupEntity.getAttrGroupName());
        }
    }

    //根据最后一级分类id到categoryService中查询完整三级分类id
    Long catelogId = attrEntity.getCatelogId();
    Long[] catelogPath = categoryService.findCatelogPath(catelogId);
    if (catelogPath!=null){
        attrRespVo.setCatelogPath(catelogPath);
    }
    return attrRespVo;
}
image-20220513111825693
image-20220513111825693

6、测试

重启gulimall-product模块,刷新前端页面,已成功显示属性分类所属分组

image-20220513112029811
image-20220513112029811
image-20220513112042807
image-20220513112042807

4.5.4、规格参数修改发送请求

1、前端提交时规则校验失败

1、值类型不能为空
image-20220513114101205
image-20220513114101205
2、查看数据
image-20220513114127890
image-20220513114127890
3、寻找原因
1、绑定valueType
image-20220513114746867
image-20220513114746867
2、valueType数据字段
image-20220513115002459
image-20220513115002459
3、valueType校验字段
image-20220513115126361
image-20220513115126361
4、初始化方法重新给valueType赋值

初始化的时候重新给"valueType"赋值了 很有可能是在这个时候没有获取到"valueType"

image-20220513115444277
image-20220513115444277
5、表单提交后重新给valueType赋值

表单提交的时候也重新赋值了 但现在表单还没提交,应该不是这里

image-20220513115757511
image-20220513115757511
6、查看初始化请求返回的数据

可以看到没有valueType字段

image-20220513120136532
image-20220513120136532
7、返回对象没有valueType字段
image-20220513120635878
image-20220513120635878
8、数据库也没有valueType字段
image-20220513120831498
image-20220513120831498
4、数据库添加valueType字段
image-20220513121750821
image-20220513121750821
5、pojo对象添加字段

gulimall-product模块的com.atguigu.gulimall.product.entity.AttrEntity类里添加字段

/**
 * 值类型【0-只能单个值,1-允许多个值】
 */
private Integer valueType;
image-20220513122050410
image-20220513122050410

gulimall-product模块的com.atguigu.gulimall.product.vo.AttrVo类里添加字段

image-20220513122208202
image-20220513122208202

由于attrRespVo继承了attrVo,所以也有valueType字段

image-20220513122301927
image-20220513122301927

由于使用的是MyBatisPuls,所以增删查改会自动将entity类的所有需要的字段都带上,因此不需要修改业务代码

6、重新测试

现在提交已经校验成功了

image-20220513122505664
image-20220513122505664

2、查看请求的URL

请求的URL为:http://localhost:88/api/product/attr/update

image-20220513222311216
image-20220513222311216

3、修改

gulimall-product模块里修改com.atguigu.gulimall.product.controller.AttrController类的update方法

@RequestMapping("/update")
public R update(@RequestBody AttrVo attr) {
    attrService.updateAttr(attr);

    return R.ok();
}
image-20220513222402532
image-20220513222402532

4、添加updateAttr抽象方法

gulimall-product模块的com.atguigu.gulimall.product.service.AttrService接口里添加updateAttr抽象方法

void updateAttr(AttrVo attr);
image-20220513222606908
image-20220513222606908

5、实现updateAttr抽象方法

gulimall-product模块的com.atguigu.gulimall.product.service.impl.AttrServiceImpl类里实现updateAttr抽象方法

image-20220513224031536
image-20220513224031536

6、测试

重启gulimall-product模块,刷新前端页面

可以看到当没有设置所属分组时,修改失败了,原因就是没有查到attrId为该值的数据,因此当查不到数据时应该增加该数据

GIF 2022-5-13 22-45-38
GIF 2022-5-13 22-45-38

当设置了所属分组后,修改正常

GIF 2022-5-13 22-53-56
GIF 2022-5-13 22-53-56

7、修改updateAttr方法

gulimall-product模块里修改com.atguigu.gulimall.product.service.impl.AttrServiceImpl类的updateAttr方法

@Transactional
@Override
public void updateAttr(AttrVo attr) {
    AttrEntity attrEntity = new AttrEntity();
    BeanUtils.copyProperties(attr,attrEntity);
    this.updateById(attrEntity);


    AttrAttrgroupRelationEntity attrAttrgroupRelationEntity = new AttrAttrgroupRelationEntity();
    attrAttrgroupRelationEntity.setAttrGroupId(attr.getAttrGroupId());

    LambdaQueryWrapper<AttrAttrgroupRelationEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    lambdaQueryWrapper.eq(AttrAttrgroupRelationEntity::getAttrId,attr.getAttrId());
    Integer count = attrAttrgroupRelationDao.selectCount(lambdaQueryWrapper);
    //如果有attr和attrgroup的关联关系就修改该,没有就新增
    if (count>0) {
        //根据attr_id修改 pms_attr_attrgroup_relation 里的attr_group_id 字段
        attrAttrgroupRelationDao.update(attrAttrgroupRelationEntity, lambdaQueryWrapper);
    }else {
        //添加attr和attrgroup的关联关系
        attrAttrgroupRelationEntity.setAttrId(attr.getAttrId());
        attrAttrgroupRelationDao.insert(attrAttrgroupRelationEntity);
    }
}
image-20220513230716943
image-20220513230716943

8、重新测试

重启gulimall-product模块,刷新前端页面

GIF 2022-5-13 23-02-23
GIF 2022-5-13 23-02-23

4.5.5、销售属性维护

1、查看url

点击商品系统下的平台属性下的销售属性,会发送一个请求,查看销售属性列表

url:http://localhost:88/api/product/attr/sale/list/0?t=1652454755415&page=1&limit=10&key=

image-20220513231338361
image-20220513231338361

pms_attr表的attr_type字段为0,则表示的是销售属性

image-20220513232155146
image-20220513232155146

接口文档: 09、获取分类销售属性 - 谷粒商城 - 易文档 (easydoc.net)open in new window

image-20220518194022173
image-20220518194022173

2、修改baseAttrList方法

gulimall-product模块里修改com.atguigu.gulimall.product.controller.AttrController类的baseAttrList方法

image-20220513233818883
image-20220513233818883
image-20220513233835501
image-20220513233835501

3、转到接口方法

按住ctrl键,然后点击queryBaseAttrPage,跳转到com.atguigu.gulimall.product.service.AttrService类的queryBaseAttrPage方法

image-20220513233933096
image-20220513233933096
image-20220513233956714
image-20220513233956714

4、修改queryBaseAttrPage方法

gulimall-product模块的com.atguigu.gulimall.product.service.impl.AttrServiceImpl类的queryBaseAttrPage方法里添加比较条件

点击查看queryBaseAttrPage方法完整代码

lambdaQueryWrapper.eq(AttrEntity::getAttrType,"base".equalsIgnoreCase(attrType)?1:0);
image-20220513234038547
image-20220513234038547

5、测试

重启gulimall-product模块,刷新前端页面

1、新增销售属性
image-20220513234339081
image-20220513234339081
2、点击查询全部

点击查询全部,可以看到请求的url

url: http://localhost:88/api/product/attr/sale/list/0?t=1652456634834&page=1&limit=10&key=

image-20220513234412544
image-20220513234412544
3、报空指针

com.atguigu.gulimall.product.service.impl.AttrServiceImpl类的queryBaseAttrPage方法里的这一行报空指针

image-20220513234555986
image-20220513234555986
4、加个判断

com.atguigu.gulimall.product.service.impl.AttrServiceImpl类的queryBaseAttrPage方法里这个位置加个判断

image-20220513234720363
image-20220513234720363
5、重新测试

重启gulimall-product项目,已经正常显示了

image-20220513234805564
image-20220513234805564

6、修改销售属性报错

1、修改销售属性

可选值里添加蓝色,然后点击确定,成功了

image-20220516230532173
image-20220516230532173

再次提示,系统未知异常

image-20220516215335766
image-20220516215335766
2、查看报错

com.atguigu.gulimall.product.service.impl.AttrServiceImpl类的updateAttr方法里的172行有一个sql语法错误

UPDATE pms_attr_attrgroup_relation WHERE (attr_id = ?) 
image-20220516215659753
image-20220516215659753
3、attrGroup没有获取到

可以看到传过来的attr里面没有封装attrGroup

image-20220516220802079
image-20220516220802079

查看前端请求,发现修改,没有涉及到所属分组,因此没有attrGroup属性

image-20220516221537247
image-20220516221537247
4、查看调用关系

使用以下方法查看调用关系(或按alt+F7)

  1. 双击updateAttr方法
  2. 右键,选择Find Usages
  3. 查看调用

可以看到,只有AttrControllerupdate方法调用了

image-20220516223003550
image-20220516223003550

使用以下方法查看调用关系链(或按快捷键ctrl+alt+H)

  1. 双击updateAttr方法
  2. 点击Navigate
  3. 选择Call Hierarchy
  4. 查看被调用关系链

可以看到,只有AttrControllerupdate方法调用了

image-20220516223538909
image-20220516223538909
5、分析
1、查看数据库

查看数据库发现,已经新增了一条记录,但是attr_group_id为null

(其实这条数据不是在调用修改方法新增的数据,而是调用save方法新增的数据,调用save方法可以看到会执行

com.atguigu.gulimall.product.service.impl.AttrServiceImpl类的saveAttr方法

@Override
    public void saveAttr(AttrVo attr) {
        AttrEntity attrEntity = new AttrEntity();
        //将attr中的数据复制到attrEntity对应的字段里
        BeanUtils.copyProperties(attr, attrEntity);
        //1、保存基本数据
        this.save(attrEntity);
        //2、保存关联关系
        AttrAttrgroupRelationEntity attrAttrgroupRelationEntity = new AttrAttrgroupRelationEntity();
        attrAttrgroupRelationEntity.setAttrGroupId(attr.getAttrGroupId());
        //调用this.save(attrEntity);方法后,会将数据库生成的attrId封装到AttrEntity里面
        attrAttrgroupRelationEntity.setAttrId(attrEntity.getAttrId());
        attrAttrgroupRelationDao.insert(attrAttrgroupRelationEntity);
    }

此时会在attr_attrgroup_relation表里添加数据,此时也只有attr_id有值,attr_group_id也为null)

先删除这条数据,再进行调试

image-20220516231712740
image-20220516231712740
2、执行了insert方法

第一次请求时,在attr_attrgroup_relation查不到数据(最初写修改规格参数时,代表的是新增规格参数没有设置所属分组的情况)

,所以就插入了一条数据,但是由于是修改销售属性,没有所属分组选项,自然就没有attrGroupId

image-20220516230926422
image-20220516230926422
3、查看insert的sql语句

可以看到只插入了attr_id字段

image-20220516231035665
image-20220516231035665
4、查看数据库

可以看到attr_group_id字段为空

image-20220516231851424
image-20220516231851424
5、再次发送修改销售属性请求

再次发送修改销售属性请求,可以看到已经查到了一条数据,走到了update方法里面去了

此时同样没有attrGroupId字段

image-20220516231421001
image-20220516231421001
6、此时出现了sql语句异常
image-20220516231604459
image-20220516231604459
6、解决问题
1、规格参数销售属性的请求参数一样

点击规格参数里的修改按钮,可以看到url为: http://localhost:88/api/product/attr/update

image-20220516233308432
image-20220516233308432

此时有所属分组,因此有attrGroup属性

此时的attrType1,表示的是规格参数

image-20220516233334265
image-20220516233334265

点击销售属性里的修改按钮,可以看到url为: http://localhost:88/api/product/attr/update

image-20220516233824438
image-20220516233824438

此时没有有所属分组,因此没有attrGroup属性

此时的attrType0,表示的是销售属性

image-20220516233859672
image-20220516233859672
2、updateAttr方法添加关联关系前加一个判断
@Transactional
@Override
public void updateAttr(AttrVo attr) {
    AttrEntity attrEntity = new AttrEntity();
    BeanUtils.copyProperties(attr,attrEntity);
    this.updateById(attrEntity);


    AttrAttrgroupRelationEntity attrAttrgroupRelationEntity = new AttrAttrgroupRelationEntity();
    attrAttrgroupRelationEntity.setAttrGroupId(attr.getAttrGroupId());

    LambdaQueryWrapper<AttrAttrgroupRelationEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    lambdaQueryWrapper.eq(AttrAttrgroupRelationEntity::getAttrId,attr.getAttrId());
    Integer count = attrAttrgroupRelationDao.selectCount(lambdaQueryWrapper);
    //如果有attr和attrgroup的关联关系就修改该,没有就新增
    if (count>0) {
        //根据attr_id修改 pms_attr_attrgroup_relation 里的attr_group_id 字段
        attrAttrgroupRelationDao.update(attrAttrgroupRelationEntity, lambdaQueryWrapper);
    }else {
        if (attr.getAttrGroupId()!=null) {
            //添加attr和attrgroup的关联关系
            attrAttrgroupRelationEntity.setAttrId(attr.getAttrId());
            attrAttrgroupRelationDao.insert(attrAttrgroupRelationEntity);
        }
    }
}
image-20220517000120934
image-20220517000120934

此时的attrType0,表示的是销售属性,没有attrattrGroup关联关系,不需要添加

因此也可以这样判断,如果是基本属性才添加attrattrGroup关联关系

这样想其实不规范,如果是销售属性,应该直接不查attrattrGroup关联关系,因为销售属性没有分组,自然没有它们的关联关系

image-20220518125355814
image-20220518125355814
3、使用枚举

gulimall-common模块的src/main/java/com/atguigu/common文件下新建constant文件夹

com.atguigu.common.constant包下新建ProductConstant枚举类

package com.atguigu.common.constant;

/**
 * @author 无名氏
 * @date 2022/5/17
 * @Description:
 */
public enum ProductConstant {
    /**
     * 基本属性
     */
    ATTR_TYPE_BASE(1,"基本属性"),
    /**
     * 销售属性
     */
    ATTR_TYPES_SALE(0,"销售属性");

    private int code;
    private String msg;

    ProductConstant(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public int getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }
}
image-20220518115507158
image-20220518115507158
4、修改updateAttr方法

修改gulimall-product模块下的com.atguigu.gulimall.product.service.impl.AttrServiceImpl类里的updateAttr方法

@Transactional
@Override
public void updateAttr(AttrVo attr) {
    AttrEntity attrEntity = new AttrEntity();
    BeanUtils.copyProperties(attr,attrEntity);
    this.updateById(attrEntity);


    AttrAttrgroupRelationEntity attrAttrgroupRelationEntity = new AttrAttrgroupRelationEntity();
    attrAttrgroupRelationEntity.setAttrGroupId(attr.getAttrGroupId());

    LambdaQueryWrapper<AttrAttrgroupRelationEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    lambdaQueryWrapper.eq(AttrAttrgroupRelationEntity::getAttrId,attr.getAttrId());
    Integer count = attrAttrgroupRelationDao.selectCount(lambdaQueryWrapper);
    //如果有attr和attrgroup的关联关系就修改该,没有就新增
    if (count>0) {
        //根据attr_id修改 pms_attr_attrgroup_relation 里的attr_group_id 字段
        attrAttrgroupRelationDao.update(attrAttrgroupRelationEntity, lambdaQueryWrapper);
    }else {
        //基本属性
        if (attr.getAttrType()== ProductConstant.ATTR_TYPE_BASE.getCode()) {
            //添加attr和attrgroup的关联关系
            attrAttrgroupRelationEntity.setAttrId(attr.getAttrId());
            attrAttrgroupRelationDao.insert(attrAttrgroupRelationEntity);
        }
    }
}
image-20220518124449446
image-20220518124449446

最好这样写

如果是基本属性,才更新添加关联关系

@Transactional
@Override
public void updateAttr(AttrVo attr) {
    AttrEntity attrEntity = new AttrEntity();
    BeanUtils.copyProperties(attr, attrEntity);
    this.updateById(attrEntity);

    //如果是基本属性,就更新或添加关联关系
    if (attr.getAttrType() == ProductConstant.ATTR_TYPE_BASE.getCode()) {
        AttrAttrgroupRelationEntity attrAttrgroupRelationEntity = new AttrAttrgroupRelationEntity();
        attrAttrgroupRelationEntity.setAttrGroupId(attr.getAttrGroupId());

        LambdaQueryWrapper<AttrAttrgroupRelationEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(AttrAttrgroupRelationEntity::getAttrId, attr.getAttrId());
        Integer count = attrAttrgroupRelationDao.selectCount(lambdaQueryWrapper);
        //如果有attr和attrgroup的关联关系就修改该,没有就新增
        if (count > 0) {
            //根据attr_id修改 pms_attr_attrgroup_relation 里的attr_group_id 字段
            attrAttrgroupRelationDao.update(attrAttrgroupRelationEntity, lambdaQueryWrapper);
        } else {
            //添加attr和attrgroup的关联关系
            attrAttrgroupRelationEntity.setAttrId(attr.getAttrId());
            attrAttrgroupRelationDao.insert(attrAttrgroupRelationEntity);
        }
    }
}
image-20220518130019768
image-20220518130019768
5、修改saveAttr方法

修改gulimall-product模块下的com.atguigu.gulimall.product.service.impl.AttrServiceImpl类里的saveAttr方法

@Override
public void saveAttr(AttrVo attr) {
    AttrEntity attrEntity = new AttrEntity();
    //将attr中的数据复制到attrEntity对应的字段里
    BeanUtils.copyProperties(attr, attrEntity);
    //1、保存基本数据
    this.save(attrEntity);
    //2、如果是基本属性,则还要保存关联关系
    if (attr.getAttrType() == ProductConstant.ATTR_TYPE_BASE.getCode()) {
        AttrAttrgroupRelationEntity attrAttrgroupRelationEntity = new AttrAttrgroupRelationEntity();
        attrAttrgroupRelationEntity.setAttrGroupId(attr.getAttrGroupId());
        //调用this.save(attrEntity);方法后,会将数据库生成的attrId封装到AttrEntity里面
        attrAttrgroupRelationEntity.setAttrId(attrEntity.getAttrId());
        attrAttrgroupRelationDao.insert(attrAttrgroupRelationEntity);
    }
}
image-20220518123217004
image-20220518123217004
6、修改queryBaseAttrPage方法

修改gulimall-product模块下的com.atguigu.gulimall.product.service.impl.AttrServiceImpl类里的queryBaseAttrPage方法,也使用枚举来代表0,或者1

@Override
public PageUtils queryBaseAttrPage(Map<String, Object> params, Long categoryId, String attrType) {
    LambdaQueryWrapper<AttrEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //判断是"基本属性"还是"销售属性"
    lambdaQueryWrapper.eq(AttrEntity::getAttrType,
            "base".equalsIgnoreCase(attrType)?
                    ProductConstant.ATTR_TYPE_BASE.getCode():
                    ProductConstant.ATTR_TYPES_SALE.getCode());
    if (categoryId != 0) {
        //如果有categoryId,则查该categoryId的数据
        lambdaQueryWrapper.eq(AttrEntity::getCatelogId, categoryId);
    }

    String key = (String) params.get("key");
    if (StringUtils.hasLength(key)) {
        lambdaQueryWrapper.and((obj) -> {
            //如果有查询条件,则判断该条件是否 与attrId相等 或 名字包含该条件
            obj.eq(AttrEntity::getAttrId, key).or().like(AttrEntity::getAttrName, key);
        });
    }
    IPage<AttrEntity> page = this.page(new Query<AttrEntity>().getPage(params), lambdaQueryWrapper);

    //查询categoryName字段和groupName字段
    List<AttrEntity> list = page.getRecords();
    List<AttrRespVo> respVos = list.stream().map(attrEntity -> {
        AttrRespVo attrRespVo = new AttrRespVo();
        BeanUtils.copyProperties(attrEntity, attrRespVo);
        //根据attrId到attr和attrgroup的中间表查询 attrgroupId
        LambdaQueryWrapper<AttrAttrgroupRelationEntity> attrAttrgroupRelationQueryWrapper = new LambdaQueryWrapper<>();
        attrAttrgroupRelationQueryWrapper.eq(AttrAttrgroupRelationEntity::getAttrId, attrEntity.getAttrId());
        //根据attrgroupId查询中间表的该行数据,并封装到对象
        if ("base".equalsIgnoreCase(attrType)) {
            AttrAttrgroupRelationEntity attrAttrgroupRelationEntity = attrAttrgroupRelationDao.selectOne(attrAttrgroupRelationQueryWrapper);
            if (attrAttrgroupRelationEntity != null) {
                //如果查到attrgroupId,则根据attrgroupId查询attrgroupName,并添加到attrRespVo中
                AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(attrAttrgroupRelationEntity.getAttrGroupId());
                attrRespVo.setGroupName(attrGroupEntity.getAttrGroupName());
            }
        }

        LambdaQueryWrapper<CategoryEntity> categoryQueryWrapper = new LambdaQueryWrapper<>();
        categoryQueryWrapper.eq(CategoryEntity::getCatId, attrEntity.getCatelogId());
        //根据catelogId查询该行数据,并封装到对象
        CategoryEntity categoryEntity = categoryDao.selectOne(categoryQueryWrapper);
        if (categoryEntity != null) {
            attrRespVo.setCatelogName(categoryEntity.getName());
        }
        return attrRespVo;
    }).collect(Collectors.toList());

    PageUtils pageUtils = new PageUtils(page);
    //重新给数据
    pageUtils.setList(respVos);
    return pageUtils;
}
image-20220518121753838
image-20220518121753838
7、修改getAttrInfo方法

修改gulimall-product模块下的com.atguigu.gulimall.product.service.impl.AttrServiceImpl类里的getAttrInfo方法

@Override
public AttrRespVo getAttrInfo(Long attrId) {
    AttrRespVo attrRespVo = new AttrRespVo();
    //根据attrId到attr表中查该行数据
    AttrEntity attrEntity = this.getById(attrId);
    BeanUtils.copyProperties(attrEntity, attrRespVo);

    //如果是基本属性,需要设置分组信息
    if (attrEntity.getAttrType()== ProductConstant.ATTR_TYPE_BASE.getCode()) {
        LambdaQueryWrapper<AttrAttrgroupRelationEntity> attrAttrgroupRelationQueryWrapper = new LambdaQueryWrapper<>();
        attrAttrgroupRelationQueryWrapper.eq(AttrAttrgroupRelationEntity::getAttrId, attrId);
        //根据attrId到attrAttrgroupRelation关联关系表里查attrGroupId
        AttrAttrgroupRelationEntity attrAttrgroupRelationEntity = attrAttrgroupRelationDao.selectOne(attrAttrgroupRelationQueryWrapper);
        if (attrAttrgroupRelationEntity != null) {
            attrRespVo.setAttrGroupId(attrAttrgroupRelationEntity.getAttrGroupId());
            //根据attrGroupId到attrGroup表里查groupName
            AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(attrAttrgroupRelationEntity.getAttrGroupId());
            if (attrGroupEntity != null) {
                attrRespVo.setGroupName(attrGroupEntity.getAttrGroupName());
            }
        }
    }

    //根据最后一级分类id到categoryService中查询完整三级分类id
    Long catelogId = attrEntity.getCatelogId();
    Long[] catelogPath = categoryService.findCatelogPath(catelogId);
    if (catelogPath!=null){
        attrRespVo.setCatelogPath(catelogPath);
    }
    return attrRespVo;
}
image-20220518122837889
image-20220518122837889

7、测试

重启gulimall-product模块,刷新前端页面,点击销售属性里的新增

image-20220518130652375
image-20220518130652375

已经新增成功了

image-20220518130752310
image-20220518130752310

查看新增的属性的attrId

image-20220518150614220
image-20220518150614220

可以看到已经不再pms_attr_attrgroup_relation表里添加关联关系了

image-20220518150726876
image-20220518150726876

4.5.6、属性分组查询&删除分组关联

1、属性分组查询分组关联

1、查看请求

点击商品系统下的平台属性下的属性分组,点击操作下的关联,会发送一个请求,来查询属性分组关联的销售属性(基本属性)

url: http://localhost:88/api/product/attrgroup/1/attr/relation?t=1652858166118

image-20220518151725272
image-20220518151725272

接口文档:10、获取属性分组的关联的所有属性 - 谷粒商城 - 易文档 (easydoc.net)open in new window

image-20220518193749778
image-20220518193749778
2、添加attrRelation方法

gulimall-product模块的com.atguigu.gulimall.product.controller.AttrGroupController类里添加attrRelation方法

@Autowired
private AttrService attrService;

/**
 *  localhost:88/api/product/attrgroup/1/attr/relation?t=1652858166118
 * @return
 */
@GetMapping("/{attrgroupId}/attr/relation")
public R attrRelation(@PathVariable("attrgroupId") Long attrgroupId){
    List<AttrEntity> list = attrService.getRelationAttr(attrgroupId);
    return R.ok().put("data",list);
}
image-20220518154325812
image-20220518154325812
3、添加getRelationAttr抽象方法

gulimall-product模块的com.atguigu.gulimall.product.service.AttrService接口里添加getRelationAttr抽象方法

image-20220518154451842
image-20220518154451842
4、实现getRelationAttr抽象方法

gulimall-product模块的com.atguigu.gulimall.product.service.impl.AttrServiceImpl类里添加方法

实现未实现的getRelationAttr抽象方法

image-20220518154507704
image-20220518154507704
5、测试

重启gulimall-product模块,刷新前端页面,重新点击关联,可以看到已经显示出来了

image-20220518154635843
image-20220518154635843

2、属性分组删除分组关联

1、查看请求

点击移除按钮,查看请求的url

url: http://localhost:88/api/product/attrgroup/attr/relation/delete

传递的参数为: attrIdattrGroupId

image-20220518155321920
image-20220518155321920
image-20220518155457971
image-20220518155457971

接口文档:12、删除属性与分组的关联关系 - 谷粒商城 - 易文档 (easydoc.net)open in new window

image-20220518194431585
image-20220518194431585
2、新建AttrGroupRelationVo

gulimall-product模块下的com.atguigu.gulimall.product.vo包下新建AttrGroupRelationVo

package com.atguigu.gulimall.product.vo;

import lombok.Data;

/**
 * @author 无名氏
 * @date 2022/5/18
 * @Description:
 */
@Data
public class AttrGroupRelationVo {

    private Long attrId;

    private Long attrGroupId;
}
image-20220518194549006
image-20220518194549006
3、添加deleteRelation方法

gulimall-product模块的com.atguigu.gulimall.product.controller.AttrGroupController类里添加deleteRelation方法

/**
 * localhost:88/api/product/attrgroup/attr/relation/delete
 * @return
 */
@PostMapping("/attr/relation/delete")
public R deleteRelation(AttrGroupRelationVo[] attrGroupRelationVos){
    attrService.deleteRelation(attrGroupRelationVos);
    return R.ok();
}
image-20220518194834132
image-20220518194834132
4、添加deleteRelation抽象方法

gulimall-product模块下的com.atguigu.gulimall.product.service.AttrService类里添加deleteRelation抽象方法

void deleteRelation(AttrGroupRelationVo[] attrGroupRelationVos);
image-20220518195422431
image-20220518195422431
5、实现deleteRelation抽象方法

gulimall-product模块下的com.atguigu.gulimall.product.service.impl.AttrServiceImpl类里实现未实现的deleteRelation抽象方法

/**
 * delete from gulimall_pms.pms_attr_attrgroup_relation
 * where (attr_id = 1 and attr_group_id=1)
 * or (attr_id = 2 and attr_group_id=2)
 * or (attr_id = 3 and attr_group_id=3);
 * @param attrGroupRelationVos
 */
@Override
public void deleteRelation(AttrGroupRelationVo[] attrGroupRelationVos) {
    List<AttrAttrgroupRelationEntity> attrAttrgroupRelationEntities = Arrays.stream(attrGroupRelationVos).map((attrGroupRelationVo -> {
        AttrAttrgroupRelationEntity attrAttrgroupRelationEntity = new AttrAttrgroupRelationEntity();
        BeanUtils.copyProperties(attrGroupRelationVo, attrAttrgroupRelationEntity);
        return attrAttrgroupRelationEntity;
    })).collect(Collectors.toList());

    attrAttrgroupRelationDao.deleteBatchRelation(attrAttrgroupRelationEntities);
}
image-20220518200544137
image-20220518200544137
6、添加deleteBatchRelation抽象方法

gulimall-product模块里的com.atguigu.gulimall.product.dao.AttrAttrgroupRelationDao类里

添加deleteBatchRelation抽象方法

void deleteBatchRelation(@Param("entities") List<AttrAttrgroupRelationEntity> attrAttrgroupRelationEntities);
image-20220518201025915
image-20220518201025915
7、添加deleteBatchRelation抽象方法的sql语句

gulimall-product模块的src/main/resources/mapper/product/AttrAttrgroupRelationDao.xml文件里添加sql语句

<!--批量删除attr_attrgroup_relation-->
<!--
    delete from gulimall_pms.pms_attr_attrgroup_relation
    where (attr_id = 1 and attr_group_id=1)
    or (attr_id = 2 and attr_group_id=2)
    or (attr_id = 3 and attr_group_id=3);
-->
<delete id="deleteBatchRelation">
    delete from gulimall_pms.pms_attr_attrgroup_relation where
    <foreach collection="entities" item="item" separator=" or ">
        (attr_id = #{item.attrId} and attr_group_id=#{item.attrGroupId})
    </foreach>
</delete>
image-20220518201815102
image-20220518201815102
8、测试

重启gulimall-product模块,刷新前端页面

点击属性分组操作下里的关联,点击删除,显示系统未知异常

1、系统未知异常
image-20220518202518600
image-20220518202518600
2、查看报错信息

查看gulimall-product模块的控制台的报错信息

No primary or default constructor found for class [Lcom.atguigu.gulimall.product.vo.AttrGroupRelationVo;
没有找到类 [Lcom.atguigu.gulimall.product.vo.AttrGroupRelationVo; 的主要或默认构造函数;
image-20220518202535851
image-20220518202535851
3、加@RequestBody注解

gulimall-product模块的com.atguigu.gulimall.product.controller.AttrGroupController类里修改deleteRelation方法

image-20220518202629893
image-20220518202629893
4、重新测试
image-20220518202749246
image-20220518202749246

4.5.7、属性分组查询分组未关联的属性

1、查看请求

点击新建关联会发送一个请求,查询本分类下,没有被其他分组关联的属性

(比方说主机属性分组查询 手机/手机通讯/手机下的未被其他属性分组关联的基本属性)

image-20220518204930720
image-20220518204930720

接口地址:13、获取属性分组没有关联的其他属性 - 谷粒商城 - 易文档 (easydoc.net)open in new window

image-20220520093458443
image-20220520093458443

2、添加attrNoRelation方法

gulimall-product模块的com.atguigu.gulimall.product.controller.AttrGroupController类里添加attrNoRelation方法

/**
 * 查询本分类下,没有被其他分组关联的属性
 * (比方说"主机"属性分组查询 "手机/手机通讯/手机"下的未被其他属性分组关联的基本属性)
 * localhost:88/api/product/attrgroup/1/noattr/relation?t=1652878342763&page=1&limit=10&key=
 * @param attrgroupId 属性分组id
 * @param params      分页参数
 * @return
 */
@GetMapping("/{attrgroupId}/noattr/relation")
public R attrNoRelation(@PathVariable("attrgroupId") Long attrgroupId,@RequestParam Map<String, Object> params){
    PageUtils page = attrService.getNoRelationAttr(attrgroupId,params);
    return R.ok().put("page",page);
}
image-20220518210857053
image-20220518210857053

3、添加getNoRelationAttr抽象方法

gulimall-product模块的com.atguigu.gulimall.product.service.AttrService类里添加getNoRelationAttr抽象方法

PageUtils getNoRelationAttr(Long attrgroupId, Map<String, Object> params);
image-20220518210924259
image-20220518210924259

4、实现getNoRelationAttr抽象方法

gulimall-product模块下的com.atguigu.gulimall.product.service.impl.AttrServiceImpl类里添加未实现的getNoRelationAttr抽象方法

/**
 * 查询本分类下,没有被其他分组关联的属性
 * (比方说"主机"属性分组查询 "手机/手机通讯/手机"下的未被其他属性分组关联的基本属性)
 * localhost:88/api/product/attrgroup/1/noattr/relation?t=1652878342763&page=1&limit=10&key=
 * @param attrgroupId 属性分组id
 * @param params      分页参数
 * @return            分页对象
 */
@Override
public PageUtils getNoRelationAttr(Long attrgroupId, Map<String, Object> params) {
    //1、查询该attrgroupId的catelogId(当前分组只能关联自己所属的分类里面的所有属性)
    AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(attrgroupId);
    Long catelogId = attrGroupEntity.getCatelogId();

    //2、当前分组只能关联本分类下的其他分组没有引用的属性
    //2.1)、当前分类下的其他分组
    LambdaQueryWrapper<AttrGroupEntity> attrGroupQueryWrapper = new LambdaQueryWrapper<>();
    //ne:not equal
    attrGroupQueryWrapper.eq(AttrGroupEntity::getCatelogId,catelogId).ne(AttrGroupEntity::getAttrGroupId,attrgroupId);
    List<AttrGroupEntity> otherGroups = attrGroupDao.selectList(attrGroupQueryWrapper);
    List<Long> otherAttrGroupIds = otherGroups.stream().map(AttrGroupEntity::getAttrGroupId).collect(Collectors.toList());

    LambdaQueryWrapper<AttrEntity> attrQueryWrapper = new LambdaQueryWrapper<>();
    attrQueryWrapper.eq(AttrEntity::getCatelogId, catelogId);
    //2.2)、如果有其他分组,则查询这些分组关联的属性
    //otherAttrGroupIds!=null && 有些多余
    if (otherAttrGroupIds!=null && otherAttrGroupIds.size()>0) {
        LambdaQueryWrapper<AttrAttrgroupRelationEntity> attrAttrgroupRelationQueryWrapper = new LambdaQueryWrapper<>();
        attrAttrgroupRelationQueryWrapper.in(AttrAttrgroupRelationEntity::getAttrGroupId, otherAttrGroupIds);
        List<AttrAttrgroupRelationEntity> otherAttrAttrgroupRelations = attrAttrgroupRelationDao.selectList(attrAttrgroupRelationQueryWrapper);
        List<Long> otherAttrIds = otherAttrAttrgroupRelations.stream().map(AttrAttrgroupRelationEntity::getAttrId).collect(Collectors.toList());
        //2.3)、如果有已被关联的属性,则从当前分类的所有属性中移除这些已被关联的属性;
        if (otherAttrIds!=null && otherAttrIds.size()>0) {
            attrQueryWrapper.notIn(AttrEntity::getAttrId, otherAttrIds);
        }
    }
    //如果有查询条件,则添加查询条件
    String key = (String) params.get("key");
    if (StringUtils.hasLength(key)){
        attrQueryWrapper.and(item->{
            item.eq(AttrEntity::getAttrId,key).or().like(AttrEntity::getAttrName,key);
        });
    }
    IPage<AttrEntity> page = this.page(new Query<AttrEntity>().getPage(params), attrQueryWrapper);
    return new PageUtils(page);
}
image-20220518225631000
image-20220518225631000

5、测试

重启gulimall-product模块,刷新前端页面

1、已经显示出来了
image-20220518225512338
image-20220518225512338
2、还显示了销售属性
image-20220518230754485
image-20220518230754485
image-20220518230708756
image-20220518230708756
3、修改实现类的getNoRelationAttr方法

修改gulimall-product模块的com.atguigu.gulimall.product.service.impl.AttrServiceImpl类里的getNoRelationAttr方法

attrQueryWrapper.eq(AttrEntity::getAttrType,ProductConstant.ATTR_TYPE_BASE.getCode());
image-20220518230935342
image-20220518230935342
4、重新测试

重启gulimall-product模块,刷新前端页面

已经不显示销售属性了

image-20220518231201080
image-20220518231201080

6、修改关联关系

1、重新查询

删除该分类下其他属性分组基本属性关联关系后,再重新查询,发现可以查询出来

1
1
2、添加关系

gulimall_pms数据库下的pms_attr_attrgroup_relation表里添加一行数据

使主机组关联入网型号

image-20220518232418576
image-20220518232418576
image-20220518233043776
image-20220518233043776
3、还可以再次关联入网型号

可以看到关联入网型号后,还可以再次关联入网型号

image-20220518233117780
image-20220518233117780
4、修改getNoRelationAttr方法

修改gulimall-product模块的com.atguigu.gulimall.product.service.impl.AttrServiceImpl类里的getNoRelationAttr方法

删除.ne(AttrGroupEntity::getAttrGroupId,attrgroupId),取消排除本分组

查询所有分组(包括本分组,因为本分组已经关联的属性,不能再次关联)

image-20220518233615200
image-20220518233615200
5、重新测试

重启gulimall-product模块,刷新前端页面

可以看到关联入网型号后,不可以再次关联入网型号

image-20220518234041402
image-20220518234041402

7、模糊查询

1、根据属性id
image-20220518234459174
image-20220518234459174
image-20220518234430923
image-20220518234430923
2、根据属性名
image-20220518234352730
image-20220518234352730
image-20220518234413311
image-20220518234413311

可以看到,模糊查询没有什么问题

4.5.8、属性分组添加关联关系

1、查看请求

先删除主机入网型号的关联关系,再重新添加,查看url

url: http://localhost:88/api/product/attrgroup/attr/relation

image-20220518235355704
image-20220518235355704

查询参数

image-20220518235618863
image-20220518235618863

接口文档:11、添加属性与分组关联关系 - 谷粒商城 - 易文档 (easydoc.net)open in new window

image-20220518235700293
image-20220518235700293

2、添加addRelation方法

gulimall-product模块里的com.atguigu.gulimall.product.controller.AttrGroupController类里添加addRelation方法

@Autowired
private AttrAttrgroupRelationService attrAttrgroupRelationService;

/**
 * localhost:88/api/product/attrgroup/attr/relation
 */
@PostMapping("/attr/relation")
public R addRelation(@RequestBody List<AttrGroupRelationVo> attrGroupRelationVos){
    attrAttrgroupRelationService.saveBatch(attrGroupRelationVos);
    return R.ok();
}
image-20220519160608120
image-20220519160608120

3、添加saveBatch抽象方法

gulimall-product模块的com.atguigu.gulimall.product.service.AttrAttrgroupRelationService接口里添加saveBatch抽象方法

void saveBatch(List<AttrGroupRelationVo> attrGroupRelationVos);
image-20220519160636605
image-20220519160636605

4、实现saveBatch抽象方法

gulimall-product模块里的com.atguigu.gulimall.product.service.impl.AttrAttrgroupRelationServiceImpl类里实现saveBatch抽象方法

@Override
public void saveBatch(List<AttrGroupRelationVo> attrGroupRelationVos) {
    List<AttrAttrgroupRelationEntity> attrAttrgroupRelationEntities = attrGroupRelationVos.stream().map((item) -> {
        AttrAttrgroupRelationEntity attrAttrgroupRelationEntity = new AttrAttrgroupRelationEntity();
        BeanUtils.copyProperties(item, attrAttrgroupRelationEntity);
        return attrAttrgroupRelationEntity;
    }).collect(Collectors.toList());

    this.saveBatch(attrAttrgroupRelationEntities);
}
image-20220519161255770
image-20220519161255770

5、测试

重启gulimall-product模块,刷新前端页面,添加属性分组里的关联关系

1、添加关联关系
GIF 2022-5-19 16-11-17
GIF 2022-5-19 16-11-17
2、新增规格参数
image-20220519162129172
image-20220519162129172
3、新增成功,但是没有显示出来
image-20220519162615473
image-20220519162615473
4、报空指针异常

gulimall-product模块里的com.atguigu.gulimall.product.service.impl.AttrServiceImpl类里的queryBaseAttrPage方法的这一行报空指针

很明显,attrRespVo对象是103new出来的,不会是空指针

只有attrGroupEntity是查出来,然后返回的对象,有可能在数据库中没找到

image-20220519163106480
image-20220519163106480

传入的attr_group_idnull,数据库肯定查不到数据,所以返回0行数据,接收的``attrGroupEntity自然为null`

image-20220519163207584
image-20220519163207584
5、多加一个判断

gulimall-product模块的com.atguigu.gulimall.product.service.impl.AttrServiceImpl类里的queryBaseAttrPage方法里 将if (attrAttrgroupRelationEntity != null)改为

if (attrAttrgroupRelationEntity != null && attrAttrgroupRelationEntity.getAttrGroupId()!=null)

image-20220519164444114
image-20220519164444114

同理,也应该修改其他类似代码

6、修改saveAttr方法

gulimall-product模块的com.atguigu.gulimall.product.service.impl.AttrServiceImpl类里的saveAttr方法里

if (attr.getAttrType() == ProductConstant.ATTR_TYPE_BASE.getCode())改为

if (attr.getAttrType() == ProductConstant.ATTR_TYPE_BASE.getCode() && attr.getAttrGroupId()!=null)

image-20220519165943509
image-20220519165943509
7、修改updateAttr方法

gulimall-product模块的com.atguigu.gulimall.product.service.impl.AttrServiceImpl类里的updateAttr方法里

if (attr.getAttrType() == ProductConstant.ATTR_TYPE_BASE.getCode())改为

if (attr.getAttrType() == ProductConstant.ATTR_TYPE_BASE.getCode() && attr.getAttrGroupId()!=null)

image-20220519174303778
image-20220519174303778

6、重新测试

重启gulimall-product模块,刷新前端页面

1、查询全部

可以看到,刚刚添加的机身颜色已经显示出来了

image-20220519175025352
image-20220519175025352
2、建立关联关系

商品系统/平台属性/属性分组里建立关联关系也没有问题

GIF 2022-5-19 17-54-56
GIF 2022-5-19 17-54-56
3、再次点击规格参数,查询不到数据

再次点击规格参数,又查询不到数据了

image-20220519180029672
image-20220519180029672
4、查看控制台

查看gulimall-product模块的控制台,可以看到查询到了两条数据,而需要的是0条或1条数据(查不到数据只查询到一条数据)

nested exception is org.apache.ibatis.exceptions.TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 2
嵌套异常是 org.apache.ibatis.exceptions.TooManyResultsException:期望 selectOne() 返回一个结果(或 null),但发现:2
image-20220519180338886
image-20220519180338886
5、查看数据库

gulimall_pms数据库下的pms_attr_attrgroup_relation表里将attr_id=6并且attr_group_idnull的字段删掉

也把attr_id4的也删除掉,attr_id4的是销售属性,不需要关联关系(这是以前修改代码前,插入的错误数据)

image-20220519181010383
image-20220519181010383
image-20220519213127175
image-20220519213127175
6、刷新前端页面
image-20220519213334536
image-20220519213334536

7、规格参数测试

规格参数里修改所属分组

GIF 2022-5-19 21-35-51
GIF 2022-5-19 21-35-51

不过这样做并不规范,最好在添加关联关系的时候查询一下再添加,防止恶意攻击

4.6、商品服务-API-新增商品

4.6.1、调试会员等级相关接口

1、发送请求

1、本应该发送的请求

点击/商品系统/商品维护/发送商品,这是应该发送这个请求,

调用gulimall-member模块的com.atguigu.gulimall.member.controller.MemberLevelController类的list方法

url: http://localhost:88/api/member/memberlevel/list?t=1652972635593&page=1&limit=500

dddd
dddd
2、我的发送的请求

可以看到,我这里只发送了两给tree请求,并没有发送应该发送的请求

image-20220519225349292
image-20220519225349292
3、方法一(非常不推荐)
1、查看发送请求的方法

这个请求是src\views\modules\product\spuadd.vue文件的getMemberLevels方法发送的

image-20220519230722823
image-20220519230722823
2、在created阶段,调用该方法
this.getMemberLevels();

其实在mounted中调用这个方法了,但是mounted里先用的PubSub报错了,所以没执行

image-20220519230014448
image-20220519230014448
3、查看请求

可以看到已经发送这个请求了

url: http://localhost:88/api/member/memberlevel/list?t=1652972635593&page=1&limit=500

image-20220519225853391
image-20220519225853391
4、方法二(建议)
1、控制台报错
PubSub is not defined :PubSub 未定义
image-20220519233039674
image-20220519233039674
2、安装 pubsub-js
npm install --save pubsub-js
image-20220520000459261
image-20220520000459261
3、提升权限

方法一:

以管理员身份运行"VS Code"

image-20220519234508600

方法二:(不是这个用户)

  1. 打开nodejs的安装目录
  2. 点击"npm_cache",右键选择"属性"
  3. 选择"SYSTEM"
  4. 点击"编辑"
  5. 选择"Authenticated Users"
  6. 点击完全控制
  7. 点击确定
image-20220519235446253
image-20220519235446253
  1. 打开nodejs的安装目录
  2. 点击"npm_gloa",右键选择"属性"
  3. 选择"SYSTEM"
  4. 点击"编辑"
  5. 选择"Authenticated Users"
  6. 点击完全控制
  7. 点击确定
image-20220520000204114
image-20220520000204114
4、重新安装 pubsub-js
image-20220519232622362
image-20220519232622362
5、导入pubsub-js

B:\renren-fast-vue\src\views\modules\product\spuadd.vue文件里的<script>标签里导入pubsub-js

import PubSub from "pubsub-js";
image-20220519234016831
image-20220519234016831
6、查看发送的请求

重启后,已经发送这个请求了

image-20220519232903557
image-20220519232903557
7、接口文档

url: https://easydoc.net/s/78237135/ZUqEdvA4/jCFganpf

image-20220520092904263
image-20220520092904263

2、加入注册中心

gulimall-membersrc\main\resources\application.yml配置文件中添加配置,加入到注册中心(已经配置过了)

spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
image-20220520085328741
image-20220520085328741

3、添加配置中心

可以在gulimall-member模块的src\main\resources里新建bootstrap.properties文件,添加配置中心(在这里就先不做了)

image-20220520085535283
image-20220520085535283

4、开启服务注册与发现功能

可以在gulimall-member模块的com.atguigu.gulimall.member.GulimallMemberApplication类里

添加@EnableDiscoveryClient注解,开启服务注册与发现功能(这个已经做了)

image-20220520085840532
image-20220520085840532

5、添加list方法

gulimall-member模块的com.atguigu.gulimall.member.controller.MemberLevelController类里添加list方法

其实这个方法已经写好了,没有访问成功的原因是没有启动gulimall-member模块,并且没有配置gulimall-member模块的网关

image-20220520093657422
image-20220520093657422

6、添加路由规则

gulimall-gateway模块的src/main/resources/application.yml配置文件中添加路由规则,匹配gulimall-member模块

- id: member_route
  uri: lb://gulimall-member
  predicates:
    - Path=/api/member/**
  filters:
    #http://localhost:88/api/member/memberlevel/list 变为 http://localhost:8000/member/memberlevel/list
    - RewritePath=/api/(?<segment>/?.*),/$\{segment}
image-20220520100339120
image-20220520100339120

7、访问接口

1、直接访问成功

直接访问没有问题 http://localhost:8000/member/memberlevel/list

image-20220520100105298
image-20220520100105298
2、通过网关访问失败

通过网关访问失败: http://localhost:88/api/member/memberlevel/list

image-20220520100123403
image-20220520100123403

8、查找原因

1、查看控制台
nacos registry, gulimall-member 192.168.19.1:8000 register finished
nacos 注册,gulimall-member 192.168.19.1:8000 注册完成

控制台显示已经注册成功了

image-20220520095821795
image-20220520095821795
2、查看报错

这个是NacosConfigProperties的报错,不用管,现在还没有配置配置中心

image-20220520095757543
image-20220520095757543
3、查看服务列表

url:http://localhost:8848/nacos

用户名和密码都为 nacos

可以看到没有发现gulimall-member这个服务

image-20220520100857910
image-20220520100857910
4、查看网关控制台

可以看到gulimall-gateway模块的控制台显示,已正确匹配到了gulimall-member模块

image-20220520100931896
image-20220520100931896
5、关闭隐藏空服务

关闭服务管理/服务列表里的隐藏空服务,这时可以看到gulimall-member服务,不过是空服务,实例数健康实例数都为0

这个服务是用代码注册进去的,而不是点创建服务创建的,应该不会出现是空服务啊

image-20220520102947342
image-20220520102947342
6、重启nacos

关闭nacos,再重新打开

image-20220520104734701
image-20220520104734701
7、刷新nacos的页面

打开服务管理/服务列表里的隐藏空服务,这时还可以看到gulimall-member服务,实例数健康实例数也都为1

image-20220520103649758
image-20220520103649758
8、重新通过网关访问

url:http://localhost:88/api/member/memberlevel/list

重新刷新通过网关访问的页面,这时已经访问成功了,真是奇葩😡

image-20220520103733124
image-20220520103733124

9、请求成功

url:http://localhost:88/api/member/memberlevel/list?t=1652972635593&page=1&limit=500

image-20220520105207935
image-20220520105207935

10、接口文档

url: https://easydoc.net/s/78237135/ZUqEdvA4/jCFganpf

4.6.1.10
4.6.1.10

4.6.2、查询包含本分类的所以品牌

1、复制代码

复制之前建议先备份

  1. 打开"1.分布式基础(全栈开发篇)\资料源码.zip\docs\代码\前端\modules"目录
  2. 选择这些文件(也可以全选)(建议全选复制,不然后面会有报错)
  3. 选择"src/views'目录下的"moudules",然后右键
  4. 选择"在文件资源管理器中显示"
  5. 进入"modules"文件夹
  6. 右键,选择"粘贴",粘贴到这里面
image-20220520110715920
image-20220520110715920

然后点击替换目标中的文件

image-20220520110731311
image-20220520110731311

2、添加会员等级

1、应该有个测试数据

点击用户系统/会员等级,应该会有个测试数据

image-20220520111518924
image-20220520111518924
2、不过我没有
image-20220520111430051
image-20220520111430051
3、添加普通会员

普通会员设置为默认等级

image-20220520111355788
image-20220520111355788
4、添加铜牌会员
image-20220520111821514
image-20220520111821514
5、添加银牌会员
image-20220520112008322
image-20220520112008322

3、又缺少了这个请求

又少了这个请求: http://localhost:88/api/member/memberlevel/list?t=1652972635593&page=1&limit=500

image-20220520113911427
image-20220520113911427

再把pubsub-js引进来就行了

B:\renren-fast-vue\src\views\modules\product\spuadd.vue文件里的<script>标签里导入pubsub-js

import PubSub from "pubsub-js";
image-20220520113858591
image-20220520113858591

请求已经出来了

image-20220520114016202
image-20220520114016202

4、选择分类后缺少了一个请求

当选择完选择分类后,应该发送一个请求,但是我的没有发送,并且在选择完选择分类后控制台报了两个错

url: http://localhost:88/api/product/categorybrandrelation/brands/list?t=1653047129509&catId=225

image-20220520115525930
image-20220520115525930
vue.esm.js?efeb:591 [Vue warn]: Error in callback for watcher "paths": "TypeError: Cannot read properties of undefined (reading 'publish')"
[Vue 警告]:观察者“路径”的回调错误:“TypeError:无法读取未定义的属性(正在读取'publish')”
vue.esm.js?efeb:1741 TypeError: Cannot read properties of undefined (reading 'publish')
TypeError:无法读取未定义的属性(读取“publish”)
image-20220520115547934
image-20220520115547934

5、添加pubsub-js到全局

删掉在B:\renren-fast-vue\src\views\modules\product\spuadd.vue文件里的<script>标签里导入的pubsub-js

image-20220520120720294
image-20220520120720294

src\main.js里导入并使用pubsub-js

import PubSub from 'pubsub-js'
Vue.prototype.PubSub = PubSub
image-20220520120813028
image-20220520120813028

.eslintrc.js里全局使用PubSub

,
  globals: {
    PubSub: true,
  }
image-20220520120827362
image-20220520120827362

已经发送请求了:http://localhost:88/api/product/categorybrandrelation/brands/list?t=1653047129509&catId=225

image-20220520120954196
image-20220520120954196

接口文档: https://easydoc.net/s/78237135/ZUqEdvA4/HgVjlzWV

image-20220520201548014
image-20220520201548014

6、新建BrandVo

gulimall-product模块的com.atguigu.gulimall.product.vo包里新建BrandVo

package com.atguigu.gulimall.product.vo;

import lombok.Data;

/**
 * @author 无名氏
 * @date 2022/5/20
 * @Description:
 */
@Data
public class BrandVo {
    /**
     * 品牌id
     */
    private Long brandId;
    /**
     * 品牌名字
     */
    private String brandName;
}
image-20220520203545414
image-20220520203545414

7、新建relationBrandsList方法

gulimall-product模块的com.atguigu.gulimall.product.controller.CategoryBrandRelationController类里

新建relationBrandsList方法

/**
 *  localhost:88/api/product/categorybrandrelation/brands/list?t=1653048395592&catId=225
 *  获取分类关联的品牌
 *  1、Controller:处理请求,接受和校验数据
 *  2、Service接受controller传来的数据,进行业务处理
 *  3、Controller接受service处理完的数据,封装页面指定的vo
 * @return
 */
@GetMapping("/brands/list")
public R relationBrandsList(@RequestParam(value = "catId",required = true) Long catId){
    List<BrandEntity> brandEntities = categoryBrandRelationService.getBrandsByCatId(catId);
    List<BrandVo> brandVos = brandEntities.stream().map((item) -> {
        BrandVo brandVo = new BrandVo();
        brandVo.setBrandId(item.getBrandId());
        brandVo.setBrandName(item.getName());
        return brandVo;
    }).collect(Collectors.toList());
    return R.ok().put("data",brandVos);
}
image-20220520214042927
image-20220520214042927

8、添加getBrandsByCatId抽象方法

gulimall-product模块的com.atguigu.gulimall.product.service.CategoryBrandRelationService接口里

添加getBrandsByCatId抽象方法

List<BrandEntity> getBrandsByCatId(Long catId);
image-20220520203454859
image-20220520203454859

9、实现getBrandsByCatId抽象方法

gulimall-product模块的com.atguigu.gulimall.product.service.impl.CategoryBrandRelationServiceImpl类里

实现getBrandsByCatId抽象方法

@Override
public List<BrandEntity> getBrandsByCatId(Long catId) {
    LambdaQueryWrapper<CategoryBrandRelationEntity> categoryBrandRelationQueryWrapper = new LambdaQueryWrapper<>();
    categoryBrandRelationQueryWrapper.eq(CategoryBrandRelationEntity::getCatelogId,catId);
    List<CategoryBrandRelationEntity> categoryBrandRelationEntities = this.baseMapper.selectList(categoryBrandRelationQueryWrapper);

    List<Long> brandIds = categoryBrandRelationEntities.stream().
            map(CategoryBrandRelationEntity::getBrandId).collect(Collectors.toList());
    List<BrandEntity> brandEntities = brandDao.selectBatchIds(brandIds);
    return brandEntities;
}
image-20220520204754999
image-20220520204754999

10、测试

重启gulimall-product模块,刷新前端页面,

1、已经发送请求了
image-20220520204950145
image-20220520204950145
2、查看返回的数据
image-20220520205355868
image-20220520205355868
3、将手机1改为手机

gulimall_pms数据库中的pms_category_brand_relation表里的name手机1的字段改为手机

image-20220520205434818
image-20220520205434818
4、添加别的品牌的关联分类
GIF 2022-5-20 20-58-33
GIF 2022-5-20 20-58-33
5、选择品牌

选择好选择分类后,可以看到已返回分类包含手机/手机通讯/手机的所有品牌,点击选择品牌的选择框后,可以显示这些品牌

image-20220520210139905
image-20220520210139905

4.6.3、获取分类下所有分组&关联属性

1、查看请求

1、设置基本信息

基本信息设置完成后,点击下一步:设置基本参数,这时会发送一个请求

url:http://localhost:88/api/product/attrgroup/225/withattr?t=1653056166646

(这里的数据随便填填就行了,现在也不会提交的数据库)

(图片上传失败是因为gulimall-third-party模块没启动)

image-20220520220205600
image-20220520220205600
2、查看请求

url:http://localhost:88/api/product/attrgroup/225/withattr?t=1653056166646

image-20220520215846566
image-20220520215846566
3、接口文档

接口文档在商品系统/17、获取分类下所有分组&关联属性里:https://easydoc.net/s/78237135/ZUqEdvA4/6JM6txHf

image-20220520221908190

2、新建AttrGroupWithAttrsVo

gulimall-productcom.atguigu.gulimall.product.vo包下新建AttrGroupWithAttrsVo

复制com.atguigu.gulimall.product.entity.AttrGroupEntity类的字段,并删掉数据库相关注解

调整AttrGroupWithAttrsVo类,修改成我们需要的vo对象

package com.atguigu.gulimall.product.vo;


import com.atguigu.gulimall.product.entity.AttrEntity;
import lombok.Data;

import java.util.List;

/**
 * @author 无名氏
 * @date 2022/5/20
 * @Description:
 */
@Data
public class AttrGroupWithAttrsVo {
    /**
     * 分组id
     */
    private Long attrGroupId;
    /**
     * 组名
     */
    private String attrGroupName;
    /**
     * 排序
     */
    private Integer sort;
    /**
     * 描述
     */
    private String descript;
    /**
     * 组图标
     */
    private String icon;
    /**
     * 所属分类id
     */
    private Long catelogId;

    private List<AttrEntity> attrs;

}
image-20220520233852985
image-20220520233852985

3、添加getAttrGroupWithAttrs方法

gulimall-product模块的com.atguigu.gulimall.product.controller.AttrGroupController类里添加

getAttrGroupWithAttrs方法,通过catelogId获取当前分类下所有分组&关联属性

image-20220520231533320
image-20220520231533320

4、添加getAttrGroupWithAttrsByCatelogId抽象方法

gulimall-product模块的com.atguigu.gulimall.product.service.AttrGroupService类里添加getAttrGroupWithAttrsByCatelogId抽象方法

image-20220520231548548
image-20220520231548548

5、实现getAttrGroupWithAttrsByCatelogId抽象方法

gulimall-product模块的com.atguigu.gulimall.product.service.impl.AttrGroupServiceImpl类里实现getAttrGroupWithAttrsByCatelogId抽象方法,通过catelogId获取当前分类下所有分组&关联属性

image-20220520234608206
image-20220520234608206

6、查看回显

已正确获得数据,但是没有回显到页面上,此时控制台也报了一个错

image-20220520234852186
image-20220520234852186

7、spuadd.vue的第679行报错

可以看到spuadd.vue的第679行的foreach报错

TypeError: Cannot read properties of null (reading ' forEach')
TypeError:无法读取 null 的属性(读取“forEach”)
image-20220520235236298
image-20220520235236298

8、showBaseAttrs()方法里加一个判断

src\views\modules\product\spuadd.vueshowBaseAttrs()方法里的foreach里面加一个判断,没有attrs就不遍历

data.data.forEach(item => {
  if(item.attrs != null && item.attrs.length > 0){
    let attrArray = [];
    item.attrs.forEach(attr => {
      attrArray.push({
        attrId: attr.attrId,
        attrValues: "",
        showDesc: attr.showDesc
      });
    });
    this.dataResp.baseAttrs.push(attrArray);
  }
});
image-20220520235759910
image-20220520235759910

9、页面已成功显示

重新测试,页面已成功显示数据

image-20220520235924882
image-20220520235924882

4.6.4、新增商品(1)

1、发送请求

1、添加测试数据
1、新增机身长度(mm)

商品系统/平台属性/规格参数里新增机身长度(mm)

image-20220521200453301
image-20220521200453301
2、新增机身材质工艺

商品系统/平台属性/规格参数里新增机身材质工艺

image-20220521200845851
image-20220521200845851
3、删除测试

删除商品系统/平台属性/属性分组里的测试

image-20220521201219156
image-20220521201219156
4、添加主芯片

商品系统/平台属性/属性分组里添加主芯片

image-20220521201609609
image-20220521201609609
5、新增CPU品牌

商品系统/平台属性/规格参数里新增CPU品牌

image-20220521202050971
image-20220521202050971
6、新增CPU型号

商品系统/平台属性/规格参数里新增CPU型号

image-20220521202428453
image-20220521202428453
7、修改上市年份里的所属分组

商品系统/平台属性/规格参数里修改上市年份里的所属分组主体

image-20220521202603474
image-20220521202603474
8、修改机身颜色里的所属分组

商品系统/平台属性/规格参数里修改机身颜色里的所属分组基本信息

image-20220521202705771
image-20220521202705771
9、修改内存里的可选值

商品系统/平台属性/销售属性里修改内存里的可选值

image-20220522110005717
image-20220522110005717
10、添加版本

商品系统/平台属性/销售属性里添加版本image-20220522110314425

2、录入商品信息
1、录入基本属性
image-20220521203305473
image-20220521203305473
2、录入规格参数里的主体
image-20220521203509597
image-20220521203509597
3、录入规格参数里的基本信息
image-20220521203913840
image-20220521203913840
4、录入规格参数里的主芯片
image-20220521204015874
image-20220521204015874
5、录入销售属性
image-20220521205929197
image-20220521205929197
6、修改SKU信息
颜色版本商品名称标题副标题价格
星河银8GB+128GB华为 HUAWEI Mate30Pro 星河银 8GB+128GB华为 HUAWEI Mate30Pro 星河银 8GB+128GB 麒麟990旗舰芯片OLED环幕屏双4000万徕卡电影四摄 4G全网通手机[现货抢购!享白条12期免息!]麒麟990, OLED环幕屏双4000万徕卡电影四摄:Mate30系列享12期免息》5799
星河银8GB+256GB华为 HUAWEI Mate30Pro 星河银 8GB+256GB华为 HUAWEI Mate30Pro 星河银 8GB+256GB 麒麟990旗舰芯片OLED环幕屏双4000万徕卡电影四摄 4G全网通手机[现货抢购!享白条12期免息!]麒麟990, OLED环幕屏双4000万徕卡电影四摄:Mate30系列享12期免息》6299
亮黑色8GB+128GB华为 HUAWEI Mate30Pro 亮黑色 8GB+128GB华为 HUAWEI Mate30Pro 亮黑色 8GB+128GB 麒麟990旗舰芯片OLED环幕屏双4000万徕卡电影四摄 4G全网通手机[现货抢购!享白条12期免息!]麒麟990, OLED环幕屏双4000万徕卡电影四摄:Mate30系列享12期免息》5799
亮黑色8GB+256GB华为 HUAWEI Mate30Pro 亮黑色 8GB+256GB华为 HUAWEI Mate30Pro 亮黑色 8GB+256GB 麒麟990旗舰芯片OLED环幕屏双4000万徕卡电影四摄 4G全网通手机[现货抢购!享白条12期免息!]麒麟990, OLED环幕屏双4000万徕卡电影四摄:Mate30系列享12期免息》6299
翡冷翠8GB+128GB华为 HUAWEI Mate30Pro 翡冷翠 8GB+128GB华为 HUAWEI Mate30Pro 翡冷翠 8GB+128GB 麒麟990旗舰芯片OLED环幕屏双4000万徕卡电影四摄 4G全网通手机[现货抢购!享白条12期免息!]麒麟990, OLED环幕屏双4000万徕卡电影四摄:Mate30系列享12期免息》5799
翡冷翠8GB+256GB华为 HUAWEI Mate30Pro 翡冷翠 8GB+256GB华为 HUAWEI Mate30Pro 翡冷翠 8GB+256GB 麒麟990旗舰芯片OLED环幕屏双4000万徕卡电影四摄 4G全网通手机[现货抢购!享白条12期免息!]麒麟990, OLED环幕屏双4000万徕卡电影四摄:Mate30系列享12期免息》6299
罗兰紫8GB+128GB华为 HUAWEI Mate30Pro 罗兰紫 8GB+128GB华为 HUAWEI Mate30Pro 罗兰紫 8GB+128GB 麒麟990旗舰芯片OLED环幕屏双4000万徕卡电影四摄 4G全网通手机[现货抢购!享白条12期免息!]麒麟990, OLED环幕屏双4000万徕卡电影四摄:Mate30系列享12期免息》5799
罗兰紫8GB+256GB华为 HUAWEI Mate30Pro 罗兰紫 8GB+256GB华为 HUAWEI Mate30Pro 罗兰紫 8GB+256GB 麒麟990旗舰芯片OLED环幕屏双4000万徕卡电影四摄 4G全网通手机[现货抢购!享白条12期免息!]麒麟990, OLED环幕屏双4000万徕卡电影四摄:Mate30系列享12期免息》6299
image-20220521213940120
image-20220521213940120
7、修改SKU信息里的星河银 8GB+128GB信息
image-20220521214707364
image-20220521214707364
8、修改SKU信息里的星河银8GB+256GB信息
image-20220521214923139
image-20220521214923139
9、修改SKU信息里的亮黑色 8GB+128GB信息
image-20220521215050927
image-20220521215050927
10、修改SKU信息里的亮黑色 8GB+256GB信息
image-20220521215354005
image-20220521215354005
11、修改SKU信息里的翡冷翠 8GB+128GB信息
image-20220521215557469
image-20220521215557469
12、修改SKU信息里的翡冷翠 8GB+256GB信息
image-20220521215911728
image-20220521215911728
13、修改SKU信息里的罗兰紫 8GB+128GB信息
image-20220521220119171
image-20220521220119171
14、修改SKU信息里的罗兰紫 8GB+256GB信息
image-20220521220313889
image-20220521220313889
15、查看发送的数据
  1. 打开控制台
  2. 点击下一步:保存商品信息
  3. 点击Copy
  4. 先放到记事本里保存,免得后面操作多了,不小心丢了(后面写完后端代码后一定会用到,一定要存着,否者只能重新添加商品了)

点击查看刚刚复制的json

image-20220521221004925
image-20220521221004925
3、接口文档

接口文档 在商品系统/19、新增商品里: https://easydoc.net/s/78237135/ZUqEdvA4/5ULdV3dd

image-20220521220653670
image-20220521220653670

2、JSON生成Java实体类

1、格式化JSON

将刚刚复制到JSON粘贴到输入框,点击格式化校验,检查JSON

url: 在线JSON校验格式化工具(Be JSON)open in new window

点击查看格式化后的Json

image-20220521223643977
image-20220521223643977
2、复制vo包的路径

复制gulimall-product模块的com.atguigu.gulimall.product.vo包的路径

image-20220521223237967
image-20220521223237967
3、生成Java实体类
  1. 粘贴JOSN
  2. 输入"SpuSaveVo"
  3. 粘贴刚刚复制的路径 "com.atguigu.gulimall.product.vo"
  4. 点击"生成JavaBean"
  5. 检查一下代码是否正确
  6. 点击"下载代码"
image-20220521223304810
image-20220521223304810

3、添加vo对象

1、使用生成的Java实体类

解压刚刚生成的Java实体类,复制这些实体类

image-20220521223941902
image-20220521223941902

粘贴到gulimall-product模块的com.atguigu.gulimall.product.vo包下

image-20220521224052736
image-20220521224052736
2、修改粘贴的Java实体类(不推荐)
1、修改SpuSaveVo

修改gulimall-product模块的com.atguigu.gulimall.product.vo包下的SpuSaveVo

/**
  * Copyright 2022 bejson.com 
  */
package com.atguigu.gulimall.product.vo;
import lombok.Data;

import java.math.BigDecimal;
import java.util.List;

/**
 * Auto-generated: 2022-05-21 22:37:36
 *
 * @author bejson.com ([email protected])
 * @website http://www.bejson.com/java2pojo/
 */
@Data
public class SpuSaveVo {

    private String spuName;
    private String spuDescription;
    private Long catalogId;
    private Long brandId;
    private BigDecimal weight;
    private int publishStatus;
    private List<String> decript;
    private List<String> images;
    private Bounds bounds;
    private List<BaseAttrs> baseAttrs;
    private List<Skus> skus;

}
image-20220521225538198
image-20220521225538198
2、修改Skus

修改gulimall-product模块的com.atguigu.gulimall.product.vo包下的Skus

/**
  * Copyright 2022 bejson.com 
  */
package com.atguigu.gulimall.product.vo;
import lombok.Data;

import java.math.BigDecimal;
import java.util.List;

/**
 * Auto-generated: 2022-05-21 22:37:36
 *
 * @author bejson.com ([email protected])
 * @website http://www.bejson.com/java2pojo/
 */
@Data
public class Skus {

    private List<Attr> attr;
    private String skuName;
    private BigDecimal price;
    private String skuTitle;
    private String skuSubtitle;
    private List<Images> images;
    private List<String> descar;
    private int fullCount;
    private BigDecimal discount;
    private int countStatus;
    private BigDecimal fullPrice;
    private BigDecimal reducePrice;
    private int priceStatus;
    private List<MemberPrice> memberPrice;

}
image-20220521225626590
image-20220521225626590
3、修改Bounds

修改gulimall-product模块的com.atguigu.gulimall.product.vo包下的Bounds

/**
  * Copyright 2022 bejson.com 
  */
package com.atguigu.gulimall.product.vo;

import lombok.Data;

import java.math.BigDecimal;

/**
 * Auto-generated: 2022-05-21 22:37:36
 *
 * @author bejson.com ([email protected])
 * @website http://www.bejson.com/java2pojo/
 */
@Data
public class Bounds {

    private BigDecimal buyBounds;
    private BigDecimal growBounds;

}
image-20220521225730419
image-20220521225730419
4、修改Attr

修改gulimall-product模块的com.atguigu.gulimall.product.vo包下的Attr

/**
  * Copyright 2022 bejson.com 
  */
package com.atguigu.gulimall.product.vo;

import lombok.Data;

/**
 * Auto-generated: 2022-05-21 22:37:36
 *
 * @author bejson.com ([email protected])
 * @website http://www.bejson.com/java2pojo/
 */
@Data
public class Attr {

    private Long attrId;
    private String attrName;
    private String attrValue;

}
image-20220521225822709
image-20220521225822709
5、修改MemberPrice

修改gulimall-product模块的com.atguigu.gulimall.product.vo包下的MemberPrice

/**
  * Copyright 2022 bejson.com 
  */
package com.atguigu.gulimall.product.vo;

import lombok.Data;

import java.math.BigDecimal;

/**
 * Auto-generated: 2022-05-21 22:37:36
 *
 * @author bejson.com ([email protected])
 * @website http://www.bejson.com/java2pojo/
 */
@Data
public class MemberPrice {

    private Long id;
    private String name;
    private BigDecimal price;

}
image-20220521225934901
image-20220521225934901
6、修改Images

修改gulimall-product模块的com.atguigu.gulimall.product.vo包下的Images

/**
  * Copyright 2022 bejson.com 
  */
package com.atguigu.gulimall.product.vo;

import lombok.Data;

/**
 * Auto-generated: 2022-05-21 22:37:36
 *
 * @author bejson.com ([email protected])
 * @website http://www.bejson.com/java2pojo/
 */
@Data
public class Images {

    private String imgUrl;
    private int defaultImg;


}
image-20220521230008068
image-20220521230008068
7、修改BaseAttrs

修改gulimall-product模块的com.atguigu.gulimall.product.vo包下的BaseAttrs

/**
  * Copyright 2022 bejson.com 
  */
package com.atguigu.gulimall.product.vo;

import lombok.Data;

/**
 * Auto-generated: 2022-05-21 22:37:36
 *
 * @author bejson.com ([email protected])
 * @website http://www.bejson.com/java2pojo/
 */
@Data
public class BaseAttrs {

    private Long attrId;
    private String attrValues;
    private int showDesc;


}
image-20220521230035175
image-20220521230035175
3、使用内部类(推荐)
1、修改成内部类报错
package com.atguigu.gulimall.product.vo;

import lombok.Data;

import java.math.BigDecimal;
import java.util.List;

@Data
public class SpuSaveVo {

    private String spuName;
    private String spuDescription;
    private Long catalogId;
    private Long brandId;
    private BigDecimal weight;
    private int publishStatus;
    private List<String> decript;
    private List<String> images;
    private Bounds bounds;
    private List<BaseAttrs> baseAttrs;
    private List<Skus> skus;

    @Data
    private class BaseAttrs {
        private Long attrId;
        private String attrValues;
        private int showDesc;
    }

    @Data
    private class Skus {
        private List<Attr> attr;
        private String skuName;
        private BigDecimal price;
        private String skuTitle;
        private String skuSubtitle;
        private List<Images> images;
        private List<String> descar;
        private int fullCount;
        private BigDecimal discount;
        private int countStatus;
        private BigDecimal fullPrice;
        private BigDecimal reducePrice;
        private int priceStatus;
        private List<MemberPrice> memberPrice;
    }

    @Data
    private class Attr {
        private Long attrId;
        private String attrName;
        private String attrValue;

    }

    @Data
    private class Images {
        private String imgUrl;
        private int defaultImg;
    }

    @Data
    private class MemberPrice {
        private Long id;
        private String name;
        private BigDecimal price;
    }

    @Data
    private class Bounds {
        private BigDecimal buyBounds;
        private BigDecimal growBounds;

    }

}
image-20220522101000555
image-20220522101000555

重启gulimall-product模块,控制台报错

org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot construct instance of `com.atguigu.gulimall.product.vo.SpuSaveVo$BaseAttrs` (although at least one Creator exists): can only instantiate non-static inner class by using default, no-argument constructor; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `com.atguigu.gulimall.product.vo.SpuSaveVo$BaseAttrs` (although at least one Creator exists): can only instantiate non-static inner class by using default, no-argument constructor

org.springframework.http.converter.HttpMessageNotReadableException:JSON解析错误:无法构造`com.atguigu.gulimall.product.vo.SpuSaveVo$BaseAttrs`的实例(尽管至少存在一个Creator):只能实例化非静态内部类 通过使用默认的无参数构造函数; 嵌套异常是 com.fasterxml.jackson.databind.exc.MismatchedInputException:无法构造 `com.atguigu.gulimall.product.vo.SpuSaveVo$BaseAttrs` 的实例(尽管至少存在一个 Creator):只能实例化非静态内部 使用默认的无参数构造函数进行类
2、修改为静态内部类

本类下的所有内部类添加staitc关键字,修改为静态内部类

image-20220522101630956
image-20220522101630956
4、测试
1、打断点

修改gulimall-product模块的com.atguigu.gulimall.product.controller.SpuInfoController类的save方法

并打断点,以测试SpuSaveVo是否可以正确封装数据

然后以debug方式启动gulimall-product模块

/**
 * 保存
 */
@RequestMapping("/save")
public R save(@RequestBody SpuSaveVo spuSaveVo) {
    //spuInfoService.save(spuInfo);
    return R.ok();
}
image-20220522120721302
image-20220522120721302
2、使用Postman发送请求
  1. 输入url:http://localhost:88/api/product/spuinfo/save
  2. 选择Post
  3. 点击Body
  4. 点击raw
  5. 选择JSON
  6. 粘贴控制台里复制的JSON
  7. 点击Send
image-20220522120320199
image-20220522120320199
3、查看封装的数据

可以看到SpuSaveVo类已正确封装了数据

image-20220522101945488
image-20220522101945488

4、修改save方法

修改gulimall-product模块的com.atguigu.gulimall.product.controller.SpuInfoController类的save方法

应该是spuInfoService.saveSpuInfo(spuSaveVo);,这里写错了

/**
 * 保存
 */
@RequestMapping("/save")
public R save(@RequestBody SpuSaveVo spuSaveVo) {
    //spuInfoService.save(spuInfo);
   spuInfoService.saveSouInfo(spuSaveVo);
   return R.ok();
}
image-20220522093324236
image-20220522093324236

5、添加saveSouInfo抽象方法

(应该是添加saveSouInfo抽象方法,这里写错了)

gulimall-product模块的com.atguigu.gulimall.product.service.SpuInfoService接口里添加saveSouInfo抽象方法

image-20220522093446271
image-20220522093446271

6、实现saveSouInfo抽象方法

(应该是添加saveSpuInfo抽象方法,这里写错了)

gulimall-product模块的com.atguigu.gulimall.product.service.impl.SpuInfoServiceImpl类里实现saveSouInfo抽象方法

@Transactional(rollbackFor = Exception.class)
@Override
public void saveSouInfo(SpuSaveVo spuSaveVo) {
    //1、保存spu基本信息 pms_spu_info

    //2、保存Spu的描述图片 pms_spu_info_desc

    //3、保存spu的图片集 pms_spu_images

    //4、保存spu的规格参数;pms_product_attr_value

    //5、保存spu的积分信息; gulimall_sms->sms_spu_bounds

    //5、保存当前spu对应的所有sku信息;
    //5.1)、sku的基本信息; pms_sku_info
    //5.2)、sku的图片信息; pms_sku_images
    //5.3)、sku的销售属性信息: pms_sku_sale_attr_value
    //5.4)、sku的优惠、满减、打折等信息;gulimall_sms->sms_sku_ladder\sms_sku_full_reduction\sms_ member_price

}
image-20220522141406494
image-20220522141406494

4.6.5、新增商品(2)

4.6.5.1、保存spu基本信息pms_spu_info

1、调用saveBaseSpuInfo方法

gulimall-product模块的com.atguigu.gulimall.product.service.impl.SpuInfoServiceImpl类里的saveSouInfo方法里编写部分代码,用于保存spu基本信息

@Transactional(rollbackFor = Exception.class)
@Override
public void saveSouInfo(SpuSaveVo spuSaveVo) {
    //1、保存spu基本信息 pms_spu_info
    SpuInfoEntity spuInfoEntity = new SpuInfoEntity();
    this.saveBaseSpuInfo(spuInfoEntity);
}
image-20220522163949897
image-20220522163949897
2、创建saveBaseSpuInfo方法

光标放在SpuInfoServiceImpl类的saveSouInfo里的this.saveBaseSpuInfo(spuInfoEntity);中的;前面

使用alt+enter快捷键,选择Create method 'saveBaseSpuInfo',然后选择SpuInfoService

gulimall-product模块的com.atguigu.gulimall.product.service.SpuInfoService接口创建saveBaseSpuInfo抽象方法

void saveBaseSpuInfo(SpuInfoEntity spuInfoEntity);

光标放在SpuInfoService接口的void saveBaseSpuInfo(SpuInfoEntity spuInfoEntity);中的;前面

使用alt+enter快捷键,选择Implement method 'saveBaseSpuInfo'(没有这个选项的随便在该抽象方法后面敲两个回车就有了)

gulimall-product模块的com.atguigu.gulimall.product.service.impl.SpuInfoServiceImpl类里实现saveBaseSpuInfo抽象方法

GIF 2022-5-22 16-37-11
GIF 2022-5-22 16-37-11
3、编写saveBaseSpuInfo方法

gulimall-product模块的com.atguigu.gulimall.product.service.impl.SpuInfoServiceImpl类里编写saveBaseSpuInfo方法

@Override
public void saveBaseSpuInfo(SpuInfoEntity spuInfoEntity) {
    this.baseMapper.insert(spuInfoEntity);
}
image-20220522171910439
image-20220522171910439
4、封装对象(方法一)
1、查看SpuSaveVo中缺少的字段

可以看到SpuSaveVo类里相较于SpuInfoEntity没有createTime字段和SpuSaveVo字段

image-20220522172310652
image-20220522172310652
2、使用代码添加时间

修改gulimall-product模块的com.atguigu.gulimall.product.service.impl.SpuInfoServiceImpl类的saveBaseSpuInfo方法

@Transactional(rollbackFor = Exception.class)
@Override
public void saveSouInfo(SpuSaveVo spuSaveVo) {
    //1、保存spu基本信息 pms_spu_info
    SpuInfoEntity spuInfoEntity = new SpuInfoEntity();
    BeanUtils.copyProperties(spuSaveVo,spuInfoEntity);
    spuInfoEntity.setCreateTime(new Date());
    spuInfoEntity.setUpdateTime(new Date());
    this.saveBaseSpuInfo(spuInfoEntity);
}
image-20220522172613649
image-20220522172613649
5、封装对象(方法二)
1、修改saveBaseSpuInfo方法

修改gulimall-product模块的com.atguigu.gulimall.product.service.impl.SpuInfoServiceImpl类的saveBaseSpuInfo方法

@Transactional(rollbackFor = Exception.class)
@Override
public void saveSouInfo(SpuSaveVo spuSaveVo) {
    //1、保存spu基本信息 pms_spu_info
    SpuInfoEntity spuInfoEntity = new SpuInfoEntity();
    BeanUtils.copyProperties(spuSaveVo,spuInfoEntity);
    this.saveBaseSpuInfo(spuInfoEntity);
}
image-20220522172710746
image-20220522172710746
2、添加MetaObjectHandler配置

gulimall-product模块的com.atguigu.gulimall.product.config包里新建MyBatisConfig类,继承MetaObjectHandler

(应该把LocalDateTime.class改为new Date())

package com.atguigu.gulimall.product.config;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.context.annotation.Configuration;

import java.time.LocalDateTime;

/**
 * @author 无名氏
 * @date 2022/5/22
 * @Description:
 */
@Slf4j
@Configuration
public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("start insert fill...");
        //this.setFieldValByName("createTime", new Date(), metaObject);
        this.setFieldValByName("createTime", LocalDateTime.class, metaObject);
        this.setFieldValByName("updateTime", LocalDateTime.class, metaObject);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("start update fill...");
        this.setFieldValByName("updateTime", LocalDateTime.class, metaObject);
    }
}
image-20220522180735126
image-20220522180735126
3、实体类@TableField添加注解

gulimall-product模块的com.atguigu.gulimall.product.entity.SpuInfoEntity实体类的createTimeupdateTime字段添加@TableField注解,测试时肯定会报错,这里想看一下会报什么错(如果使用的是new Date()就不会报错,并且还会得到正确的结果)

(如果是MetaObjectHandler配置类使用的是LocalDateTime类型 要加@DateTimeFormat不加这个注解查询的时候会报错)

/**
 * 1、如果是MetaObjectHandler配置类使用的是LocalDateTime类型 要加@DateTimeFormat不加这个注解查询的时候会报错
 *    DateUtil使用的是org.springblade.core.tool.utils.DateUtil;
 *    @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
 *    @JsonFormat(pattern = DateUtil.PATTERN_DATETIME)
 *    @TableField(fill = FieldFill.INSERT)
 * 2、如果使用的是 new Date() ,则只需要使用 @TableField(fill = FieldFill.INSERT)
 *    @TableField(fill = FieldFill.INSERT)
 */
@TableField(fill = FieldFill.INSERT)
private Date createTime;
/**
 * 
 */
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
image-20220522182337080
image-20220522182337080

别人的DateUtil工具类,不过它使用的是new Date()方式,不需要设置@DateTimeFormat,这里想看一看会报什么错

点击查看DateUtil类完整代码

4、使用Postman发送请求

重启gulimall-product模块,使用Postman发送请求

image-20220522181338567
image-20220522181338567
5、查看报错
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.reflection.ReflectionException: Could not set property 'createTime' of 'class com.atguigu.gulimall.product.entity.SpuInfoEntity' with value 'class java.time.LocalDateTime' Cause: java.lang.IllegalArgumentException: argument type mismatch

org.mybatis.spring.MyBatisSystemException:嵌套异常是 org.apache.ibatis.reflection.ReflectionException:无法将 'class com.atguigu.gulimall.product.entity.SpuInfoEntity' 的属性'createTime'设置为'class java.time' .LocalDateTime' 原因:java.lang.IllegalArgumentException:参数类型不匹配
image-20220522181444148
image-20220522181444148
6、google一下这个错

stackoverflow上说需要使用DateTimeFormatter.ISO_LOCAL_DATE_TIME

https://stackoverflow.com/questions/57972766/java-lang-illegalargumentexception-platform-class-java-time-localdatetime-with

image-20220522181635099
image-20220522181635099
7、属性值必须是常量

gulimall-product模块的com.atguigu.gulimall.product.entity.SpuInfoEntity类的createTime方法上添加注解@DateTimeFormat(pattern = DateTimeFormatter.ISO_LOCAL_DATE_TIME)提示Attribute value must be constant

删掉这个注解,测试一下

image-20220522181946429
image-20220522181946429
8、测试日期格式

gulimall-product模块的com.atguigu.gulimall.product.GulimallProductApplicationTests类添加DateTimeFormatterTest测试方法,进行测试

@Test
public void DateTimeFormatterTest(){
   DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
   LocalDateTime localDateTime = LocalDateTime.now();//获取当前时间
   String str = formatter.format(localDateTime);
   System.out.println(localDateTime);//2022-01-24T11:06:34.473
   System.out.println(str);
}

测试结果:

2022-05-22T18:30:59:498
2022-05-22T18:30:59:498
image-20220522193641644
image-20220522193641644

参考链接: https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html

image-20220522193704482修改gulimall-product模块的com.atguigu.gulimall.product.GulimallProductApplicationTests类的DateTimeFormatterTest测试方法,重新进行测试

@Test
public void DateTimeFormatterTest(){
   DateTimeFormatter formatter1 = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
   LocalDateTime localDateTime1 = LocalDateTime.now();//获取当前时间
   String str1 = formatter1.format(localDateTime1);
   System.out.println(localDateTime1);//2022-01-24T11:06:34.473
   System.out.println(str1);

   System.out.println("==========================");
   DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyy-MM-ddTHH:mm:ss");
   LocalDateTime localDateTime2 = LocalDateTime.now();//获取当前时间
   String str2 = formatter2.format(localDateTime2);
   System.out.println(localDateTime2);
   System.out.println(str2);

}

测试失败了:

java.lang.IllegalArgumentException: Unknown pattern letter: T
java.lang.IllegalArgumentException:未知模式字母:T
image-20220522194537130
image-20220522194537130

修改gulimall-product模块的com.atguigu.gulimall.product.GulimallProductApplicationTests类的DateTimeFormatterTest测试方法,重新进行测试

@Test
public void DateTimeFormatterTest(){
   DateTimeFormatter formatter1 = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
   LocalDateTime localDateTime1 = LocalDateTime.now();//获取当前时间
   String str1 = formatter1.format(localDateTime1);
   System.out.println(localDateTime1);//2022-01-24T11:06:34.473
   System.out.println(str1);

   System.out.println("==========================");
   DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
   LocalDateTime localDateTime2 = LocalDateTime.now();//获取当前时间
   String str2 = formatter2.format(localDateTime2);
   System.out.println(localDateTime2);
   System.out.println(str2);

}

输出的格式还是不对:

2022-05-22T19:46:24.884
2022-05-22T19:46:24.884
==========================
2022-05-22T19:46:24.886
2022-05-22T19:46:24
image-20220522194710291
image-20220522194710291

查看DateTimeFormatter.ISO_LOCAL_DATE_TIME

image-20220522195349302
image-20220522195349302

gulimall-product模块的com.atguigu.gulimall.product.entity.SpuInfoEntity实体类的createTimeupdateTime字段上添加注解,重启gulimall-product模块,使用Postman发送请求后,还是报错

/**
 * 1、如果是MetaObjectHandler配置类使用的是LocalDateTime类型 要加@DateTimeFormat不加这个注解查询的时候会报错
 *       DateUtil使用的是org.springblade.core.tool.utils.DateUtil;
 *    @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
 *    @JsonFormat(pattern = DateUtil.PATTERN_DATETIME)
 *    @TableField(fill = FieldFill.INSERT)
 * 2、如果使用的是 new Date() ,则只需要使用 @TableField(fill = FieldFill.INSERT)
 *       @TableField(fill = FieldFill.INSERT)
 */
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@TableField(fill = FieldFill.INSERT)
private Date createTime;
/**
 * 
 */
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
image-20220522195303101
image-20220522195303101
9、最终方案

还是使用了最初应该使用的方法

修改gulimall-product模块的com.atguigu.gulimall.product.config.MyMetaObjectHandler

LocalDateTime.class全部修改为new Date()

package com.atguigu.gulimall.product.config;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.context.annotation.Configuration;

import java.util.Date;

/**
 * @author 无名氏
 * @date 2022/5/22
 * @Description:
 */
@Slf4j
@Configuration
public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("start insert fill...");
        //this.setFieldValByName("createTime", new Date(), metaObject);
        this.setFieldValByName("createTime", new Date(), metaObject);
        this.setFieldValByName("updateTime", new Date(), metaObject);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("start update fill...");
        this.setFieldValByName("updateTime", new Date(), metaObject);
    }
}
image-20220522201105155
image-20220522201105155

重启gulimall-product模块,使用Postman发送请求

image-20220522201604822
image-20220522201604822

gulimall-product模块的com.atguigu.gulimall.product.entity.SpuInfoEntity

package com.atguigu.gulimall.product.entity;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;

/**
 * spu信息
 * 
 * @author 无名氏
 * @email [email protected]
 * @date 2022-04-17 18:19:58
 */
@Data
@TableName("pms_spu_info")
public class SpuInfoEntity implements Serializable {
   private static final long serialVersionUID = 1L;

   /**
    * 商品id
    */
   @TableId
   private Long id;
   /**
    * 商品名称
    */
   private String spuName;
   /**
    * 商品描述
    */
   private String spuDescription;
   /**
    * 所属分类id
    */
   private Long catalogId;
   /**
    * 品牌id
    */
   private Long brandId;
   /**
    * 
    */
   private BigDecimal weight;
   /**
    * 上架状态[0 - 下架,1 - 上架]
    */
   private Integer publishStatus;
   /**
    * 1、如果是MetaObjectHandler配置类使用的是LocalDateTime类型 要加@DateTimeFormat不加这个注解查询的时候会报错
    *       DateUtil使用的是org.springblade.core.tool.utils.DateUtil;
    *    @DateTimeFormat(pattern = DateUtil.PATTERN_DATETIME)
    *    @JsonFormat(pattern = DateUtil.PATTERN_DATETIME)
    *    @TableField(fill = FieldFill.INSERT)
    * 2、如果使用的是 new Date() ,则只需要使用 @TableField(fill = FieldFill.INSERT)
    *       @TableField(fill = FieldFill.INSERT)
    */
   @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
   @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
   @TableField(fill = FieldFill.INSERT)
   private Date createTime;
   /**
    * 
    */
   @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
   @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
   @TableField(fill = FieldFill.INSERT_UPDATE)
   private Date updateTime;

}

可以看到插入到时间精确到了毫秒

image-20220522201403809
image-20220522201403809

查看数据库,数据库的时间精确到了(四舍五入)

image-20220522201644762
image-20220522201644762

删掉gulimall-product模块的com.atguigu.gulimall.product.entity.SpuInfoEntitycreateTime字段和updateTime字段上的@DateTimeFormat@JsonFormat注解

image-20220522201719091
image-20220522201719091

重启gulimall-product模块,重新使用Postman发送请求,控制台还是精确到毫秒,数据库还是精确到(四舍五入)

image-20220522201908544
image-20220522201908544

截断gulimall_pms模块的pms_spu_info,删除数据并使id重新从1开始

image-20220522212108872
image-20220522212108872
6、修改saveSouInfosaveSpuInfo

修改saveSouInfosaveSpuInfo,之前这个方法名写错了

双击选中gulimall-product模块的com.atguigu.gulimall.product.service.impl.SpuInfoServiceImpl类的saveSpuInfo方法

右键选择Find Usages,查看哪些类调用了该方法

image-20220522225959312
image-20220522225959312

gulimall-product模块的com.atguigu.gulimall.product.controller.SpuInfoController类的save方法里,

saveSouInfo修改为saveSpuInfo

image-20220522221105775
image-20220522221105775

gulimall-product模块的com.atguigu.gulimall.product.service.SpuInfoService接口里,把saveSouInfo抽象方法修改为saveSpuInfo

image-20220522221206062
image-20220522221206062

gulimall-product模块的com.atguigu.gulimall.product.service.impl.SpuInfoServiceImpl类里把saveSpuInfo方法改为saveSpuInfo

image-20220522230202137
image-20220522230202137

4.6.5.2、保存Spu的描述信息pms_spu_info_desc

1、调用saveSpuInfoDesc方法

gulimall-product模块的com.atguigu.gulimall.product.service.impl.SpuInfoServiceImpl类里的saveSouInfo方法里编写部分代码,用于保存Spu的描述图片

@Autowired
SpuInfoDescService spuInfoDescService;

@Transactional(rollbackFor = Exception.class)
@Override
public void saveSpuInfo(SpuSaveVo spuSaveVo) {
    //1、保存spu基本信息 pms_spu_info
    SpuInfoEntity spuInfoEntity = new SpuInfoEntity();
    BeanUtils.copyProperties(spuSaveVo,spuInfoEntity);
    this.saveBaseSpuInfo(spuInfoEntity);

    //2、保存Spu的描述信息 pms_spu_info_desc
    List<String> decript = spuSaveVo.getDecript();
    if (decript!=null && decript.size()>0) {
        SpuInfoDescEntity spuInfoDescEntity = new SpuInfoDescEntity();
        //this.saveBaseSpuInfo(spuInfoEntity);执行后,会将生成的id赋值到puInfoDescEntity的spuId里
        spuInfoDescEntity.setSpuId(spuInfoEntity.getId());
        //将List<String>转为String,用逗号分隔
        spuInfoDescEntity.setDecript(String.join(",", decript));
        spuInfoDescService.saveSpuInfoDesc(spuInfoDescEntity);
    }
}
image-20220523001135275
image-20220523001135275
2、创建saveSpuInfoDesc抽象方法

gulimall-product模块的com.atguigu.gulimall.product.service.SpuInfoDescService接口里添加saveSpuInfoDesc抽象方法

void saveSpuInfoDesc(SpuInfoDescEntity spuInfoDescEntity);

gulimall-product模块的com.atguigu.gulimall.product.service.impl.SpuInfoDescServiceImpl类里实现saveSpuInfoDesc抽象方法

GIF 2022-5-23 0-13-40
GIF 2022-5-23 0-13-40
3、修改saveSpuInfoDesc方法

修改gulimall-product模块的com.atguigu.gulimall.product.service.impl.SpuInfoDescServiceImpl类里的saveSpuInfoDesc方法

@Override
public void saveSpuInfoDesc(SpuInfoDescEntity spuInfoDescEntity) {
    this.baseMapper.insert(spuInfoDescEntity);
}
image-20220522224957138
image-20220522224957138

4.6.5.3、保存spu的图片集pms_spu_images

1、调用saveImages方法

gulimall-product模块的com.atguigu.gulimall.product.service.impl.SpuInfoServiceImpl类里的saveSouInfo方法里编写部分代码,用于保存Spu的图片集

@Autowired
SpuInfoDescService spuInfoDescService;
@Autowired
SpuImagesService spuImagesService;

@Transactional(rollbackFor = Exception.class)
@Override
public void saveSpuInfo(SpuSaveVo spuSaveVo) {
    //1、保存spu基本信息 pms_spu_info
    SpuInfoEntity spuInfoEntity = new SpuInfoEntity();
    BeanUtils.copyProperties(spuSaveVo,spuInfoEntity);
    this.saveBaseSpuInfo(spuInfoEntity);

    //2、保存Spu的描述图片 pms_spu_info_desc
    List<String> decript = spuSaveVo.getDecript();
    if (decript!=null && decript.size()>0) {
        SpuInfoDescEntity spuInfoDescEntity = new SpuInfoDescEntity();
        spuInfoDescEntity.setSpuId(spuInfoEntity.getId());
        spuInfoDescEntity.setDecript(String.join(",", decript));
        spuInfoDescService.saveSpuInfoDesc(spuInfoDescEntity);
    }
    
    //3、保存spu的图片集 pms_spu_images
    List<String> images = spuSaveVo.getImages();
    if (images!=null && images.size()>0) {
        spuImagesService.saveImages(spuInfoEntity.getId(), images);
    }
}
image-20220523201222020
image-20220523201222020
2、添加saveImages抽象方法

gulimall-product模块的com.atguigu.gulimall.product.service.SpuImagesService类里添加saveImages抽象方法

void saveImages(Long id, List<String> images);

gulimall-product模块的com.atguigu.gulimall.product.service.impl.SpuImagesServiceImpl类实现saveImages抽象方法

GIF 2022-5-23 20-13-04
GIF 2022-5-23 20-13-04
3、实现saveImages方法

gulimall-product模块的com.atguigu.gulimall.product.service.impl.SpuImagesServiceImpl类里的saveImages方法添加具体实现

/**
 * 批量保存图片
 * @param id        spuId
 * @param images    图片集的url
 */
@Override
public void saveImages(Long id, List<String> images) {
    if (CollectionUtils.isEmpty(images)){
        return;
    }

    List<SpuImagesEntity> spuImagesEntities = images.stream().map(image -> {
        SpuImagesEntity spuImagesEntity = new SpuImagesEntity();
        spuImagesEntity.setSpuId(id);
        spuImagesEntity.setImgUrl(image);
        return spuImagesEntity;
    }).collect(Collectors.toList());
    this.saveBatch(spuImagesEntities);
}
image-20220523202313469
image-20220523202313469

4.6.5.4、保存spu的规格参数pms_product_attr_value

1、私有访问权限无法访问
'com.atguigu.gulimall.product.vo.SpuSaveVo.BaseAttrs' has private access in 'com.atguigu.gulimall.product.vo.SpuSaveVo'

'com.atguigu.gulimall.product.vo.SpuSaveVo.BaseAttrs''com.atguigu.gulimall.product.vo.SpuSaveVo' 中具有私有访问权限
image-20220523204943855
image-20220523204943855
2、将SpuSaveVo的内部类都改为公有

gulimall-product模块的com.atguigu.gulimall.product.vo.SpuSaveVo类的所有内部类的访问权限都改为public

点击查看SpuSaveVo类完整代码

image-20220523204847342
3、调用saveProductAttr方法

gulimall-product模块的com.atguigu.gulimall.product.service.impl.SpuInfoServiceImpl类里的saveSouInfo方法里编写部分代码,保存spu的规格参数

@Autowired
SpuInfoDescService spuInfoDescService;
@Autowired
SpuImagesService spuImagesService;
@Autowired
AttrService attrService;
@Autowired
ProductAttrValueService productAttrValueService;

@Transactional(rollbackFor = Exception.class)
@Override
public void saveSpuInfo(SpuSaveVo spuSaveVo) {
    //1、保存spu基本信息 pms_spu_info
    SpuInfoEntity spuInfoEntity = new SpuInfoEntity();
    BeanUtils.copyProperties(spuSaveVo,spuInfoEntity);
    this.saveBaseSpuInfo(spuInfoEntity);

    //2、保存Spu的描述图片 pms_spu_info_desc
    List<String> decript = spuSaveVo.getDecript();
    if (decript!=null && decript.size()>0) {
        SpuInfoDescEntity spuInfoDescEntity = new SpuInfoDescEntity();
        spuInfoDescEntity.setSpuId(spuInfoEntity.getId());
        spuInfoDescEntity.setDecript(String.join(",", decript));
        spuInfoDescService.saveSpuInfoDesc(spuInfoDescEntity);
    }

    //3、保存spu的图片集 pms_spu_images
    List<String> images = spuSaveVo.getImages();
    if (images!=null && images.size()>0) {
        spuImagesService.saveImages(spuInfoEntity.getId(), images);
    }

    //4、保存spu的规格参数;pms_product_attr_value
    List<SpuSaveVo.BaseAttrs> baseAttrs = spuSaveVo.getBaseAttrs();
    if (!CollectionUtils.isEmpty(baseAttrs)) {
        List<ProductAttrValueEntity> productAttrValueEntities = baseAttrs.stream().map(attr -> {
            ProductAttrValueEntity productAttrValueEntity = new ProductAttrValueEntity();
            productAttrValueEntity.setSpuId(spuInfoEntity.getId());
            if (attr.getAttrId() != null) {
                productAttrValueEntity.setAttrId(attr.getAttrId());
                productAttrValueEntity.setAttrValue(attr.getAttrValues());
                productAttrValueEntity.setQuickShow(attr.getShowDesc());
                AttrEntity attrEntity = attrService.getById(attr.getAttrId());
                if (attrEntity != null) {
                    productAttrValueEntity.setAttrName(attrEntity.getAttrName());
                }
            }
            return productAttrValueEntity;
        }).collect(Collectors.toList());

        productAttrValueService.saveProductAttr(productAttrValueEntities);
    }
}
image-20220523211034830
image-20220523211034830
4、添加saveProductAttr抽象方法

gulimall-product模块的com.atguigu.gulimall.product.service.ProductAttrValueService类里添加saveProductAttr抽象方法

void saveProductAttr(List<ProductAttrValueEntity> productAttrValueEntities);

gulimall-product模块的com.atguigu.gulimall.product.service.impl.ProductAttrValueServiceImpl类里实现saveProductAttr抽象方法

GIF 2022-5-23 21-11-06
GIF 2022-5-23 21-11-06
5、实现saveProductAttr抽象方法

gulimall-product模块的com.atguigu.gulimall.product.service.impl.ProductAttrValueServiceImpl类里的saveProductAttr方法里添加具体实现

@Override
public void saveProductAttr(List<ProductAttrValueEntity> productAttrValueEntities) {
    this.saveBatch(productAttrValueEntities);
}
image-20220523211149789
image-20220523211149789

4.6.5.5、保存当前spu对应的所有sku信息

1、复制sku的信息到skuInfoEntity

gulimall-product模块的com.atguigu.gulimall.product.service.impl.ProductAttrValueServiceImpl类里的saveProductAttr方法里复制sku的信息到skuInfoEntity

image-20220523213603802
image-20220523213603802
2、查看可以复制的字段

比对gulimall-product模块的com.atguigu.gulimall.product.vo.SpuSaveVo.Skus

com.atguigu.gulimall.product.entity.SkuInfoEntity

可以看到skuNamepriceskuTitleskuSubtitle这些字段可以拷贝

image-20220523213242536
image-20220523213242536
3、保存sku的基本信息

gulimall-product模块的com.atguigu.gulimall.product.service.impl.ProductAttrValueServiceImpl类里的saveProductAttr方法里保存sku的基本信息,调用skuImagesService类的saveBatch方法

点击查看SpuInfoServiceImpl类完整代码

image-20220523221527652
image-20220523221527652
4、添加saveSkuInfo抽象方法

gulimall-product模块的com.atguigu.gulimall.product.service.SkuInfoService接口添加saveSkuInfo抽象方法

gulimall-product模块的com.atguigu.gulimall.product.service.impl.SkuInfoServiceImpl类里实现saveSkuInfo抽象方法

GIF 2022-5-23 22-17-54
GIF 2022-5-23 22-17-54
5、实现saveSkuInfo抽象方法

gulimall-product模块的com.atguigu.gulimall.product.service.impl.SkuInfoServiceImpl类里的saveSkuInfo方法里添加具体实现

image-20220523221930967
image-20220523221930967
6、批量保存sku的图片信息

gulimall-product模块的com.atguigu.gulimall.product.service.impl.ProductAttrValueServiceImpl类里的saveProductAttr方法里调用skuImagesService对象的saveBatch方法,批量保存sku的图片信息

点击查看SpuInfoServiceImpl类完整代码

image-20220523223744919
image-20220523223744919
7、批量保存sku的销售属性信息

gulimall-product模块的com.atguigu.gulimall.product.service.impl.ProductAttrValueServiceImpl类里的saveProductAttr方法里调用skuSaleAttrValueService对象的saveBatch方法,批量保存sku的销售属性信息

点击查看SpuInfoServiceImpl类完整代码

image-20220523230155354
image-20220523230155354

4.6.6、调用远程服务

1、调用远程服务步骤

  1. 服务双方上线,并且放到注册中心中
  2. 服务双方开启服务注册和发现功能(@EnableDiscoveryClient)
  3. 服务消费方编写feign接口,在接口声明调用哪个服务(@FeignClient)的哪个接口(@RequestMapping)
  4. 服务消费方开启远程调用功能(@EnableFeignClients)

2、保存spu的积分信息

gulimall-product模块的com.atguigu.gulimall.product.service.impl.SpuInfoServiceImpl类的saveSpuInfo方法,想要保存spu的积分信息,即修改gulimall_sms数据库的sms_spu_bounds

image-20220601114158761
image-20220601114158761

此时需要远程调用gulimall-coupon模块的com.atguigu.gulimall.coupon.controller.SpuBoundsController类的save方法

image-20220601114238502
image-20220601114238502
1、新建CouponFeignService

gulimall-product模块的com.atguigu.gulimall.product包下新建feign文件夹

gulimall-product模块的com.atguigu.gulimall.product.feign包里新建CouponFeignService

image-20220601121222193
image-20220601121222193
2、开启远程调用

gulimall-product模块的启动类GulimallProductApplication上添加注解

@EnableFeignClients(basePackages = "com.atguigu.gulimall.product.feign")
image-20220601121502104
image-20220601121502104
3、新建SpuBoundTo

gulimall-common模块的com.atguigu.common包下新建to文件夹

gulimall-common模块的com.atguigu.common.to包下新建SpuBoundTo类用于远程调用gulimall-coupon模块的com.atguigu.gulimall.coupon.controller.SpuBoundsController类的save方法时传输对象数据

image-20220601121411961
image-20220601121411961
4、修改请求方式

gulimall-coupon模块里修改com.atguigu.gulimall.coupon.controller.SpuBoundsController类的save的请求方式为@PostMapping

  @PostMapping("/save")
      public R save(@RequestBody SpuBoundsEntity spuBounds){
spuBoundsService.save(spuBounds);

      return R.ok();
  }
image-20220601122053306
image-20220601122053306
5、添加saveSpuBounds抽象方法

gulimall-product模块的com.atguigu.gulimall.product.feign.CouponFeignService接口来里添加saveSpuBounds抽象方法

package com.atguigu.gulimall.product.feign;

import com.atguigu.common.to.SpuBoundTo;
import com.atguigu.common.utils.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

/**
 * @author 无名氏
 * @date 2022/6/1
 * @Description:
 */
@FeignClient("gulimall-coupon")
public interface CouponFeignService {

    /**
     * 1、CouponFeignService . saveSpuBounds(spuBoundTo);
     *   1)、@RequestBody将这个对象转为json。
     *   2)、找到gul imall-coupon服务, 给/coupon/spubounds/save发送请求。
     *       将上一步转的json放在请求体位置,发送请求;
     *   3)、对方服务收到请求。请求体里有json数据。
     *       (@RequestBody SpuBoundsEntity spuBounds );将请求体的json转为SpuBoundsEntity;
     * 只要ison数据模型是兼容的。双方服务无需使用同一个to
     * @param spuBoundTo
     * @return
     */
    @PostMapping("/coupon/spubounds/save")
    R saveSpuBounds(@RequestBody SpuBoundTo spuBoundTo);
}
image-20220601122610199
image-20220601122610199
6、保存spu的积分信息

gulimall-product模块的com.atguigu.gulimall.product.service.impl.ProductAttrValueServiceImpl类里的saveProductAttr方法里调用couponFeignService对象的saveSpuBounds方法,用于保存spu的积分信息

@Autowired
CouponFeignService couponFeignService;

//5、保存spu的积分信息; gulimall_sms->sms_spu_bounds
SpuSaveVo.Bounds bounds = spuSaveVo.getBounds();
SpuBoundTo spuBoundTo = new SpuBoundTo();
BeanUtils.copyProperties(bounds,spuBoundTo);
spuBoundTo.setSpuId(spuInfoEntity.getId());
couponFeignService.saveSpuBounds(spuBoundTo);
image-20220601122753256
image-20220601122753256

3、保存sku的优惠、满减、打折等信息

image-20220601123339903
image-20220601123339903
1、新建SkuReductionTo

复制gulimall-product模块的com.atguigu.gulimall.product.vo.SpuSaveVo类的这些属性和MemberPrice内部类

image-20220601123703015
image-20220601123703015

gulimall-common模块新建com.atguigu.common.to包下新建SkuReductionTo类,粘贴刚刚复制的SpuSaveVo类的属性和MemberPrice内部类,再添加skuId字段

package com.atguigu.common.to;

import lombok.Data;

import java.math.BigDecimal;
import java.util.List;

/**
 * @author 无名氏
 * @date 2022/6/1
 * @Description:
 */
@Data
public class SkuReductionTo {

    private Long skuId;
    /**
     * 满几件打几折
     * countStatus: 是否可以叠加优惠
     */
    private int fullCount;
    private BigDecimal discount;
    private int countStatus;
    /**
     * 满多少减多少
     * priceStatus:是否可以叠加优惠
     */
    private BigDecimal fullPrice;
    private BigDecimal reducePrice;
    private int priceStatus;
    /**
     * 会员价格
     */
    private List<MemberPrice> memberPrice;

    @Data
    public static class MemberPrice {
        private Long id;
        private String name;
        private BigDecimal price;
    }
}
image-20220601124110777
image-20220601124110777
2、调用gulimall-coupon服务

gulimall-product模块的com.atguigu.gulimall.product.service.impl.SpuInfoServiceImpl类的saveSpuInfo方法里添加代码,调用gulimall-coupon服务,用于保存sku的优惠、满减、打折等信息

//5.4)、sku的优惠、满减、打折等信息;gulimall_sms->sms_sku_ladder\sms_sku_full_reduction\sms_ member_price
SkuReductionTo skuReductionTo = new SkuReductionTo();
BeanUtils.copyProperties(sku,skuReductionTo);
skuReductionTo.setSkuId(skuId);
R r1 = couponFeignService.saveSkuReduction(skuReductionTo);
image-20220601124401637
image-20220601124401637
3、添加saveSkuReduction抽象方法

gulimall-product模块的com.atguigu.gulimall.product.feign.CouponFeignService接口里添加saveSkuReduction抽象方法

void saveSkuReduction(SkuReductionTo skuReductionTo);
image-20220601150651848
image-20220601150651848
4、添加saveInfo方法

gulimall-coupon模块的com.atguigu.gulimall.coupon.service.SkuFullReductionService类里添加saveInfo方法,用于保存满减和折扣信息

/**
 * 保存满减和折扣信息
 */
@PostMapping("/saveinfo")
public R saveInfo(@RequestBody SkuReductionTo reductionTo){
    skuFullReductionService.saveSkuReduction(reductionTo);

    return R.ok();
}
image-20220601151040320
image-20220601151040320
5、修改saveSkuReduction抽象方法

gulimall-product模块的com.atguigu.gulimall.product.feign.CouponFeignService接口里修改saveSkuReduction抽象方法

@PostMapping("/coupon/skufullreduction/saveinfo")
R saveSkuReduction(@RequestBody SkuReductionTo skuReductionTo);
image-20220601150926019
image-20220601150926019
6、添加saveSkuReduction抽象方法

gulimall-coupon模块的com.atguigu.gulimall.coupon.service.SkuFullReductionService接口里添加saveSkuReduction抽象方法

void saveSkuReduction(SkuReductionTo reductionTo);
image-20220601151118002
image-20220601151118002
image-20220601151149222
image-20220601151149222
7、实现saveSkuReduction抽象方法

gulimall-coupon模块的com.atguigu.gulimall.coupon.service.impl.SkuFullReductionServiceImpl类里实现saveSkuReduction抽象方法

@Override
public void saveSkuReduction(SkuReductionTo reductionTo) {
    //1、sku的 优惠(满几件打几折) 信息;gulimall_sms->sms_sku_ladder
    SkuLadderEntity skuLadderEntity = new SkuLadderEntity();
    skuLadderEntity.setSkuId(reductionTo.getSkuId());
    skuLadderEntity.setFullCount(reductionTo.getFullCount());
    skuLadderEntity.setDiscount(reductionTo.getDiscount());
    //是否可以叠加优惠
    skuLadderEntity.setAddOther(reductionTo.getCountStatus());
    skuLadderService.save(skuLadderEntity);

    //2、sku的 满减(满多少减多少) 信息;sms_sku_full_reduction
    SkuFullReductionEntity skuFullReductionEntity = new SkuFullReductionEntity();
    BeanUtils.copyProperties(reductionTo, skuFullReductionEntity);
    skuFullReductionEntity.setAddOther(reductionTo.getPriceStatus());
    this.save(skuFullReductionEntity);

    //3、sku的 打折 信息;sms_ member_price
    List<SkuReductionTo.MemberPrice> memberPrice = reductionTo.getMemberPrice();
    List<MemberPriceEntity> memberPriceEntities = memberPrice.stream().map(member -> {
        MemberPriceEntity memberPriceEntity = new MemberPriceEntity();
        memberPriceEntity.setSkuId(reductionTo.getSkuId());
        memberPriceEntity.setMemberLevelId(member.getId());
        memberPriceEntity.setMemberLevelName(member.getName());
        memberPriceEntity.setMemberPrice(member.getPrice());
        memberPriceEntity.setAddOther(1);
        return memberPriceEntity;
    }).collect(Collectors.toList());
    memberPriceService.saveBatch(memberPriceEntities);
}
image-20220601153731834
image-20220601153731834
8、添加getCode方法

gulimall-common模块的com.atguigu.common.utils.R类里添加getCode方法,用于获取状态码

public Integer getCode(){
   return (Integer) this.get("code");
}
image-20220601154315951
image-20220601154315951
9、获取远程访问的状态码,在失败后记录日志

gulimall-product模块的com.atguigu.gulimall.product.service.impl.SpuInfoServiceImpl类的saveSpuInfo方法里面添加部分代码,用于获取远程访问gulimall-coupon模块时返回的状态码,并在失败后记录日志

保存spu的积分信息couponFeignService.saveSpuBounds(spuBoundTo);方法里接收返回值,如果远程访问失败则记录日志

//5、保存spu的积分信息; gulimall_sms->sms_spu_bounds
SpuSaveVo.Bounds bounds = spuSaveVo.getBounds();
SpuBoundTo spuBoundTo = new SpuBoundTo();
BeanUtils.copyProperties(bounds,spuBoundTo);
spuBoundTo.setSpuId(spuInfoEntity.getId());
R r = couponFeignService.saveSpuBounds(spuBoundTo);
if (r.getCode()!=0){
    log.error("远程保存spu积分信息失败");
}
image-20220601155006225
image-20220601155006225

保存sku的优惠、满减、打折等信息couponFeignService.saveSkuReduction(skuReductionTo);方法里接收返回值,如果远程访问失败则记录日志

//5.4)、sku的优惠、满减、打折等信息;gulimall_sms->sms_sku_ladder\sms_sku_full_reduction\sms_ member_price
SkuReductionTo skuReductionTo = new SkuReductionTo();
BeanUtils.copyProperties(sku,skuReductionTo);
skuReductionTo.setSkuId(skuId);
R r1 = couponFeignService.saveSkuReduction(skuReductionTo);
if (r1.getCode()!=0){
    log.error("远程保存sku优惠信息失败");
}
image-20220601154858415
image-20220601154858415

gulimall-product模块的com.atguigu.gulimall.product.service.impl.SpuInfoServiceImpl类的完整代码

点击查看SpuInfoServiceImpl类完整代码

4.6.7、测试

测试之前最好把gulimall_pms数据库备份一下,免得后面调试发现代码写错了,不知道这次添加了哪些数据,从而导致删错数据

image-20220612161346948
image-20220612161346948

1、修改配置

1、点击Edit Configurations...

点击功能区内运行类的右边、运行按钮左边的下三角,选择Edit Configurations...

image-20220601155532934
image-20220601155532934
2、新建Compound

点击弹出的Run/Debug Configurations框里左上角的+号,在下方的框里选择Compound

image-20220601155708909
image-20220601155708909
3、在Compound里添加各个模块

在新建的Compound里面的右边的框的左上角点击+号,把下面这些服务添加进来

  1. GulimallCouponApplication
  2. GulimallGatewayApplication
  3. GulimallMemberApplication
  4. GulimallProductApplication
  5. GulimallThirdPartyApplication
  6. RenrenApplication
image-20220601155949929
image-20220601155949929
4、修改模块最大内存占用
1、点击编辑按钮

点击Spring Boot'GulimallCouponApplication',然后点击Compound里面的右边的框的左上角的编辑按钮

image-20220601160037029
image-20220601160037029
2、限制单个模块最大内存

GulimallCouponApplication模块的运行配置里的Environment栏的VM options里的右方框里输入-Xmx100m,限制GulimallCouponApplication模块的最大内存占用为100m

同理修改其他7个模块的最大内存占用

-Xmx100m
image-20220601160256195
image-20220601160256195

GulimallGatewayApplication模块的运行配置里的Environment栏的VM options里的右方框里输入-Xmx100m,限制GulimallGatewayApplication模块的最大内存占用为100m

image-20220601160348699
image-20220601160348699

GulimallMemberApplication模块的运行配置里的Environment栏的VM options里的右方框里输入-Xmx100m,限制GulimallMemberApplication模块的最大内存占用为100m

image-20220601160419110
image-20220601160419110

GulimallOrderApplication模块的运行配置里的Environment栏的VM options里的右方框里输入-Xmx100m,限制GulimallOrderApplication模块的最大内存占用为100m

image-20220601160507405
image-20220601160507405

GulimallProductApplication模块的运行配置里的Environment栏的VM options里的右方框里输入-Xmx100m,限制GulimallProductApplication模块的最大内存占用为100m

image-20220601160536846
image-20220601160536846

GulimallThirdPartyApplication模块的运行配置里的Environment栏的VM options里的右方框里输入-Xmx100m,限制GulimallThirdPartyApplication模块的最大内存占用为100m

image-20220601160602726
image-20220601160602726

GulimallWareApplication模块的运行配置里的Environment栏的VM options里的右方框里输入-Xmx100m,限制GulimallWareApplication模块的最大内存占用为100m

image-20220601160630466
image-20220601160630466

RenrenApplication模块的运行配置里的Environment栏的VM options里的右方框里输入-Xmx100m,限制RenrenApplication模块的最大内存占用为100m

image-20220601160655782
image-20220601160655782

2、添加断点

1、打断点

gulimall-product模块的com.atguigu.gulimall.product.service.impl.SpuInfoServiceImpl类的saveSpuInfo方法的开头打一个断点,用于测试

image-20220611235807652
image-20220611235807652
2、打开Run Dashboard
1、没有Run Dashboard选项
image-20220606232040208
image-20220606232040208
2、添加组件

gulimall\.idea\workspace.xml里添加组件

<component name="RunDashboard">
    <option name="configurationTypes">
      <set>
        <option value="SpringBootApplicationConfigurationType" />
      </set>
    </option>
    <option name="ruleStates">
      <list>
        <RuleState>
          <option name="name" value="ConfigurationTypeDashboardGroupingRule" />
        </RuleState>
        <RuleState>
          <option name="name" value="StatusDashboardGroupingRule" />
        </RuleState>
      </list>
    </option>
  </component>
image-20220606232154637
image-20220606232154637
3、添加的组件被删除了

点别的文件,在返回gulimall\.idea\workspace.xml发现,刚刚添加的组件被删除了

image-20220606232544250
image-20220606232544250
4、改成Services了😥

我这个版本的IDEA已经把Run Dashboard改成Services

image-20220606232751462
image-20220606232751462
3、以debug方式启动GulimallProductApplication模块
image-20220606232934382
image-20220606232934382
4、发送请求

使用Postman发送以前新增商品时(保存的有json数据)的那个请求

url:http://localhost:88/api/product/spuinfo/save

image-20220606235141570
image-20220606235141570

然后点击调试的Step Over(步过)按钮,直到执行this.saveBaseSpuInfo(spuInfoEntity);方法完毕

image-20220612160514320
image-20220612160514320
5、修改事务隔离级别

查看gulimall_pms数据库的pms_sou_info表,发现并没有数据,这是因为事务没有提交,可以修改当前会话的隔离级别为读未提交这样就可以看到还未提交的数据了

image-20220606234532035
image-20220606234532035

//这个isolation少了一个o,果然百度的东西不靠谱

set session transaction isolatin level READ UNCOMMITTED;

这个是正确的

set session transaction isolation level read uncommitted;
image-20220606234716582
image-20220606234716582

重新刷新gulimall_pms数据库下的pms_spu_info表,可以看到已经有数据了

image-20220607000248976
image-20220607000248976
📝 如果没有数据

如果重新刷新发现还没有,这是navicate软件的问题😥

image-20220607000740209
image-20220607000740209

选中gulimall_pms数据库,右键选择新建查询

image-20220606235935459
image-20220606235935459

在新创建的对话框内输入如下语句,查看pms_spu_info表,点击运行

SELECT * FROM pms_spu_info;
image-20220607000040075
image-20220607000040075

或者直接在设置当前会话事务隔离级别的命令行界面里执行也行

image-20220607000145379
image-20220607000145379

3、spu_id异常

1、报了个异常

点击调试的Step Over(步过)按钮,直到执行this.saveBaseSpuInfo(spuInfoEntity);方法完毕

在执行this.saveBaseSpuInfo(spuInfoEntity);方法的时候,抛了个异常

Error updating database.  Cause: java.sql.SQLException: Field 'spu_id' doesn't have a default value
更新数据库时出错。 原因:java.sql.SQLException:字段 'spu_id' 没有默认值
image-20220612162902508
image-20220612162902508

可以看到,代码明明设置了spuIddescipt,但是执行的sql语句却只插入了descipt字段

INSERT INTO pms_spu_info_desc ( decript ) VALUES ( ? ) 
2、查看pms_spu_info_desc表结构

查看gulimall_pns数据库的pms_spu_info_desc表的表结构

选中gulimall_pns数据库的pms_spu_info_desc表,右键选择设计表

image-20220612163500782
image-20220612163500782

可以看到spu_id字段不是自动递增的,这个字段是spu的id,是需要指定的

mybatis当成了自增的,所以插入的时候只插入了descipt字段,所以就抛了个异常

image-20220612163540243
image-20220612163540243
3、修改SpuInfoDescEntity类的字段注解

gulimall-product模块的com.atguigu.gulimall.product.entity.SpuInfoDescEntity类的spuId字段的@TableId注解上添加参数,指出id为输入的

@TableId(type = IdType.INPUT)
private Long spuId;
image-20220612164332539
image-20220612164332539
4、重新测试

重新以debug方式启动GulimallProductApplication模块

image-20220612164854483
image-20220612164854483

重新在Postman里面发送请求

image-20220612165111157
image-20220612165111157

继续点击调试的Step Over(步过)按钮,直到执行this.saveBaseSpuInfo(spuInfoEntity);方法完毕

image-20220612165210715
image-20220612165210715

打开navicat,在命令行里查询pms_spu_info_desc表,可以看到执行成功了

select * from pms_spu_info_desc;
image-20220612165408926
image-20220612165408926

可以看到,这次执行的sql语句就没有问题了

image-20220612165628681
image-20220612165628681

4、保存spu的图片集

点击调试的Step Over(步过)按钮,直到执行spuImagesService.saveImages(spuInfoEntity.getId(), images);方法完毕

控制台可以看到插入了很多的数据

image-20220612165941643
image-20220612165941643

打开navicat,在命令行里查询pms_spu_info_desc表,可以看到spu的图片集已经保存成功了

select * from pms_spu_images;
image-20220612170119892
image-20220612170119892

5、保存spu的规格参数

productAttrValueService.saveProductAttr(productAttrValueEntities);这段代码上打个断点

image-20220612170714934
image-20220612170714934

点击Resume Program按钮,执行到下一个断点,到productAttrValueService.saveProductAttr(productAttrValueEntities);这条语句

image-20220612171237828
image-20220612171237828

点击调试的Step Over(步过)按钮,执行productAttrValueService.saveProductAttr(productAttrValueEntities);

控制台可以看到已经执行成功了

image-20220612171013680
image-20220612171013680

打开navicat,在命令行里查询pms_product_attr_value;表,可以看到执行成功了

select * from pms_product_attr_value;
image-20220612171503231
image-20220612171503231

6、远程保存spu的积分信息

点击调试的Step Over(步过)按钮,执行R r = couponFeignService.saveSpuBounds(spuBoundTo);

执行这一步时间稍微会长一些,大概几秒

image-20220612173109279
image-20220612173109279

选择gulimall_sms数据库,右键选择新建查询

image-20220612183822411
image-20220612183822411

在里面输入sql语句,选中刚刚输入sql语句,点击运行已选择的,就可以看到已经执行成功了

#设置事务隔离级别
set session transaction isolation level read uncommitted;
select * from sms_spu_bounds;
image-20220613172225685
image-20220613172225685

📝 如果抛了类型转换异常

java.lang.classCastException: java.lang.Integer cannot be cast to java.lang.String
java.lang.classCastException:java.lang.Integer 不能转换为 java.lang.String
image-20220612173525024
image-20220612173525024

只需要修改gulimall-common模块的com.atguigu.common.utils.R类的getCode方法

public Integer getCode(){
   return (Integer) this.get("code");
}
image-20220612173828646
image-20220612173828646

📝如果请求超时了,可以在gulimall-product模块的src\main\resources\application.yml配置文件里配置超时时间

ribbon:
  ReadTimeout: 5000
  ConnectTimeout: 5000
image-20220612194409602
image-20220612194409602

7、保存sku的基本信息

在这些代码上添加断点

skuInfoService.saveSkuInfo(skuInfoEntity);
skuImagesService.saveBatch(skuImagesEntities);
skuSaleAttrValueService.saveBatch(skuSaleAttrValueEntities);
R r1 = couponFeignService.saveSkuReduction(skuReductionTo);
image-20220612184727878
image-20220612184727878

点击Resume Program按钮,执行到下一个断点,到skuInfoService.saveSkuInfo(skuInfoEntity);这里

image-20220612184840945
image-20220612184840945

点击调试的Step Over(步过)按钮,执行skuInfoService.saveSkuInfo(skuInfoEntity);

image-20220612185003469
image-20220612185003469

gulimall_pms数据库的命令行里查询pms_sku_info表,可以看到已经执行成功了

select * from pms_sku_info;
image-20220612185214403
image-20220612185214403

8、保存sku的图片信息

点击Resume Program按钮,执行到下一个断点,到skuImagesService.saveBatch(skuImagesEntities);这里

image-20220612185351159
image-20220612185351159

点击调试的Step Over(步过)按钮,执行skuImagesService.saveBatch(skuImagesEntities);

image-20220612185428681
image-20220612185428681

gulimall_pms数据库的命令行里查询pms_sku_images表,可以看到已经执行成功了,但是有很多空的img_url也被插入进来了

 select * from pms_sku_images;
image-20220612185714965
image-20220612185714965

skuImagesService.saveBatch(skuImagesEntities);这里添加一个待办事项

//TODO 没有图片;路径的无需保存
image-20220612185924619
image-20220612185924619

9、保存sku的销售属性信息

点击Resume Program按钮,执行到下一个断点,到 skuSaleAttrValueService.saveBatch(skuSaleAttrValueEntities);这里

image-20220612190054935
image-20220612190054935

点击调试的Step Over(步过)按钮,执行 skuSaleAttrValueService.saveBatch(skuSaleAttrValueEntities);

image-20220612190159595
image-20220612190159595

gulimall_pms数据库的命令行里查询pms_sku_sale_attr_value表,可以看到已经执行成功了

select * from pms_sku_sale_attr_value;
image-20220612190332004
image-20220612190332004

10、远程保存sku的优惠、满减、打折等信息

点击Resume Program按钮,执行到下一个断点,到R r1 = couponFeignService.saveSkuReduction(skuReductionTo);这里

image-20220612190433233
image-20220612190433233

点击调试的Step Over(步过)按钮,执行R r1 = couponFeignService.saveSkuReduction(skuReductionTo);

image-20220612190957155
image-20220612190957155

gulimall_sms数据库的命令行里查询sms_sku_full_reduction表,可以看到已经执行成功了,但是有很多都为0的数据

select * from sms_sku_full_reduction;
image-20220612210049416
image-20220612210049416

gulimall_sms数据库的命令行里查询sms_sku_ladder表,可以看到已经执行成功了,但是有很多都为0的数据

select * from sms_sku_ladder;
image-20220612205848035
image-20220612205848035

gulimall_sms数据库的命令行里查询sms_member_price表,可以看到已经执行成功了,但是有很多都为0的数据

select * from sms_member_price;
image-20220612205651548
image-20220612205651548

11、保存所有spu信息

点击Resume Program按钮,执行到下一个断点,到skuInfoService.saveSkuInfo(skuInfoEntity);这里,用于保存第二个spu信息

image-20220612191302481
image-20220612191302481

一直点击Resume Program按钮,直到执行完所有

image-20220612191443305
image-20220612191443305

gulimall_pms数据库的命令行中执行sql语句,查询pms_sku_info信息,可以看到8条数据已成功插入

select * from pms_sku_info;
image-20220612191706272
image-20220612191706272

4.6.8、商品保存其他问题

1、图片的url为空时不保存到数据库

gulimall-product模块的com.atguigu.gulimall.product.service.impl.SpuInfoServiceImpl类的saveSpuInfo方法里,

skuImagesService.saveBatch(skuImagesEntities);方法调用之前

收集数据之前,添加过滤条件,如果图片的url为空,就过滤掉;当图片的url不为空时才保留

List<SkuImagesEntity> skuImagesEntities = sku.getImages().stream().map(img -> {
    SkuImagesEntity skuImagesEntity = new SkuImagesEntity();
    skuImagesEntity.setSkuId(skuId);
    skuImagesEntity.setImgUrl(img.getImgUrl());
    skuImagesEntity.setDefaultImg(img.getDefaultImg());
    return skuImagesEntity;
}).filter(entry->{
    //如果图片的url为空,就过滤掉
    return StringUtils.hasLength(entry.getImgUrl());
}).collect(Collectors.toList());
skuImagesService.saveBatch(skuImagesEntities);
image-20220613210153936
image-20220613210153936

2、当有打折满减信息才调用远程服务

gulimall-product模块的com.atguigu.gulimall.product.service.impl.SpuInfoServiceImpl类的saveSpuInfo方法里,

在执行R r1 = couponFeignService.saveSkuReduction(skuReductionTo);方法之前,添加判断当有打折满减信息时才调用远程服务

//满几件打几折、满多少减多少,如果有一项有数据才调用远程服务
if (skuReductionTo.getFullCount()>0 || skuReductionTo.getFullPrice().compareTo(BigDecimal.ONE) > 0){
    R r1 = couponFeignService.saveSkuReduction(skuReductionTo);
    if (r1.getCode()!=0){
        log.error("远程保存sku优惠信息失败");
    }
}
image-20220612200805478
image-20220612200805478

gulimall-product模块的com.atguigu.gulimall.product.service.impl.SpuInfoServiceImpl类的完整代码

点击查看SpuInfoServiceImpl类完整代码

3、设置会员价格也远程调用

gulimall-product模块的com.atguigu.gulimall.product.service.impl.SpuInfoServiceImpl类的saveSpuInfo方法里在5.4功能里修改代码,当有打折满减设置会员价格信息才调用远程服务

这些价格应该与BigDecimal.ZERO比,之前代码写的有问题

//5.4)、sku的优惠、满减、打折等信息;gulimall_sms->sms_sku_ladder\sms_sku_full_reduction\sms_ member_price
SkuReductionTo skuReductionTo = new SkuReductionTo();
BeanUtils.copyProperties(sku,skuReductionTo);
skuReductionTo.setSkuId(skuId);

//查询是否有会员价格
Optional<SkuReductionTo.MemberPrice> memberPriceList = skuReductionTo.getMemberPrice().stream().filter(memberPrice -> {
    return memberPrice.getPrice().compareTo(BigDecimal.ZERO) > 0;
}).findFirst();
//满几件打几折、满多少减多少、会员价格,如果有一项有数据才调用远程服务
if (skuReductionTo.getFullCount()>0
        || skuReductionTo.getFullPrice().compareTo(BigDecimal.ZERO) > 0
        || memberPriceList.isPresent()){
    R r1 = couponFeignService.saveSkuReduction(skuReductionTo);
    if (r1.getCode()!=0){
        log.error("远程保存sku优惠信息失败");
    }
}
image-20220612205101515
image-20220612205101515

4、当有满减信息才保存

修改gulimall-coupon模块的com.atguigu.gulimall.coupon.service.impl.SkuFullReductionServiceImpl类的saveSkuReduction方法,当有满减信息才保存

//有满减信息才保存
if (reductionTo.getFullPrice().compareTo(BigDecimal.ZERO)>0) {
    //2、sku的 满减(满多少减多少) 信息;sms_sku_full_reduction
    SkuFullReductionEntity skuFullReductionEntity = new SkuFullReductionEntity();
    BeanUtils.copyProperties(reductionTo, skuFullReductionEntity);
    skuFullReductionEntity.setAddOther(reductionTo.getPriceStatus());
    this.save(skuFullReductionEntity);
}
image-20220612203149627
image-20220612203149627

5、当会员价格大于0才保存

修改gulimall-coupon模块com.atguigu.gulimall.coupon.service.impl.SkuFullReductionServiceImpl类里的saveSkuReduction方法,当会员价格大于0(修改了会员价格)才保存

    //3、sku的会员优惠信息;sms_ member_price
    List<SkuReductionTo.MemberPrice> memberPrice = reductionTo.getMemberPrice();
    List<MemberPriceEntity> memberPriceEntities = memberPrice.stream().filter(member->{
        return member.getPrice().compareTo(BigDecimal.ZERO)>0;
    }).map(member -> {
        MemberPriceEntity memberPriceEntity = new MemberPriceEntity();
        memberPriceEntity.setSkuId(reductionTo.getSkuId());
        memberPriceEntity.setMemberLevelId(member.getId());
        memberPriceEntity.setMemberLevelName(member.getName());
        memberPriceEntity.setMemberPrice(member.getPrice());
        memberPriceEntity.setAddOther(1);
        return memberPriceEntity;
    }).collect(Collectors.toList());
    memberPriceService.saveBatch(memberPriceEntities);
}
image-20220612204104653
image-20220612204104653

4.6.9、重新测试

1、重启项目

选择IDEA下边选项框的Services选项,点击GulimallProductApplication,右键选择Return,重新以debug方式启动GulimallProductApplication项目

image-20220612210946079
image-20220612210946079

点击GulimallCouponApplication,右键选择Return,重新以debug方式启动GulimallCouponApplication项目

image-20220612211004238
image-20220612211004238

2、添加基本信息

商品系统->商品维护->发布商品里添加商品的基本信息

商品名称里输入Apple iPhoneXS 苹果XS手机

商品描述输入苹果手机

选择分类选择手机/手机通讯/手机

选择品牌选择Apple

商品重量(Kg)输入0.198

设置积分里,金币输入500成长值输入500

image-20220612211954522
image-20220612211954522

商品介绍选以下两个图片

image-20220612212014107
image-20220612212014107

然后点击下一步:设置基本参数

3、添加规格参数

规格参数里,主体内的入网型号选择A2100,上市年份选择2018(基本信息和主芯片在这里我就不选了)

然后点击下一步:设置销售属性

image-20220612212342961
image-20220612212342961

4、添加销售属性

销售属性里的选择销售属性颜色里,添加并选择银色深空灰色金色

内存选择4G

版本里,添加并选择64GB256GB512GB

然后点击下一步:设置SKU信息

image-20220612212701344
image-20220612212701344

5、添加SKU信息

1、添加基本信息

销售属性里添加如下信息,其中有些信息是已经自动生成好的(顺序可能不同)

颜色版本商品名称标题副标题价格
银色64GBApple IPhoneXS 苹果XS手机 银色 64GBApple IPhoneXS 苹果XS手机 银色 64GB国行正品【白条六期免息】5999
银色256GBApple IPhoneXS 苹果XS手机 银色 256GBApple IPhoneXS 苹果XS手机 银色 256GB国行正品【白条六期免息】6799
银色512GBApple IPhoneXS 苹果XS手机 银色 512GBApple IPhoneXS 苹果XS手机 银色 512GB国行正品【白条六期免息】苹果XS手机 银色 512GB6999
深空灰色64GBApple IPhoneXS 苹果XS手机 深空灰色 64GBApple IPhoneXS 苹果XS手机 深空灰色 64GB国行正品【白条六期免息】5999
深空灰色256GBApple IPhoneXS 苹果XS手机 深空灰色 256GBApple IPhoneXS 苹果XS手机 深空灰色 256GB国行正品【白条六期免息】6799
深空灰色512GBApple IPhoneXS 苹果XS手机 深空灰色 512GBApple IPhoneXS 苹果XS手机 深空灰色 512GB国行正品【白条六期免息】6999
金色64GBApple IPhoneXS 苹果XS手机 金色 64GBApple IPhoneXS 苹果XS手机 金色 64GB国行正品【白条六期免息】5999
金色256GBApple IPhoneXS 苹果XS手机 金色 256GBApple IPhoneXS 苹果XS手机 金色 256GB国行正品【白条六期免息】6799
金色512GBApple IPhoneXS 苹果XS手机 金色 512GBApple IPhoneXS 苹果XS手机 金色 512GB国行正品【白条六期免息】6999
image-20220612213201736
image-20220612213201736
2、添加详细信息

银色 64GB Apple IPhoneXS 苹果XS手机 银色 64GB Apple IPhoneXS 苹果XS手机 银色 64GB 国行正品【白条六期免息】 5999 (第一条信息)的价格右边,点击>右箭头,在这里添加详细信息

点击+号,然后选择一张图片,以添加这个图片,选择一些图片作为这个SKU的图片集,并选择其中一个点击设为默认,用来做默认图片

设置折扣里,满2件,打0.98折,并勾选可叠加优惠

设置满减里,满10000元,减50元,并勾选可叠加优惠

设置会员价里,铜牌会员设置会员价为5980元,银牌会员(这里前端字打错了)设置会员价为5970

image-20220612214656192
image-20220612214656192

f12打开控制台,在控制台里选择Console,然后点击下一步:保留商品信息,先要点击提示里的确定

在控制台输出的信息那点击右键,选择Save as...先放到记事本里保存,免得后面操作多了,不小心丢了(华为的那个也保留着,后面还用得上)

image-20220612214825166
image-20220612214825166
4、发送的完整json

删除最开头的spuadd.vue?c0e3:697 ~~~~~,后面部分即为正确的、完整的json

点击查看完整json

5、格式化后的json

点击查看格式后的json

6、抛出类型强转异常

1、系统未知异常

点击前端提示对话框的确定按钮后,提示保存失败,原因【系统未知异常】,查看请求,发现msg系统未知异常

image-20220612215347322
image-20220612215347322
2、查看后端的控制台

查看GulimallProductApplication模块的控制台,可以看到抛出了.ClassCastException(类强转)异常

com.atguigu.gulimall.product.vo.SpuSaveVo$MemberPrice不能被强转为com.atguigu.common.to.SkuReductionTo$MemberPrice

java.lang.ClassCastException: com.atguigu.gulimall.product.vo.SpuSaveVo$MemberPrice cannot be cast to com.atguigu.common.to.SkuReductionTo$MemberPrice
	at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:174) ~[na:1.8.0_301]
image-20220612234832710
image-20220612234832710

调试发现BeanUtils.copyProperties(sku,skuReductionTo);Skus里的MemberPrice集合,赋给了SkuReductionTo里的memberPrice,这样就导致memberPricecom.atguigu.gulimall.product.vo.SpuSaveVo$MemberPrice类型的集合,而不是本类的com.atguigu.common.to.SkuReductionTo$MemberPrice类型的集合,

所以当遍历时,编译器强转为本类的com.atguigu.gulimall.product.vo.SpuSaveVo$MemberPrice集合,就发生了类强转异常

(遍历时编译器检查的memberPrice集合类型为com.atguigu.common.to.SkuReductionTo$MemberPrice;而BeanUtils.copyProperties(sku,skuReductionTo);是在运行时执行的,编译器无法预知其类型,其把com.atguigu.gulimall.product.vo.SpuSaveVo$MemberPrice类型的集合赋给了memberPrice;在运行memberPrice遍历代码时发现与预期类型不一致,强转类型就发生了类强转异常)

image-20220613161427467
image-20220613161427467
3、尝试不显式获取返回类型会不会报错

测试之前报错是不是因为以下代码显式获取了返回类型为SkuReductionTo.MemberPrice而报的错

Optional<SkuReductionTo.MemberPrice> memberPriceList = skuReductionTo.getMemberPrice().stream().filter(memberPrice -> {
    return memberPrice.getPrice().compareTo(BigDecimal.ZERO) > 0;
}).findFirst();

在该代码前,添加代码,如下所示

//5.4)、sku的优惠、满减、打折等信息;gulimall_sms->sms_sku_ladder\sms_sku_full_reduction\sms_ member_price
SkuReductionTo skuReductionTo = new SkuReductionTo();
System.out.println(skuReductionTo.getMemberPrice());
BeanUtils.copyProperties(sku,skuReductionTo);
System.out.println(skuReductionTo.getMemberPrice());
skuReductionTo.setSkuId(skuId);
skuReductionTo.getMemberPrice().forEach(e->{
    System.out.println(e);
    System.out.println(e.getId()+" "+e.getName()+" "+e.getPrice());
});

GulimallProductApplication模块控制台打印skuReductionTo.getMemberPrice()可以看到memberPrice()SpuSaveVo$MemberPrice类型的集合

image-20220613001145096
image-20220613001145096

点击调试的Step Over(步过)按钮,继续向下运行。

在点击调试的Step Over(步过)按钮,步过skuReductionTo.getMemberPrice().forEach(e->{发现没有再向下运行了,这时应该是发生异常了,再次点击Step Over(步过)按钮,就跳到的捕获异常的类里去了

image-20220613165957636
image-20220613165957636

点击Step Out(步出),直接执行到本类相应方法结束

多次点击Step Out(步出),执行完所有异常捕获类,就看到了控制台异常信息

image-20220613165144512
image-20220613165144512
4、解决方法

📝 可以先把远程调用的先注释掉,因为没使用分布式事务,这些远程调用无法回滚,注释掉避免许多无用的数据被提交

image-20220613163711487
image-20220613163711487
方法一

不使用BeanUtils.copyProperties(sku, skuReductionTo);方法拷贝的SkuReductionTo类里的memberPrice

直接使用原数据的sku对象的memberPrice进行判断

//5.4)、sku的优惠、满减、打折等信息;gulimall_sms->sms_sku_ladder\sms_sku_full_reduction\sms_ member_price
SkuReductionTo skuReductionTo = new SkuReductionTo();
BeanUtils.copyProperties(sku, skuReductionTo);
skuReductionTo.setSkuId(skuId);
System.out.println(skuReductionTo.getMemberPrice());

Optional<SpuSaveVo.MemberPrice> firstMemberPrice = sku.getMemberPrice().stream()
        .filter(memberPrice -> memberPrice.getPrice().compareTo(BigDecimal.ZERO) > 0)
        .findFirst();

//满几件打几折、满多少减多少、会员价格,如果有一项有数据才调用远程服务
if (skuReductionTo.getFullCount() > 0
        || skuReductionTo.getFullPrice().compareTo(BigDecimal.ZERO) > 0
        || firstMemberPrice.isPresent()) {
    R r1 = couponFeignService.saveSkuReduction(skuReductionTo);
    if (r1.getCode() != 0) {
        log.error("远程保存sku优惠信息失败");
    }
}
image-20220613163009555
image-20220613163009555

可以看到sku里的memberPrice类型为SpuSaveVo$MemberPrice,就是它应该的类型,此时肯定不会报错

下面的方法在处理skuReductionTo里的错误的SpuSaveVo$MemberPrice类型没有报错的原因是:发生了远程调用,远程调用是通过json来传输数据,SpuSaveVo$MemberPrice类和SkuReductionTo$MemberPrice类的所有数据字段名称和类型都相同,所以转化的json数据都一样,在服务提供方接收json转化为SkuReductionTo$MemberPrice类型时也不会出错

(SpuSaveVo$MemberPrice类和SkuReductionTo$MemberPrice类的所有数据字段名称和类型都相同,但是类强转失败的原因是它们之间没有继承关系,所以这两个类不能强转)

image-20220613171053930
image-20220613171053930
方法二

SpuSaveVo$MemberPrice类和SkuReductionTo$MemberPrice类的所有数据字段名称和类型都相同,所以转化的json数据都一样,可以先将SpuSaveVo$MemberPrice类集合转化为json,再通过json转化为SkuReductionTo$MemberPrice类集合,最后再赋值给skuReductionTo类的memberPrice字段,然后进行处理

(不过没必要使用这种方法来回转,很影响性能,这里只提供一种思路)

//5.4)、sku的优惠、满减、打折等信息;gulimall_sms->sms_sku_ladder\sms_sku_full_reduction\sms_ member_price
SkuReductionTo skuReductionTo = new SkuReductionTo();
BeanUtils.copyProperties(sku,skuReductionTo);
skuReductionTo.setSkuId(skuId);
System.out.println(skuReductionTo.getMemberPrice());

String s = JSON.toJSONString(sku.getMemberPrice());
List<SkuReductionTo.MemberPrice> memberPriceList = JSON.parseArray(s, SkuReductionTo.MemberPrice.class);
skuReductionTo.setMemberPrice(memberPriceList);
Optional<SkuReductionTo.MemberPrice> firstMemberPrice = skuReductionTo.getMemberPrice().stream()
        .filter(memberPrice -> memberPrice.getPrice().compareTo(BigDecimal.ZERO) > 0)
        .findFirst();

//满几件打几折、满多少减多少、会员价格,如果有一项有数据才调用远程服务
if (skuReductionTo.getFullCount()>0
        || skuReductionTo.getFullPrice().compareTo(BigDecimal.ZERO) > 0
        || firstMemberPrice.isPresent()){
    R r1 = couponFeignService.saveSkuReduction(skuReductionTo);
    if (r1.getCode()!=0){
        log.error("远程保存sku优惠信息失败");
    }
}
image-20220613162718750
image-20220613162718750

调试可以看到skuReductionTo类的memberPrice字段已经变为正确的SkuReductionTo$MemberPrice集合

image-20220613164406730
image-20220613164406730
方法三

可以将新建一个pubic修饰的MemberPrice类,并删除SpuSaveVo类和SkuReductionTo类的MemberPrice内部类,

SpuSaveVo类和SkuReductionTo类都使用新建的MemberPrice类,这样也不会有异常类

7、重新测试

1、清空数据

打开刚刚注释的远程调用,使其可以远程调用

image-20220613171446817
image-20220613171446817

清空gulimall_pms数据库中pms_product_attr_value表的数据

image-20220613200905318
image-20220613200905318

截断gulimall_pms数据库中pms_product_attr_value表的数据(其实可以不用清空,直接截断)

清空表只会删除表中的数据,插入时如果不指定id的话,id还是会继续向后递增;

而截断表不仅会删除表中的数据,id也会重新从1开始

image-20220613200938323
image-20220613200938323

需要清空截断如下表(可以不用清空,直接截断),可以先备份这两个数据库,防止删错了

gulimall_pms数据库

  1. pms_product_attr_value
  2. pms_sku_images
  3. pms_sku_info
  4. pms_sku_sale_attr_value
  5. pms_spu_images
  6. pms_spu_info
  7. pms_spu_info_desc

gulimall_sms数据库

  1. sms_member_price
  2. sms_sku_full_reduction
  3. sms_sku_ladder
  4. sms_spu_bounds
image-20220613173419233image-20220613173527547

点击gulimall_pms数据库,右键选择转储 SQL 文件 ,然后选择结构和数据...,以复制gulimall_pms数据库,如果误删了可以恢复数据

image-20220613203942743
image-20220613203942743

同理,点击gulimall_sms数据库,右键选择转储 SQL 文件 ,然后选择结构和数据...,以复制gulimall_sms数据库,如果误删了可以恢复数据(还可以复制其他数据库结构和数据...,防止误删除其他数据库中的数据了)

image-20220613204017921
image-20220613204017921
2、重启模块

重启GulimallProductApplication模块

image-20220613204112485
image-20220613204112485

重启GulimallCouponApplication数据库

image-20220613204145602
image-20220613204145602

gulimall-product模块的com.atguigu.gulimall.product.service.impl.SpuInfoServiceImpl类的saveSpuInfo方法里,对所有操作数据库的代码都打断点(包括远程调用代码),总共9处断点

image-20220613211118601
3、发送请求

打开保存的苹果的请求,从spuadd.vue?c0e3:697 ~~~~~后面的第一个{开始复制,一直复制到最后

image-20220613204932016
image-20220613204932016

打开Postman

  1. 输入"http://localhost:88/api/product/spuinfo/save"
  2. 请求方式选择"POST"
  3. 点击Body
  4. 点击raw
  5. 选择JSON
  6. 粘贴刚刚复制的JSON
  7. 点击Send
image-20220613204726065
image-20220613204726065
4、查看数据
1、保存spu基本信息 pms_spu_info

点击调试的Step Over(步过)按钮,执行this.saveBaseSpuInfo(spuInfoEntity);这段代码

image-20220613211238135
image-20220613211238135

navicat软件里点击gulimall_pms数据库,右键选择新建查询

📝 以下sql为该新建查询用到的所有sql语句

set session transaction isolation level read uncommitted;
select * from pms_spu_info;
select * from pms_spu_info_desc;
select * from pms_spu_images;
select * from pms_product_attr_value;
select * from pms_sku_info;
select * from pms_sku_images;
select * from pms_sku_sale_attr_value;
image-20220613225416756
image-20220613225416756

输入以下sql,设置当前会话的隔离级别为读未提交,并查询gulimall_pms数据库里,pms_spu_info表中的数据

set session transaction isolation level read uncommitted;
select * from pms_spu_info;
image-20220613224818155
image-20220613224818155
2、保存Spu的描述 pms_spu_info_desc

点击Resume Program按钮,执行到下一个断点停止(不执行该断点),然后点击调试的Step Over(步过)按钮,执行spuInfoDescService.saveSpuInfoDesc(spuInfoDescEntity);这段代码

image-20220613223914215
image-20220613223914215

gulimall_pms数据库的新建查询里添加sql语句,并选中刚刚添加到sql语句,点击运行已选择的,即可查看刚刚保存的数据

select * from pms_spu_info_desc;
image-20220613224920966
image-20220613224920966
3、保存spu的图片集 pms_spu_images

点击Resume Program按钮,执行到下一个断点停止(不执行该断点),然后点击调试的Step Over(步过)按钮,执行spuImagesService.saveImages(spuInfoEntity.getId(), images);这行代码

image-20220613224052953
image-20220613224052953

gulimall_pms数据库的新建查询里添加sql语句,并选中刚刚添加到sql语句,点击运行已选择的,即可查看刚刚保存的数据

select * from pms_spu_images;
image-20220613225025623
image-20220613225025623
4、保存spu的规格参数;pms_product_attr_value

点击Resume Program按钮,执行到下一个断点停止(不执行该断点),然后点击调试的Step Over(步过)按钮,执行productAttrValueService.saveProductAttr(productAttrValueEntities);这行代码

image-20220613224503399
image-20220613224503399

gulimall_pms数据库的新建查询里添加sql语句,并选中刚刚添加到sql语句,点击运行已选择的,即可查看刚刚保存的数据

select * from pms_product_attr_value;
image-20220613224716339
image-20220613224716339
5、远程保存spu的积分信息; gulimall_sms->sms_spu_bounds

点击Resume Program按钮,执行到下一个断点停止(不执行该断点),然后点击调试的Step Over(步过)按钮,执行R r = couponFeignService.saveSpuBounds(spuBoundTo);这行代码

由于该方法要进行远程调用,所以这这行代码要执行的久一点

image-20220613225213723
image-20220613225213723

navicat软件里点击gulimall_sms数据库,右键选择新建查询

📝 以下sql为该新建查询用到的所有sql语句

set session transaction isolation level read uncommitted;
select * from sms_spu_bounds;
select * from sms_sku_ladder;
select * from sms_sku_full_reduction;
select * from sms_member_price;
image-20220613225452678
image-20220613225452678

输入以下sql,设置当前会话的隔离级别为读未提交,并查询gulimall_sms数据库里,sms_spu_bounds表中的数据

set session transaction isolation level read uncommitted;
select * from sms_spu_bounds;
image-20220613225559481
image-20220613225559481
5.1、sku的基本信息; pms_sku_info

点击Resume Program按钮,执行到下一个断点停止(不执行该断点),然后点击调试的Step Over(步过)按钮,执行skuInfoService.saveSkuInfo(skuInfoEntity);这行代码

image-20220613225814817
image-20220613225814817

gulimall_pms数据库的新建查询里添加sql语句,并选中刚刚添加到sql语句,点击运行已选择的,即可查看刚刚保存的数据

(不小心把select * from pms_product_attr_value;这行代码删了)

select * from pms_sku_info;
image-20220613225728853
image-20220613225728853
5.2、sku的图片信息; pms_sku_images

点击Resume Program按钮,执行到下一个断点停止(不执行该断点),然后点击调试的Step Over(步过)按钮,执行skuImagesService.saveBatch(skuImagesEntities);这行代码

image-20220613230444086
image-20220613230444086

gulimall_pms数据库的新建查询里添加sql语句,并选中刚刚添加到sql语句,点击运行已选择的,即可查看刚刚保存的数据

select * from pms_sku_images;

可以看到这时没有img_urlnull的数据了(img_urlnull的数据都被过滤掉了,不会保存到数据库了)

image-20220613230557481
image-20220613230557481
5.3、sku的销售属性信息: pms_sku_sale_attr_value

点击Resume Program按钮,执行到下一个断点停止(不执行该断点),然后点击调试的Step Over(步过)按钮,执行skuSaleAttrValueService.saveBatch(skuSaleAttrValueEntities);这行代码

image-20220613230754102
image-20220613230754102

gulimall_pms数据库的新建查询里添加sql语句,并选中刚刚添加到sql语句,点击运行已选择的,即可查看刚刚保存的数据

select * from pms_sku_sale_attr_value;
image-20220613230855981
image-20220613230855981
5.4、远程保存sku的优惠、满减、打折等信息;gulimall_sms->sms_sku_ladder\sms_sku_full_reduction\sms_ member_price

点击Resume Program按钮,执行到下一个断点停止(不执行该断点),然后点击调试的Step Over(步过)按钮,执行R r1 = couponFeignService.saveSkuReduction(skuReductionTo);这行代码

image-20220613231325398
image-20220613231325398

gulimall-coupon模块的com.atguigu.gulimall.coupon.service.impl.SkuFullReductionServiceImpl类的saveSkuReduction方法里设置了有打折信息才保存有满减信息才保存有会员优惠信息才保存

image-20220613231505771
image-20220613231505771

gulimall_sms数据库的新建查询里添加sql语句,并选中刚刚添加到sql语句,点击运行已选择的,即可查看刚刚保存的数据

select * from sms_sku_ladder;

可以看到没有打折信息为空的数据

image-20220613231951960
image-20220613231951960

gulimall_sms数据库的新建查询里添加sql语句,并选中刚刚添加到sql语句,点击运行已选择的,即可查看刚刚保存的数据

select * from sms_sku_full_reduction;

可以看到没有满减信息为空的数据

image-20220613232056519
image-20220613232056519

gulimall_sms数据库的新建查询里添加sql语句,并选中刚刚添加到sql语句,点击运行已选择的,即可查看刚刚保存的数据

select * from sms_member_price;

可以看到没有会员优惠信息为空的数据

image-20220613232324450
image-20220613232324450
5、截断表,重新发送数据

清空并截断表(其实可以不清空,直接截断),删除gulimall_pms数据库pms_product_attr_value表里面的数据(含有大量垃圾数据)

同理清空并截断(其实可以不清空,直接截断)以下所有表

gulimall_pms数据库

  1. pms_product_attr_value
  2. pms_sku_images
  3. pms_sku_info
  4. pms_sku_sale_attr_value
  5. pms_spu_images
  6. pms_spu_info
  7. pms_spu_info_desc

gulimall_sms数据库

  1. sms_member_price
  2. sms_sku_full_reduction
  3. sms_sku_ladder
  4. sms_spu_bounds
image-20220613233757008
image-20220613233757008
image-20220613233828662
image-20220613233828662

重新向后端发送华为手机苹果手机商品的json数据

image-20220619174732338
image-20220619174732338
image-20220613233312152
image-20220613233312152
6、附录
1、gulimall_pms数据库的新建查询里的所有sql语句
set session transaction isolation level read uncommitted;
select * from pms_spu_info;
select * from pms_spu_info_desc;
select * from pms_spu_images;
select * from pms_product_attr_value;
select * from pms_sku_info;
select * from pms_sku_images;
select * from pms_sku_sale_attr_value;
2、gulimall_sms数据库的新建查询里的所有sql语句
set session transaction isolation level read uncommitted;
select * from sms_spu_bounds;
select * from sms_sku_ladder;
select * from sms_sku_full_reduction;
select * from sms_member_price;

4.7、商品服务-API-商品管理

4.7.1、SPU检索

1、查看请求

商品系统->商品维护->spu管理里,分类选择手机/手机通讯/手机品牌选择华为,状态选择上架,

f12选择Network,清空Network里的数据,然后点击查询

然后查看请求url为

http:😕/localhost:88/api/product/spuinfo/list?t=1655170424813&status=1&key=&brandId=1&catelogId=225&page=1&limit=10

image-20220614093602135
image-20220614093602135

点击Payload,查看发送的json数据

image-20220614093637205
image-20220614093637205

接口文档在商品系统/18、spu检索里 : https://easydoc.net/s/78237135/ZUqEdvA4/9LISLvy7

image-20220614093404399
image-20220614093404399

2、修改list方法

修改gulimall-product模块的com.atguigu.gulimall.product.controller.SpuInfoController类的list方法

/**
 * 列表
 */
@RequestMapping("/list")
public R list(@RequestParam Map<String, Object> params) {
    PageUtils page = spuInfoService.queryPageByCondition(params);

    return R.ok().put("page", page);
}
image-20220614094259175
image-20220614094259175

3、添加queryPageByCondition抽象方法

gulimall-product模块的com.atguigu.gulimall.product.service.SpuInfoService接口里添加queryPageByCondition抽象方法

PageUtils queryPageByCondition(Map<String, Object> params);
image-20220614094320514
image-20220614094320514

4、实现queryPageByCondition抽象方法

gulimall-product模块的com.atguigu.gulimall.product.service.impl.SpuInfoServiceImpl类里实现未实现的queryPageByCondition抽象方法

/**
 * 根据条件分页查询
 * {
 *    page: 1,//当前页码
 *    limit: 10,//每页记录数
 *    sidx: 'id',//排序字段
 *    order: 'asc/desc',//排序方式
 *    key: '华为',//检索关键字
 *    catelogId: 6,//三级分类id
 *    brandId: 1,//品牌id
 *    status: 0,//商品状态
 * }
 * @param params
 * @return
 */
@Override
public PageUtils queryPageByCondition(Map<String, Object> params) {
    LambdaQueryWrapper<SpuInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //根据"key",精确匹配商品id 或 模糊查询spu_name
    String key = (String) params.get("key");
    lambdaQueryWrapper.and(StringUtils.hasLength(key),wrapper->{
        wrapper.eq(SpuInfoEntity::getId,key).or().like(SpuInfoEntity::getSpuName,key);
    });
    //根据status精确匹配状态
    String status = (String) params.get("status");
    lambdaQueryWrapper.eq(StringUtils.hasLength(status),SpuInfoEntity::getPublishStatus,status);
    //根据brandId精确匹配品牌id
    String brandId = (String) params.get("brandId");
    lambdaQueryWrapper.eq(StringUtils.hasLength(brandId),SpuInfoEntity::getBrandId,brandId);
    //根据catelogId精确匹配所属分类id(注意:前端发来的是catelogId,数据库写的是catalogId)
    String catelogId = (String) params.get("catelogId");
    lambdaQueryWrapper.eq(StringUtils.hasLength(catelogId),SpuInfoEntity::getCatalogId,catelogId);

    IPage<SpuInfoEntity> page = this.page(
            new Query<SpuInfoEntity>().getPage(params),
            lambdaQueryWrapper
    );

    return new PageUtils(page);
}
image-20220614112141228
image-20220614112141228

5、测试

重启gulimall-product模块,打开前端页面进行测试

测试一

打开商品系统->商品维护->spu管理,修改状态上架,点击查询,可以看到没有数据

image-20220614112300493
image-20220614112300493

查看GulimallProductApplication模块的控制台输出的sql语句,可以看到sql语句正常

SELECT COUNT(1) FROM pms_spu_info WHERE (publish_status = ? AND brand_id = ? AND catalog_id = ?) 
image-20220614112931073
image-20220614112931073
测试二

打开商品系统->商品维护->spu管理,修改状态新建,点击查询,可以看到一条数据

image-20220614112329904
image-20220614112329904

查看GulimallProductApplication模块的控制台输出的sql语句,可以看到sql语句正常

SELECT COUNT(1) FROM pms_spu_info WHERE (brand_id = ? AND catalog_id = ?) 
SELECT id,spu_description,spu_name,catalog_id,create_time,brand_id,weight,update_time,publish_status FROM pms_spu_info WHERE (brand_id = ? AND catalog_id = ?) LIMIT ?,? 
image-20220614113208157
image-20220614113208157

测试三

打开商品系统->商品维护->spu管理检索的输入框中输入1,点击查询

image-20220614112545011
image-20220614112545011

查看GulimallProductApplication模块的控制台输出的sql语句,可以看到sql语句正常

SELECT COUNT(1) FROM pms_spu_info WHERE (((id = ? OR spu_name LIKE ?)) AND brand_id = ? AND catalog_id = ?) 

SELECT id,spu_description,spu_name,catalog_id,create_time,brand_id,weight,update_time,publish_status FROM pms_spu_info WHERE (( (id = ? OR spu_name LIKE ?) ) AND brand_id = ? AND catalog_id = ?) LIMIT ?,? 
image-20220614113252034
image-20220614113252034

6、状态异常问题

如果状态新建时,status1则是前端和数据库的状态码没有对应起来

查看src\views\modules\product\spuinfo.vue文件的以下代码,这里指定了新建已上架已下架状态分别为012

<el-table-column prop="publishStatus" header-align="center" align="center" label="上架状态">
  <template slot-scope="scope">
    <el-tag v-if="scope.row.publishStatus == 0">新建</el-tag>
    <el-tag v-if="scope.row.publishStatus == 1">已上架</el-tag>
    <el-tag v-if="scope.row.publishStatus == 2">已下架</el-tag>
  </template>
</el-table-column>
image-20220614114116404
image-20220614114116404

检索条件在src\views\modules\product\spu.vue文件里,可以看到spu.vue文件引用了Spuinfo文件

image-20220614113806046
image-20220614113806046

修改spu.vue文件里的状态里的不同状态对应的值,新建已上架已下架状态分别为012

<el-form-item label="状态">
  <el-select style="width:160px" v-model="dataForm.status" clearable>
    <el-option label="新建" :value="0"></el-option>
    <el-option label="上架" :value="1"></el-option>
    <el-option label="下架" :value="2"></el-option>
  </el-select>
</el-form-item>
image-20220614113850905
image-20220614113850905

7、修改时间格式和时区

老师的时间格式不符合中国人习惯

image-20220619200710866
image-20220619200710866

而我的项目时区不对

image-20220614115448525
image-20220614115448525

修改gulimall-product模块的src/main/resources/application.yml配置文件,设置时间显示的格式

spring:
  jackson:
    date-format: yyyy-MM-dd HH-mm-ss
image-20220614114631103
image-20220614114631103

时区最好也设置一下

spring:
  jackson:
    date-format: yyyy-MM-dd HH-mm-ss
    time-zone: GMT+8
image-20220614114810747
image-20220614114810747

4.7.2、SKU检索

1、查看接口

商品系统/商品维护/商品管理里点击查询,查看请求

url: http://localhost:88/api/product/skuinfo/list?t=1655193479594&page=1&limit=10&key=&catelogId=0&brandId=0

image-20220614155909254
image-20220614155909254

接口文档在商品系统/21、sku检索里:https://easydoc.net/s/78237135/ZUqEdvA4/ucirLq1D

image-20220614115218657
image-20220614115218657

2、修改list方法

修改gulimall-product模块的com.atguigu.gulimall.product.controller.SkuInfoController模块的list方法

image-20220614115720234
image-20220614115720234

3、添加queryPageByCondition抽象方法

gulimall-product模块的com.atguigu.gulimall.product.service.SkuInfoService接口里添加queryPageByCondition抽象方法

PageUtils queryPageByCondition(Map<String, Object> params);
image-20220614115816925
image-20220614115816925

4、实现queryPageByCondition抽象方法

gulimall-product模块的com.atguigu.gulimall.product.service.impl.SkuInfoServiceImpl类里实现未实现的queryPageByCondition抽象方法

/**
 * 根据条件分页查询
 * {
 * page: 1,//当前页码
 * limit: 10,//每页记录数
 * sidx: 'id',//排序字段
 * order: 'asc/desc',//排序方式
 * key: '华为',//检索关键字
 * catelogId: 0,
 * brandId: 0,
 * min: 0,
 * max: 0
 * }
 *
 * @param params
 * @return
 */
@Override
public PageUtils queryPageByCondition(Map<String, Object> params) {
    LambdaQueryWrapper<SkuInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //根据"key",精确匹配商品id 或 模糊查询spu_name
    String key = (String) params.get("key");
    lambdaQueryWrapper.and(StringUtils.hasLength(key)  && !"0".equalsIgnoreCase(key), wrapper -> {
        wrapper.eq(SkuInfoEntity::getSkuId, key).or().like(SkuInfoEntity::getSkuName, key);
    });

    //根据catelogId精确匹配所属分类id(注意:前端发来的是catelogId,数据库写的是catalogId)
    String catelogId = (String) params.get("catelogId");
    lambdaQueryWrapper.eq(StringUtils.hasLength(catelogId) && !"0".equalsIgnoreCase(catelogId), SkuInfoEntity::getCatalogId, catelogId);

    //根据brandId精确匹配品牌id
    String brandId = (String) params.get("brandId");
    lambdaQueryWrapper.eq(StringUtils.hasLength(brandId) && !"0".equalsIgnoreCase(brandId), SkuInfoEntity::getBrandId, brandId);

    // price >= min
    String min = (String) params.get("min");
    if (StringUtils.hasLength(min)) {
        try {
            BigDecimal mimBigDecimal = new BigDecimal(min);
            lambdaQueryWrapper.ge(mimBigDecimal.compareTo(BigDecimal.ZERO)>0, SkuInfoEntity::getPrice, min);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // price <= max
    String max = (String) params.get("max");
    if (StringUtils.hasLength(max)) {
        try {
            BigDecimal maxBigDecimal = new BigDecimal(max);
            lambdaQueryWrapper.le( maxBigDecimal.compareTo(BigDecimal.ZERO)>0, SkuInfoEntity::getPrice, max);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    IPage<SkuInfoEntity> page = this.page(
            new Query<SkuInfoEntity>().getPage(params),
            lambdaQueryWrapper
    );

    return new PageUtils(page);
}
image-20220614174410390
image-20220614174410390

5、测试

重启gulimall-product模块,打开商品系统/商品维护/商品管理,可以看到价格的两个输入框没输入时都为0

image-20220619220828891
image-20220619220828891

手动把价格的第二个输入框(最大值)的0删了;打开控制台,点击vue,最左边选择APP 1

中间依次选择Root->APP->Main->MainContent->ElTabs->ElPane->ElCard->Manager,

可以看到当价格的输入框没有数据时,值为undefined

image-20220614155157775
image-20220614155157775

打卡Vs Code,点击搜索框(或使用快捷键ctrl+shift+F),输入价格,然后点击enter,

选择spu.vue里的这个把data->return->dataForm->price里的minmax值都改为undefined

data() {
  return {
    catPathSub: null,
    brandIdSub: null,
    dataForm: {
      key: "",
      brandId: 0,
      catelogId: 0,
      price: {
        min: undefined,
        max: undefined
      }
    },
    dataList: [],
    pageIndex: 1,
    pageSize: 10,
    totalPage: 0,
    dataListLoading: false,
    dataListSelections: [],
    addOrUpdateVisible: false,
    catelogPath: []
  };
},
image-20220619222303036
image-20220619222303036

修改后查看页面,可以看到价格的两个输入框没输入时都没有0了,就不显示数据了

image-20220614155349645
image-20220614155349645

这样修改后,没有设置的在发送请求时就不会就不会带上这个字段了

image-20220614155607613
image-20220614155607613

4.8、仓库服务-API-仓库管理

仓库服务-API-仓库管理对应于gulimall_wms数据库

image-20220614160337441
image-20220614160337441

仓库服务-API-仓库管理对应于gulimall-ware模块

image-20220614160421031
image-20220614160421031

4.8.1、整合ware服务

1、配置注册中心地址

gulimall-ware模块的src/main/resources/application.yml配置文件里,添加配置注册中心地址应用名

spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
  application:
    name: gulimall-ware
image-20220614160600771
image-20220614160600771

2、开启服务发现

gulimall-ware模块的com.atguigu.gulimall.ware.GulimallWareApplication启动类上添加@EnableDiscoveryClient服务发现注解

@EnableDiscoveryClient
image-20220614160709664
image-20220614160709664

3、指定要扫描的Mapper文件所在的包

gulimall-ware模块的com.atguigu.gulimall.ware.GulimallWareApplication启动类上添加@MapperScan("com.atguigu.gulimall.ware.dao")注解,并指定要扫描的Mapper文件所在的包

@MapperScan("com.atguigu.gulimall.ware.dao")
image-20220614160821054
image-20220614160821054

4、开启事务管理

gulimall-ware模块的com.atguigu.gulimall.ware.GulimallWareApplication启动类上添加@EnableTransactionManagement注解,用于开启事务管理功能

@EnableTransactionManagement
image-20220614160924438
image-20220614160924438

4.8.2、运行gulimall-ware`模块

1、将gulimall-ware模块添加到Compond

点击Unnamed,选择Edit Configurations...

image-20220614161220452
image-20220614161220452

点击右侧的+号,在弹出的选择框中选择GulimallWareApplication

image-20220614161055713
image-20220614161055713

可以看到,已经添加到名称为Unnamedcompond里了

image-20220614161149410
image-20220614161149410

2、启动gulimall-ware模块

点击IDEA底部的Services,选择GulimallWareApplication,然后点击Run运行按钮

image-20220614161339771
image-20220614161339771

可以看到GulimallWareApplication的控制台报错了,这里报错是因为加了配置中心的依赖,但是没有配置配置中心地址命名空间等,这里可以先不用管

image-20220614161420866
image-20220614161420866

打开nacos的前端页面,点击服务管理里的服务列表,可以看到gulimall-ware已经注册的注册中心里了

image-20220614161506499
image-20220614161506499

4.8.3、仓库管理打不开

点击库存系统里的仓库管理,可以看到一直刷新不出来数据,打开控制台,点击失败的那个list的请求,右侧选择Preview,可以看到path的值为/renren-fast/ware/wareinfo/list,这表明网关路由给了renren-fast模块,而不是gulimall-ware模块

image-20220614161724134
image-20220614161724134

gulimall-gateway模块的src/main/resources/application.yml配置文件里添加配置,配置负载均衡到gulimall-ware模块的路径匹配规则(注意写到admin_route前面)

- id: ware_route
  uri: lb://gulimall-ware
  predicates:
    - Path=/api/ware/**
  filters:
    #http://localhost:88/api/ware/wareinfo/list 变为 http://localhost:11000/ware/wareinfo/list
    - RewritePath=/api/(?<segment>/?.*),/$\{segment}
image-20220614162009550
image-20220614162009550

重启gulimall-ware模块,刷新前端页面,可以看到请求已经成功了

image-20220614162150162
image-20220614162150162

4.8.4、添加测试数据

1、添加一条测试数据

库存系统里的仓库维护里,点击新建,新建一条数据;仓库名1号仓库仓库地址北京区域编码124

image-20220614162259394
image-20220614162259394

可以看到已经添加成功了

image-20220614162329695
image-20220614162329695

2、修改该测试数据的字段

库存系统里的仓库维护里,点击刚刚添加的数据右边的修改按钮,把仓库地址修改为北京xx

image-20220614162409669
image-20220614162409669

可以看到已经修改成功了

image-20220614162440788
image-20220614162440788

3、再添加一条测试数据

image-20220614162532455
image-20220614162532455

4.8.5、添加仓库维护查询功能

1、查看接口

先打开控制台,点击Network,清空数据,然后点击库存系统里的仓库维护里的查询按钮,查看发送请求url

url:http://localhost:88/api/ware/wareinfo/list?t=1655195182345&page=1&limit=10&key=

image-20220614162709471
image-20220614162709471

2、修改queryPage方法

修改gulimall-ware模块的com.atguigu.gulimall.ware.service.impl.WareInfoServiceImpl类的queryPage方法

@Override
public PageUtils queryPage(Map<String, Object> params) {
    LambdaQueryWrapper<WareInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();

    String key = (String) params.get("key");
    if (StringUtils.hasLength(key)){
        lambdaQueryWrapper.eq(WareInfoEntity::getId,key)
                .or().like(WareInfoEntity::getName,key)
                .or().like(WareInfoEntity::getAddress,key)
                .or().like(WareInfoEntity::getAreacode,key);
    }

    IPage<WareInfoEntity> page = this.page(
            new Query<WareInfoEntity>().getPage(params),
            lambdaQueryWrapper
    );

    return new PageUtils(page);
}
image-20220614163435122
image-20220614163435122

3、修改日志级别为debug

gulimall-ware模块的src/main/resources/application.yml配置文件里添加配置,修改com.atguigu包及其子包的输出日志的级别为debug级别

logging:
  level:
    com.atguigu: debug
image-20220614163644567
image-20220614163644567

4、查看sql语句

重启gulimall-ware模块,再次点击库存系统里的仓库维护里的查询按钮

image-20220614163824115
image-20220614163824115

查看GulimallWareApplication模块的控制台输出的sql语句,可以看到sql语句正常

SELECT id,address,name,areacode FROM wms_ware_info WHERE (id = ? OR name LIKE ? OR address LIKE ? OR areacode LIKE ?) 
image-20220614163937587
image-20220614163937587

5、分页还有问题

复制gulimall-product模块的com.atguigu.gulimall.product.config.MyBatisConfig类文件

image-20220615210545460
image-20220615210545460

粘贴到gulimall-ware模块的com.atguigu.gulimall.ware.config包下

image-20220615210644264
image-20220615210644264

剪切gulimall-ware模块的com.atguigu.gulimall.ware.GulimallWareApplication启动类的开启事务管理注解Mapper包扫描注解

@EnableTransactionManagement
@MapperScan("com.atguigu.gulimall.ware.dao")
image-20220615210741669
image-20220615210741669

将刚刚粘贴的代码,替换到gulimall-ware模块的com.atguigu.gulimall.ware.config.MyBatisConfig

@EnableTransactionManagement
@MapperScan("com.atguigu.gulimall.product.dao")

将其改为

@EnableTransactionManagement
@MapperScan("com.atguigu.gulimall.ware.dao")
image-20220615210757239
image-20220615210757239

完整代码

package com.atguigu.gulimall.product.config;

import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;

/**
 * @author 无名氏
 * @date 2022/5/10
 * @Description:
 * @EnableTransactionManagement :开启事务功能
 */
@Configuration
@EnableTransactionManagement
@MapperScan("com.atguigu.gulimall.product.dao")
public class MyBatisConfig {

    /**
     * 引入分页插件
     * @return
     */
    @Bean
    public PaginationInterceptor paginationInterceptor(){
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        //设置请求的页面大于最大页后操作,true调回到首页,false 继续请求 默认false
        paginationInterceptor.setOverflow(false);
        //设置最大单页限制数量,默认500条,-1 不受限制
        paginationInterceptor.setLimit(1000);

        return paginationInterceptor;
    }
}

6、重新测试

重启gulimall-ware模块,刷新前端界面

1、重新点击查询
image-20220619234648667
image-20220619234648667
2、查看sql语句

查看GulimallWareApplication模块的控制台输出的sql语句,可以看到已近带上分页信息了

SELECT COUNT(1) FROM wms_ware_info WHERE (id = ? OR name LIKE ? OR address LIKE ? OR areacode LIKE ?) 

SELECT id,address,name,areacode FROM wms_ware_info WHERE (id = ? OR name LIKE ? OR address LIKE ? OR areacode LIKE ?) LIMIT ?,? 
image-20220619235039841
image-20220619235039841

4.8.6、商品库存

1、查看接口

先打开控制台,点击Network,清空数据,然后点击库存系统/商品库存仓库选择1号仓库,skuId输入1,点击查询,可以看到请求的url为:http://localhost:88/api/ware/waresku/list?t=1655196148943&page=1&limit=10&skuId=1&wareId=1

image-20220614164256829
image-20220614164256829

接口文档在库存系统/02、查询商品库存里: https://easydoc.net/s/78237135/ZUqEdvA4/hwXrEXBZ

image-20220614164553204
image-20220614164553204

2、修改queryPage方法

修改gulimall-ware模块里的com.atguigu.gulimall.ware.service.impl.WareSkuServiceImpl类的queryPage方法

/**
 * {
 *    page: 1,//当前页码
 *    limit: 10,//每页记录数
 *    sidx: 'id',//排序字段
 *    order: 'asc/desc',//排序方式
 *    wareId: 123,//仓库id
 *    skuId: 123//商品id
 * }
 * @param params
 * @return
 */
@Override
public PageUtils queryPage(Map<String, Object> params) {

    LambdaQueryWrapper<WareSkuEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();

    String skuId = (String) params.get("skuId");
    lambdaQueryWrapper.eq(StringUtils.hasLength(skuId),WareSkuEntity::getSkuId,skuId);

    String wareId = (String) params.get("wareId");
    lambdaQueryWrapper.eq(StringUtils.hasLength(wareId),WareSkuEntity::getWareId,wareId);

    IPage<WareSkuEntity> page = this.page(
            new Query<WareSkuEntity>().getPage(params),
            lambdaQueryWrapper
    );

    return new PageUtils(page);
}
image-20220614165103674
image-20220614165103674

3、重新发送请求

重启gulimall-ware模块,刷新前端页面;打开控制台,点击Network,清空数据,然后点击库存系统/商品库存里的查询

url:http://localhost:88/api/ware/waresku/list?t=1655196600891&page=1&limit=10&skuId=1&wareId=1

image-20220614165026503
image-20220614165026503

查看GulimallWareApplication模块的控制台打印的sql语句

SELECT id,sku_name,ware_id,stock_locked,stock,sku_id FROM wms_ware_sku WHERE (sku_id = ? AND ware_id = ?)
image-20220614165215365
image-20220614165215365

4、新增商品库存

点击库存系统/商品库存里的新增按钮,新增一个商品库存

sku_id输入1仓库选择1号仓库库存数输入10sku_name输入华为锁定库存输入0,然后点击确定

image-20220614165300900
image-20220614165300900

可以看到已经新增成功了

image-20220614165328584
image-20220614165328584

5、修改商品库存

点击刚刚添加的那行数据的修改按钮,把库存数修改为100

image-20220614165400506
image-20220614165400506

可以看到已经修改成功了

image-20220614165425437
image-20220614165425437

6、商品管理跳转到库存管理携带skuId

选择商品系统/商品维护/商品管理,点击一条数据的更多按钮,再更多里面选择库存管理

image-20220614165640587
image-20220614165640587

可以看到跳转到库存管理时已自动携带刚刚选择的那个商品管理的那条数据的skuId

image-20220614165720231
image-20220614165720231

7、采购需求添加数据

库存系统/采购单维护/采购需求里点击新增采购商品id输入3采购数量输入2仓库选择1号仓库,然后点击确定

image-20220614165934717
image-20220614165934717

库存系统/采购单维护/采购需求里点击新增采购商品id输入1采购数量输入10仓库选择1号仓库,然后点击确定

image-20220614170034342
image-20220614170034342

8、合并整单

选中表头中id左侧的可选按钮,以全选所有采购需求,然后点击批量操作,在批量操作里选择合并整单,稍后完成这个功能

image-20220614170203075
image-20220614170203075

4.8.7、查询采购需求

1、查看接口

先打开控制台,点击Network,清空数据,然后点击库存系统/采购单维护/采购需求,在采购需求里,仓库选择1号仓库状态选择已分配,点击查询,可以看到请求的url为

http://localhost:88/api/ware/purchasedetail/list?t=1655197388337&page=1&limit=10&key=&status=1&wareId=1

image-20220614170329492
image-20220614170329492

接口文档在库存系统/03.查询采购需求里: https://easydoc.net/s/78237135/ZUqEdvA4/Ss4zsV7R

image-20220614170424163
image-20220614170424163

2、修改queryPage方法

gulimall-ware模块的com.atguigu.gulimall.ware.service.impl.PurchaseDetailServiceImpl类里修改queryPage方法

/**
 * {
 *    page: 1,//当前页码
 *    limit: 10,//每页记录数
 *    sidx: 'id',//排序字段
 *    order: 'asc/desc',//排序方式
 *    key: '华为',//检索关键字
 *    status: 0,//状态
 *    wareId: 1,//仓库id
 * }
 * @param params
 * @return
 */
@Override
public PageUtils queryPage(Map<String, Object> params) {

    LambdaQueryWrapper<PurchaseDetailEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();

    String key = (String) params.get("key");
        lambdaQueryWrapper.and(StringUtils.hasLength(key),wrapper -> {
            wrapper.eq(PurchaseDetailEntity::getSkuId, key).or().eq(PurchaseDetailEntity::getPurchaseId, key);
        });

    String status = (String) params.get("status");
    lambdaQueryWrapper.eq(StringUtils.hasLength(status),PurchaseDetailEntity::getStatus,status);

    String wareId = (String) params.get("wareId");
    lambdaQueryWrapper.eq(StringUtils.hasLength(wareId),PurchaseDetailEntity::getWareId,wareId);

    IPage<PurchaseDetailEntity> page = this.page(
            new Query<PurchaseDetailEntity>().getPage(params),
            lambdaQueryWrapper
    );

    return new PageUtils(page);
}
image-20220614183804323
image-20220614183804323

3、测试

重启gulimall-ware模块,点击库存系统/采购单维护/采购需求,在采购需求里,仓库选择1号仓库状态选择已分配,点击查询

image-20220614184255818
image-20220614184255818

查看GulimallWareApplication模块的控制台打印的sql语句

SELECT id,ware_id,purchase_id,sku_price,sku_num,sku_id,status FROM wms_purchase_detail WHERE (( (sku_id = ? OR purchase_id = ?) ) AND status = ? AND ware_id = ?)
image-20220614184415655
image-20220614184415655

4.8.8、合并采购需求(1.查询采购单)

1、采购简要流程

image-20220614184722625

2、新增采购单

库存系统/采购单维护/采购单里,点击新增优先级输入1,然后点击确定

image-20220614184928961
image-20220614184928961

3、查询采购单接口

先打开控制台,点击Network,清空数据;选中表头中id左侧的可选按钮,以全选所有采购需求,然后点击批量操作,在批量操作里选择合并整单

image-20220614185015067
image-20220614185015067

合并到整单的对话框中需要查询新建已分配采购单,可以看到url为

http://localhost:88/api/ware/purchase/unreceive/list?t=1655203843040

image-20220614185105320
image-20220614185105320

新建已分配采购单库存系统/采购单维护/采购单里的状态中可以看到

image-20220614185229357
image-20220614185229357

接口文档在库存系统/05、查询未领取的采购单里:https://easydoc.net/s/78237135/ZUqEdvA4/hI12DNrH

image-20220614185453482
image-20220614185453482

4、添加unreceiveList方法

gulimall-ware模块的com.atguigu.gulimall.ware.controller.PurchaseController里添加unreceiveList方法

/**
 * 分页查询未领取的采购单
 */
@RequestMapping("/unreceive/list")
public R unreceiveList(@RequestParam Map<String, Object> params){
    PageUtils page = purchaseService.queryPageUnreceivePurchase(params);

    return R.ok().put("page", page);
}
image-20220614190152959
image-20220614190152959

5、添加queryPageUnreceivePurchase抽象方法

gulimall-ware模块的com.atguigu.gulimall.ware.service.PurchaseService接口里添加queryPageUnreceivePurchase抽象方法

PageUtils queryPageUnreceivePurchase(Map<String, Object> params);
image-20220614190254861
image-20220614190254861

6、实现queryPageUnreceivePurchase抽象方法

gulimall-ware模块的com.atguigu.gulimall.ware.service.impl.PurchaseServiceImpl里实现未实现的queryPageUnreceivePurchase抽象方法

@Override
public PageUtils queryPageUnreceivePurchase(Map<String, Object> params) {

    LambdaQueryWrapper<PurchaseEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //查询状态为0(新建) 或 1(已分配) 的采购单
    lambdaQueryWrapper.eq(PurchaseEntity::getStatus,0).or().eq(PurchaseEntity::getStatus,1);

    IPage<PurchaseEntity> page = this.page(
            new Query<PurchaseEntity>().getPage(params),
            lambdaQueryWrapper
    );

    return new PageUtils(page);
}
image-20220614190912851
image-20220614190912851

7、测试

点击以前创建库存系统/采购单维护/采购单里的采购单id1操作里的分配按钮,在弹出的分配采购人员里选择admin,然后点击确定,即可分配采购人员

image-20220614191038467
image-20220614191038467

库存系统/采购单维护/采购需求里,选中表头中id左侧的可选按钮,以全选所有采购需求,然后点击批量操作,在批量操作里选择合并整单

image-20220614191329442
image-20220614191329442

然后就可以看到以前创建的库存系统/采购单维护/采购单里的采购单状态为新建已分配采购单,如果分配了采购人员,就可以在下拉列表中的采购单id的右边显示对应的分配的采购人员姓名采购人员电话

image-20220614191349478
image-20220614191349478

8、修改分配的采购人员

系统管理/管理员列表里新建管理员;用户名选择leifengyang密码输入123456确认密码里输入123456邮箱输入[email protected]手机号输入12345678912状态默认正常不用管,然后点击确定

image-20220614191547642
image-20220614191547642

然后点击库存系统/采购单维护/采购单里的上次创建的采购单id1的那行数据的操作里的分配按钮,在弹出的分配采购人员里就可以看到刚刚添加管理员里添加的管理员了,这些管理员就是可以分配的采购人员

选择刚刚创建的leifengyang,然后点击确定按钮

image-20220614191715258
image-20220614191715258

这样上次创建的采购单id1的那行数据的采购人名就变成了leifengyang,联系电话就变为了12345678912

image-20220614191740042
image-20220614191740042

选中表头中id左侧的可选按钮,以全选所有采购需求,然后点击批量操作,在批量操作里选择合并整单,然后就可以看到采购单id1采购人员姓名已经修改为leifengyang了,联系电话已经被修改为12345678912

image-20220614191939490
image-20220614191939490

4.8.9、合并采购需求(2.完成合并)

1、查看接口

1、选择想要合并的采购单id

选择1 leifengyang: 12345678912后 ,先打开控制台,点击Network,清空数据,然后点击确定

可以看到请求的url为: http://localhost:88/api/ware/purchase/merge

image-20220614192020545
image-20220614192020545

发送到json数据为{purchaseId: 1, items: {1, 2}}

image-20220614192448552
image-20220614192448552

接口文档在库存系统/04、合并采购需求里:https://easydoc.net/s/78237135/ZUqEdvA4/cUlv9QvK

image-20220614192149087
image-20220614192149087
2、不选择想要合并的采购单id

合并到整单里可以不选择想要合并的采购单,直接点击确定

image-20220614192556641
image-20220614192556641

在弹出的提示对话框里点击确定

image-20220614192615039
image-20220614192615039

这时提交的json数据,没有purchaseId(采购单id),只有item,这时需要自动创建一个新的采购单

image-20220614192702323
image-20220614192702323

2、新建MergeVo

gulimall-ware模块的com.atguigu.gulimall.ware包下,新建vo文件夹,在刚刚新建的com.atguigu.gulimall.ware.vo文件夹里新建MergeVo

package com.atguigu.gulimall.ware.vo;

import lombok.Data;

import java.util.List;

/**
 * @author 无名氏
 * @date 2022/6/14
 * @Description:
 */
@Data
public class MergeVo {

    /**
     * 采购单id
     */
    private Long purchaseId;
    /**
     * 要合并的采购项集合
     */
    private List<Long> items;
}
image-20220614193510395
image-20220614193510395

3、添加merge方法

gulimall-ware模块的com.atguigu.gulimall.ware.controller.PurchaseController类里添加merge方法

/**
 * 合并采购需求
 * @param mergeVo
 * @return
 */
@PostMapping("/merge")
public R merge(@RequestBody MergeVo mergeVo){
    purchaseService.mergePurchase(mergeVo);
    return R.ok();
}
image-20220614193952336
image-20220614193952336

4、添加mergePurchase抽象方法

gulimall-ware模块的com.atguigu.gulimall.ware.service.PurchaseService接口里添加mergePurchase抽象方法

void mergePurchase(MergeVo mergeVo);
image-20220614194027812
image-20220614194027812

5、调整常量类的代码结构

gulimall-common模块的com.atguigu.common.constant包下新建product文件夹,把ProductConstant枚举类移动到product文件夹下

image-20220614200450052
image-20220614200450052

选中ProductConstant枚举类,右键选择Refactor(重构),然后选择Rename...

image-20220614200633959
image-20220614200633959

在弹出的框内,修改名字为AttrEnum,然后点击Refactor

image-20220614200617454
image-20220614200617454

6、新建采购商品枚举类

gulimall-common模块的com.atguigu.common.constant包下新建ware文件夹,在com.atguigu.common.constant.ware文件夹下新建PurchaseStatusEnum(采购商品的采购单完成状态)枚举类

package com.atguigu.common.constant.ware;

/**
 * @author 无名氏
 * @date 2022/6/14
 * @Description:
 */
public enum PurchaseStatusEnum {
    /**
     * 刚新建状态
     */
    CREATED(0,"新建"),
    /**
     * 已分配给采购员
     */
    ASSIGNED(1,"已分配"),
    /**
     * 采购员已领取
     */
    RECEIVE(2,"已领取"),
    /**
     * 采购员已完成采购
     */
    FINISHED(3,"已完成"),
    /**
     * 采购异常
     */
    HASERROR(4,"有异常");

    private int status;
    private String msg;

    PurchaseStatusEnum(int status, String msg) {
        this.status = status;
        this.msg = msg;
    }

    public int getStatus() {
        return status;
    }

    public String getMsg() {
        return msg;
    }
}
image-20220614202825301
image-20220614202825301

gulimall-common模块的com.atguigu.common.constant.ware包下新建PurchaseStatusEnum(采购单具体采购的商品的完成状态)枚举类

package com.atguigu.common.constant.ware;

/**
 * @author 无名氏
 * @date 2022/6/14
 * @Description:
 */
public enum PurchaseDetailStatusEnum {
    /**
     * 刚新建状态
     */
    CREATED(0,"新建"),
    /**
     * 已分配给采购员
     */
    ASSIGNED(1,"已分配"),
    /**
     * 采购员正在采购
     */
    BUYING(2,"正在采购"),
    /**
     * 采购员已完成采购
     */
    FINISHED(3,"已完成"),
    /**
     * 采购员采购失败
     */
    HASERROR(4,"采购失败");

    private int status;
    private String msg;

    PurchaseDetailStatusEnum(int status, String msg) {
        this.status = status;
        this.msg = msg;
    }

    public int getStatus() {
        return status;
    }

    public String getMsg() {
        return msg;
    }
}
image-20220614203245139
image-20220614203245139

7、实现mergePurchase抽象方法

gulimall-ware模块的com.atguigu.gulimall.ware.service.impl.PurchaseServiceImpl类里修改空的mergePurchase方法

@Transactional(rollbackFor = Exception.class)
@Override
public void mergePurchase(MergeVo mergeVo) {
    Long purchaseId = mergeVo.getPurchaseId();
    if (purchaseId == null){
        PurchaseEntity purchaseEntity = new PurchaseEntity();
        purchaseEntity.setStatus(PurchaseStatusEnum.CREATED.getStatus());
        this.save(purchaseEntity);
        purchaseId = purchaseEntity.getId();
    }

    List<Long> items = mergeVo.getItems();
    Long finalPurchaseId = purchaseId;
    List<PurchaseDetailEntity> purchaseDetailEntities = items.stream().map(item -> {
        PurchaseDetailEntity purchaseDetailEntity = new PurchaseDetailEntity();
        purchaseDetailEntity.setId(item);
        purchaseDetailEntity.setPurchaseId(finalPurchaseId);
        purchaseDetailEntity.setStatus(PurchaseDetailStatusEnum.ASSIGNED.getStatus());
        return purchaseDetailEntity;
    }).collect(Collectors.toList());

    purchaseDetailService.updateBatchById(purchaseDetailEntities);
}
image-20220614204427993
image-20220614204427993

8、在PurchaseEntity类添加注解

gulimall-ware模块的com.atguigu.gulimall.ware.entity.PurchaseEntity类里的createTime字段上添加@TableField(fill = FieldFill.INSERT)注解,当在插入时向该字段插入当前系统时间;在updateTime字段上添加@TableField(fill = FieldFill.INSERT_UPDATE)注解,当在插入更新时向该字段插入或更新为当前系统时间

点击查看PurchaseEntity类完整代码

image-20220614204518437
image-20220614204518437

9、设置时间格式时区

gulimall-ware模块的src/main/resources/application.yml配置文件里设置时间格式时区

spring:
  jackson:
    date-format: yyyy-MM-dd HH-mm-ss
    time-zone: GMT+8
image-20220614205149464
image-20220614205149464

10、修改mergePurchase方法

gulimall-ware模块的com.atguigu.gulimall.ware.service.impl.PurchaseServiceImpl类里修改mergePurchase方法

@Autowired
PurchaseDetailService purchaseDetailService;

@Transactional(rollbackFor = Exception.class)
@Override
public void mergePurchase(MergeVo mergeVo) {
    Long purchaseId = mergeVo.getPurchaseId();
    if (purchaseId == null){
        PurchaseEntity purchaseEntity = new PurchaseEntity();
        purchaseEntity.setStatus(PurchaseStatusEnum.CREATED.getStatus());
        //自动更新PurchaseEntity的更新时间
        this.save(purchaseEntity);
        purchaseId = purchaseEntity.getId();
    }else {
        //更新PurchaseEntity(采购单)的更新时间
        PurchaseEntity purchaseEntity = new PurchaseEntity();
        purchaseEntity.setId(purchaseId);
        purchaseEntity.setUpdateTime(new Date());
        this.updateById(purchaseEntity);
    }

    List<Long> items = mergeVo.getItems();
    Long finalPurchaseId = purchaseId;
    List<PurchaseDetailEntity> purchaseDetailEntities = items.stream().map(item -> {
        PurchaseDetailEntity purchaseDetailEntity = new PurchaseDetailEntity();
        purchaseDetailEntity.setId(item);
        purchaseDetailEntity.setPurchaseId(finalPurchaseId);
        purchaseDetailEntity.setStatus(PurchaseDetailStatusEnum.ASSIGNED.getStatus());
        return purchaseDetailEntity;
    }).collect(Collectors.toList());

    //合并采购需求,分派到指定采购单
    purchaseDetailService.updateBatchById(purchaseDetailEntities);
}
image-20220614210320804
image-20220614210320804

11、测试一

重启gulimall-ware模块,点击库存系统/采购单维护/采购需求,选中表头中id左侧的可选按钮,以全选所有采购需求,然后点击批量操作,在批量操作里选择合并整单,然后选择1 leifengyang: 12345678912后 ,点击确定

image-20220614210524845
image-20220614210524845

可以看到,刚刚全选的两个采购需求采购单id状态都已经更新了

image-20220614210635314
image-20220614210635314

点击``库存系统/采购单维护/采购`单已经更新了,设置时区后,更新的时间也是系统时间了

image-20220614211030868
image-20220614211030868

12、测试二

库存系统/采购单维护/采购需求里,点击新增,在采购商品id里输入2采购数量输入20仓库选择2号仓库,然后点击确定

image-20220614211131709
image-20220614211131709

库存系统/采购单维护/采购需求里,点击新建,在新增对话框里,采购商品id输入2采购数量输入20仓库选择2号仓库

然后选中刚刚创建的id3的采购需求的左侧按钮,然后点击批量操作,在批量操作里选择合并整单

image-20220614211230326
image-20220614211230326

合并到整单里可以不选择想要合并的采购单,直接点击确定

image-20220614211309852
image-20220614211309852

在弹出的提示对话框里点击确定

image-20220614211325353
image-20220614211325353

可以看到在库存系统/采购单维护/采购需求里,刚刚创建的id3采购需求状态已变为已分配

image-20220614211354619
image-20220614211354619

库存系统/采购单维护/采购单里,可以看到已自动创建了一个采购单,这个采购单的状态新建

image-20220614211426208
image-20220614211426208

点击这个采购单操作里的分配按钮,在分配采购人员的对话框中选择admin,然后点击确定

image-20220614211502224
image-20220614211502224

13、修改没有自动创建时间的bug

可以看到刚刚自动创建的那个采购单创建时间更新时间没有自动创建

image-20220614211426208
image-20220614211426208

调试后,发现create_timeupdate_time传入的参数都为null

INSERT INTO wms_ purchase ( create_time,update_time,status ) VALUES ( ?, ? ,? )
image-20220614211753133
image-20220614211753133

原因是没有设置mybatisPlus的属性自动填充配置

gulimall-ware模块的com.atguigu.gulimall.ware包里新建config文件夹,

复制gulimall-product模块的com.atguigu.gulimall.product.config.MyMetaObjectHandler类,粘贴到gulimall-ware模块的com.atguigu.gulimall.ware.config包里

package com.atguigu.gulimall.ware.config;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.context.annotation.Configuration;

import java.util.Date;

/**
 * @author 无名氏
 * @date 2022/6/14
 * @Description:
 */
@Slf4j
@Configuration
public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("start insert fill...");
        this.setFieldValByName("createTime", new Date(), metaObject);
        this.setFieldValByName("updateTime", new Date(), metaObject);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("start update fill...");
        this.setFieldValByName("updateTime", new Date(), metaObject);
    }
}
image-20220614212341199
image-20220614212341199

14、重新测试

重启gulimall-ware模块,刷新前端界面,在库存系统/采购单维护/采购单里,点击新增,在弹出的新增对话框里的优先级里输入3,然后点击确定

image-20220614212524728
image-20220614212524728

可以看到create_timeupdate_time都已经成功插入进去了

image-20220614212551163
image-20220614212551163

4.8.10、领取采购单

1、查看接口

领取采购单为采购员使用app领取,不属于后台管理系统,所以可以使用Postman模拟采购员领取采购单

接口文档在库存系统/06、领取采购单里: https://easydoc.net/s/78237135/ZUqEdvA4/vXMBBgw1

image-20220614213006277
image-20220614213006277

Postman里新建一个请求,url输入http://localhost:88/api/ware/purchase/received,请求方式选择POST,然后按ctrl+s保存

image-20220614213232724
image-20220614213232724

在弹出的SAVE REQUEST对话框里,Request name里输入领取采购单,然后点击下面的Create a collection

image-20220614213431165
image-20220614213431165

SAVE REQUEST对话框里的Save to里输入采购人员app,点击右侧的Create按钮

image-20220614213539031
image-20220614213539031

然后点击Save按钮

image-20220614213552521
image-20220614213552521

在刚刚新建的请求中,点击Body、然后点击raw,在GraphQL右侧的下拉列表中选择JSON,然后在下方输入框中输入[3,4],最后点击Send(输入[3,4]表示要领取id为34的采购单)

image-20220615104015989
image-20220615104015989

2、创建received方法

gulimall-ware模块的com.atguigu.gulimall.ware.controller.PurchaseController类中创建received方法

/**
 * 采购员领取采购单
 * @param purchaseIds 采购单id
 * @return
 */
@PostMapping("/received")
public R received(@RequestBody List<Long> purchaseIds){
    purchaseService.received(purchaseIds);
    return R.ok();
}
image-20220615083311741
image-20220615083311741

3、新建received抽象方法

gulimall-ware模块的com.atguigu.gulimall.ware.service.PurchaseService接口里新建received抽象方法

void received(List<Long> purchaseIds);
image-20220615083427579
image-20220615083427579

4、实现received抽象方法

需要完成的效果为:

1、首先需要修改刚刚领取的所有采购单状态,把采购单状态修改为已领取

(这里我把采购单id为3的采购单分配给admin用户了,点击采购单id为3的右侧的分配,选择admin即可)

image-20220615083759255
image-20220615083759255

2、这些采购单对应的所有采购需求都要改为正在采购

(我先把id为3的采购需求的采购单id修改为3了,选中该数据左侧的按钮,点击批量操作,在批量操作里选择合并整单,在弹出的分配采购人员里选择admin,然后点击确定按钮)

image-20220615083915584
image-20220615083915584

gulimall-ware模块的com.atguigu.gulimall.ware.service.impl.PurchaseServiceImpl类里实现未实现的received抽象方法

streatmap那,IDEA提示建议使用peek来代替mapjava.util.Stream.peek()主要用于支持调试。如果流管道不包含终端操作,则不会使用任何元素,并且根本不会调用peek()操作。所以最好不要使用peek

/**
 * 采购员领取采购单
 *
 * @param purchaseIds 采购单id
 */
@Override
public void received(List<Long> purchaseIds) {
    //1、确认当前采购单的id是"新建"或者是"已分配"状态
    LambdaQueryWrapper<PurchaseEntity> purchaseQueryWrapper = new LambdaQueryWrapper<>();
    purchaseQueryWrapper.and(wrapper -> {
        wrapper.eq(PurchaseEntity::getStatus, PurchaseStatusEnum.CREATED.getStatus())
                .or().eq(PurchaseEntity::getStatus, PurchaseStatusEnum.ASSIGNED.getStatus());
    }).in(PurchaseEntity::getId, purchaseIds);
    List<PurchaseEntity> purchaseEntities = this.list(purchaseQueryWrapper);

    //2、改变采购单状态(已使用注解在更新字段时自动更新updateTime)
    List<PurchaseEntity> newPurchaseEntities = purchaseEntities.stream().map(purchaseEntity -> {
        purchaseEntity.setStatus(PurchaseStatusEnum.RECEIVE.getStatus());
        return purchaseEntity;
    }).collect(Collectors.toList());
    this.updateBatchById(newPurchaseEntities);

    //3、改变采购项状态
    PurchaseDetailEntity purchaseDetailEntity = new PurchaseDetailEntity();
    purchaseDetailEntity.setStatus(PurchaseDetailStatusEnum.BUYING.getStatus());
    purchaseDetailService.updatePurchaseDetailBatchByPurchaseId(purchaseDetailEntity,newPurchaseEntities);
}
image-20220615095031917
image-20220615095031917

5、新建批量修改采购需求抽象方法

gulimall-ware模块的com.atguigu.gulimall.ware.service.PurchaseDetailService接口里新建updatePurchaseDetailBatchByPurchaseId抽象方法

void updatePurchaseDetailBatchByPurchaseId(PurchaseDetailEntity purchaseDetailEntity, List<PurchaseEntity> purchaseEntities);
image-20220615100049272
image-20220615100049272

6、实现批量修改采购需求抽象方法

gulimall-ware模块的com.atguigu.gulimall.ware.service.impl.PurchaseDetailServiceImpl类里实现未实现的updatePurchaseDetailBatchByPurchaseId抽象方法

image-20220615100252009
image-20220615100252009

7、给这些具体方法添加事务注解

gulimall-ware模块的com.atguigu.gulimall.ware.service.impl.PurchaseServiceImpl类的received方法添加事务注解,并指定有异常就回滚事务

@Transactional(rollbackFor = Exception.class)
image-20220615100748683
image-20220615100748683

gulimall-ware模块的com.atguigu.gulimall.ware.service.impl.PurchaseDetailServiceImpl类的updatePurchaseDetailBatchByPurchaseId方法添加事务注解,并指定有异常就回滚事务

@Transactional(rollbackFor = Exception.class)
image-20220615100803171
image-20220615100803171

8、测试

gulimall-ware模块的com.atguigu.gulimall.ware.service.impl.PurchaseServiceImpl类的received方法里的第一条语句打断点,然后点击IDEA底部的Service按钮,选择GulimallWareApplication,右键选择Return in Debug Mode,以debug方式重新启动GulimallWareApplication模块

image-20220615100934480
image-20220615100934480

gulimall_wms数据库中的wms_purchase表中修改id3status2

image-20220615104336853
image-20220615104336853

刷新前端页面,可以看到在库存系统/采购单维护/采购单里,id3采购需求状态已变为已领取

image-20220615104512089
image-20220615104512089

打开Postman,发送领取采购单的请求

image-20220615104045438
image-20220615104045438

切换到IDEA,可以看到已经接收到purchase_id34的两条数据了

image-20220615104124184
image-20220615104124184

继续向下执行,直到确认当前采购单的id是"新建"或者是"已分配"状态完成,到改变采购单状态(已使用注解在更新字段时自动更新updateTime)停止,可以看到刚刚修改的id3的状态为已领取的那条数据已经被过滤掉了,只剩下id4的状态为新建的那条数据了

image-20220615104627577
image-20220615104627577

选择GulimallWareApplication模块的控制台,此时的sql语句也正确

SELECT id, amount , ware_id, create_time, phone, assignee_name , update_time, priority, assignee_id, status FROM wms_purchase WHERE ( (status = ? OR status = ?) ) AND id IN (?,?)
image-20220615104801580
image-20220615104801580

继续向下执行,直到映射结束,停在this.updateBatchById(newPurchaseEntities);方法上,可以看到此时的newPurchaseEntitiesstatus已修改为2

image-20220615104859953
image-20220615104859953

继续向下执行,执行完this.updateBatchById(newPurchaseEntities);方法,查看GulimallWareApplication模块的控制台,此时的sql语句也正确

UPDATE wms_purchase SET create_time=?, phone=?, assignee_name=?,update_time=?,priority=?, status=? WHERE id=?
image-20220615105044203
image-20220615105044203

继续向下执行,直到改变采购项状态所有代码都执行完,可以看到PurchaseDetailEntitystatus2

image-20220615105320878
image-20220615105320878

查看GulimallWareApplication模块的控制台,此时的sql语句也正确

UPDATE wms_purchase_detail SET status=? WHERE (purchase_id IN (?))
image-20220615105420470
image-20220615105420470

9、添加待办事项

库存系统/采购单维护/采购需求里,选中表头中id左侧的可选按钮,以全选所有采购需求,然后点击批量操作,在批量操作里选择合并整单,在合并到整单的下拉列表里选择1 leifengyang 12345678912,然后点击确定

image-20220615105740827
image-20220615105740827

gulimall-ware模块的com.atguigu.gulimall.ware.service.impl.PurchaseServiceImpl类里的mergePurchase里添加待办事项,后面完成确认采购单状态是0,1才可以合并功能

//TODO 确认采购单状态是0,1才可以合并
image-20220615110011514
image-20220615110011514

可以看到在领取到采购单后,这些被领取的采购单,并没有修改采购人id采购人名联系方式,这个功能目前由于没有登录,所以目前实现不了

image-20220615155836991
image-20220615155836991

gulimall-ware模块的com.atguigu.gulimall.ware.service.impl.PurchaseServiceImpl类里的received方法里添加待办事项,后面完成设置采购人id,采购人名、联系方式功能

//TODO 设置采购人id,采购人名、联系方式
image-20220615160034810
image-20220615160034810

4.8.11、完成采购

1、查看接口

完成采购为采购员使用app领取,不属于后台管理系统,所以可以使用Postman模拟采购员完成采购

接口文档在库存系统/07、完成采购里: https://easydoc.net/s/78237135/ZUqEdvA4/cTQHGXbK

image-20220615155034336
image-20220615155034336

库存系统/采购单维护/采购需求里,id3的采购需求的采购单id3

image-20220615160507927
image-20220615160507927

gulimall_wms数据库的wms_purchase_detail表里将id3purchase_id修改为4

image-20220615160630004
image-20220615160630004

再新增一条数据,id输入4purchase_id输入4sku_id输入4sku_num输入30ware_id输入2status输入2,然后点击下面的

image-20220615160917672
image-20220615160917672

发送的请求中id对应库存系统/采购单维护/采购需求里的iditems里的itemId对应库存系统/采购单维护/采购需求里的采购单id,即为完成某个采购单的部分或全部采购项(采购需求)

请求的url为: http://localhost:88/api/ware/purchase/done ,请求方式为POST

{
   "id": 4,
   "items": [
       {"itemId":3,"status":3,"reason":""},
       {"itemId":4,"status":4,"reason":"无货"}
    ]
}
image-20220615162226790
image-20220615162226790

2、新建PurchaseDoneVo

gulimall-ware模块的com.atguigu.gulimall.ware.vo包里新建PurchaseDoneVo

package com.atguigu.gulimall.ware.vo;

import lombok.Data;

import javax.validation.constraints.NotNull;
import java.util.List;

/**
 * @author 无名氏
 * @date 2022/6/15
 * @Description: 采购完成
 *
 * {
 *    "id": 4,
 *    "items": [
 *        {"itemId":3,"status":3,"reason":""},
 *        {"itemId":4,"status":4,"reason":"无货"}
 *     ]
 * }
 */
@Data
public class PurchaseDoneVo {

    /**
     * 采购单id
     */
    @NotNull
    private Long  id;
    private List<PurchaseItemDone> items;

    /**
     * 采购项
     */
    @Data
    public class PurchaseItemDone{
        /**
         * 采购项id
         */
        private Long itemId;
        /**
         * 采购状态(3:采购完成 ; 4:采购失败)
         */
        private Integer status;
        /**
         * 失败原因
         */
        private String reason;
    }

}
image-20220615163320567
image-20220615163320567

3、新建finish方法

gulimall-ware模块的com.atguigu.gulimall.ware.controller.PurchaseController类里新建finish方法

/**
 * 采购员完成采购
 * /ware/purchase/done
 * @param purchaseDoneVo
 * @return
 */
@PostMapping("/done")
public R finish(@RequestBody PurchaseDoneVo purchaseDoneVo){
    purchaseService.donePurchase(purchaseDoneVo);
    return R.ok();
}
image-20220615163521858
image-20220615163521858

4、新建donePurchase抽象接口

gulimall-ware模块的com.atguigu.gulimall.ware.service.PurchaseService接口里新建donePurchase抽象接口

image-20220615163603585
image-20220615163603585

5、实现donePurchase抽象接口

gulimall-ware模块的com.atguigu.gulimall.ware.service.impl.PurchaseServiceImpl类里实现未实现的donePurchase抽象方法

@Autowired
WareSkuService wareSkuService;

/**
 * 采购员完成采购
 * @param purchaseDoneVo
 */
@Transactional(rollbackFor = Exception.class)
@Override
public void donePurchase(PurchaseDoneVo purchaseDoneVo) {

    AtomicBoolean flag = new AtomicBoolean(true);
    //1、改变采购项状态
    List<PurchaseDetailEntity> purchaseDetailEntities = purchaseDoneVo.getItems().stream().map(purchaseItemDone -> {
        PurchaseDetailEntity purchaseDetailEntity = new PurchaseDetailEntity();
        if (purchaseItemDone.getStatus() == PurchaseDetailStatusEnum.HASERROR.getStatus()) {
            flag.set(false);
            purchaseDetailEntity.setStatus(PurchaseDetailStatusEnum.HASERROR.getStatus());
        } else if (purchaseItemDone.getStatus() == PurchaseDetailStatusEnum.BUYING.getStatus()){
            purchaseDetailEntity.setStatus(PurchaseDetailStatusEnum.FINISHED.getStatus());
        }
        purchaseDetailEntity.setId(purchaseItemDone.getItemId());
        return purchaseDetailEntity;
    }).collect(Collectors.toList());
    purchaseDetailService.updateBatchById(purchaseDetailEntities);

    //2、改变采购单状态
    Long purchaseId = purchaseDoneVo.getId();
    PurchaseEntity purchaseEntity = new PurchaseEntity();
    purchaseEntity.setId(purchaseId);
    Integer status = flag.get()?PurchaseStatusEnum.FINISHED.getStatus() : PurchaseStatusEnum.HASERROR.getStatus();
    purchaseEntity.setStatus(status);
    this.updateById(purchaseEntity);

    //3、将成功采购的进行入库
    List<Long> purchaseDetailIds = purchaseDoneVo.getItems().stream().filter(purchaseItemDone -> {
        return purchaseItemDone.getStatus() == PurchaseDetailStatusEnum.BUYING.getStatus();
    }).map(PurchaseDoneVo.PurchaseItemDone::getItemId).collect(Collectors.toList());
    Collection<PurchaseDetailEntity> purchaseDetailList = purchaseDetailService.listByIds(purchaseDetailIds);

    wareSkuService.addOrUpdateStockBatchByskuIdAndwareId(purchaseDetailList);
}
image-20220615200305836
image-20220615200305836

6、新建添加或更新库存抽象接口

gulimall-ware模块的com.atguigu.gulimall.ware.service.WareSkuService接口里新建addOrUpdateStockBatchByskuIdAndwareId抽象接口

void addOrUpdateStockBatchByskuIdAndwareId(Collection<PurchaseDetailEntity> purchaseDetailList);
image-20220615200456324
image-20220615200456324

7、实现添加或更新库存抽象接口

gulimall-ware模块的com.atguigu.gulimall.ware.service.impl.WareSkuServiceImpl类里实现未实现的addOrUpdateStockBatchByskuIdAndwareId抽象方法

@Autowired
WareSkuDao wareSkuDao;

@Transactional(rollbackFor = Exception.class)
@Override
public void addOrUpdateStockBatchByskuIdAndwareId(Collection<PurchaseDetailEntity> purchaseDetailList) {
    purchaseDetailList.forEach(this::addOrUpdateStockByskuIdAndwareId);
}

@Transactional(rollbackFor = Exception.class)
public void addOrUpdateStockByskuIdAndwareId(PurchaseDetailEntity purchaseDetailEntity) {
    WareSkuEntity wareSkuEntity = new WareSkuEntity();
    wareSkuEntity.setSkuId(purchaseDetailEntity.getSkuId());
    wareSkuEntity.setWareId(purchaseDetailEntity.getWareId());

    LambdaQueryWrapper<WareSkuEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    lambdaQueryWrapper.eq(WareSkuEntity::getSkuId,purchaseDetailEntity.getSkuId())
            .eq(WareSkuEntity::getWareId,purchaseDetailEntity.getWareId());
    WareSkuEntity query = wareSkuDao.selectOne(lambdaQueryWrapper);
    if (query==null){
            wareSkuEntity.setStock(purchaseDetailEntity.getSkuNum());
            wareSkuDao.insert(wareSkuEntity);
    }else {
        wareSkuEntity.setId(query.getId());
        wareSkuEntity.setStock(query.getStock()+purchaseDetailEntity.getSkuNum());
        wareSkuDao.updateById(wareSkuEntity);
    }
}
image-20220615204549277
image-20220615204549277

8、测试

重启gulimall-ware模块,打开Postman

选择请求的url为: http://localhost:88/api/ware/purchase/done的对话框,按ctrl+S快捷键保存,在弹出的SAVE REQUEST对话框里,Request name里输入完成采购,点击Save to里的采购人员app,把完成采购放到采购人员app里面,然后点击Save

GIF 2022-6-15 23-10-02
GIF 2022-6-15 23-10-02

点击Send后,显示报了400的错误

image-20220615231715243
image-20220615231715243

查看GulimallWareApplication模块的控制台,可以发现已经报错了

Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot construct instance of `com.atguigu.gulimall.ware.vo.PurchaseDoneVo$PurchaseItemDone` (although at least one Creator exists): can only instantiate non-static inner class by using default, no-argument

已解决 [org.springframework.http.converter.HttpMessageNotReadableException:JSON 解析错误:无法构造 `com.atguigu.gulimall.ware.vo.PurchaseDoneVo$PurchaseItemDone` 的实例(尽管至少存在一个 Creator):只能实例化非静态 使用默认的无参数的内部类
image-20220615231358244
image-20220615231358244

gulimall-ware模块的com.atguigu.gulimall.ware.vo.PurchaseDoneVo类的PurchaseItemDone内部类上添加static关键字

image-20220615231750046
image-20220615231750046

重启gulimall-ware模块,打开Postman,再次发送请求,这次报了500的错误

image-20220615233005159
image-20220615233005159

查看GulimallWareApplication模块的控制台,查看sql语句可以看到执行update操作时只有更新条件,却没有更新的字段

UPDATE wms_ purchase_ detail WHERE id=?
image-20220615233148420
image-20220615233148420

调试发现,purchaseDetailEntitiesid3的数据的statusnull,整条数据只有id

image-20220615233304771
image-20220615233304771

在该gulimall-ware模块的com.atguigu.gulimall.ware.service.impl.PurchaseServiceImpl类的donePurchase方法里添加过滤条件,只有purchaseDetailEntity.getStatus()!=null的数据才保留

//1、改变采购项状态
List<PurchaseDetailEntity> purchaseDetailEntities = purchaseDoneVo.getItems().stream().map(purchaseItemDone -> {
    PurchaseDetailEntity purchaseDetailEntity = new PurchaseDetailEntity();
    if (purchaseItemDone.getStatus() == PurchaseDetailStatusEnum.HASERROR.getStatus()) {
        flag.set(false);
        purchaseDetailEntity.setStatus(PurchaseDetailStatusEnum.HASERROR.getStatus());
    } else if (purchaseItemDone.getStatus() == PurchaseDetailStatusEnum.FINISHED.getStatus()){
        purchaseDetailEntity.setStatus(PurchaseDetailStatusEnum.FINISHED.getStatus());
    }
    purchaseDetailEntity.setId(purchaseItemDone.getItemId());
    return purchaseDetailEntity;
}).filter(purchaseDetailEntity->{
    return purchaseDetailEntity.getStatus()!=null;
}).collect(Collectors.toList());
purchaseDetailService.updateBatchById(purchaseDetailEntities);
image-20220615233734194
image-20220615233734194

重启gulimall-ware模块,再次发送请求又报错了,再次调试

查看GulimallWareApplication模块的控制台,查看sql语句,可以看到在in()里面没有传递数据

SELECT id,ware_id, purchase_id, sku_price, sku_num,sku_id, status FROM wms_purchase_detail WHERE id IN ( )
image-20220615234429118
image-20220615234429118

在该gulimall-ware模块的com.atguigu.gulimall.ware.service.impl.PurchaseServiceImpl类的donePurchase方法里,修改purchaseItemDone.getStatus()PurchaseDetailStatusEnum.FINISHED.getStatus();相比,并判断purchaseDetailIds不为空才添加或更新库存

image-20220615234255037
image-20220615234255037

点击查看PurchaseServiceImpl类完整代码

9、重新测试一

重启gulimall-ware模块,再次发送请求,可以看到这次成功了

image-20220615234546911
image-20220615234546911

库存系统/采购单维护/采购单里,采购单id4状态已正确变为有异常

image-20220615234624963
image-20220615234624963

库存系统/采购单维护/采购需求里,id3状态已正确变为已完成id4状态已正确变为采购失败

image-20220615234706291
image-20220615234706291

10、重新测试二

库存系统/采购单维护/采购需求里,点击新建,在采购商品id里输入2采购数量里输入10仓库里选择1号仓库,然后点击确定

image-20220615234821431
image-20220615234821431

库存系统/采购单维护/采购需求里,点击新建,在采购商品id里输入3采购数量里输入15仓库里选择1号仓库,然后点击确定

image-20220615234903216
image-20220615234903216

库存系统/采购单维护/采购需求里,点击新建,在采购商品id里输入4采购数量里输入5仓库里选择1号仓库,然后点击确定

image-20220615234940017
image-20220615234940017

库存系统/采购单维护/采购需求里选择刚刚新建的采购单id567的三个采购单,然后点击批量操作,在批量操作里选择合并整单;在合并到整单里不选择想要合并的采购单,直接点击确定,在弹出的提示对话框里点击确定

库存系统/采购单维护/采购单里,点击刚刚自动创建的采购单的右侧修改按钮,在弹出的合并到整单的对话框中的下拉列表中选择admin,然后点击确定

GIF 2022-6-15 23-51-32
GIF 2022-6-15 23-51-32

使用Postma发送领取采购单,在json的输入框里输入刚刚自动创建的采购单的id:[5],然后点击Send

image-20220615235334954
image-20220615235334954

可以看到在库存系统/采购单维护/采购单里,采购单id5的状态已经变为已领取

image-20220615235441128
image-20220615235441128

点击库存系统/采购单维护/采购需求,可以看到刚刚新建的采购单id567的三个采购单的状态已全部变为正在采购

image-20220615235505421
image-20220615235505421

使用Postma发送完成采购,在json的输入框里输入以下json,然后点击Send

发送的请求中id对应库存系统/采购单维护/采购需求里的iditems里的itemId对应库存系统/采购单维护/采购需求里的采购单id,即为完成某个采购单的部分或全部采购项(采购需求)

这里将采购单id5的所有采购项(采购需求)的状态都变为3,表示全部采购成功

{
   "id": 5,
   "items": [
       {"itemId":5,"status":3,"reason":""},
       {"itemId":6,"status":3,"reason":""},
       {"itemId":7,"status":3,"reason":""}
    ]
}
image-20220616000426342
image-20220616000426342

可以看到在库存系统/采购单维护/采购单里,采购单id5的状态已经变为已完成

image-20220616002409091
image-20220616002409091

点击库存系统/采购单维护/采购需求,可以看到刚刚新建的采购单id567的三个采购单的状态已全部变为已完成

image-20220616002426774
image-20220616002426774

4.8.12、远程调用gulimall-product模块

1、查出sku_name

想查出库存系统/商品库存里,每条数据的sku_name的值

image-20220618181715170
image-20220618181715170

2、查看远程提供的服务接口

gulimall-ware模块的com.atguigu.gulimall.ware.service.impl.WareSkuServiceImpl类的addOrUpdateStockByskuIdAndwareId方法里添加代办事项,稍后完成远程查询sku的名字这个功能

image-20220616233031996
image-20220616233031996

此时想要调用gulimall-product模块的com.atguigu.gulimall.product.controller.SkuInfoController类的info这个方法

image-20220616233341069
image-20220616233341069

3、新建CouponFeignService接口

gulimall-ware模块里com.atguigu.gulimall.ware包下新建feign文件夹,在这个文件夹里新建CouponFeignService接口

image-20220616233614305
image-20220616233614305

添加上一些注释

package com.atguigu.gulimall.ware.feign;

import com.atguigu.common.utils.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @author  无名氏
 * @date  2022/6/16
 * @Description:
 */
@FeignClient("gulimall-gateway")
public interface ProductFeignService {

    /**
     * 1)、让所有请求过网关;
     *    1、@FeignClient( "gulimall-gateway"):给gulimall-gateway所在的机器发请求
     *    2、/api/product/skuinfo/info/{skuId}
     * 2)、直接让后台指定服务处理
     *    1、@FeignClient( "gulimall-gateway")
     *    2、/product/skuinfo/info/{skuId}
     * @param skuId
     * @return
     */
    @RequestMapping("/api/product/skuinfo/info/{skuId}")
    public R info(@PathVariable("skuId") Long skuId);
}
image-20220616233927770
image-20220616233927770

4、修改添加或更新库存方法

gulimall-ware模块的com.atguigu.gulimall.ware.service.impl.WareSkuServiceImpl类里修改addOrUpdateStockByskuIdAndwareId方法

@Autowired
ProductFeignService productFeignService;

@Transactional(rollbackFor = Exception.class)
public void addOrUpdateStockByskuIdAndwareId(PurchaseDetailEntity purchaseDetailEntity) {
    WareSkuEntity wareSkuEntity = new WareSkuEntity();
    wareSkuEntity.setSkuId(purchaseDetailEntity.getSkuId());
    wareSkuEntity.setWareId(purchaseDetailEntity.getWareId());

    LambdaQueryWrapper<WareSkuEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    lambdaQueryWrapper.eq(WareSkuEntity::getSkuId, purchaseDetailEntity.getSkuId())
            .eq(WareSkuEntity::getWareId, purchaseDetailEntity.getWareId());
    WareSkuEntity query = wareSkuDao.selectOne(lambdaQueryWrapper);
    if (query == null) {
        wareSkuEntity.setStockLocked(0);
        //远程查询sku的名字;如果失败,整个事务不回滚
        //1、 自己catch异常
        //TODO 还可以用什么办法让异常出现以后不回滚?高级
        try {
            R info = productFeignService.info(wareSkuEntity.getSkuId());
            if (info.getCode() == 0){
                Map<String, Object> skuInfo = (Map<String, Object>) info.get("skuInfo");
                wareSkuEntity.setSkuName((String) skuInfo.get("skuName"));
            }
        } catch (Exception e) {

        }
        wareSkuEntity.setStock(purchaseDetailEntity.getSkuNum());
        wareSkuDao.insert(wareSkuEntity);
    } else {
        wareSkuEntity.setId(query.getId());
        wareSkuEntity.setStock(query.getStock() + purchaseDetailEntity.getSkuNum());
        wareSkuDao.updateById(wareSkuEntity);
    }
}
image-20220616235351460
image-20220616235351460

最后经过优化,改成了这样

@Autowired
ProductFeignService productFeignService;

@Transactional(rollbackFor = Exception.class)
public void addOrUpdateStockByskuIdAndwareId(PurchaseDetailEntity purchaseDetailEntity) {
    WareSkuEntity wareSkuEntity = new WareSkuEntity();
    wareSkuEntity.setSkuId(purchaseDetailEntity.getSkuId());
    wareSkuEntity.setWareId(purchaseDetailEntity.getWareId());

    LambdaQueryWrapper<WareSkuEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    lambdaQueryWrapper.eq(WareSkuEntity::getSkuId, purchaseDetailEntity.getSkuId())
            .eq(WareSkuEntity::getWareId, purchaseDetailEntity.getWareId());
    WareSkuEntity query = wareSkuDao.selectOne(lambdaQueryWrapper);
    if (query==null || query.getSkuName()==null){
        //远程查询sku的名字;如果失败,整个事务不回滚
        //1、 自己catch异常
        //TODO 还可以用什么办法让异常出现以后不回滚?高级
        try {
            R info = productFeignService.info(wareSkuEntity.getSkuId());
            if (info.getCode() == 0){
                Map<String, Object> skuInfo = (Map<String, Object>) info.get("skuInfo");
                wareSkuEntity.setSkuName((String) skuInfo.get("skuName"));
            }
        } catch (Exception e) {
        }
    }
    if (query == null) {
        wareSkuEntity.setStockLocked(0);
        wareSkuEntity.setStock(purchaseDetailEntity.getSkuNum());
        wareSkuDao.insert(wareSkuEntity);
    } else {
        wareSkuEntity.setId(query.getId());
        wareSkuEntity.setStock(query.getStock() + purchaseDetailEntity.getSkuNum());
        wareSkuDao.updateById(wareSkuEntity);
    }
}

5、解决IDEA报红

可以看到在gulimall-ware模块的com.atguigu.gulimall.ware.service.impl.WareSkuServiceImpl类里注入的productFeignService对象报红,这个报红属于IDEA的问题,是IDEA检测到无法注入所以报红

image-20220618175013156
image-20220618175013156

gulimall-ware模块的com.atguigu.gulimall.ware.feign.ProductFeignService接口上添加@Service注解

image-20220618175103940
image-20220618175103940

这样gulimall-ware模块的com.atguigu.gulimall.ware.service.impl.WareSkuServiceImpl类里注入的productFeignService对象就不报红了

image-20220618224651290
image-20220618224651290

6、启动gulimall-ware模块失败

重启gulimall-ware模块时,控制台报错

ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'purchaseController': Unsatisfied dependency expressed through field 'purchaseService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'purchaseService': Unsatisfied dependency expressed through field 'wareSkuService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'wareSkuService': Unsatisfied dependency expressed through field 'productFeignService'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.atguigu.gulimall.ware.feign.ProductFeignService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}

ConfigServletWebServerApplicationContext:上下文初始化期间遇到异常 - 取消刷新尝试:org.springframework.beans.factory.UnsatisfiedDependencyException:创建名称为“purchaseController”的bean时出错:通过字段“purchaseService”表示不满足的依赖关系;嵌套异常是 org.springframework.beans.factory.UnsatisfiedDependencyException:创建名称为“purchaseService”的 bean 时出错:通过字段“wareSkuService”表示的依赖关系不满足;嵌套异常是 org.springframework.beans.factory.UnsatisfiedDependencyException:创建名为 'wareSkuService' 的 bean 时出错:通过字段 'productFeignService' 表达的依赖关系不满足;嵌套异常是 org.springframework.beans.factory.NoSuchBeanDefinitionException:没有“com.atguigu.gulimall.ware.feign.ProductFeignService”类型的合格 bean 可用:预计至少有 1 个有资格作为自动装配候选者的 bean。依赖注解:{@org.springframework.beans.factory.annotation.Autowired(required=true)}

'com.atguigu.gulimall.ware.feign.ProductFeignService' that could not be found这句话说得很清楚了,就是ProductFeignService这个接口没有找到(准确的说是它的实现类没有找到,不能成功注入,Spring中不能注入接口)

Field productFeignService in com.atguigu.gulimall.ware.service.impl.WareSkuServiceImpl required a bean of type 'com.atguigu.gulimall.ware.feign.ProductFeignService' that could not be found.

com.atguigu.gulimall.ware.service.impl.WareSkuServiceImpl 中的字段 productFeignService 需要类型为“com.atguigu.gulimall.ware.feign.ProductFeignService”的 bean。但是没有找到
image-20220618174435966
image-20220618174435966

完整报错信息

2022-06-18 17:43:29.843  WARN 18808 --- [           main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'purchaseController': Unsatisfied dependency expressed through field 'purchaseService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'purchaseService': Unsatisfied dependency expressed through field 'wareSkuService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'wareSkuService': Unsatisfied dependency expressed through field 'productFeignService'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.atguigu.gulimall.ware.feign.ProductFeignService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
2022-06-18 17:43:29.845  INFO 18808 --- [           main] o.apache.catalina.core.StandardService   : Stopping service [Tomcat]
2022-06-18 17:43:29.856  INFO 18808 --- [           main] ConditionEvaluationReportLoggingListener : 

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2022-06-18 17:43:29.944 ERROR 18808 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   : 

***************************
APPLICATION FAILED TO START
***************************

Description:

Field productFeignService in com.atguigu.gulimall.ware.service.impl.WareSkuServiceImpl required a bean of type 'com.atguigu.gulimall.ware.feign.ProductFeignService' that could not be found.

The injection point has the following annotations:
	- @org.springframework.beans.factory.annotation.Autowired(required=true)


Action:

Consider defining a bean of type 'com.atguigu.gulimall.ware.feign.ProductFeignService' in your configuration.

7、开启远程调用

gulimall-ware模块的com.atguigu.gulimall.ware.GulimallWareApplication启动类上添加注解,以开启远程调用

@EnableFeignClients(basePackages = "com.atguigu.gulimall.ware.feign")
image-20220618224737308
image-20220618224737308

8、重新测试

重启gulimall-ware模块,刷新前端页面

库存系统/采购单维护/采购需求里,点击新建,在采购商品id里输入7采购数量里输入7仓库里选择1号仓库,然后点击确定,

选中刚刚新建的id8的采购需求按钮,然后点击批量操作,在批量操作里选择合并整单;在合并到整单里不选择想要合并的采购单,直接点击确定,在弹出的提示对话框里点击确定

GIF 2022-6-18 22-03-49
GIF 2022-6-18 22-03-49

库存系统/采购单维护/采购单里,点击刚刚自动创建的采购单的右侧分配按钮,在弹出的分配采购人员的对话框中的下拉列表中选择admin,然后点击确定

GIF 2022-6-18 22-05-31
GIF 2022-6-18 22-05-31

打开Postman,在领取采购单json输入框里输入[6],点击Send

完成采购json输入框里输入如下json,点击Send

{
   "id": 6,
   "items": [
       {"itemId":8,"status":3,"reason":""}
    ]
}
GIF 2022-6-18 22-06-55
GIF 2022-6-18 22-06-55

可以看到,此时可以看到已经成功插入sku_name字段了

image-20220618224951824
image-20220618224951824

4.9、商品服务-API-商品管理

1、无法访问

点击商品系统/商品维护/spu管理,随便点击一条数据的右边的规格按钮,可以看到报了400的异常

GIF 2022-6-18 22-57-00
GIF 2022-6-18 22-57-00

可以在选中gulimall_admin数据库,右键选择新建查询,输入以下sql,手动创建一个路由的路径

INSERT INTO sys_menu (menu_id, parent_id, name, url, perms, type, icon, order_num) VALUES (76, 37, '规格维护', 'product/attrupdate', '', 2, 'log', 0);
GIF 2022-6-18 22-59-57
GIF 2022-6-18 22-59-57

点击商品系统/商品维护/spu管理,再随便点击一条数据的右边的规格按钮,可以看到已经可以正常显示出来规格维护

GIF 2022-6-18 23-01-32
GIF 2022-6-18 23-01-32

如果还是无法访问,可以在navicat里选择gulimall_admin数据库中的sys_menu表,在里面把刚刚插入name为规格维护的那条数据的type2改成1就可以了

image-20220618232306790
image-20220618232306790

如果还是无法访问,可以打开VS Code,在src\router\index.js文件里的mainRoutes里的children里添加一条路径

{ path: '/product-attrupdate', component: _import('modules/product/attrupdate'), name: 'attr-update', meta: { title: '规格维护', isTab: true } }
image-20220618232119164
image-20220618232119164

2、查看接口

接口文档在商品系统/22、获取spu规格里:https://easydoc.net/s/78237135/ZUqEdvA4/GhhJhkg7

image-20220616235801916
image-20220616235801916

3、添加baseAttrlistforspu方法

gulimall-product模块的com.atguigu.gulimall.product.controller.AttrController类里添加baseAttrlistforspu方法

/**
 * /product/attr/base/listforspu/{spuId}
 * @param spuId
 * @return
 */
@GetMapping("/base/listforspu/{spuId}")
public R baseAttrlistforspu(@PathVariable("spuId") Long spuId){

    List<ProductAttrValueEntity> data = productAttrValueService.baseAttrlistforspu(spuId);
    return R.ok().put("data",data);
}
image-20220618234506140
image-20220618234506140

4、添加baseAttrlistforspu抽象方法

gulimall-product模块的com.atguigu.gulimall.product.service.ProductAttrValueService类里添加baseAttrlistforspu抽象方法

List<ProductAttrValueEntity> baseAttrlistforspu(Long spuId);
image-20220618233809045
image-20220618233809045

5、实现baseAttrlistforspu抽象方法

gulimall-product模块的com.atguigu.gulimall.product.service.impl.ProductAttrValueServiceImpl类里实现未实现的baseAttrlistforspu抽象方法

@Override
public List<ProductAttrValueEntity> baseAttrlistforspu(Long spuId) {
    LambdaQueryWrapper<ProductAttrValueEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    lambdaQueryWrapper.eq(ProductAttrValueEntity::getSpuId,spuId);
    return this.baseMapper.selectList(lambdaQueryWrapper);
}
image-20220618234037898
image-20220618234037898

6、无法回显数据

重启gulimall-product模块,如果前端无法回显数据,可以修改src\views\modules\product\spuinfo.vue里的attrUpdateShow方法的名为querycatalogIdrow.catalogId

attrUpdateShow(row) {
  console.log(row);
  this.$router.push({
    path: "/product-attrupdate",
    query: { spuId: row.id, catalogId: row.catalogId }
  });
},

数据库中 与商品spu sku有关的两张表 pms_spu_info 和 pms_sku_info中关于商品分类的字段catelog_id在建表时都打成了catalog_id,如果要矫正那么除了修改数据库,对应由工具生成的实体类Entity,controller,service和mapper中的字段配置都要改。此外,表的catelog_id没有错误。点击“规格”报404是因为在规格点击后,前端不会做路由跳转,需要参照评论回复中的小伙伴的方法在前端src/router/index.js中添加对应配置(你们找一找把)。这个“spu管理”界面对应vue的spuinfo.vue,他在跳转时会把选中行的id和分类id都封装进去做跳转,承接开头,找到spuinfo.vue的101行,把 【query: {spuId: row.id, catelogId: row.catelogId}】中的row.catelogId改成row.catalogId即可,因为是从pms_spu_info表中来的catalog_id字段去和pms_attr_group中欧冠的catelog_id字段做逻辑上的关联查询的。字段不对应查不出结果就只剩下一个确认修改,其他组件也不会渲染出来。

image-20220618235131788
image-20220618235131788

如果遇到多选无法回显问题可以在src\views\modules\product\attrupdate.vue文件的showBaseAttrs方法里加一个判断

if (v.length == 1 && attr.valueType == 0) {
          v = v[0] + "";
        }

另外当属性分组中,有的分组没有任何属性时候,也会报Cannot read property 'forEach' of null。 因为该分组的attrs会查出null值。

可以修改为以下代码

//先对表单的baseAttrs进行初始化
data.data.forEach((item) => {
  let attrArray = [];
  if (item.attrs != null) {
    item.attrs.forEach((attr) => {
      let v = "";
      if (_this.spuAttrsMap["" + attr.attrId]) {
        v = _this.spuAttrsMap["" + attr.attrId].attrValue.split(";");
        if (v.length == 1 && attr.valueType == 0) {
          v = v[0] + "";
        }
      }
      attrArray.push({
        attrId: attr.attrId,
        attrName: attr.attrName,
        attrValues: v,
        showDesc: _this.spuAttrsMap["" + attr.attrId]
          ? _this.spuAttrsMap["" + attr.attrId].quickShow
          : attr.showDesc,
      });
    });
  }
  this.dataResp.baseAttrs.push(attrArray);
});
this.dataResp.attrGroups = data.data;
image-20220619000414268
image-20220619000414268

7、查看接口

点击商品系统/商品维护/spu管理,随便点击一条数据的右边的规格按钮,先打开控制台,点击Network,清空数据,然后点击确认修改查看接口为: http://localhost:88/api/product/attr/update/1

image-20220619001237932
image-20220619001237932

接口文档在商品系统/23、修改商品规格里: https://easydoc.net/s/78237135/ZUqEdvA4/GhnJ0L85

image-20220619083101912
image-20220619083101912

8、新建updateSpuAttr方法

gulimall-product模块的com.atguigu.gulimall.product.controller.AttrController类里新建updateSpuAttr方法

/**
 * 根据spuid修改规格参数
 */
@PostMapping("/update/{spuId}")
public R updateSpuAttr(@PathVariable("spuId") Long spuId,@RequestBody List<ProductAttrValueEntity> productAttrValueEntities) {
    productAttrValueService.updateSpuAttr(spuId,productAttrValueEntities);

    return R.ok();
}
image-20220619083218353
image-20220619083218353

9、新建updateSpuAttr抽象方法

gulimall-product模块的com.atguigu.gulimall.product.service.ProductAttrValueService接口里新建updateSpuAttr抽象方法

image-20220619083256215
image-20220619083256215

10、实现updateSpuAttr抽象方法

gulimall-product模块的com.atguigu.gulimall.product.service.impl.ProductAttrValueServiceImpl类里实现未实现的updateSpuAttr抽象方法

@Transactional(rollbackFor = Exception.class)
@Override
public void updateSpuAttr(Long spuId, List<ProductAttrValueEntity> productAttrValueEntities) {
    LambdaQueryWrapper<ProductAttrValueEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    lambdaQueryWrapper.eq(ProductAttrValueEntity::getSpuId,spuId);
    this.baseMapper.delete(lambdaQueryWrapper);


    List<ProductAttrValueEntity> collect = productAttrValueEntities.stream().map(productAttrValueEntity -> {
        productAttrValueEntity.setSpuId(spuId);
        return productAttrValueEntity;
    }).collect(Collectors.toList());
    this.saveBatch(collect);
}
image-20220619084026551
image-20220619084026551

11、测试

选择商品系统/商品维护/spu管理,点击id1的那条数据的右边的规格按钮,将基本信息中的机身颜色修改为黑色,然后点击确认修改,在弹出的提示对话框中点击确定,再次从spu管理里进入该页面,可以发现并没有修改成功

GIF 2022-6-19 8-48-41
GIF 2022-6-19 8-48-41

查看GulimallProductApplication控制台,可以看到已经报错了

No primary or default constructor found for interface java.util.List
未找到接口 java.util.List 的主构造函数或默认构造函数
image-20220619085027587
image-20220619085027587

gulimall-product模块的com.atguigu.gulimall.product.controller.AttrController类的updateSpuAttr方法的List<ProductAttrValueEntity> productAttrValueEntities这个参数左边添加@RequestBody注解,指明该数据在请求体里面

image-20220619085149599
image-20220619085149599

重启gulimall-product模块,刷新前端页面

再次选择商品系统/商品维护/spu管理,点击id1的那条数据的右边的规格按钮,将基本信息中的机身颜色修改为黑色,然后点击确认修改,在弹出的提示对话框中点击确定,再次从spu管理里进入该页面,可以发现已经修改成功了

GIF 2022-6-19 8-52-52
GIF 2022-6-19 8-52-52

4.10、分布式基础篇总结

1、分布式基础概念

•微服务、注册中心、配置中心、远程调用、Feign、网关

2、基础开发

再次选择商品系统/商品维护/spu管理,点击id1的那条数据的右边的规格按钮,将基本信息中的机身颜色修改为黑色,然后点击确认修改,在弹出的提示对话框中点击确定,再次从spu管理里进入该页面,可以发现已经修改成功了

GIF 2022-6-19 8-52-52
GIF 2022-6-19 8-52-52

4.10、分布式基础篇总结

1、分布式基础概念

•微服务、注册中心、配置中心、远程调用、Feign、网关

2、基础开发

•SpringBoot2.0、SpringCloud、Mybatis-Plus、Vue组件化、阿里云对象存储

3、环境

•Vagrant、Linux、Docker、MySQL、Redis、逆向工程&人人开源

4、开发规范

•数据校验JSR303、全局异常处理、全局统一返回、全局跨域处理

•枚举状态、业务状态码、VO与TO与PO划分、逻辑删除

•Lombok:@Data、@Slf4j

评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v3.0.0-alpha.8