[project series] - Fundamentals of grain Mall (second brush)

Posted by Possum on Tue, 21 Sep 2021 19:09:39 +0200

1. Environmental construction

1.1 create project microservices



Paste a pom file from the sub module into the parent project, and then modify it:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <!--Modify coordinates-->
    <groupId>com.atguigu.gulimall</groupId>
    <artifactId>gulimall</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>gulimall</name>
    <description>Aggregate services</description>

    <packaging>pom</packaging>
	<!--Aggregation sub module-->
    <modules>
        <module>gulimall-order</module>
        <module>gulimall-ware</module>
        <module>gulimall-coupon</module>
        <module>gulimall-product</module>
        <module>gulimall-member</module>
    </modules>
</project>

1.2 front end scaffold configuration

① In a folder such as gulimall_ qianduan_ Clone the following two items in the code folder:

git clone https://gitee.com/renrenio/renren-fast.git
git clone https://gitee.com/renrenio/renren-fast-vue.git

② Remove the. git file in the Ren fast folder and put the whole folder into the background project:

③ Because a sub module is added under the gulimall project, this sub module is added in the pom file of gulimall:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.atguigu.gulimall</groupId>
    <artifactId>gulimall</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>gulimall</name>
    <description>Aggregate services</description>

    <packaging>pom</packaging>

    <modules>
        <module>gulimall-order</module>
        <module>gulimall-ware</module>
        <module>gulimall-coupon</module>
        <module>gulimall-product</module>
        <module>gulimall-member</module>
        <!--Add a new sub module-->
        <module>renren-fast</module>
    </modules>
</project>

④ Configure the database in the dev-applicaiton.yml file of the Ren fast module:

spring:
    datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        druid:
            driver-class-name: com.mysql.cj.jdbc.Driver
            url: jdbc:mysql://192.168.38.22:3306/gulimall_admin?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
            username: root
            password: root

⑤ Download nodejs on the official website and configure npm image in cmd window:

node -v
npm config set registry http://registry.npm.taobao.org/

⑥ Open the project Ren fast Vue with vscode and install npm on the terminal:

npm install

The first installation may fail. Please reinstall as follows:

# Clear cache first
npm rebuild node-sass
npm uninstall node-sass

Then in VS Code Delete in node_modules

# implement
npm i node-sass --sass_binary_site=https://npm.taobao.org/mirrors/node-sass/

# If there is no error reported above, continue to execute
npm install 

# Run project
npm run dev 

1.3 code generator generates gulimall product code

① Clone the code generator project, then delete the. git file and put it under the project project:

git clone https://gitee.com/renrenio/renren-generator.git


② Modify application.yml:

server:
  port: 8082
# mysql
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    #MySQL configuration
    driverClassName: com.mysql.cj.jdbc.Driver
    # Configure the database for the microservice code to be generated
    url: jdbc:mysql://192.168.38.22:3306/gulimall_pms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: root

③ Modify generator.properties:

mainPath=com.atguigu
package=com.atguigu.gulimall
# Change the module name to product
moduleName=product
author=hengheng
email=hengheng@gmail.com
# Change the header to pms_
tablePrefix=pms_

④ Start the Ren generator project and visit: localhost:8082

⑤ Replace the main folder in the generated code with the main folder of the gulimall product project

⑥ Create a gulimall common service to configure the dependencies and classes common to all microservices: use new mouse - > Maven to create

1.4 code generator generates gulimall coupon code

① Modify the application.yml file:

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    #MySQL configuration
    driverClassName: com.mysql.cj.jdbc.Driver
    # Configure the database for the microservice code to be generated
    url: jdbc:mysql://192.168.38.22:3306/gulimall_sms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: root

② Modify the generator.approvals file:

mainPath=com.atguigu
package=com.atguigu.gulimall
# Change the module name to coupon
moduleName=coupon
author=hengheng
email=hengheng@gmail.com
# Change the header to sms_
tablePrefix=sms_

③ Start the Ren generator project and access the generated code:

④ Replace the main folder under the generated code folder with the main folder under the gulimall coupling folder

⑤ Add the gulimall common service dependency in the pom file of the gulimall coupling service:

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

Repeat the above steps and continue to use the code generator to generate the code of gulimall order, gulimall member and gulimall ware services.

1.5 test the basic CRUD functions of microservices

① Add the mysql dependency in the GUI mall common:

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.17</version>
</dependency>

② Add mybatis related configuration:

server:
  port: 7000
spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://192.168.38.22:3306/gulimall_sms?useUnicode=true&characterEncoding= utf-8
    driver-class-name: com.mysql.jdbc.Driver
  application:
    name: gulimall-product

mybatis-plus:
  mapper-locations: classpath:/mapper/**/*.xml
  global-config:
    db-config:
      id-type: auto

③ Add a piece of data to the test database:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = GulimallProductApplicaiton.class)
public class GulimallProductApplicationTests {

    @Autowired
    BrandService brandService;

    @Test
    public void contextLoads() {
        BrandEntity brandEntity = new BrandEntity();
        brandEntity.setName("test data");
        brandService.save(brandEntity);
        System.out.println("Saved successfully");
    }
}

④ Because the version of the project is changed (from 2.5.4 to 2.1.8.RELEASE), the test fails to load ApplicationContext. You only need to add the following to the pom file:

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

⑤ The data inserted into the database is garbled in Chinese, and the file Encoding of idea is still garbled. Therefore, modify the application.yml file:

spring:
  datasource:
    url: jdbc:mysql://192.168.38.22:3306/gulimall_pms?useUnicode=true&characterEncoding= utf-8

2. SpringCloud Alibaba distributed components

https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.md

Introduce coordinates into the gulimall common project and configure the version of SpringCloud Alibaba We use:

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

2.1 nacos registry

Next, take the coupon service as an example to register the gulimall coupon service into nacos.

① First, you need to configure the port number and service name of the gulimall coupling service:

server:
  port: 7000
spring:
  application:
    name: gulimall-coupon

② To http://github.com/alibaba/nacos/releases Download and unzip the Nacos compressed package and run Nacos

③ Nacos Discovery Starter is introduced into the gulimall common module, so that the corresponding coordinates will be introduced into each service

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

④ Configure the Nacos Server address in the configuration file of the gulimall coupon service

spring:
  application:
    name: gulimall-coupon
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848

⑤ Use the @ EnableDiscoveryClient annotation to enable the service registration and discovery function in the gulimall coupon service

@EnableDiscoveryClient
@SpringBootApplication
public class GulimallCouponApplication {
    public static void main(String[] args) {
        SpringApplication.run(GulimallCouponApplication.class, args);
    }
}

⑥ Start project, access http://localhost:8848/nacos/

⑦ Put the gulimall member into the nacos registry as well

2.2 OpenFeign remote call

Requirements: the gulimall member member service calls the gulimall coupon member service remotely

① Import remote call dependency in gulimall member

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

② Write the interface of remote call in gulimall member

/**
 * Interface for remote invocation of gulimall coupon service
 */
@FeignClient("gulimall-coupon")
public interface CouponFeignService {
    
}

③ The remote call function is enabled in the main configuration class of gulimall member, and the methods annotated with @ FeignClient will be automatically scanned when the service is started

//Enable the remote call function, and the class with @ FeignClient annotation will be automatically scanned when the service is started
@EnableFeignClients("com.atguigu.gulimall.member.feign")
@EnableDiscoveryClient
@SpringBootApplication
public class GulimallMemberApplication {
    public static void main(String[] args) {
        SpringApplication.run(GulimallMemberApplication.class, args);
    }
}

④ Write the method to be called remotely in the GUI mall coupon

@RestController
@RequestMapping("coupon/coupon")
public class CouponController {
    @Autowired
    private CouponService couponService;

    @RequestMapping("/member/list")
    public R membercoupons(){
        CouponEntity couponEntity = new CouponEntity();
        couponEntity.setCouponName("10 minus 100");
        return R.ok().put("coupons",Arrays.asList(couponEntity));
    }
}

⑤ Specify the remote method to be called in the remote call interface of gulimall member

/**
 * Interface for remote invocation of gulimall coupon service
 */
@FeignClient("gulimall-coupon")
public interface CouponFeignService {
    /**
     *  Call the method with the mapping path of / coupon/coupon/member/list in the gulimall coupon service
     */
    @RequestMapping("/coupon/coupon/member/list")
    public R membercoupons();
}

⑥ Test the remote invocation of the gulimall coupon service in the gulimall member

@RestController
@RequestMapping("member/member")
public class MemberController {
    @Autowired
    private MemberService memberService;

    @Autowired
    CouponFeignService couponFeignService;

    @RequestMapping("/coupons")
    public R test(){
        MemberEntity memberEntity = new MemberEntity();
        memberEntity.setNickname("Zhang San");
        //Call the method in the couponFeignService interface
        R membercoupons = couponFeignService.membercoupons();
        return R.ok()
                .put("member",memberEntity)
                .put("coupons",membercoupons.get("coupons"));
    }
}

⑦ Start two projects, access http://localhost:8000/member/member/coupons

2.3 Nacos configuration center

https://github.com/alibaba/spring-cloud-alibaba/blob/master/spring-cloud-alibaba-examples/nacos-example/nacos-config-example/readme-zh.md

Take coupon service as an example:

① First, Nacos Config Starter is introduced into the gulimall common service

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

② Configure the service name and nacos configuration center address in the bootstrap.properties of gulimall coupon. Bootstrap.properties will take precedence over other configuration files

spring.application.name=gulimall-coupon
# Specify the server address of the configuration center. The nacos server is a configuration center
spring.cloud.nacos.config.server-addr=127.0.0.1:8848

③ Write a configuration file application.properties

coupon.username=hengheng
coupon.age=20

④ The test uses @ Value to get values directly from the configuration file

@RestController
@RequestMapping("coupon/coupon")
public class CouponController {
    @Autowired
    private CouponService couponService;

    //Use the @ Value annotation to take values from the configuration file
    @Value("${coupon.username}")
    private String username;

    @Value("${coupon.age}")
    private String age;

    @RequestMapping("/test")
    public R test(){
        return R.ok().put("name",username).put("age",age);
    }
}


Existing problems: if we need to modify the value in the configuration file after the project goes online, we need to modify it in the code and repackage it online. If the project is configured on 10 servers, all 10 servers should do so. If we put the configuration of the configuration file in the configuration center, we only need to modify the configuration item of the configuration center.

⑤ Put the configuration in application.properties into the nacos configuration center, where the dataID is gullimall-coupling.propertyis by default

⑥ Add the @ RefreshScope annotation on the CouponController class, which can dynamically obtain the corresponding configuration from Nacos Config

⑦ Change the age in the nacos configuration file to 25

⑧ Revisit: http://localhost:7000/coupon/coupon/test

2.4 Nacos configuration center - namespace and configuration grouping

In this project, a namespace is created for each micro service, so that only the configuration file of the namespace will be loaded when the service is started

① Create a namespace for gulimall coupon

② Configure the namespace in the bootstrap.properties file of the GUI mall coupon

spring.cloud.nacos.config.namespace=8c2dbbf4-986a-4485-a1b2-06bb3fa2472f

③ In this way, the configuration under the coupling namespace will be loaded when the service is started in the future. (under DEFAULT_GROUP loaded by default)

In this project, set the Data ID to gulimall-coupling.properties, and different group s to prod\dev\test \, so that different configuration files can be loaded according to different environments

④ Configure the group in the bootstrap.properties file of gulimall coupon

spring.cloud.nacos.config.group=dev

⑤ In this way, only the configuration file of group=dev will be loaded in the future

2.5 Nacos configuration center - load multiple configurations

Requirement: put the contents of the application.yml configuration file in the configuration file of the configuration center instead of the application.yml file

① Put the configuration related to the data source into the configuration of the coupling namespace

② Put the configuration related to mybatis into the configuration of the coupling namespace


③ Put other related configurations in the configuration of the coupling namespace
④ Configure the configuration file to be loaded in the bootstrap.properties of gulimall coupon

spring.application.name=gulimall-coupon
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.namespace=8c2dbbf4-986a-4485-a1b2-06bb3fa2472f
spring.cloud.nacos.config.group=dev

spring.cloud.nacos.config.ext-config[0].data-id=datasource.yml
spring.cloud.nacos.config.ext-config[0].group=dev
spring.cloud.nacos.config.ext-config[0].refresh=true

spring.cloud.nacos.config.ext-config[1].data-id=mybatis.yml
spring.cloud.nacos.config.ext-config[1].group=dev
spring.cloud.nacos.config.ext-config[1].refresh=true

spring.cloud.nacos.config.ext-config[2].data-id=other.yml
spring.cloud.nacos.config.ext-config[2].group=dev
spring.cloud.nacos.config.ext-config[2].refresh=true

2.6 Gateway

https://cloud.spring.io/spring-cloud-gateway/2.2.x/reference/html/

① Create the gulimall Gateway project and add the pom file

<dependency>
    <groupId>com.atguigu.gulimall</groupId>
    <artifactId>gulimall-common</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <!--Because this service does not need to be used MyBatis,If a dependency is imported but not configured, an error will be reported. The dependency will be excluded here-->
    <exclusions>
        <exclusion>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

② Configure the service name into nacos, and write the relevant configuration of nacos configuration center in bootstrap.propertyeis

spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.namespace=80127ed3-2158-4409-a62b-1bd2046c9f14

③ Write the nacos registry and other related configurations in application.yml

server:
  port: 88
spring:
  application:
    name: gulimall-gateway
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    gateway:
      routes:
        - id: test_route
          uri: https://www.baidu.com/
          predicates:
            - Query=url,baidu  # If you visit url=baidu, you will route to Baidu web page
        - id: qq_route
          uri: https://www.qq.com/
          predicates:
            - Query=url,qq     # If you visit url=qq, you will be routed to the qq web page

④ Start the project and access http://localhost:88/?url=baidu , you will enter www.baidu.com

//Enable service registration and discovery
@EnableDiscoveryClient
@SpringBootApplication
public class GulimallGatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GulimallGatewayApplication.class, args);
    }
}

3. Goods and services - tertiary classification

3.1 query - recursive tree structure data acquisition

Demand analysis: query all classified data and assemble them in a tree structure

① Add the property chrilden in the CategoryEntity class

package com.atguigu.gulimall.product.entity;

@Data
@TableName("pms_category")
public class CategoryEntity implements Serializable {
   /**
    * Sub classification data of classification data
    * The field does not exist in the database
    */
   @TableField(exist = false)
   private List<CategoryEntity> chrilden;
}

② Controller layer:

package com.atguigu.gulimall.product.controller;

@RestController
@RequestMapping("product/category")
public class CategoryController {
    @Autowired
    private CategoryService categoryService;
    
    @ApiOperation("Query all classification and sub classification data,And with json Tree structure return")
    @GetMapping("/list/tree")
    public R listWithTree(){
        List<CategoryEntity> categoryEntityList = categoryService.listWithTree();
        return R.ok().put("data", categoryEntityList);
    }
}

③ Service layer:

package com.atguigu.gulimall.product.service.impl;

@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {
    
    @Override
    public List<CategoryEntity> listWithTree() {
        //Query all classification data
        List<CategoryEntity> categoryEntityList = baseMapper.selectList(null);
        /**
         * 1,Filter the first level classification from all classification data, that is, parentId==0
         * 2,->On the left is the method parameter. If there is only one parameter, the parentheses can be omitted. - > on the right is the method body. If there is only one line of code, the braces can be omitted
         * 3,Find sub category of current category
         * 4,Sort the menus according to the natural sorting method of sort field. Compare the size of pairs, exchange the position of negative numbers, and do not exchange positive numbers
         */
        List<CategoryEntity> levelMenus = categoryEntityList.stream()
                .filter(categoryEntity -> categoryEntity.getParentCid() == 0)
                .map(menu -> {
                    menu.setChrilden(getChrildens(menu, categoryEntityList));
                    return menu;
                }).sorted((menu1, menu2)->{
                    return (menu1.getSort()==null?0:menu1.getSort()) - (menu2.getSort()==null?0:menu2.getSort());
                }).collect(Collectors.toList());
        return levelMenus;
    }

    private List<CategoryEntity> getChrildens(CategoryEntity root, List<CategoryEntity> categoryEntityList) {
        /**
         * 1,Find the sub category under the current category
         * 2,Find sub category of current category
         * 3,Sort classifications
         */
        List<CategoryEntity> chridlens = categoryEntityList.stream().filter(categoryEntity -> {
            return categoryEntity.getParentCid().equals(root.getCatId());
        }).map(menu -> {
            menu.setChrilden(getChrildens(menu,categoryEntityList));
            return menu;
        }).sorted((menu1,menu2)->{
            return (menu1.getSort()==null?0:menu1.getSort()) - (menu2.getSort()==null?0:menu2.getSort());
        }).collect(Collectors.toList());
        return chridlens;
    }
}

④ Test: http://localhost:10001/product/category/list/tree

3.2 front end - configure gateway routing and path rewriting

① In the front-end project Ren fast Vue, create the folder product under the Ren fast Vue \ SRC \ views \ modules folder, and create the file category.vue under the product folder


② Use the tree control in element UI to display the three-level classification

<template>
  <el-tree :data="data" :props="defaultProps" @node-click="handleNodeClick"></el-tree>
</template>

<script>
export default {
  components: {},
  props: {},
  data () {
    return {
      data: [],
      defaultProps: {
        children: 'children',
        label: 'label'
      }
    }
  },
  methods: {
    handleNodeClick (data) {
      console.log(data)
    },
    //Write a method to send a request to obtain the data of three-level classification
    getMenus () {
        this.$http({
          //Request path
          url: this.$http.adornUrl('/product/category/list/tree'),
          method: 'get',
        }).then(data=>{
          console.log("Successfully obtained data:",data)
        })
      },
  },
  created () {
    //Call method
    this.getMenus();
  },
}
</script>
<style scoped>
</style>

③ Refresh Renren rapid development platform, press f12, and find that the result of tree request is 404,

And the request path of the tree is http://localhost:8080/renren-fast/product/category/list/tree,

The real request path of the backend tree is: http://localhost:10001/product/category/list/tree ,


Modify the api interface request address in the static\config\index.js file to send a request to the gateway, and jump to the back-end services such as gulimall product, gulimall order, and gulimall coupon through the gateway

④ Refresh everyone's rapid development platform and find that the verification code is missing

At this time, the request sent by the access verification code is: http://localhost:88/api/captcha.jpg?uuid=?

The original verification code request is: http://localhost:8080/renren-fast/captcha.jpg?uuid=?

⑤ All requests from the front desk are via“ http://localhost:88/api ”To forward, configure the gateway routing, add routing rules in the applicaiton.yml file of the gulimall gateway service, and jump the request to the Ren fast service

# Rewrite request (1) to request (2) through the gateway path
(1)http://localhost:88/api/captcha.jpg?uuid=?
(2)http://localhost:8080/renren-fast/captcha.jpg?uuid=?
spring:
  cloud:
    gateway:
      routes:
        - id: admin_route
          uri: lb://Ren fast # load balancing to Ren fast service
          predicates:
            - Path=/api/**      # As long as the request is localhost:88/api / * * this path is routed to the Ren fast service
          filters:
            # This means to rewrite the path / api/xxx to / Ren fast / xxx
            - RewritePath=/api/(?<segment>/?.*), /renren-fast/$\{segment}
            # http://localhost:88/api/captcha.jpg?uuid=?
            # http://localhost:8080/renren-fast/captcha.jpg?uuid=?


⑥ 403 Forbidden error was found when accessing the login request:

3.3 gateway unified configuration cross domain

① Cross domain: it means that the browser cannot execute scripts of other websites. It is caused by the browser's homology policy and the security restriction imposed by the browser on javascript. Among them, the same origin strategy means that the protocol, domain name and port should be the same, and one difference will lead to cross domain;

② Cross domain solutions:

  • Access control allow origin: which sources of requests are supported across domains
  • Access control allow methods: which methods are supported across domains
  • Access control allow credentials: cross domain requests do not contain cookies by default. If set to true, cookies can be included
  • Access control expose headers: fields exposed by cross domain requests
  • During CORS request, the getResponseHeader() method of XMLHttpRequest object can only get six basic fields: cache control, content language, content type, Expires, last modified and Pragma. If you want to get other fields, you must specify them in access control expose headers.
  • Access control Max age: indicates the effective time of the response in seconds. Within the valid time, the browser does not need to initiate a pre check request again for the same request. Please note that the browser maintains a maximum effective time. If the value of the header field exceeds the maximum effective time, it will not take effect.

③ Define the "GulimallCorsConfiguration" class in the gateway, which is used for filtering and allows all requests to cross domains

@Configuration
public class GulimallCorsConfiguration {
    @Bean
    public CorsWebFilter corsWebFilter(){
        UrlBasedCorsConfigurationSource source=new UrlBasedCorsConfigurationSource();
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("*");
        corsConfiguration.addAllowedOrigin("*");
        corsConfiguration.setAllowCredentials(true);
        
        source.registerCorsConfiguration("/**",corsConfiguration);
        return new CorsWebFilter(source);
    }
}

3.4 the gateway forwards the request to gulimall product

① Restart the Ren fast project. After logging in successfully, enter the classification maintenance menu and no classification data is obtained. Open f12 and report Error 404, that is, the requested data http://localhost:88/api/product/category/list/tree non-existent

② Because the request path for the front end to access the three-level classification data is http://localhost:88/api/product/category/list/tree , but the request path of the back end is http://localhost:10001/product/category/list/tree Therefore, it is necessary to route to the gulimall product service through the gateway, so that the gateway can route all requests related to gulimall product to gulimall product

spring:
  cloud:
    gateway:
      routes:
        - id: product_route
          uri: lb://gulimall-product
          predicates:
            - Path=/api/product/** # As long as the request is / api/product / * *, it is routed to the gulimall product service
          filters:
            # This means rewriting the path / api/xxx to / xxx
            - RewritePath=/api/(?<segment>/?.*),/$\{segment}

http://localhost:88/api/product/category/list/tree Route to http://localhost:10001/product/category/list/tree

③ Register the gulimall product service in the registry, otherwise the gulimall gateway will not be able to discover the service

application.yml:

server:
  port: 10001
spring:
  application:
    name: gulimall-product
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848

bootstrap.properties:

spring.application.name=gulimall-product
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.namespace=80fa1f00-1d72-49e0-bd03-fdcc12cda9e8
@EnableDiscoveryClient
@SpringBootApplication
public class GulimallProductApplication {
    public static void main(String[] args) {
        SpringApplication.run(GulimallProductApplication.class, args);
    }
}

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


⑤ Display the queried classification data

<template>
  <el-tree :data="menus" :props="defaultProps" @node-click="handleNodeClick"></el-tree>
</template>

<script>
export default {
  //The components introduced by import need to be injected into the object before they can be used
  components: {},
  props: {},
  data () {
    return {
      //Data returned after calling getMenus() method
      menus: [],
      //The data to be displayed, in which the right side is the property of the data class returned after the getMenus() method
      defaultProps: {
        children: 'children',
        label: 'name'
      }
    }
  },
  methods: {
    handleNodeClick (data) {
      console.log(data)
    },
    getMenus () {
      this.$http({
        url: this.$http.adornUrl('/product/category/list/tree'),
        method: 'get',
      }).then(({ data }) => {
        console.log("Successfully obtained data:", data.data),
          this.menus = data.data
      })
    },
  },
  created () {
    this.getMenus()
  },
}

3.5 delete - logically delete the nodes of the classification tree

① The front end is configured with a three-level classification tree: Append is added behind the primary and secondary nodes. Delete is added after the node as long as there are no child nodes. After clicking delete, the front end sends a request to the back end to delete the node

② Configure the logical deletion function of mybatis plus

mybatis-plus:
  mapper-locations: classpath:/mapper/**/*.xml
  global-config:
    db-config:
      id-type: auto
      logic-delete-value: 1 # Logical deleted value (default is 1)
      logic-not-delete-value: 0 # Logical undeleted value (0 by default)

③ Add @ TableLogic annotation to the entity class field. If the above configuration is opposite to the database field, you can customize the logical deletion value

package com.atguigu.gulimall.product.entity;

@Data
@TableName("pms_category")
public class CategoryEntity implements Serializable {
   /**
    * Whether to display [0-do not display, 1 display]
    * Because application.yml is contrary to the logic of showStatus in our database,
    * Therefore, you can customize logical deletion and logical undelete. value represents logical undelete and delvalue represents logical deletion
    */
   @TableLogic(value = "1",delval = "0")
   private Integer showStatus;
}

④ Controller layer

package com.atguigu.gulimall.product.controller;

@RestController
@RequestMapping("product/category")
public class CategoryController {
    @Autowired
    private CategoryService categoryService;
    /**
     * Logical deletion
     * @RequestBody: Note the front end sends a post request to delete nodes in batch
     */
    @ApiOperation("Logically delete nodes in the classification tree")
    @RequestMapping("/delete")
    public R delete(@RequestBody Long[] catIds){
        categoryService.removeMenuByIds(Arrays.asList(catIds));
        return R.ok();
    }
}

⑤ Service layer

package com.atguigu.gulimall.product.service.impl;

@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {
    
    @Override
    public void removeMenuByIds(List<Long> asList) {
        //Logical deletion
        baseMapper.deleteBatchIds(asList);
    }
}

⑥ Test: http://localhost:api/product/category/delete


⑦ Click Delete at the front end to send a request to Delete the node

//Delete node
remove(node, data) {
    var ids = [data.catId];
    //Before deleting, give a prompt to confirm whether to delete the node
    this.$confirm(`Delete[ ${data.name}]menu?`, "Tips", {
        confirmButtonText: "determine",
        cancelButtonText: "cancel",
        type: "warning"
    }).then(() => {
        this.$http({
            url: this.$http.adornUrl("/product/category/delete"),
            method: "post",
            data: this.$http.adornData(ids, false)
        }).then(({ data }) => {
            //After the deletion is successful, a prompt message will be given
            this.$message({
                message: "Menu deleted successfully",
                type: "success"
            });
            //Refresh new menu
            this.getMenus();
            //Set the menu that needs to be expanded by default
            this.expandedKey = [node.parent.data.catId];
        });
    }).catch(() => {});
    console.log("remove", node, data);
}

3.6 add - classification tree node

① vue adds the El dialog component to the dialog box

<template>
  <div>
      <el-tree
      :data="menus"
      :props="defaultProps"
      @node-click="handleNodeClick"
      :expand-on-click-node="false"
      show-checkbox
      node-key="catId"
    >
      <span class="custom-tree-node" slot-scope="{ node, data }">
        <span>{{ node.label }}</span>
        <span>
          <el-button v-if="node.level<=2" type="text" size="mini" @click="() => append(data)">Append</el-button>
          <el-button
            v-if="node.childNodes.length==0"
            type="text"
            size="mini"
            @click="() => remove(node, data)"
          >Delete</el-button>
        </span>
      </span>
    </el-tree>
      
    <el-dialog
      :title="Tips"
      :visible.sync="dialogVisible"
      width="30%"
    >
      <el-form :model="category">
        <el-form-item label="Classification name">
          <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">Cancel</el-button>
        <el-button type="primary" @click="addCategory">determine</el-button>
      </span>
    </el-dialog>
  </div>
</template>

② Click OK to send a request to the backend new classification node

export default {
  //The components introduced by import need to be injected into the object before they can be used
  components: {},
  props: {},
  data () {
    return {
      //The default value of submitted data initialized when adding a node
      category: {
        name: "",
        parentCid: 0,
        catLevel: 0,
        showStatus: 1,
        sort: 0,
      },
      dialogVisible: false,
      //Data returned after calling getMenus() method
      menus: [],
      defaultProps: {
        children: 'children',
        label: 'name'
      }
    }
  },
  methods: {
    //Obtain menu data and display three-level classification
    getMenus () {
      this.$http({
        url: this.$http.adornUrl('/product/category/list/tree'),
        method: 'get',
      }).then(({ data }) => {
        console.log("Successfully obtained data:", data.data),
          this.menus = data.data
      })
    },
      
    //Add a node (data of the node where you click append)
    append (data) {
      console.log(data)
      this.dialogVisible = true;
      //The parentCid of the new node is the cid of the currently clicked node
      this.category.parentCid = data.catId;
      //The level of the new node is the level of the currently clicked node + 1
      this.category.catLevel = data.catLevel*1+1;
    },
      
    //The front end sends a request to add three-level classification data
    addCategory() {
      console.log("Three level classification data submitted", this.category);
      this.$http({
        url: this.$http.adornUrl("/product/category/save"),
        method: "post",
        data: this.$http.adornData(this.category, false)
      }).then(({ data }) => {
        this.$message({
          message: "Menu saved successfully",
          type: "success"
        });
        //close dialog boxes
        this.dialogVisible = false;
        //Refresh new menu
        this.getMenus();
        //Set the menu that needs to be expanded by default
        this.expandedKey = [this.category.parentCid];
      });
    },
  created () {
    this.getMenus()
  },
}

3.7 modify - classification tree node

First, add the edit icon, click Edit and call edit(data) method.

<template>
  <div>
    <el-tree
      :data="menus"
      :props="defaultProps"
      @node-click="handleNodeClick"
      :expand-on-click-node="false"
      show-checkbox
      node-key="catId"
    >
      <span class="custom-tree-node" slot-scope="{ node, data }">
        <span>{{ node.label }}</span>
        <span>
          <el-button type="text" size="mini" @click="edit(data)">edit</el-button>
      </span>
    </el-tree>

    <el-dialog
      :title="title"
      :visible.sync="dialogVisible"
      width="30%"
      :close-on-click-modal="false"
    >
      <el-form :model="category">
        <el-form-item label="Classification name">
          <el-input v-model="category.name" autocomplete="off"></el-input>
        </el-form-item>
        <el-form-item label="Icon">
          <el-input v-model="category.icon" autocomplete="off"></el-input>
        </el-form-item>
        <el-form-item label="Unit of measure">
          <el-input v-model="category.productUnit" autocomplete="off"></el-input>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button @click="dialogVisible = false">Cancel</el-button>
        <el-button type="primary" @click="submitData">determine</el-button>
      </span>
    </el-dialog>
  </div>
</template>

② In the edit(data) method, send a request / product/category/info/${data.catId} to get the data to be echoed, and addCategory() saves the modified data

<script>
export default {
  components: {},
  props: {},
  data () {
    return {
      //The default value of submitted data initialized when adding a node
      title: "",
      dialogType: "", //edit,add
      category: {
        name: "",
        parentCid: 0,
        catLevel: 0,
        showStatus: 1,
        sort: 0,
        productUnit: "",
        icon: "",
        catId: null
      },
      dialogVisible: false,
      //Data returned after calling getMenus() method
      menus: [],
      defaultProps: {
        children: 'children',
        label: 'name'
      }
    }
  },
  methods: {
    // Data of new node
    append (data) {
      console.log("append", data)
      this.dialogType = "add"
      this.title = "Add category"
      this.dialogVisible = true
      this.category.parentCid = data.catId
      this.category.catLevel = data.catLevel * 1 + 1
      this.category.catId = null
      this.category.name = ""
      this.category.icon = ""
      this.category.productUnit = ""
      this.category.sort = 0
      this.category.showStatus = 1
    },
        
    // Data of the node to be modified
    edit (data) {
      console.log("Data to modify", data)
      this.dialogType = "edit"
      this.title = "Modify classification"
      this.dialogVisible = true
      //Send a request to obtain the latest data of the current node
      this.$http({
        //Modify the catId of node data 
        url: this.$http.adornUrl(`/product/category/info/${data.catId}`),
        method: "get"
      }).then(({ data }) => {
        //Request succeeded
        console.log("Data to echo", data)
        this.category.name = data.data.name
        this.category.catId = data.data.catId
        this.category.icon = data.data.icon
        this.category.productUnit = data.data.productUnit
        this.category.parentCid = data.data.parentCid
        this.category.catLevel = data.data.catLevel
        this.category.sort = data.data.sort
        this.category.showStatus = data.data.showStatus
      })
    },
    
    //If you call the method of adding a node for add, and if you call the method of modifying a node for edit
    submitData () {
      if (this.dialogType == "add") {
        this.addCategory()
      }
      if (this.dialogType == "edit") {
        this.editCategory()
      }
    },
        
    //Add tertiary classification
    addCategory () {
      console.log("Three level classification data submitted", this.category)
      this.$http({
        url: this.$http.adornUrl("/product/category/save"),
        method: "post",
        data: this.$http.adornData(this.category, false)
      }).then(({ data }) => {
        this.$message({
          message: "Menu saved successfully",
          type: "success"
        })
        //close dialog boxes
        this.dialogVisible = false
        //Refresh new menu
        this.getMenus()
        //Set the menu that needs to be expanded by default
        this.expandedKey = [this.category.parentCid]
      })
    },
        
    //Modify Level 3 classification data
    editCategory () {
      //Data to send to the backend
      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({
          message: "Menu modified successfully",
          type: "success"
        })
        //close dialog boxes
        this.dialogVisible = false
        //Refresh new menu
        this.getMenus()
        //Set the menu that needs to be expanded by default
        this.expandedKey = [this.category.parentCid]
      })
    },
  created () {
    this.getMenus()
  },
}
</script>
<script>
export default {
  components: {},
  props: {},
  data () {
    return {
      //The default value of submitted data initialized when adding a node
      title: "",
      dialogType: "", //edit,add
      category: {
        name: "",
        parentCid: 0,
        catLevel: 0,
        showStatus: 1,
        sort: 0,
        productUnit: "",
        icon: "",
        catId: null
      },
      dialogVisible: false,
      //Data returned after calling getMenus() method
      menus: [],
      defaultProps: {
        children: 'children',
        label: 'name'
      }
    }
  },
  methods: {
    // Data of new node
    append (data) {
      console.log("append", data)
      this.dialogType = "add"
      this.title = "Add category"
      this.dialogVisible = true
      this.category.parentCid = data.catId
      this.category.catLevel = data.catLevel * 1 + 1
      this.category.catId = null
      this.category.name = ""
      this.category.icon = ""
      this.category.productUnit = ""
      this.category.sort = 0
      this.category.showStatus = 1
    },
        
    // Data of the node to be modified
    edit (data) {
      console.log("Data to modify", data)
      this.dialogType = "edit"
      this.title = "Modify classification"
      this.dialogVisible = true
      //Send a request to obtain the latest data of the current node
      this.$http({
        //Modify the catId of node data 
        url: this.$http.adornUrl(`/product/category/info/${data.catId}`),
        method: "get"
      }).then(({ data }) => {
        //Request succeeded
        console.log("Data to echo", data)
        this.category.name = data.data.name
        this.category.catId = data.data.catId
        this.category.icon = data.data.icon
        this.category.productUnit = data.data.productUnit
        this.category.parentCid = data.data.parentCid
        this.category.catLevel = data.data.catLevel
        this.category.sort = data.data.sort
        this.category.showStatus = data.data.showStatus
      })
    },
    
    //If you call the method of adding a node for add, and if you call the method of modifying a node for edit
    submitData () {
      if (this.dialogType == "add") {
        this.addCategory()
      }
      if (this.dialogType == "edit") {
        this.editCategory()
      }
    },
        
    //Add tertiary classification
    addCategory () {
      console.log("Three level classification data submitted", this.category)
      this.$http({
        url: this.$http.adornUrl("/product/category/save"),
        method: "post",
        data: this.$http.adornData(this.category, false)
      }).then(({ data }) => {
        this.$message({
          message: "Menu saved successfully",
          type: "success"
        })
        //close dialog boxes
        this.dialogVisible = false
        //Refresh new menu
        this.getMenus()
        //Set the menu that needs to be expanded by default
        this.expandedKey = [this.category.parentCid]
      })
    },
        
    //Modify Level 3 classification data
    editCategory () {
      //Data to send to the backend
      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({
          message: "Menu modified successfully",
          type: "success"
        })
        //close dialog boxes
        this.dialogVisible = false
        //Refresh new menu
        this.getMenus()
        //Set the menu that needs to be expanded by default
        this.expandedKey = [this.category.parentCid]
      })
    },
  created () {
    this.getMenus()
  },
}
</script>

③ Back end method for obtaining classification node information:

@RestController
@RequestMapping("product/category")
public class CategoryController {
    @Autowired
    private CategoryService categoryService;

    /**
     * information
     */
    @RequestMapping("/info/{catId}")
    public R info(@PathVariable("catId") Long catId){
      CategoryEntity category = categoryService.getById(catId);
        return R.ok().put("data", category);
    }

    /**
     * modify
     */
    @RequestMapping("/update")
    public R update(@RequestBody CategoryEntity category){
      categoryService.updateById(category);
        return R.ok();
    }
}

3.8 batch save - batch modify node sequence

① The front end sends a batch modification request

export default {
  //The components introduced by import need to be injected into the object before they can be used
  components: {},
  props: {},
  data() {
    return {
      updateNodes: [],
      }
    };
  },
  methods: {
    batchSave() {
      this.$http({
        url: this.$http.adornUrl("/product/category/update/sort"),
        method: "post",
        data: this.$http.adornData(this.updateNodes, false)
      }).then(({ data }) => {
        this.$message({
          message: "The menu sequence is modified successfully",
          type: "success"
        });
        //Refresh new menu
        this.getMenus();
        //Set the menu that needs to be expanded by default
        this.expandedKey = this.pCid;
        this.updateNodes = [];
        this.maxLevel = 0;
      });
    },
};
</script>
<style scoped>
</style>

② The back end receives json data from the front end

@RestController
@RequestMapping("product/category")
public class CategoryController {
    @Autowired
    private CategoryService categoryService;
    
    @RequestMapping("/update/sort")
    public R updateSort(@RequestBody CategoryEntity[] categoryEntities){
        categoryService.updateBatchById(Arrays.asList(categoryEntities));
        return R.ok();
    }
}

4. Goods and services - commodity management

If you want to start the whole project, at least start the gulimall product, gulimall gateway and Ren fast projects and start the nacos service.

4.1 status switch function

Requirement Description: add a switch button to display the status. When the switch is turned on or off, the value of the show_status field in the database will be updated

① Add a switch button to display the status. When you click the switch, @ change = "updatebrandstatus" (scope. Row) will be triggered

<el-table-column prop="showStatus" header-align="center" align="center" label="Display status">
    <template slot-scope="scope">
        <el-switch
                   v-model="scope.row.showStatus"
                   active-color="#13ce66"
                   inactive-color="#ff4949"
                   :active-value="1"
                   :inactive-value="0"
                   @change="updateBrandStatus(scope.row)">
        </el-switch>
    </template>
</el-table-column>

② Send a request to the backend to change the value of show_status in the database

updateBrandStatus (data) {
    console.log("Latest information", data)
    let { brandId, showStatus } = data
    //Send request modification status
    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: "Status update succeeded"
        })
    })
},

③ Back end method

@RestController
@RequestMapping("product/brand")
public class BrandController {
    
    @Autowired
    private BrandService brandService;
    /**
     * modify
     */
    @RequestMapping("/update")
    public R update(@RequestBody BrandEntity brand){
        brandService.updateById(brand);
        return R.ok();
    }
}

4.2 Spring integrates alicloud object storage OSS to upload files

Demand analysis: click Add to display the new brand. We want to upload the brand logo. Unlike the traditional single application, here we choose to upload the data to the distributed file server.

① Enter https://www.aliyun.com/ Select product > storage > > object store OSS > > Open > > Alipay login - >bucket list.

② Click file upload to upload several pictures:

③ Click the picture details and copy the url to access the picture in the browser:

④ The above method is to upload pictures manually. In fact, we can set automatic uploading of pictures to alicloud object storage in the program.

⑤ SpringCloudAlibaba integrates alicloud object storage OSS. First, import dependencies in the pom file of the gulimall common service:

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

⑥ Configure the access key, secret key, and endpoint in the application.yml file of the gulimall product service, and obtain their values from alicloud's object storage OSS (click user Avatar - > Accesskey management - > start using sub-user Accesskey, object storage - > gulimall Hengheng - > overview to get the endpoint)

spring:
  application:
    name: renren-fast
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    alicloud:
      access-key: LTAI5tFCEYUMBn4eWQKoC68V
      secret-key: hhUDcncbXfFDg0fuMKW6S6CoBJ8wZ0
      oss:
        endpoint: oss-cn-hangzhou.aliyuncs.com

⑦ Test upload file:

@RunWith(SpringRunner.class)
@SpringBootTest
public class GulimallProductApplicationTests {
    @Autowired
    private OSSClient ossClient;

    @Test
    public void test() throws FileNotFoundException {
        // Upload file stream.
        InputStream inputStream 
            = new FileInputStream("C:\\Users\\18751\\Desktop\\2.jpg");
        ossClient.putObject("gulimall-hengheng", "2.jpg", inputStream);
        // Close the OSSClient.
        ossClient.shutdown();
        System.out.println("Upload succeeded");
    }
}

4.3 spring cloud integrates Alibaba cloud object storage OSS

① Create a gulimall third-party service to integrate third-party services. Modify the pom file and put the OSS third-party SDK into the gulimall third-party service instead of the gulimall common service:

<dependencies>
    <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>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alicloud-oss</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <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>
    </dependencies>
</dependencyManagement>

② Put the configuration file of the gulimall third party service into the nacos configuration center, and the configuration file under the namespace will be loaded as soon as the service is started:

Create a namespace third party for gulimall third party in nacos, and configure the namespace and the loaded configuration file oss.yml in the bootstrap.properties file

oss.yml configuration details:

spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.application.name=gulimall-third-party
spring.cloud.nacos.config.namespace=cc589d72-014d-4db3-8b5a-b7b7238c25bb

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

③ Register the gulimall third party service in the nacos registry in application.yml:

spring:
  application:
    name: gulimall-third-party
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    alicloud:
      access-key: LTAI5tFCEYUMBn4eWQKoC68V
      secret-key: hhUDcncbXfFDg0fuMKW6S6CoBJ8wZ0
      oss:
        endpoint: oss-cn-hangzhou.aliyuncs.com
        bucket: gulimall-hengheng
server:
  port: 30000

④ Add the annotation @ EnableDiscoveryClient on the main startup class of the service to start the registration and discovery of the service:

@EnableDiscoveryClient
@SpringBootApplication
public class GulimallThirdPartyApplication {

    public static void main(String[] args) {
        SpringApplication.run(GulimallThirdPartyApplication.class, args);
    }
}

⑤ When the JavaScript client is used for direct signature, the AccessKey ID and AcessKey Secret will be exposed on the front-end page, so there are serious security risks. Therefore, OSS provides a scheme of direct transmission after server signature

@RestController
public class OssController {
    @Autowired
    private OSS ossClient;
    
    //Get value from configuration file
    @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;

    @RequestMapping("/oss/policy")
    public R policy() {
        String host = "https://" + bucket + "." + endpoint;
        String format = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
        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));
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        return R.ok().put("data",respMap);
    }
}

⑥ Visit http://localhost:30000/oss/policy Get signature:

⑦ Configure the gateway route in the GUI mall gateway

- id: third_party_route
  uri: lb://gulimall-third-party
  predicates:
    - Path=/api/thirdparty/**
  filters:
    - RewritePath=/api/thirdparty/(?<segment>.*),/$\{segment}

⑧ Front end: drop the upload files provided by the project into the Ren fast Vue \ SRC \ components directory, change the file upload addresses of the two files to the Bucket domain name provided by Alibaba cloud: http://gulimall-hengheng.oss-cn-hangzhou.aliyuncs.com

4.4 front end form verification

① Add a new product. After uploading the product logo image, the image link is displayed instead of the image:

② Dynamically bind the picture address in brand.vue:

<el-table-column prop="logo" header-align="center" align="center" label="brand logo address">
    <template slot-scope="scope">
        <img :src="scope.row.logo" style="width: 100px; height: 80px" />
    </template>
</el-table-column>

③ Front end form verification:

Add form verification in brand-add-or-update.vue:

<el-form-item label="sort" prop="sort">
    <!--take sort Field is bound to a number-->
    <el-input v-model.number="dataForm.sort" placeholder="sort"></el-input>
</el-form-item>
dataRule: {
        name: [{ required: true, message: "Brand name cannot be empty", trigger: "blur" }],
        logo: [{ required: true, message: "brand logo Address cannot be empty", trigger: "blur" }],
        descript: [{ required: true, message: "Introduction cannot be empty", trigger: "blur" }],
        showStatus: [
          {
            required: true,
            message: "Display status[0-Not displayed; one-display]Cannot be empty",
            trigger: "blur",
          },
        ],
        firstLetter: [
          {
            validator: (rule, value, callback) => {
              if (value == "") {
                callback(new Error("Initials are required"))
              } else if (!/^[a-zA-Z]$/.test(value)) {
                callback(new Error("Initial must be a-z perhaps A-Z between"))
              } else {
                callback()
              }
            },
            trigger: "blur"
          }
        ],
        sort: [
          {
            validator: (rule, value, callback) => {
              if (value == "") {
                callback(new Error("Sorting field must be filled in"))
              } else if (!Number.isInteger(value) || value < 0) {
                callback(new Error("Sort must be an integer greater than or equal to 0"))
              } else {
                callback()
              }
            },
            trigger: "blur"
          }
 },

4.5 JSR303 backend parameter verification

Note: when the usage rules of these annotations are uncertain, just check the source code

① Add verification annotations to the Bean and add your own message prompt:

@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
   private static final long serialVersionUID = 1L;

   @TableId
   private Long brandId;

   //Cannot submit an empty string
   @NotBlank(message = "Brand name must be submitted")
   private String name;

   //The annotation cannot be null and contains at least one non empty character.
   @NotBlank
   @URL(message = "logo Must be a legal url address")
   private String logo;

   private String descript;
   private Integer showStatus;
  
   //This parameter must be submitted
   @NotEmpty
   @Pattern(regexp="^[a-zA-Z]$",message = "The search initial must be a letter")
   private String firstLetter;

   @NotNull
   @Min(value = 0,message = "Sort must be greater than or equal to 0")
   private Integer sort;
}

② After the verification annotation is marked on the entity class, the verification function @ Valid must be enabled. BingResult is used to receive the results of verification errors:

@RestController
@RequestMapping("product/brand")
public class BrandController {
    
    @Autowired
    private BrandService brandService;

    /**
     * preservation
     * After you annotate the entity class, use @ Valid
     * @param brand brand
     * @param result Receive abnormal results of parameter verification
     */
    @RequestMapping("/save")
    public R save(@Valid @RequestBody BrandEntity brand, BindingResult result){
        Map<String,String> map = new HashMap<>();
        if(result.hasErrors()){
            result.getFieldErrors().forEach((item)->{
                //attribute
                String field = item.getField();
                //Verify the message of the annotation
                String message = item.getDefaultMessage();
                map.put(field,message);
            });
            return R.ok().put("data",map);
        }else{
            brandService.save(brand);
        }
        return R.ok();
    }
}

③ Test with postman: http://localhost:88/api/product/brand/save

4.6 unified exception handling

We use the BindingResult result variable to receive exceptions in parameter verification, but this is too complicated. Because there are many entity classes for parameter verification, we need to add parameter verification to the corresponding methods of each Controller layer and receive exception response results. Therefore, we only need to do unified exception handling, That is, throw out all exceptions in the Controller layer, and then handle the exceptions in the Controller layer uniformly. If there is a parameter verification exception, it will be thrown directly.

① The exception result will not be processed any more. When an exception occurs, it will be thrown directly. Finally, the exception processing will be unified:

@RestController
@RequestMapping("product/brand")
public class BrandController {
    
    @Autowired
    private BrandService brandService;
    
    /**
     * preservation
     * After you annotate the entity class, use @ Valid
     */
    @RequestMapping("/save")
    public R save(@Valid @RequestBody BrandEntity brand/*, BindingResult result*/){
        brandService.save(brand);
        return R.ok();
    }
}

② Exception handling class:

// This annotation is equivalent to @ RequestBody and @ ControllerAdvice
@RestControllerAdvice
@Slf4j
public class GulimallExceptionControllerAdvice {

    /**
     * If a MethodArgumentNotValidException occurs, it will be caught
     */
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public R handleVaildException(MethodArgumentNotValidException e){
        //If an exception occurs, print the log
        log.error("There is a problem with data verification{},Exception type{}",e.getMessage(),e.getClass());

        BindingResult bindingResult = e.getBindingResult();
        Map<String,String> errorMap = new HashMap<>();
        bindingResult.getFieldErrors().forEach((fieldError -> {
            errorMap.put(fieldError.getField(),fieldError.getDefaultMessage());
        }));
        return R.error(BizCodeEnum.VAILD_EXCEPTION.getCode(),BizCodeEnum.VAILD_EXCEPTION.getMessage()).put("data",errorMap);
    }

    @ExceptionHandler(value = Throwable.class)
    public R handleException(Throwable throwable){
        //If an exception occurs, print the log
        log.error("Error occurred:{}",throwable.getMessage());
        return R.error(BizCodeEnum.UNKNOW_EXCEPTION.getCode(),BizCodeEnum.UNKNOW_EXCEPTION.getMessage());
    }
}

③ Enumeration class defined for the exception status code and exception information returned to the front end:

public enum BizCodeEnum {
    UNKNOW_EXCEPTION(10000,"System unknown exception"),
    VAILD_EXCEPTION(10001,"Parameter format verification failed");

    private int code;
    private String message;

    private BizCodeEnum(int code,String message){
        this.code = code;
        this.message = message;
    }

    public int getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }
}

4.7 group verification of jsr303 backend parameters

When the annotation verification rules of some fields of a new brand and a modified brand are different, they can be verified in groups. The verification annotations will only take effect under the specified group. Moreover, if the group verification annotation function is enabled, the verification annotations without a specified group will not take effect.

① Define two groups, one for identifying new goods and the other for identifying modified goods:

package com.atguigu.common.valid;

public interface AddGroup {
}
package com.atguigu.common.valid;

public interface UpdateGroup {
}

② Annotations marked with UpdataGroup.class will take effect when modifying, and annotations marked with AddGroup.class will take effect when adding:

@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
   private static final long serialVersionUID = 1L;
   
   @NotNull(message = "Brand must be specified for modification id",groups = {UpdateGroup.class})
   @Null(message = "New cannot be specified id",groups = {AddGroup.class})
   @TableId
   private Long brandId;

   @NotBlank(message = "Brand name must be submitted",groups = {UpdateGroup.class,AddGroup.class})
   private String name;


   @NotBlank(groups = {AddGroup.class})
   @URL(message = "logo Must be a legal url address",groups = {AddGroup.class,UpdateGroup.class})
   private String logo;

   private String descript;
   private Integer showStatus;

   @NotEmpty(groups = {AddGroup.class})
   @Pattern(regexp="^[a-zA-Z]$",message = "The search initial must be a letter",groups = {AddGroup.class,UpdateGroup.class})
   private String firstLetter;

   @NotNull(groups = {AddGroup.class})
   @Min(value = 0,message = "Sort must be greater than or equal to 0",groups = {AddGroup.class,UpdateGroup.class})
   private Integer sort;
}

③ Enable the group verification annotation function, and @ Validated indicates the group class to which the verification field belongs:

@RestController
@RequestMapping("product/brand")
public class BrandController {
    
    @Autowired
    private BrandService brandService;

    @RequestMapping("/save")
    public R save(@Validated(value = AddGroup.class) @RequestBody BrandEntity brand){
        brandService.save(brand);
        return R.ok();
    }

   @RequestMapping("/update")
   public R update(@Validated(value = UpdateGroup.class) @RequestBody BrandEntity brand){
        brandService.updateById(brand);
        return R.ok();
    }
}

4.8 JSR303 user defined parameter verification

Custom parameter verification is written directly following the source code of other verification annotations, such as @ NotNull annotation

① Verification requirements: the parameter values of showStatus can only be 0 and 1

@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
    
   @ListValue(vals={0,1},message = "The specified value must be provided")
   private Integer showStatus;
}

② Customize an annotation following the @ NotNull annotation:

@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
//Add custom validator
@Constraint(validatedBy = {ListValueConstraintValidator.class})
public @interface ListValue {

    String message() default "{com.atguigu.common.valid.ListValue.message}";

    Class<?>[] groups() default { };

    Class<? extends Payload>[] payload() default { };

    int[] vals() default { };
}

③ Customize a verifier:

public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {

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

    @Override
    public void initialize(ListValue constraintAnnotation) {
        //Properties of annotations
        int[] vals = constraintAnnotation.vals();
        for (int val : vals) {
            set.add(val);
        }
    }

    /**
     * Judge whether the verification is successful
     * @param value Value to verify
     * @param context
     * @return
     */
    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext context) {
        return set.contains(value);
    }
}

④ Because group verification is defined, it is necessary to specify the effective group of annotation:

@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {

   @ListValue(vals={0,1},message = "The specified value must be provided",groups = {AddGroup.class})
   private Integer showStatus;
}


5. Goods and services - attribute grouping

5.1 get classification attribute group

There are multiple categories under each brand (for example, Huawei has mobile phones, TVs, notebooks, etc.), and there are many attribute groups under each category (for example, mobile phones have subjects, basic information, main chips, etc.)

Current effect of front-end attribute grouping:

Requirement: query the attribute grouping under the third level classification according to the categoryId of the classification

① Controller layer:

@RestController
@RequestMapping("product/attrgroup")
public class AttrGroupController {

    @Autowired
    private AttrGroupService attrGroupService;

    /**
     * Query attribute grouping under three-level classification according to categoryId
     */
    @RequestMapping("/list/{catelogId}")
    public R list(@RequestParam Map<String, Object> params,
                  @PathVariable("catelogId") Long catelogId){

        PageUtils page = attrGroupService.queryPage(params,catelogId);
        return R.ok().put("page", page);
    }
}

② Service layer:

@Service("attrGroupService")
public class AttrGroupServiceImpl extends ServiceImpl<AttrGroupDao, AttrGroupEntity> implements AttrGroupService {
    
    @Override
    public PageUtils queryPage(Map<String, Object> params, Long catelogId) {
        //select * from pms_attr_group where catelog_id=? and (attr_group_id=key or attr_group_name like %key%)
        String key = (String) params.get("key");
        QueryWrapper<AttrGroupEntity> wrapper = new QueryWrapper<AttrGroupEntity>();
        if(!StringUtils.isEmpty(key)){
            wrapper.and((obj)->{
                obj.eq("attr_group_id",key).or().like("attr_group_name",key);
            });
        }

        if( catelogId == 0){
            IPage<AttrGroupEntity> page 
                    = this.page(new Query<AttrGroupEntity>().getPage(params), wrapper);
            return new PageUtils(page);
        }else {
            wrapper.eq("catelog_id",catelogId);
            IPage<AttrGroupEntity> page 
                    = this.page(new Query<AttrGroupEntity>().getPage(params), wrapper);
            return new PageUtils(page);
        }
    }
}

③ Test: clicking the mobile phone will display the attribute group under the mobile phone. When catalogId=1, it will be further queried

5.2 add classification attribute grouping

After modifying the front-end code, it is found that there is no level 4 classification, but the optional box of level 4 classification will still be displayed:

① Modify the CategoryEntity class so that it will be returned to the front end only when Children have data:

@Data
@TableName("pms_category")
public class CategoryEntity implements Serializable {

   /**
    * Sub classification data of classification data. This field does not exist in the database
    */
   @TableField(exist = false)
   //Only if this field is not null will it be returned to the front end
   @JsonInclude(JsonInclude.Include.NON_DEFAULT)
   private List<CategoryEntity> children;
}

5.3 modifying classification attribute grouping

① It can be seen from the front-end code that the front-end does not pass the complete path of the three-level classification to the back-end, but only the catalogId of the last level to the back-end

dataFormSubmit () {
    this.$refs["dataForm"].validate(valid => {
        if (valid) {
            this.$http({
                url: this.$http.adornUrl(
                    `/product/attrgroup/${!this.dataForm.attrGroupId ? "save" : "update"
                    }`
                ),
                method: "post",
                data: this.$http.adornData({
                    attrGroupId: this.dataForm.attrGroupId || undefined,
                    attrGroupName: this.dataForm.attrGroupName,
                    sort: this.dataForm.sort,
                    descript: this.dataForm.descript,
                    icon: this.dataForm.icon,
                    //Submit the categotyId of the last level to the back end
                    catelogId: this.catelogPath[this.catelogPath.length - 1]
                })
            }).then(({ data }) => {
                if (data && data.code === 0) {
                    this.$message({
                        message: "Operation succeeded",
                        type: "success",
                        duration: 1500,
                        onClose: () => {
                            this.visible = false
                            this.$emit("refreshDataList")
                        }
                    })
                } else {
                    this.$message.error(data.msg)
                }
            })
        }
    })
}

② When modifying the classification attribute grouping, we want to echo the three-level classification information and add a catalogPath attribute in AttrGroupEntity:

@Data
@TableName("pms_attr_group")
public class AttrGroupEntity implements Serializable {
   private static final long serialVersionUID = 1L;

   /**
    * Packet id
    */
   @TableId
   private Long attrGroupId;
   /**
    * Group name
    */
   private String attrGroupName;
   /**
    * sort
    */
   private Integer sort;
   /**
    * describe
    */
   private String descript;
   /**
    * Group icon
    */
   private String icon;
   /**
    * Category id
    */
   private Long catelogId;

   /**
    * Classification path. This field does not exist in the database
    */
   @TableField(exist = false)
   private Long[] catelogPath;
}

③ Controller layer:

@RestController
@RequestMapping("product/attrgroup")
public class AttrGroupController {
    @Autowired
    private AttrGroupService attrGroupService;

    @Autowired
    private CategoryService categoryService;

    /**
     * information
     */
    @RequestMapping("/info/{attrGroupId}")
    public R info(@PathVariable("attrGroupId") Long attrGroupId){
      AttrGroupEntity attrGroup = attrGroupService.getById(attrGroupId);
      //Query the complete path of the classification according to the catalogId
        Long[] catalogPath = categoryService.findCatalogPath(attrGroup.getCatelogId());
        attrGroup.setCatelogPath(catalogPath);

        return R.ok().put("attrGroup", attrGroup);
    }
}

④ Service layer:

@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    /**
     * Obtain the full path of the three-level classification according to the catalogId of the last level: [2,25225]
     */
    @Override
    public Long[] findCatalogPath(Long catalogId) {
        List<Long> list = new ArrayList<>();
        List<Long> parentPath = findParentPath(catalogId,list);
        Collections.reverse(parentPath);
        return parentPath.toArray(new Long[parentPath.size()]);
    }

    /**
     * Recursively obtain the complete path of three-level classification: [2,25225]
     */
    private List<Long> findParentPath(Long catalogId, List<Long> paths) {
        paths.add(catalogId);
        CategoryEntity categoryEntity = getById(catalogId);
        if(categoryEntity.getParentCid()!=0){
            findParentPath(categoryEntity.getParentCid(),paths);
        }
        return paths;
    }
}

6. Goods and services - brand management

6.1 fuzzy query brand list

@Service("brandService")
public class BrandServiceImpl extends ServiceImpl<BrandDao, BrandEntity> implements BrandService {

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        QueryWrapper<BrandEntity> wrapper = new QueryWrapper<>();
        //If the parameter contains a key
        String key = (String)params.get("key");
        if(StringUtils.isNotEmpty(key)){
            wrapper.eq("brand_id",key).or().like("name",key);
        }
        IPage<BrandEntity> page = this.page(
                new Query<BrandEntity>().getPage(params),wrapper
        );
        return new PageUtils(page);
    }
}

The front-end code is no longer written, but E:\studyresource\gulimall_resources \ source code of distributed foundation article \ code \ front end \ modules copy the code under the front end folder to modules.

6.2 get the classification of brand association


One brand (Huawei) will be associated with multiple categories (mobile phones, TV), and one category (mobile phones) will be associated with multiple brands (Huawei, Xiaomi). This is a many to many relationship. Generally, there will be an intermediate table in the database:

@RestController
@RequestMapping("product/categorybrandrelation")
public class CategoryBrandRelationController {
    
    @Autowired
    private CategoryBrandRelationService categoryBrandRelationService;

    /**
     * list
     */
    @RequestMapping("/list")
    public R list(@RequestParam Map<String, Object> params){
        PageUtils page = categoryBrandRelationService.queryPage(params);

        return R.ok().put("page", page);
    }

    @RequestMapping("/catalog/list")
    public R list(@RequestParam("brandId") Long brandId){
        List<CategoryBrandRelationEntity> categoryEntities = categoryBrandRelationService.list(
                new QueryWrapper<CategoryBrandRelationEntity>().eq("brand_id", brandId));

        return R.ok().put("data", categoryEntities);
    }
}

6.3 classification of new brand associations

① Controller layer:

@RestController
@RequestMapping("product/categorybrandrelation")
public class CategoryBrandRelationController {
    
    @Autowired
    private CategoryBrandRelationService categoryBrandRelationService;

    /**
     * preservation
     */
    @RequestMapping("/save")
    public R save(@RequestBody CategoryBrandRelationEntity categoryBrandRelation){
      categoryBrandRelationService.saveDetail(categoryBrandRelation);
        return R.ok();
    }
}

② Service layer:

@Service("categoryBrandRelationService")
public class CategoryBrandRelationServiceImpl extends ServiceImpl<CategoryBrandRelationDao, CategoryBrandRelationEntity> implements CategoryBrandRelationService {

    @Autowired
    private CategoryService categoryService;

    @Autowired
    private BrandService brandService;

    @Override
    public void saveDetail(CategoryBrandRelationEntity categoryBrandRelation) {
        Long brandId = categoryBrandRelation.getBrandId();
        Long catelogId = categoryBrandRelation.getCatelogId();

        CategoryEntity categoryEntity = categoryService.getById(catelogId);
        BrandEntity brandEntity = brandService.getById(brandId);

        categoryBrandRelation.setCatelogName(categoryEntity.getName());
        categoryBrandRelation.setBrandName(brandEntity.getName());
        this.save(categoryBrandRelation);
    }
}

③ Test:

6.4 brand and classification cascade update

Requirement: when the brand name and category name are modified, the brand name and category name in the associated category should also be modified.

1. When updating the brand name in brand management, the brand name in brand association classification shall be updated at the same time

① Controller layer:

@RestController
@RequestMapping("product/brand")
public class BrandController {
    
    @Autowired
    private BrandService brandService;

    /**
     * modify
     */
   @RequestMapping("/update")
   public R update(@Validated(value = UpdateGroup.class) @RequestBody BrandEntity brand){
      brandService.updateDetail(brand);
        return R.ok();
    }
}

② Service layer:

@Service("brandService")
public class BrandServiceImpl extends ServiceImpl<BrandDao, BrandEntity> implements BrandService {

    @Autowired
    private CategoryBrandRelationService categoryBrandRelationService;

    @Transactional
    @Override
    public void updateDetail(BrandEntity brand) {
        // Update data in brand table
        this.updateById(brand);
        if(StringUtils.isNotEmpty(brand.getName())){
            //If the brand name is updated, cascade to update the brandName in the brand and category association table
            categoryBrandRelationService.updateBrand(brand.getBrandId(),brand.getName());
        }
    }
}
@Service("categoryBrandRelationService")
public class CategoryBrandRelationServiceImpl extends ServiceImpl<CategoryBrandRelationDao, CategoryBrandRelationEntity> implements CategoryBrandRelationService {

    @Autowired
    private CategoryService categoryService;

    @Autowired
    private BrandService brandService;

    @Override
    public void updateBrand(Long brandId, String name) {
        CategoryBrandRelationEntity categoryBrandRelationEntity = new CategoryBrandRelationEntity();
        categoryBrandRelationEntity.setBrandId(brandId);
        categoryBrandRelationEntity.setBrandName(name);
        //Updated conditions
        this.update(categoryBrandRelationEntity,
                new UpdateWrapper<CategoryBrandRelationEntity>().eq("brand_id",brandId));
    }
}

2. When updating the classification name of the three-level classification, the classification name in the brand association classification is updated at the same time

① Controller layer:

@RestController
@RequestMapping("product/category")
public class CategoryController {
    
    @Autowired
    private CategoryService categoryService;

    /**
     * modify
     */
    @RequestMapping("/update")
    public R update(@RequestBody CategoryEntity category){
        categoryService.updateDetail(category);
        return R.ok();
    }
}

② Service layer:

@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    @Autowired
    private CategoryBrandRelationService categoryBrandRelationService;

    @Transactional
    @Override
    public void updateDetail(CategoryEntity category) {
        //Update classification name in classification table
        this.updateById(category);
        //Update the category name in the brand classification table
        if(StringUtils.isNotEmpty(category.getName())){
            categoryBrandRelationService.updateCategory(category.getCatId(),category.getName());
        }
    }
}
@Service("categoryBrandRelationService")
public class CategoryBrandRelationServiceImpl extends ServiceImpl<CategoryBrandRelationDao, CategoryBrandRelationEntity> implements CategoryBrandRelationService {

    @Autowired
    private CategoryService categoryService;

    @Autowired
    private BrandService brandService;

    @Override
    public void updateCategory(Long catId, String name) {
        CategoryBrandRelationEntity categoryBrandRelationEntity = new CategoryBrandRelationEntity();
        categoryBrandRelationEntity.setCatelogId(catId);
        categoryBrandRelationEntity.setCatelogName(name);
        this.update(categoryBrandRelationEntity,
                new UpdateWrapper<CategoryBrandRelationEntity>().eq("catelog_id",catId));
    }
}

7. Goods and services - platform attributes

7.1 new specifications and parameters

Demand analysis:

When adding specification parameters, the data submitted on the page is as shown in the figure

After adding successfully, only PMS is found_ Data in attr table:

The corresponding attribute and attribute grouping association table PMS_ attr_ attrgroup_ No data in relation:

Therefore, the requirement is: when adding specification parameters, we should not only save the basic attributes to PMS_ In the attr table, the attributes corresponding to the attributes are also saved and grouped into pms_attr_ attrgroup_ In the relationship table.

① Because there is no attrGroupId attribute in attrenty class, a new AttrVo contains not only the basic attribute of attrenty, but also the attrGroupId passed from the front end

@Data
public class AttrVo {
    /**
     * Attribute id
     */
    @TableId
    private Long attrId;
    /**
     * Attribute name
     */
    private String attrName;
    /**
     * Need to retrieve [0-no, 1-required]
     */
    private Integer searchType;
    /**
     * Attribute Icon
     */
    private String icon;
    /**
     * List of optional values [separated by commas]
     */
    private String valueSelect;
    /**
     * Attribute type [0-sales attribute, 1-basic attribute, 2-both sales attribute and basic attribute]
     */
    private Integer attrType;
    /**
     * Enabled status [0 - disabled, 1 - enabled]
     */
    private Long enable;
    /**
     * Classification
     */
    private Long catelogId;
    /**
     * Quick display [display on introduction; 0-no, 1-yes], which can still be adjusted in sku
     */
    private Integer showDesc;

    /**
     * The added attribute is used to receive the attrGroupId transmitted from the front end and save the association relationship between attribute groups and attributes
     */
    private Long attrGroupId;
}

② Controller layer:

@RestController
@RequestMapping("product/attr")
public class AttrController {
    @Autowired
    private AttrService attrService;

    /**
     * preservation
     */
    @RequestMapping("/save")
    public R save(@RequestBody AttrEntity attr){
      attrService.saveAttr(attr);

        return R.ok();
    }
}

③ Service layer:

@Service("attrService")
public class AttrServiceImpl extends ServiceImpl<AttrDao, AttrEntity> implements AttrService {

    @Autowired
    AttrAttrgroupRelationService attrAttrgroupRelationService;
    
    @Transactional
    @Override
    public void saveAttr(AttrVo attrVo) {
        //Save basic information to PMS_ In attr
        AttrEntity attrEntity = new AttrEntity();
        BeanUtils.copyProperties(attrVo,attrEntity);
        this.save(attrEntity);
        //Save attributes and attribute grouping to the association table PMS_ attr_ attrgroup_ In relation
        AttrAttrgroupRelationEntity attrgroupRelationEntity = new AttrAttrgroupRelationEntity();
        attrgroupRelationEntity.setAttrId(attrEntity.getAttrId());
        attrgroupRelationEntity.setAttrGroupId(attrVo.getAttrGroupId());
        attrAttrgroupRelationService.save(attrgroupRelationEntity);
    }
}

7.2 obtain specification parameters under classification

Requirement analysis: Interface API address https://easydoc.net/s/78237135/ZUqEdvA4/Ld1Vfkcd

In addition to the basic information, the response result also needs to include the classification name and group name:

① Response data vo:

//In addition to the attributes of AttrVo, it also includes the attributes of
@Data
public class AttrRespVo extends AttrVo {

    /**
     * Classification name
     */
    private String catelogName;
    /**
     * The name of the attribute group must be consistent with the name of the response data in the interface api
     */
    private String groupName;
}

② Controller layer:

@RestController
@RequestMapping("product/attr")
public class AttrController {
    @Autowired
    private AttrService attrService;

    @GetMapping("/base/list/{catelogId}")
    public R baseAttrList(@RequestParam Map<String,Object> params,
                          @PathVariable("catelogId") Long catelogId){
        //The results returned by paging queries are PageUtils
        PageUtils page = attrService.queryBaseAttrPage(params,catelogId);
        return R.ok().put("page",page);
    }
}

② Service layer:

Note that try not to use the getById() method of the Service layer. There are some bug s later. Because this method can't query data, it is used instead

attrAttrgroupRelationService.getOne(
  new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id",attrEntity.getAttrId()));

But it's better not to use it like this, but to use the interface call of Dao layer.

@Service("attrService")
public class AttrServiceImpl extends ServiceImpl<AttrDao, AttrEntity> implements AttrService {

    @Autowired
    AttrAttrgroupRelationService attrAttrgroupRelationService;

    @Autowired
    CategoryService categoryService;

    @Autowired
    AttrGroupService attrGroupService;

   @Override
    public PageUtils queryBaseAttrPage(Map<String, Object> params, Long catelogId,String attrType) {
        QueryWrapper<AttrEntity> queryWrapper = new QueryWrapper<AttrEntity>();
        // 1. Query the specification parameters under a category. If catelogId==0, query all specification parameters
        if(catelogId!=0){
            queryWrapper.eq("catelog_id",catelogId);
        }
        // 2. Fuzzy query
        String key = (String) params.get("key");
        if(StringUtils.isNotEmpty(key)){
            queryWrapper.and((item->{
                item.eq("attr_id",key).or().like("attr_name",key);
            }));
        }
        IPage<AttrEntity> page = this.page(new Query<AttrEntity>().getPage(params),queryWrapper);
        PageUtils pageUtils = new PageUtils(page);
        // 3. The data of the response needs to include classification name and group name
        List<AttrEntity> records = page.getRecords();
        List<AttrRespVo> respVoList = records.stream().map(attrEntity -> {
            AttrRespVo attrRespVo = new AttrRespVo();
            BeanUtils.copyProperties(attrEntity, attrRespVo);
            //Classification name
            CategoryEntity categoryEntity 
                = categoryService.getById(attrEntity.getCatelogId());
            if(categoryEntity!=null){
                attrRespVo.setCatelogName(categoryEntity.getName());
            }
            //Group name
            //The grouping information can only be set if it is a basic attribute (specification parameter)
            AttrAttrgroupRelationEntity relationEntity = attrAttrgroupRelationService.getOne(
                new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id",attrEntity.getAttrId()));
            if(relationEntity!=null && relationEntity.getAttrGroupId()!=null){
                AttrGroupEntity attrGroupEntity 
                    = attrGroupService.getById(relationEntity.getAttrGroupId());
                attrRespVo.setGroupName(attrGroupEntity.getAttrGroupName());
            }
            return attrRespVo;
        }).collect(Collectors.toList());

        pageUtils.setList(respVoList);
        return pageUtils;
    }
}

7.3 modifying specifications and parameters

Demand analysis:

When modifying a specification parameter, query the specification parameter details and echo the complete path of the three-level classification. After modification, save the specification parameter data

1. When modifying a specification parameter, query the specification parameter details and echo the full path of the three-level classification

① Add the three-level classification path attribute to the response class AttrRespVo:

@Data
public class AttrRespVo extends AttrVo {
    /**
     * Classification name
     */
    private String catelogName;

    /**
     * Attribute group name
     */
    private String groupName;

    /**
     * The full path of the category to which it belongs
     */
    private Long[] catelogPath;
}

② Controller layer:

@RestController
@RequestMapping("product/attr")
public class AttrController {
    
    @Autowired
    private AttrService attrService;

    /**
     * information
     */
    @RequestMapping("/info/{attrId}")
    public R info(@PathVariable("attrId") Long attrId){
      AttrRespVo attrRespVo = attrService.getAttrInfo(attrId);
        return R.ok().put("attr", attrRespVo);
    }
}

② Service layer:

@Service("attrService")
public class AttrServiceImpl extends ServiceImpl<AttrDao, AttrEntity> implements AttrService {

    @Autowired
    AttrAttrgroupRelationService attrAttrgroupRelationService;

    @Autowired
    CategoryService categoryService;

    @Autowired
    AttrGroupService attrGroupService;

    @Override
    public AttrRespVo getAttrInfo(Long attrId) {
        AttrEntity attrEntity = getById(attrId);
        AttrRespVo attrRespVo = new AttrRespVo();
        BeanUtils.copyProperties(attrEntity,attrRespVo);

        //Set grouping information
        AttrAttrgroupRelationEntity relationEntity = attrAttrgroupRelationService
                .getOne(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id",attrEntity.getAttrId()));
        
        if(relationEntity!=null){
            AttrGroupEntity attrGroupEntity = attrGroupService.getOne(
                new QueryWrapper<AttrGroupEntity>().eq("attr_group_id",relationEntity.getAttrGroupId()));
            if(attrGroupEntity!=null){
                attrRespVo.setGroupName(attrGroupEntity.getAttrGroupName());
            }
        }

        //Set classification information
        CategoryEntity categoryEntity = categoryService.getById(attrRespVo.getCatelogId());
        if(categoryEntity!=null){
            attrRespVo.setCatelogName(categoryEntity.getName());
        }
        attrRespVo.setCatelogPath(categoryService.findCatelogPath(attrRespVo.getCatelogId()));
        return attrRespVo;
    }
}

2. Save the specification parameter data after modification, not only save the specification parameters, but also save the associated attribute group pms_attr_attrgroup_relation

① Controller layer:

@RestController
@RequestMapping("product/attr")
public class AttrController {
    @Autowired
    private AttrService attrService;

    /**
     * modify
     */
    @RequestMapping("/update")
    public R update(@RequestBody AttrVo attrVo){
      attrService.updateAttr(attrVo);
        return R.ok();
    }
}

② Service layer:

@Service("attrService")
public class AttrServiceImpl extends ServiceImpl<AttrDao, AttrEntity> implements AttrService {

    @Autowired
    AttrAttrgroupRelationService attrAttrgroupRelationService;

    @Autowired
    CategoryService categoryService;

    @Autowired
    AttrGroupService attrGroupService;

    @Transactional
    @Override
    public void updateAttr(AttrVo attrVo) {
        // Update specification parameters
        AttrEntity attrEntity = new AttrEntity();
        BeanUtils.copyProperties(attrVo,attrEntity);
        this.updateById(attrEntity);

        // Update Association
        AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
        relationEntity.setAttrGroupId(attrVo.getAttrGroupId());
        relationEntity.setAttrId(attrVo.getAttrId());
        //According to attr_id query Association attribute grouping
        int count = attrAttrgroupRelationService.count(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attrVo.getAttrId()));
        if(count>0){
            // Update the attribute grouping information associated with the specification parameter
            attrAttrgroupRelationService.update(relationEntity,new UpdateWrapper<AttrAttrgroupRelationEntity>().eq("attr_id",attrVo.getAttrId()));
        }else{
            // Add attribute grouping information associated with specification parameters
            attrAttrgroupRelationService.save(relationEntity);
        }
    }
}

7.4 get sales attributes under classification

Attributes include two types: basic attributes and sales attributes. Basic attributes are specification parameters

The sales attributes and specification parameters are the same table, which can be accessed through attr_type, attr_type=1 represents specification parameter, attr_type=0 represents sales attribute

① Controller layer:

@RestController
@RequestMapping("product/attr")
public class AttrController {
    @Autowired
    private AttrService attrService;

    // /base/list/{catelogId}
    // /sale/list/{catelogId}
    @GetMapping("/{attrType}/list/{catelogId}")
    public R baseAttrList(@RequestParam Map<String,Object> params,
                          @PathVariable("catelogId") Long catelogId,
                          @PathVariable("attrType") String attrType){
        //The results returned by paging queries are PageUtils
        PageUtils page = attrService.queryBaseAttrPage(params,catelogId,attrType);
        return R.ok().put("page",page);
    }
}

② Service layer: in all places where the specification parameter association group needs to be set, it is necessary to judge whether it is a specification parameter or a sales attribute request, because the sales attribute has no association group. If it is not judged, the new sales attribute will also have an association group, but the value is null

7.5 get attributes associated with attribute groups

Requirements interface document: https://easydoc.net/s/78237135/ZUqEdvA4/LnjzZHPj , get all the associated attributes under a certain attribute group

① Controller layer:

@RestController
@RequestMapping("product/attrgroup")
public class AttrGroupController {
    @Autowired
    private AttrGroupService attrGroupService;

    @Autowired
    private CategoryService categoryService;

    @Autowired
    private AttrService attrService;

    @GetMapping("/{attrgroupId}/attr/relation")
    public R attrRelation(@PathVariable("attrgroupId") String attrgroupId){
        List<AttrEntity> data = attrService.getRelationAttr(attrgroupId);
        return R.ok().put("data",data);
    }
}

② Service layer:

@Service("attrService")
public class AttrServiceImpl extends ServiceImpl<AttrDao, AttrEntity> implements AttrService {

    @Autowired
    AttrAttrgroupRelationService attrAttrgroupRelationService;

    @Autowired
    CategoryService categoryService;

    @Autowired
    AttrGroupService attrGroupService;

    @Override
    public List<AttrEntity> getRelationAttr(String attrgroupId) {

        List<AttrAttrgroupRelationEntity> relationEntities = attrAttrgroupRelationService.list(
                new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_group_id", attrgroupId));

        //Get all attrId
        List<Long> attrIds = relationEntities.stream().map(relationEntity -> {
            Long attrId = relationEntity.getAttrId();
            return attrId;
        }).collect(Collectors.toList());

        if(attrIds == null || attrIds.size() == 0){
            return null;
        }
        
        Collection<AttrEntity> attrEntities = this.listByIds(attrIds);
        return (List<AttrEntity>) attrEntities;
    }
}

7.6 delete attributes associated with attribute groups

Demand analysis: enables batch deletion

① Controller layer:

@RestController
@RequestMapping("product/attrgroup")
public class AttrGroupController {
    @Autowired
    private AttrGroupService attrGroupService;

    @Autowired
    private CategoryService categoryService;

    @Autowired
    private AttrService attrService;

    @PostMapping("/attr/relation/delete")
    public R deleteRelation(@RequestBody AttrGroupRelationVo[] relationVos){
        attrService.deleteRelation(relationVos);
        return R.ok();
    }
}

② Service layer:

@Service("attrService")
public class AttrServiceImpl extends ServiceImpl<AttrDao, AttrEntity> implements AttrService {

    @Autowired
    AttrAttrgroupRelationService attrAttrgroupRelationService;

    @Override
    public void deleteRelation(AttrGroupRelationVo[] relationVos) {
        //Delete attribute grouping and attribute Association in batch
        List<AttrAttrgroupRelationEntity> relationEntityList = Arrays.asList(relationVos).stream().map(relationVo -> {
            AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
            BeanUtils.copyProperties(relationVo, relationEntity);
            return relationEntity;
        }).collect(Collectors.toList());
        attrAttrgroupRelationService.deleteBatchRelation(relationEntityList);
    }
}

7.7 query properties not associated with groups

① Controller layer:

@RestController
@RequestMapping("product/attrgroup")
public class AttrGroupController {
    
    @Autowired
    private AttrService attrService;

    @GetMapping("/{attrgroupId}/noattr/relation")
    public R attrNoRelation(@PathVariable("attrgroupId") Long attrgroupId,
                            @RequestParam Map<String, Object> params){
        PageUtils page = attrService.getNoRelationAttr(params,attrgroupId);
        return R.ok().put("page",page);
    }
}

② Service layer:

@Service("attrService")
public class AttrServiceImpl extends ServiceImpl<AttrDao, AttrEntity> implements AttrService {

    @Autowired
    AttrGroupDao attrGroupDao;

    @Autowired
    AttrAttrgroupRelationDao attrAttrgroupRelationDao;

    @Autowired
    CategoryDao categoryDao;

    @Autowired
    AttrDao attrDao;

    @Override
    public PageUtils getNoRelationAttr(Map<String, Object> params, Long attrgroupId) {
        // The current group can only be associated with attributes that are not associated with all groups under the current category
        AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(attrgroupId);
        Long catelogId = attrGroupEntity.getCatelogId();

        // All groups under the current category
        List<AttrGroupEntity> attrGroupEntities = attrGroupDao.selectList(
                new QueryWrapper<AttrGroupEntity>().eq("catelog_id", catelogId));

        List<Long> attrGroupIdList = attrGroupEntities.stream().map(attrGroupEntity1 -> {
            return attrGroupEntity1.getAttrGroupId();
        }).collect(Collectors.toList());

        // Properties associated with these groups
        List<AttrAttrgroupRelationEntity> groupId = attrAttrgroupRelationDao.selectList(
                new QueryWrapper<AttrAttrgroupRelationEntity>().in("attr_group_id", attrGroupIdList));

        List<Long> attrIds = groupId.stream().map(item -> {
            return item.getAttrId();
        }).collect(Collectors.toList());

        // Remove these attributes from all attributes of the current classification
        QueryWrapper<AttrEntity> queryWrapper = new QueryWrapper<AttrEntity>()
                .eq("catelog_id", catelogId)
                .eq("attr_type",ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode());

        if(attrIds!=null && attrIds.size()>0){
            queryWrapper.notIn("attr_id",attrIds);
        }

        //Fuzzy query
        String key = (String)params.get("key");
        if(StringUtils.isNotEmpty(key)){
            queryWrapper.and(item->{
                item.eq("attr_id",key).or().like("attr_name",key);
            });
        }

        IPage<AttrEntity> page = this.page(
                new Query<AttrEntity>().getPage(params),
                queryWrapper
        );
        return new PageUtils(page);
    }
}

At present, there are four projects that need to be opened: start nacos, start mysql and start the front end. If you don't want to be the front end, you can directly use the complete project in the future

7.8 add attribute grouping and attribute Association

① Controller layer:

@RestController
@RequestMapping("product/attrgroup")
public class AttrGroupController {

    @Autowired
    private AttrAttrgroupRelationService attrAttrgroupRelationService;

    @PostMapping("/attr/relation")
    public R addRelation(@RequestBody List<AttrGroupRelationVo> attrGroupRelationVos ){
        attrAttrgroupRelationService.saveRelation(attrGroupRelationVos);
        return R.ok();
    }
}

② Service layer:

@Service("attrAttrgroupRelationService")
public class AttrAttrgroupRelationServiceImpl extends ServiceImpl<AttrAttrgroupRelationDao, AttrAttrgroupRelationEntity> implements AttrAttrgroupRelationService {
    @Override
    public void saveRelation(List<AttrGroupRelationVo> attrGroupRelationVos) {
        //Replace all vo classes in the collection with entity classes
        List<AttrAttrgroupRelationEntity> collect = attrGroupRelationVos.stream().map(attrGroupRelationVo -> {
            AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
            BeanUtils.copyProperties(attrGroupRelationVo, relationEntity);
            return relationEntity;
        }).collect(Collectors.toList());
        saveBatch(collect);
    }
}

8. Goods and services - new goods

8.1 obtain brands associated with classification

Brands and classifications are many to many relationships. There are multiple brands under a classification, and a brand can be associated with multiple classifications

① Controller layer:

@RestController
@RequestMapping("product/categorybrandrelation")
public class CategoryBrandRelationController {
    @Autowired
    private CategoryBrandRelationService categoryBrandRelationService;

    //product/categorybrandrelation/brands/list

    @GetMapping("/brands/list")
    public R relationBrandsList(@RequestParam(value = "catId",required = true) String catId){
        List<BrandEntity> brandEntities = categoryBrandRelationService.getBrandsByCatId(catId);

        List<BrandVo> collect = 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",collect);
    }
}

② Service layer:

@Service("categoryBrandRelationService")
public class CategoryBrandRelationServiceImpl extends ServiceImpl<CategoryBrandRelationDao, CategoryBrandRelationEntity> implements CategoryBrandRelationService {

    @Autowired
    private CategoryService categoryService;

    @Autowired
    private BrandService brandService;

    @Autowired
    private BrandDao brandDao;

    @Override
    public List<BrandEntity> getBrandsByCatId(String catId) {
        List<CategoryBrandRelationEntity> relationEntityList 
            = this.baseMapper.selectList(
                new QueryWrapper<CategoryBrandRelationEntity>().eq("catelog_id", catId));

        List<Long> brandIds 
            = relationEntityList.stream().map(categoryBrandRelationEntity -> {
            return categoryBrandRelationEntity.getBrandId();
        }).collect(Collectors.toList());

        List<BrandEntity> brandEntities = brandDao.selectBatchIds(brandIds);
        return brandEntities;
    }
}

8.2 get attribute grouping and associated attributes under classification

① Controller layer

@RestController
@RequestMapping("product/attrgroup")
public class AttrGroupController {
    @Autowired
    private AttrGroupService attrGroupService;

    @GetMapping("/{catelogId}/withattr")
    public R getAttrGroupWithAttrs(@PathVariable("catelogId") String catelogId){
        List<AttrGroupWithAttrsVo> attrGroupWithAttrsVos 
            = attrGroupService.getAttrGroupWithAttrsByCategoryId(catelogId);
        return R.ok().put("data",attrGroupWithAttrsVos);
    }
}

② Service layer

@Service("attrGroupService")
public class AttrGroupServiceImpl extends ServiceImpl<AttrGroupDao, AttrGroupEntity> implements AttrGroupService {

    @Autowired
    private AttrAttrgroupRelationDao relationDao;

    @Autowired
    private AttrService attrService;

    @Override
   public List<AttrGroupWithAttrsVo> getAttrGroupWithAttrsByCategoryId(String catelogId) {
        //Query attribute grouping under current classification
        List<AttrGroupEntity> attrGroupEntities = this.baseMapper.selectList(
                new QueryWrapper<AttrGroupEntity>().eq("catelog_id", catelogId));

        List<AttrGroupWithAttrsVo> collect 
            = attrGroupEntities.stream().map(attrGroupEntity -> {
            AttrGroupWithAttrsVo attrsVo = new AttrGroupWithAttrsVo();
            BeanUtils.copyProperties(attrGroupEntity, attrsVo);
            // Get all properties under the group
            List<AttrEntity> relationAttr 
                = attrService.getRelationAttr(attrGroupEntity.getAttrGroupId());
            attrsVo.setAttrs(relationAttr);
            return attrsVo;
        }).collect(Collectors.toList());
        return collect;
    }
}

8.3 new commodities

① Request vo

@Data
public class SpuSaveVo {
    /**
     * pms_spu_info
     */
    private String spuName;
    private String spuDescription;
    private Long catalogId;
    private Long brandId;
    private BigDecimal weight;
    private int publishStatus;
    /**
     * pms_spu_info_desc
     */
    private List<String> decript;
    /**
     * pms_spu_images
     */
    private List<String> images;
    /**
     * sms_spu_bounds
     */
    private Bounds bounds;
    /**
     * pms_product_attr_value
     */
    private List<BaseAttrs> baseAttrs;
    private List<Skus> skus;
}

② Controller layer

@RestController
@RequestMapping("product/spuinfo")
public class SpuInfoController {
    @Autowired
    private SpuInfoService spuInfoService;
    /**
     * preservation
     */
    @RequestMapping("/save")
    public R save(@RequestBody SpuSaveVo spuSaveVo){
      	spuInfoService.saveSpuInfo(spuSaveVo);
        return R.ok();
    }
}

③ Service layer

@Service("spuInfoService")
public class SpuInfoServiceImpl extends ServiceImpl<SpuInfoDao, SpuInfoEntity> implements SpuInfoService {

    @Autowired
    SpuInfoDescService spuInfoDescService;

    @Autowired
    SpuImagesService spuImagesService;

    @Autowired
    ProductAttrValueService attrValueService;

    @Autowired
    SkuInfoDao skuInfoDao;

    @Autowired
    SkuImagesService skuImagesService;

    @Autowired
    SkuSaleAttrValueService skuSaleAttrValueService;

    @Autowired
    CouponFeignService couponFeignService;

    @Autowired
    SkuInfoService skuInfoService;

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void saveSpuInfo(SpuSaveVo spuSaveVo) {
        // Save PMS_ spu_ Info, basic information of SPU
        SpuInfoEntity spuInfoEntity = new SpuInfoEntity();
        BeanUtils.copyProperties(spuSaveVo,spuInfoEntity);
        spuInfoEntity.setCreateTime(new Date());
        spuInfoEntity.setUpdateTime(new Date());
        this.baseMapper.insert(spuInfoEntity);

        // Save pms_spu_info_desc, picture description information of SPU
        SpuInfoDescEntity spuInfoDescEntity = new SpuInfoDescEntity();
        spuInfoDescEntity.setSpuId(spuInfoEntity.getId());
        spuInfoDescEntity.setDecript(String.join(",",spuSaveVo.getDecript()));
        spuInfoDescService.saveInfoDesc(spuInfoDescEntity);

        // Save pms_spu_images to save the image set of SPU. One SPU corresponds to multiple images
        List<String> images = spuSaveVo.getImages();
        spuImagesService.saveSpuImages(images,spuInfoEntity.getId());

        // Save pms_product_attr_value, the specification parameter of the product
        List<BaseAttrs> baseAttrs = spuSaveVo.getBaseAttrs();
        attrValueService.saveBaseAttrs(baseAttrs,spuInfoEntity.getId());

        // Save sms_spu_bounds, remotely call the gulimall coupon service to save the score information
        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("Remote save spu Integration information failed");
        }

        // Save sku information
        List<Skus> skus = spuSaveVo.getSkus();
        if(skus!=null && skus.size()>0){
            skus.forEach(item->{
                // Save the basic information of SKU; pms_sku_info
                String defaultImg = "";
                for (Images image : item.getImages()) {
                    if(image.getDefaultImg() == 1){
                        defaultImg = image.getImgUrl();
                    }
                }
                SkuInfoEntity skuInfoEntity = new SkuInfoEntity();
                BeanUtils.copyProperties(item,skuInfoEntity);
                skuInfoEntity.setBrandId(spuInfoEntity.getBrandId());
                skuInfoEntity.setCatalogId(spuInfoEntity.getCatalogId());
                skuInfoEntity.setSaleCount(0L);
                skuInfoEntity.setSpuId(spuInfoEntity.getId());
                skuInfoEntity.setSkuDefaultImg(defaultImg);
                skuInfoService.saveSkuInfo(skuInfoEntity);

                Long skuId = skuInfoEntity.getSkuId();
				// Save SKU's picture information; pms_sku_image
                List<SkuImagesEntity> imagesEntities
                    = item.getImages().stream().map(img -> {
                    SkuImagesEntity skuImagesEntity = new SkuImagesEntity();
                    skuImagesEntity.setSkuId(skuId);
                    skuImagesEntity.setImgUrl(img.getImgUrl());
                    skuImagesEntity.setDefaultImg(img.getDefaultImg());
                    return skuImagesEntity;
                }).filter(entity->{
                    //Returning true is required, and false is eliminated
                    return !StringUtils.isEmpty(entity.getImgUrl());
                }).collect(Collectors.toList());
                skuImagesService.saveBatch(imagesEntities);
                //TODO does not need to save the image path

                List<Attr> attr = item.getAttr();
                List<SkuSaleAttrValueEntity> skuSaleAttrValueEntities 
                    = attr.stream().map(a -> {
                    SkuSaleAttrValueEntity attrValueEntity
                        = new SkuSaleAttrValueEntity();
                    BeanUtils.copyProperties(a, attrValueEntity);
                    attrValueEntity.setSkuId(skuId);
                    return attrValueEntity;
                }).collect(Collectors.toList());
                // Save SKU's sales attribute information: pms_sku_sale_attr_value
                skuSaleAttrValueService.saveBatch(skuSaleAttrValueEntities);

                // Save the discount, full discount and other information of sku, and call the remote gulimall coupon service
                // gulimall_sms->sms_sku_ladder\sms_sku_full_reduction\sms_member_price
                SkuReductionTo skuReductionTo = new SkuReductionTo();
                BeanUtils.copyProperties(item,skuReductionTo);
                skuReductionTo.setSkuId(skuId);
                if(skuReductionTo.getFullCount() >0 
                   || skuReductionTo.getFullPrice().compareTo(new BigDecimal("0")) == 1){
                    R r1 = couponFeignService.saveSkuReduction(skuReductionTo);
                    if(r1.getCode() != 0){
                        log.error("Remote save sku Offer information failed");
                    }
                }
            });
        }
    }
}

④ CouponFeignService

/**
 * 1,Called service name
 * 2,The mapping path of the method in the invoked service
 * 3,SpuBrandsTo It is used to transfer the data in the gulimall product service to the gulimall coupon service
 */
@FeignClient("gulimall-coupon")
public interface CouponFeignService {

    @PostMapping("/coupon/spubounds/save")
    R saveSpuBounds(@RequestBody SpuBoundTo spuBoundTo);

    @PostMapping("/coupon/skufullreduction/saveinfo")
    R saveSkuReduction(@RequestBody SkuReductionTo skuReductionTo);
}

⑤ SpuBoundsController

@RestController
@RequestMapping("coupon/spubounds")
public class SpuBoundsController {
    @Autowired
    private SpuBoundsService spuBoundsService;
    /**
     * preservation
     */
    @PostMapping("/save")
    public R save(@RequestBody SpuBoundsEntity spuBounds){
        spuBoundsService.save(spuBounds);
        return R.ok();
    }
}

⑥ SkuFullReductionController

@RestController
@RequestMapping("coupon/skufullreduction")
public class SkuFullReductionController {
    @Autowired
    private SkuFullReductionService skuFullReductionService;

    @PostMapping("/saveinfo")
    public R saveInfo(@RequestBody SkuReductionTo reductionTo){
        skuFullReductionService.saveSkuReduction(reductionTo);
        return R.ok();
    }
}

⑦ SkuFullReductionServiceImpl

@Service("skuFullReductionService")
public class SkuFullReductionServiceImpl extends ServiceImpl<SkuFullReductionDao, SkuFullReductionEntity> implements SkuFullReductionService {

    @Autowired
    MemberPriceService memberPriceService;

    @Autowired
    SkuFullReductionService skuFullReductionService;

    @Autowired
    SkuLadderService skuLadderService;

    @Override
    public void saveSkuReduction(SkuReductionTo skuReductionTo) {
        //Save sms_sku_ladder/sms_sku_full_reduction/sms_member_price

        //Save sms_sku_ladder
        SkuLadderEntity skuLadderEntity = new SkuLadderEntity();
        BeanUtils.copyProperties(skuReductionTo,skuLadderEntity);
        skuLadderEntity.setAddOther(skuReductionTo.getCountStatus());
        skuLadderService.save(skuLadderEntity);
        if(skuReductionTo.getFullCount() > 0){
            skuLadderService.save(skuLadderEntity);
        }

        //Save sms_sku_full_reduction
        SkuFullReductionEntity skuFullReductionEntity = new SkuFullReductionEntity();
        BeanUtils.copyProperties(skuReductionTo,skuFullReductionEntity);
        if(skuFullReductionEntity.getFullPrice().compareTo(new BigDecimal("0"))==1){
            this.save(skuFullReductionEntity);
        }
        this.save(skuFullReductionEntity);

        //Save sms_member_price
        List<MemberPrice> memberPriceList = skuReductionTo.getMemberPrice();
        List<MemberPriceEntity> collect = memberPriceList.stream().map(item -> {
            MemberPriceEntity memberPriceEntity = new MemberPriceEntity();
            memberPriceEntity.setMemberLevelName(item.getName());
            memberPriceEntity.setMemberPrice(item.getPrice());
            memberPriceEntity.setSkuId(skuReductionTo.getSkuId());
            memberPriceEntity.setMemberLevelId(item.getId());
            memberPriceEntity.setAddOther(1);
            return memberPriceEntity;
        }).filter(item-> {
            return item.getMemberPrice().compareTo(new BigDecimal("0")) == 1;
        }).collect(Collectors.toList());
        memberPriceService.saveBatch(collect);
    }
}

⑧ Add @ EnableFeignClients to the main configuration class

// Package of feign interface
@EnableFeignClients(basePackages = "com.atguigu.gulimall.product.feign")
@EnableDiscoveryClient
@SpringBootApplication
public class GulimallProductApplication {
    public static void main(String[] args) {
        SpringApplication.run(GulimallProductApplication.class, args);
    }
}

8.4 SPU retrieval

① Controller layer

@RestController
@RequestMapping("product/spuinfo")
public class SpuInfoController {
    @Autowired
    private SpuInfoService spuInfoService;

    @RequestMapping("/list")
    public R list(@RequestParam Map<String, Object> params){
        PageUtils page = spuInfoService.queryPageByCondition(params);
        return R.ok().put("page", page);
    }
}

② Service layer

@Service("spuInfoService")
public class SpuInfoServiceImpl extends ServiceImpl<SpuInfoDao, SpuInfoEntity> implements SpuInfoService {

    @Override
    public PageUtils queryPageByCondition(Map<String, Object> params) {
        QueryWrapper<SpuInfoEntity> queryWrapper = new QueryWrapper<>();

        // (id=1 or spu_name like xxx)
        String key = (String)params.get("key");
        if(StringUtils.isNotEmpty(key)){
            queryWrapper.and((w)->{
                w.eq("id",key).or().like("spu_name",key);
            });
        }

        // status=1 and (id=1 or spu_name like xxx)
        String status = (String)params.get("status");
        if(StringUtils.isNotEmpty(status)){
            queryWrapper.eq("publish_status",status);
        }

        String catelogId = (String)params.get("catelogId");
        if(StringUtils.isNotEmpty(catelogId) && "0".equalsIgnoreCase(catelogId)){
            queryWrapper.eq("catalog_id",catelogId);
        }

        String brandId = (String)params.get("brandId");
        if(StringUtils.isNotEmpty(brandId) && "0".equalsIgnoreCase(brandId)){
            queryWrapper.eq("brand_id",brandId);
        }

        IPage<SpuInfoEntity> page = this.page(
                new Query<SpuInfoEntity>().getPage(params),
                queryWrapper
        );
        return new PageUtils(page);
    }
}

③ Since the time format in the database is not yyyy MM DD HH: mm: SS, you can add the following in application.yml:

spring:
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss

8.5 SKU retrieval

① Controller layer

@RestController
@RequestMapping("product/skuinfo")
public class SkuInfoController {
    @Autowired
    private SkuInfoService skuInfoService;

    @RequestMapping("/list")
    public R list(@RequestParam Map<String, Object> params){
        PageUtils page = skuInfoService.queryPageByCondition(params);
        return R.ok().put("page", page);
    }
}

② Service layer

@Service("skuInfoService")
public class SkuInfoServiceImpl extends ServiceImpl<SkuInfoDao, SkuInfoEntity> implements SkuInfoService {

    @Override
    public PageUtils queryPageByCondition(Map<String, Object> params) {
        QueryWrapper<SkuInfoEntity> queryWrapper = new QueryWrapper<>();

        String key = (String)params.get("key");
        if(StringUtils.isNotEmpty(key)){
            queryWrapper.and((wrapper)->{
                wrapper.eq("sku_id",key).or().like("sku_name",key);
            });
        }

        String catalogId = (String)params.get("catalog_id");
        if(StringUtils.isNotEmpty(catalogId) && !"0".equalsIgnoreCase(catalogId)){
            queryWrapper.eq("catalog_id",catalogId);
        }

        String brandId = (String)params.get("brand_id");
        if(StringUtils.isNotEmpty(brandId) && !"0".equalsIgnoreCase(brandId)){
            queryWrapper.eq("brand_id",brandId);
        }

        String min = (String)params.get("min");
        if(StringUtils.isNotEmpty(min)){
            queryWrapper.ge("price",min);
        }

        String max = (String)params.get("max");
        if(StringUtils.isNotEmpty(max)){
            //Splicing occurs when the value of max is greater than 0: when compareTo returns 1, it means that the former is greater than the latter
            try {
                //If the number passed in is not a number, an exception will occur, such as abc
                if(new BigDecimal(max).compareTo(new BigDecimal("0"))==1){
                    queryWrapper.le("price",max);
                }
            }catch (Exception e){
            }
        }

        IPage<SkuInfoEntity> page = this.page(
                new Query<SkuInfoEntity>().getPage(params),
                queryWrapper
        );
        return new PageUtils(page);
    }
}

9. Goods and services - warehouse management

9.1 integrating guilimall ware services

① Add the guilimall ware service to the registry

spring:
  application:
    name: gulimall-ware
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1

② Set the log printing level: debug, and the console will print SQL information

logging:
  level:
    com.atguigu.gulimall: debug

③ Enable service registration and discovery

@EnableTransactionManagement
@MapperScan("com.atguigu.gulimall.product.dao")
@EnableDiscoveryClient
@SpringBootApplication
public class GulimallWareApplication {
    public static void main(String[] args) {
        SpringApplication.run(GulimallWareApplication.class, args);
    }
}

④ Configuring the gateway in the GUI mall gateway

- id: ware_route
  uri: lb://gulimall-ware
  predicates:
    - Path=/api/ware/**
  filters:
    - RewritePath=/api/(?<segment>.*),/$\{segment}

⑤ Add guilimall ware to the configuration center, bootstrap.properties

spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.application.name=gulimall-ware
spring.cloud.nacos.config.namespace=33deb04b-3977-4c53-9545-e1f1dc2f272e

[the external link image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (img-5rlpwI3u-1632237077788)(imgs/image-20210921152245127.png)]

⑥ Warehouse maintenance fuzzy retrieval

@RestController
@RequestMapping("ware/wareinfo")
public class WareInfoController {
    @Autowired
    private WareInfoService wareInfoService;

    @RequestMapping("/list")
    public R list(@RequestParam Map<String, Object> params){
        PageUtils page = wareInfoService.queryPageByCondition(params);
        return R.ok().put("page", page);
    }
}
@Service("wareInfoService")
public class WareInfoServiceImpl extends ServiceImpl<WareInfoDao, WareInfoEntity> implements WareInfoService {

    @Override
    public PageUtils queryPageByCondition(Map<String, Object> params) {
        QueryWrapper<WareInfoEntity> queryWrapper = new QueryWrapper<>();

        String key = (String) params.get("key");
        if(StringUtils.isNotEmpty(key)){
            queryWrapper.and(item->{
                item.eq("id",key)
                        .or().like("name",key)
                        .or().eq("areacode",key)
                        .or().like("address",key);
            });
        }
        
        IPage<WareInfoEntity> page = this.page(
                new Query<WareInfoEntity>().getPage(params),
                queryWrapper
        );
        return new PageUtils(page);
    }
}

9.3 retrieving inventory

① Controller layer

@RestController
@RequestMapping("ware/waresku")
public class WareSkuController {
    @Autowired
    private WareSkuService wareSkuService;

    @RequestMapping("/list")
    public R list(@RequestParam Map<String, Object> params){
        PageUtils page = wareSkuService.queryPage(params);
        return R.ok().put("page", page);
    }
}

② Service layer

@Service("wareSkuService")
public class WareSkuServiceImpl extends ServiceImpl<WareSkuDao, WareSkuEntity> implements WareSkuService {

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        QueryWrapper<WareSkuEntity> queryWrapper = new QueryWrapper<>();

        String skuId = (String) params.get("skuId");
        if(StringUtils.isNotEmpty(skuId)){
            queryWrapper.eq("sku_id",skuId);
        }

        String wareId = (String) params.get("wareId");
        if(StringUtils.isNotEmpty(wareId)){
            queryWrapper.eq("ware_id",wareId);
        }

        IPage<WareSkuEntity> page = this.page(
                new Query<WareSkuEntity>().getPage(params),
                queryWrapper
        );
        return new PageUtils(page);
    }
}

9.4 query purchase demand

① Controller layer

@RestController
@RequestMapping("ware/purchasedetail")
public class PurchaseDetailController {
    @Autowired
    private PurchaseDetailService purchaseDetailService;

    @RequestMapping("/unreceive/list")
    public R unreceivelist(@RequestParam Map<String, Object> params){
        PageUtils page = purchaseService.queryPageUnreceive(params);
        return R.ok().put("page", page);
    }
}

② Service layer

@Service("purchaseDetailService")
public class PurchaseDetailServiceImpl extends ServiceImpl<PurchaseDetailDao, PurchaseDetailEntity> implements PurchaseDetailService {

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        QueryWrapper<PurchaseDetailEntity> queryWrapper = new QueryWrapper<>();

        String key = (String) params.get("key");
        if(StringUtils.isNotEmpty(key)){
            queryWrapper.and(item->{
                item.eq("purchase_id",key).or().eq("sku_id",key);
            });
        }

        String status = (String) params.get("status");
        if(StringUtils.isNotEmpty(status)){
            queryWrapper.eq("status",status);
        }

        String wareId = (String) params.get("wareId");
        if(StringUtils.isNotEmpty(wareId)){
            queryWrapper.eq("ware_id",wareId);
        }

        IPage<PurchaseDetailEntity> page = this.page(
                new Query<PurchaseDetailEntity>().getPage(params),
                queryWrapper
        );
        return new PageUtils(page);
    }
}

9.5 querying unclaimed purchase orders

① Controller layer

@RestController
@RequestMapping("ware/purchase")
public class PurchaseController {
    @Autowired
    private PurchaseService purchaseService;

    @RequestMapping("/list")
    public R list(@RequestParam Map<String, Object> params){
        PageUtils page = purchaseService.queryPageUnreceive(params);
        return R.ok().put("page", page);
    }
}

② Service layer

@Service("purchaseService")
public class PurchaseServiceImpl extends ServiceImpl<PurchaseDao, PurchaseEntity> implements PurchaseService {

    @Override
    public PageUtils queryPageUnreceive(Map<String, Object> params) {
        IPage<PurchaseEntity> page = this.page(
                new Query<PurchaseEntity>().getPage(params),
                // status 0 and 1 represent purchase orders that have not been received
                new QueryWrapper<PurchaseEntity>().eq("status","0").or().eq("status","1")
        );
        return new PageUtils(page);
    }
}

9.6 consolidate purchase demand to purchase order

Demand: if no purchase order is selected, a new purchase order will be created automatically for consolidation. If there is a purchase order, the purchase demand will be consolidated into the purchase order

The so-called consolidated purchase order is to update the purchase demand item.

① Request vo

@Data
public class MergeVo {
    //Front end request parameters
    private Long purchaseId;
    private List<Long> items;
}

② Controller layer

@RestController
@RequestMapping("ware/purchase")
public class PurchaseController {
    @Autowired
    private PurchaseService purchaseService;

    @PostMapping("/merge")
    public R merge(@RequestBody MergeVo mergeVo){
        purchaseService.merge(mergeVo);
        return R.ok();
    }
}

③ Service layer

@Service("purchaseService")
public class PurchaseServiceImpl extends ServiceImpl<PurchaseDao, PurchaseEntity> implements PurchaseService {

    @Autowired
    PurchaseDetailService purchaseDetailService;

    @Override
    public void merge(MergeVo mergeVo) {
        Long purchaseId = mergeVo.getPurchaseId();
        //Note: if there is no purchase order, a new purchase order will be created
        if(purchaseId==null){
            PurchaseEntity purchaseEntity = new PurchaseEntity();
            purchaseEntity.setCreateTime(new Date());
            purchaseEntity.setUpdateTime(new Date());
            purchaseEntity.setStatus(WareConstant.PurchaseStatusEnum.CREATED.getCode());
            this.baseMapper.insert(purchaseEntity);
            purchaseId = purchaseEntity.getId();
        }

        //Update the purchase requirements to be consolidated and change their status to allocated
        List<Long> items = mergeVo.getItems();
        Long finalPurchaseId = purchaseId;
        List<PurchaseDetailEntity> collect = items.stream().map(id -> {
            PurchaseDetailEntity detailEntity = new PurchaseDetailEntity();
            detailEntity.setId(id);
            detailEntity.setPurchaseId(finalPurchaseId);
            detailEntity.setStatus(WareConstant.PurchaseDetailStatusEnum.ASSIGNED.getCode());
            return detailEntity;
        }).collect(Collectors.toList());
        purchaseDetailService.updateBatchById(collect);
    }
}

Before consolidation:

After Po consolidation:

9.7 receiving purchase order

Demand analysis: picking up a purchase order is to update the status of the purchase order. You can only pick up a purchase order when it is in new or allocated status. Picking up a purchase order requires changing the purchase demand in the purchase order to being purchased

① Controller layer

@RestController
@RequestMapping("ware/purchase")
public class PurchaseController {
    @Autowired
    private PurchaseService purchaseService;

    @PostMapping("/received")
    public R receive(@RequestBody List<Long> ids){
        purchaseService.receive(ids);
        return R.ok();
    }
}

② Service layer

@Service("purchaseService")
public class PurchaseServiceImpl extends ServiceImpl<PurchaseDao, PurchaseEntity> implements PurchaseService {

    @Autowired
    PurchaseDetailService purchaseDetailService;

    @Override
    public void receive(List<Long> ids) {
        List<PurchaseEntity> purchaseEntityList = ids.stream().map(id -> {
            return this.getById(id);
        }).filter(purchaseEntity -> {
            // The status of the filtered purchase order is created and allocated
            if(purchaseEntity.getStatus()
               .equals(WareConstant.PurchaseStatusEnum.CREATED.getCode())||
                    purchaseEntity.getStatus()
               .equals(WareConstant.PurchaseStatusEnum.ASSIGNED.getCode())){
                return true;
            }
            return false;
        }).map(purchaseEntity -> {
           // The status of the purchase order is set to collected
            purchaseEntity.setStatus(WareConstant.PurchaseStatusEnum.RECEIVE.getCode());
            purchaseEntity.setUpdateTime(new Date());
            return purchaseEntity;
        }).collect(Collectors.toList());

        // Batch update PO status
        this.updateBatchById(purchaseEntityList);

        // In order to change the status of the purchase demand, in addition to taking out each element from the stream, foreach can also be used. For double-layer list s, foreach is used first
        purchaseEntityList.forEach(purchaseEntity -> {
            List<PurchaseDetailEntity> purchaseDetailEntityList
                    = purchaseDetailService.getByPurchaseId(purchaseEntity.getId());
            List<PurchaseDetailEntity> collect 
                = purchaseDetailEntityList.stream().map(purchaseDetailEntity -> {
                purchaseDetailEntity.setStatus(
                    WareConstant.PurchaseDetailStatusEnum.BUYING.getCode());
                return purchaseDetailEntity;
            }).collect(Collectors.toList());
            purchaseDetailService.updateBatchById(collect);
        });
    }
}

9.8 completion of procurement

① Request vo

@Data
public class PurchaseDoneVo {
    private Long id;
    private List<PurchaseItemDoneVo> items;
}
@Data
public class PurchaseItemDoneVo {
    private Long itemId;
    private Integer status;
    private String reason;
}

② Controller layer

@RestController
@RequestMapping("ware/purchase")
public class PurchaseController {
    @Autowired
    private PurchaseService purchaseService;

    @PostMapping("/done")
    public R finish(@RequestBody PurchaseDoneVo purchaseDoneVo){
        purchaseService.done(purchaseDoneVo);
        return R.ok();
    }
}

③ Service layer

@Service("purchaseService")
public class PurchaseServiceImpl extends ServiceImpl<PurchaseDao, PurchaseEntity> implements PurchaseService {

    @Autowired
    PurchaseDetailService purchaseDetailService;

    @Autowired
    WareSkuService wareSkuService;

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void done(PurchaseDoneVo purchaseDoneVo) {
        // Modify the status of purchase items. Only when all purchase items are successfully purchased can the purchase order be successfully purchased
        List<PurchaseItemDoneVo> items = purchaseDoneVo.getItems();
        Boolean flag = true;
        List<PurchaseDetailEntity> purchaseDetailEntityList = new ArrayList<>();
        for(PurchaseItemDoneVo purchaseItemDoneVo:items){
            PurchaseDetailEntity detailEntity = purchaseDetailService.getById(purchaseItemDoneVo.getItemId());
            if(purchaseItemDoneVo.getStatus()
               .equals(WareConstant.PurchaseDetailStatusEnum.HASERROR.getCode())){
                // If the purchase item status transmitted from the front end is abnormal
                detailEntity.setStatus(WareConstant.PurchaseDetailStatusEnum
                                       .HASERROR.getCode());
                flag = false;
            }else{
                // If the purchase item status transmitted from the front end is successful
                detailEntity.setStatus(WareConstant.PurchaseDetailStatusEnum
                                       .FINISH.getCode());
                // Add to inventory after successful purchase
                wareSkuService.addStock(detailEntity.getSkuId(),detailEntity.getSkuNum()
                                        ,detailEntity.getWareId());
            }
            purchaseDetailEntityList.add(detailEntity);
        }
        purchaseDetailService.updateBatchById(purchaseDetailEntityList);

        // Change purchase order status
        PurchaseEntity purchaseEntity 
            = this.baseMapper.selectById(purchaseDoneVo.getId());
        if(flag){
            purchaseEntity.setStatus(WareConstant.PurchaseStatusEnum.FINISH.getCode());
        }else{
            purchaseEntity.setStatus(WareConstant.PurchaseStatusEnum.HASERROR.getCode());
        }
        purchaseEntity.setUpdateTime(new Date());
        this.updateById(purchaseEntity);
    }
}

④ Add to inventory addStock() method

@Service("wareSkuService")
public class WareSkuServiceImpl extends ServiceImpl<WareSkuDao, WareSkuEntity> implements WareSkuService {

    @Autowired
    private ProductFeignService productFeignService;

    @Override
    public void addStock(Long skuId, Integer skuNum, Long wareId) {
        // Judge whether there is this data in the inventory
        Integer count = this.baseMapper.selectCount(new QueryWrapper<WareSkuEntity>().eq("sku_id", skuId).eq("ware_id", wareId));
        if(count<=0){
            // New inventory
            WareSkuEntity wareSkuEntity = new WareSkuEntity();
            wareSkuEntity.setStock(skuNum);
            wareSkuEntity.setSkuId(skuId);
            wareSkuEntity.setWareId(wareId);
            wareSkuEntity.setStockLocked(0);
            // Remote query of gulimall product service
            try {
                R info = productFeignService.info(skuId);
                Map<String, Object> data = (Map<String, Object>) info.get("skuInfo");
                if (info.getCode() == 0) {
                    wareSkuEntity.setSkuName((String) data.get("skuName"));
                }
            }catch (Exception e){
            }
            this.baseMapper.insert(wareSkuEntity);
        }else {
            //Update inventory quantity
            this.baseMapper.addStock(skuId,skuNum,wareId);
        }
    }
}

⑤ Remote query of gulimall product service

@FeignClient("gulimall-product")
public interface ProductFeignService {
    @RequestMapping("/product/skuinfo/info/{skuId}")
    public R info(@PathVariable("skuId") Long skuId);
}

9.9 obtaining SPU specifications

① Controller layer

@RestController
@RequestMapping("product/attr")
public class AttrController {
    @Autowired
    private AttrService attrService;

    @Autowired
    private ProductAttrValueService productAttrValueService;

    @GetMapping("/base/listforspu/{spuId}")
    public R listForSpu(@PathVariable("spuId") Long spuId){
        List<ProductAttrValueEntity> data 
            = productAttrValueService.listForSpuBySpuId(spuId);
        return R.ok().put("data",data);
    }
}

② Service layer

@Service("productAttrValueService")
public class ProductAttrValueServiceImpl extends ServiceImpl<ProductAttrValueDao, ProductAttrValueEntity> implements ProductAttrValueService {

    @Override
    public List<ProductAttrValueEntity> listForSpuBySpuId(Long spuId) {
        return this.baseMapper
            .selectList(new QueryWrapper<ProductAttrValueEntity>().eq("spu_id", spuId));
    }
}

Front end access display:

The solution is to use sys in the database gulimall admin_ Add a row of data to the menu table:

9.10 modifying commodity specifications

① Request vo

@Data
public class ProductAttrValueVo {
    private Long attrId;
    private String attrName;
    private String attrValue;
    private Integer quickShow;
}

② Controller layer

@RestController
@RequestMapping("product/attr")
public class AttrController {
    @Autowired
    private AttrService attrService;

    @Autowired
    private ProductAttrValueService productAttrValueService;

    @PostMapping("/update/{spuId}")
    public R updateSpu(@PathVariable("spuId") Long spuId,
                       @RequestBody List<ProductAttrValueVo> attrValueVos){
        productAttrValueService.updateSpuAttr(spuId,attrValueVos);
        return R.ok();
    }
}

③ Service layer

@Service("productAttrValueService")
public class ProductAttrValueServiceImpl extends ServiceImpl<ProductAttrValueDao, ProductAttrValueEntity> implements ProductAttrValueService {

    @Autowired
    private AttrService attrService;

    @Override
    public void updateSpuAttr(Long spuId, List<ProductAttrValueVo> attrValueVos) {
        this.baseMapper.delete(new QueryWrapper<ProductAttrValueEntity>().eq("spu_id",spuId));
        List<ProductAttrValueEntity> collect = attrValueVos.stream().map(productAttrValueVo -> {
            ProductAttrValueEntity attrValueEntity = new ProductAttrValueEntity();
            attrValueEntity.setSpuId(spuId);
            BeanUtils.copyProperties(productAttrValueVo, attrValueEntity);
            return attrValueEntity;
        }).collect(Collectors.toList());
        this.saveBatch(collect);
    }
}

Topics: Project Visual Studio Code