• 六,购物⻋与订单


    六,购物⻋

    6.1购物⻋

    购物⻋分为⽤户登录购物⻋和未登录购物⻋操作,国内知名电商京东⽤户登录和不登录都可以操作购物⻋,如果⽤户不登录,操作购物⻋可以将数据存储到Cookie或者WebSQL或者SessionStorage中,⽤户登录后购物⻋数据可以存储到Redis中,再将之前未登录加⼊的购物⻋合并到Redis中即可。
    京东购物⻋⽅案:
    在这里插入图片描述
    天猫则采⽤了另外⼀种实现⽅案,⽤户要想将商品加⼊购物⻋,必须先登录才能操作购物⻋。我们今天实现的购物⻋是天猫解决⽅案,即⽤户必须先登录才能使⽤购物⻋功能。

    6.1.1 购物⻋分析

    (1)需求分析

    ⽤户在商品详细⻚点击加⼊购物⻋,提交商品SKU编号和购买数量,添加到购物⻋。购物⻋展示⻚⾯如下:

    在这里插入图片描述

    (2)购物⻋实现思路

    在这里插入图片描述
    我们实现的是⽤户登录后的购物⻋,⽤户将商品加⼊购物⻋的时候,直接将要加⼊购物⻋的详情存⼊到Redis即可。每次查看购物⻋的时候直接从Redis中获取。

    (3)表结构分析

    ⽤户登录后将商品加⼊购物⻋,需要存储商品详情以及购买数量,购物⻋详情表如下:数据中order_item_表:

    CREATE TABLE `order_item_` (
     `id` varchar(20) COLLATE utf8_bin NOT NULL COMMENT 'ID',
     `category_id1` int(11) DEFAULT NULL COMMENT '1级分类',
     `category_id2` int(11) DEFAULT NULL COMMENT '2级分类',
     `category_id3` int(11) DEFAULT NULL COMMENT '3级分类',
     `spu_id` varchar(20) COLLATE utf8_bin DEFAULT NULL COMMENT 'SPU_ID',
     `sku_id` bigint(20) NOT NULL COMMENT 'SKU_ID',
     `order_id` bigint(20) NOT NULL COMMENT '订单ID',
     `name` varchar(200) COLLATE utf8_bin DEFAULT NULL COMMENT '商品名称',
     `price` int(20) DEFAULT NULL COMMENT '单价',
     `num` int(10) DEFAULT NULL COMMENT '数量',
     `money` int(20) DEFAULT NULL COMMENT '总⾦额',
     `pay_money` int(11) DEFAULT NULL COMMENT '实付⾦额',
     `image` varchar(200) COLLATE utf8_bin DEFAULT NULL COMMENT '图⽚地址',
     `weight` int(11) DEFAULT NULL COMMENT '重量',
     `post_fee` int(11) DEFAULT NULL COMMENT '运费',
     `is_return` char(1) COLLATE utf8_bin DEFAULT NULL COMMENT '是否退货',
     PRIMARY KEY (`id`),
     KEY `item_id` (`sku_id`),
     KEY `order_id` (`order_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    购物⻋详情表其实就是订单详情表结构,只是⽬前临时存储数据到Redis,等⽤户下单后才将数据从Redis取出存⼊到MySQL中。

    6.1.2 搭建订单购物⻋微服务

    因为购物⻋功能⽐较简单,这⾥我们把订单和购物⻋微服务放在⼀个⼯程下
    在这里插入图片描述

    (1) pom.xml

    legou-order/pom.xml

    <?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
    http://maven.apache.org/xsd/maven-4.0.0.xsd">
      <parent>
        <artifactId>legou-parent</artifactId>
        <groupId>com.lxs</groupId>
        <version>1.0-SNAPSHOT</version>
      </parent>
      <modelVersion>4.0.0</modelVersion>
      <artifactId>legou-order</artifactId>
      <packaging>pom</packaging>
      <modules>
        <module>legou-order-interface</module>
        <module>legou-order-service</module>
      </modules>
    </project>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    legou-order/legou-order-interface/pom.xml

    <?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
    http://maven.apache.org/xsd/maven-4.0.0.xsd">
      <parent>
        <artifactId>legou-order</artifactId>
        <groupId>com.lxs</groupId>
        <version>1.0-SNAPSHOT</version>
      </parent>
      <modelVersion>4.0.0</modelVersion>
      <artifactId>legou-order-interface</artifactId>
      <dependencies>
        <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
          <groupId>com.lxs</groupId>
          <artifactId>legou-core</artifactId>
          <version>${project.version}</version>
        </dependency>
        <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
          <scope>provided</scope>
        </dependency>
      </dependencies>
      <build>
        <plugins>
          <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
              <mainClass>com.core.Starter</mainClass>
              <layout>ZIP</layout>
              <classifier>exec</classifier>
              <includeSystemScope>true</includeSystemScope>
            </configuration>
            <executions>
              <execution>
                <goals>
                  <goal>repackage</goal>
                </goals>
              </execution>
            </executions>
          </plugin>
        </plugins>
      </build>
    </project>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    legou-order/legou-order-service/pom.xml

    <?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
    http://maven.apache.org/xsd/maven-4.0.0.xsd">
      <parent>
        <artifactId>legou-order</artifactId>
        <groupId>com.lxs</groupId>
        <version>1.0-SNAPSHOT</version>
      </parent>
      <modelVersion>4.0.0</modelVersion>
      <artifactId>legou-order-service</artifactId>
      <dependencies>
        <!-- redis 使⽤-->
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-test</artifactId>
          <scope>test</scope>
        </dependency>
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
        <dependency>
          <groupId>org.springframework.retry</groupId>
          <artifactId>spring-retry</artifactId>
        </dependency>
        <dependency>
          <groupId>com.lxs</groupId>
          <artifactId>legou-core</artifactId>
          <version>${project.version}</version>
        </dependency>
        <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
          <scope>provided</scope>
        </dependency>
        <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
          <version>5.1.46</version>
          <scope>runtime</scope>
        </dependency>
        <dependency>
          <groupId>com.lxs</groupId>
          <artifactId>legou-order-interface</artifactId>
          <version>${project.version}</version>
        </dependency>
        <!--商品微服务-->
        <dependency>
          <groupId>com.lxs</groupId>
          <artifactId>legou-item-instance</artifactId>
          <version>${project.version}</version>
        </dependency>
        <!-- swagger -->
        <dependency>
          <groupId>io.springfox</groupId>
          <artifactId>springfox-swagger2</artifactId>
          <version>2.9.2</version>
        </dependency>
        <dependency>
          <groupId>io.springfox</groupId>
          <artifactId>springfox-swagger-ui</artifactId>
          <version>2.9.2</version>
        </dependency>
        <!--oauth2-->
        <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
      </dependencies>
    </project>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96

    (2) 启动器配置⽂件

    在这里插入图片描述
    public.key 拷⻉即可
    legou-order/legou-order-service/src/main/resources/bootstrap.yml

    spring:
      application:
        name: order-service
      # 多个接⼝上的@FeignClient(“相同服务名”)会报错,overriding is disabled。
      # 设置 为true ,即 允许 同名
      main:
        allow-bean-definition-overriding: true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    config-repo/order-service.yml

    server:
      port: 9009
    
    spring:
      redis:
        host: 192.168.220.110
        port: 6379
    
    mybatis-plus:
      mapper-locations: classpath*:mybatis/*/*.xml
      type-aliases-package: com.lxs.legou.*.po
      configuration:
        # 下划线驼峰转换
        map-underscore-to-camel-case: true
        lazy-loading-enabled: true
        aggressive-lazy-loading: false
    
    logging:
      #file: demo.log
      pattern:
        console: "%d - %msg%n"
      level:
        org.springframework.web: debug
        com.lxs: debug
    
    security:
      oauth2:
        resource:
          jwt:
           key-uri: http://localhost:9098/oauth/token_key #如果使⽤JWT,可以获取公钥⽤于 token 的验签
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    legou-order/legou-order-service/src/main/java/com/legou/order/OrderApplication.java

    package com.legou.order;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    import org.springframework.cloud.openfeign.EnableFeignClients;
    
    /**
     * @author
     * @version 1.0
     * @description 订单微服务
     * @createDate 2022/6/20 16:50
     **/
    @SpringBootApplication
    @EnableDiscoveryClient
    @EnableFeignClients
    @EnableCircuitBreaker
    public class OrderApplication {
        public static void main(String[] args) {
            SpringApplication.run(OrderApplication.class, args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    (3)配置类

    配置类和其他资源微服务⼯程类似,拷⻉修改即可
    legou-order/legou-order-service/src/main/java/com/legou/order/config/MybatisPlusConfig.java

    package com.legou.order.config;
    
    import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
    import com.baomidou.mybatisplus.extension.plugins.pagination.optimize.JsqlParserCountOptimize;
    import com.github.pagehelper.PageInterceptor;
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    /**
     * @author
     * @version 1.0
     * @description
     * @createDate 2022/6/20 16:52
     **/
    @Configuration
    @MapperScan("com.lxs.legou.order.dao")
    public class MybatisPlusConfig {
        /**
         * 分⻚插件
         */
        @Bean
        public PaginationInterceptor paginationInterceptor() {
            // 开启 count 的 join 优化,只针对 left join !!!
            return new PaginationInterceptor().setCountSqlParser(new JsqlParserCountOptimize(true));
        }
    
        @Bean
        public PageInterceptor pageInterceptor() {
            return new PageInterceptor();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    legou-order/legou-order-

    service/src/main/java/com/legou/order/config/JwtConfig.java
    package com.legou.order.config;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.io.ClassPathResource;
    import org.springframework.core.io.Resource;
    import org.springframework.security.oauth2.provider.token.TokenStore;
    import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
    import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
    import org.springframework.util.FileCopyUtils;
    import java.io.IOException;
    
    /**
     * @author
     * @version 1.0
     * @description
     * @createDate 2022/6/20 16:54
     **/
    @Configuration
    public class JwtConfig {
    
        public static final String public_cert = "mickey_public.key";
    
        @Autowired
        private JwtAccessTokenConverter jwtAccessTokenConverter;
        @Bean
        @Qualifier("tokenStore")
        public TokenStore tokenStore() {
            return new JwtTokenStore(jwtAccessTokenConverter);
        }
        @Bean
        protected JwtAccessTokenConverter jwtAccessTokenConverter() {
            JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
            Resource resource = new ClassPathResource(public_cert);
            String publicKey;
            try {
                publicKey = new
                        String(FileCopyUtils.copyToByteArray(resource.getInputStream()));
            }catch (IOException e) {
                throw new RuntimeException(e);
            }
            converter.setVerifierKey(publicKey); //设置校验公钥
            converter.setSigningKey("mickey"); //设置证书签名密码,否则报错
            return converter;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    legou-order/legou-order-service/src/main/java/com/legou/order/config/ResourceServerConfiguration.java

    package com.legou.order.config;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
    import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
    import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
    import org.springframework.security.oauth2.provider.token.TokenStore;
    
    /**
     * @author
     * @version 1.0
     * @description
     * @createDate 2022/6/20 16:55
     **/
    @Configuration
    @EnableResourceServer
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
    
        @Autowired
        private TokenStore tokenStore;
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
            http.csrf().disable()
                    .authorizeRequests()
                    .antMatchers("/**").permitAll(); // 未认证也可以访问
            // .antMatchers("/**").authenticated();
        }
    
        @Override
        public void configure(ResourceServerSecurityConfigurer resources) throws
                Exception {
            resources.tokenStore(tokenStore);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    6.1.3 添加购物⻋

    (1) 思路分析

    ⽤户添加购物⻋,将要加⼊购物⻋的商品存⼊到Redis中即可。⼀个⽤户可以将多件商品加⼊购物⻋,存储到Redis中的数据可以采⽤Hash类型。
    在这里插入图片描述
    选Hash类型可以将⽤户的⽤户名作为namespace,将指定商品加⼊购物⻋,则往对应的namespace中增加⼀个key和value,key是商品ID,value是加⼊购物⻋的商品详情,如下图:
    在这里插入图片描述

    (2) 代码实现

    1.实体类

    订单主表

    package com.lxs.legou.order.po;
    
    import com.baomidou.mybatisplus.annotation.TableField;
    import com.baomidou.mybatisplus.annotation.TableName;
    import com.lxs.legou.core.po.BaseEntity;
    import lombok.Data;
    import java.util.Date;
    
    /**
     * @Des 新职课商城项⽬ 订单主表
     * @Author 雪松
     * @Date 2020/11/30 14:20
     */
    @Data
    @TableName("order_")
    class Order extends BaseEntity {
    
        @TableField("total_num_")
        private Integer totalNum;//数量合计
        @TableField("total_money_")
        private Integer totalMoney;//⾦额合计
        @TableField("pre_money_")
        private Integer preMoney;//优惠⾦额
        @TableField("post_fee_")
        private Integer postFee;//邮费
        @TableField("pay_money_")
        private Integer payMoney;//实付⾦额
        @TableField("pay_type_")
        private String payType;//⽀付类型,1、在线⽀付、0 货到付款
        @TableField("create_time_")
        private Date createTime;//订单创建时间
        @TableField("update_time_")
        private Date updateTime;//订单更新时间
        @TableField("pay_time_")
        private Date payTime;//付款时间
        @TableField("consign_time_")
        private Date consignTime;//发货时间
        @TableField("end_time_")
        private Date endTime;//交易完成时间
        @TableField("close_time_")
        private Date closeTime;//交易关闭时间
        @TableField("shipping_name_")
        private String shippingName;//物流名称订单明细
        @TableField("shipping_code_")
        private String shippingCode;//物流单号
        @TableField("username_")
        private String username;//⽤户名称
        @TableField("buyer_message_")
        private String buyerMessage;//买家留⾔
        @TableField("buyer_rate_")
        private String buyerRate;//是否评价
        @TableField("receiver_contact_")
        private String receiverContact;//收货⼈
        @TableField("receiver_mobile_")
        private String receiverMobile;//收货⼈⼿机
        @TableField("receiver_address_")
        private String receiverAddress;//收货⼈地址
        @TableField("source_type_")
        private String sourceType;//订单来源:1:web,2:app,3:微信公众号,4:微信⼩程序 5H5⼿机⻚⾯
        @TableField("transaction_id_")
        private String transactionId;//交易流⽔号
        @TableField("order_status_")
        private String orderStatus;//订单状态,0:未完成,1:已完成,2:已退货
        @TableField("pay_status_")
        private String payStatus;//⽀付状态,0:未⽀付,1:已⽀付,2:⽀付失败
        @TableField("consign_status_")
        private String consignStatus;//发货状态,0:未发货,1:已发货,2:已收货
        @TableField("is_delete_")
        private String isDelete;//是否删除
       
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    订单明细

    package com.lxs.legou.order.po;
    
    import com.baomidou.mybatisplus.annotation.TableField;
    import com.baomidou.mybatisplus.annotation.TableName;
    import com.lxs.legou.core.po.BaseEntity;
    import lombok.Data;
    
    /**
     * @author
     * @version 1.0
     * @description 订单明细
     * @createDate 2022/6/21 16:19
     **/
    @Data
    @TableName("order_item_")
    public class OrderItem extends BaseEntity {
        @TableField("category_id1_")
        private Long categoryId1;//1级分类
        @TableField("category_id2_")
        private Long categoryId2;//2级分类
        @TableField("category_id3_")
        private Long categoryId3;//3级分类
        @TableField("spu_id_")
        private Long spuId;//SPU_ID
        @TableField("sku_id_")
        private Long skuId;//SKU_ID
        @TableField("order_id_")
        private String orderId;//订单ID
        @TableField("name_")
        private String name;//商品名称
        @TableField("price_")
        private Long price;//单价
        @TableField("num_")
        private Integer num;//数量
        @TableField("money_")
        private Long money;//总⾦额
        @TableField("pay_money_")
        private Long payMoney;//实付⾦额
        @TableField("image_")
        private String image;//图⽚地址
        @TableField("weight_")
        private Integer weight;//重量
        @TableField("post_fee_")
        private Integer postFee;//运费
        @TableField("is_return_")
        private String isReturn;//是否退货,0:未退货,1:已退货
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    2. Feign客户端代理

    legou-order/legou-order-

    service/src/main/java/com/legou/order/client/SkuClient.java
    package com.legou.order.client;
    
    import com.lxs.legou.item.api.SkuApi;
    import com.lxs.legou.item.po.Sku;
    import feign.hystrix.FallbackFactory;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.stereotype.Component;
    import org.springframework.web.bind.annotation.RequestMapping;
    import java.util.List;
    
    /**
     * @author
     * @version 1.0
     * @description
     * @createDate 2022/6/21 15:49
     **/
    @FeignClient(name = "item-service", fallback =
    SkuClient.SkuClientFallback.class)
    public interface SkuClient extends SkuApi {
    
        @Component
        @RequestMapping("/sku-fallback")
                //这个可以避免容器中requestMapping重复
        class SkuClientFallback implements SkuClient {
    
            private static final Logger LOGGER = LoggerFactory.getLogger(SkuClientFallback.class);
    
            @Override
            public List<Sku> selectSkusBySpuId(Long spuId) {
                LOGGER.error("异常发⽣,进⼊fallback⽅法");
                return null;
            }
            @Override
            public Sku edit(Long id) {
                LOGGER.error("异常发⽣,进⼊fallback⽅法");
                return null;
            }
    
        }
    
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    legou-order/legou-order-service/src/main/java/com/legou/order/client/SpuClient.java

    package com.legou.order.client;
    
    import com.lxs.legou.item.api.SpuApi;
    import com.lxs.legou.item.po.Spu;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.stereotype.Component;
    import org.springframework.web.bind.annotation.RequestMapping;
    import java.util.List;
    
    @FeignClient(name = "item-service", fallback = SpuClient.SpuClientFallback.class)
    public interface SpuClient extends SpuApi {
    
        @Component
        @RequestMapping("/spu-fallback") //这个可以避免容器中requestMapping重复
        class SpuClientFallback implements SpuClient {
            private static final Logger LOGGER =
                    LoggerFactory.getLogger(SpuClientFallback.class);
            @Override
            public List<Spu> selectAll() {
                LOGGER.error("异常发⽣,进⼊fallback⽅法");
                return null;
            }
            @Override
            public Spu edit(Long id) {
                LOGGER.error("异常发⽣,进⼊fallback⽅法");
                return null;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    3.业务层

    业务层接⼝
    legou-order/legou-order-service/src/main/java/com/legou/order/service/CartService.java

    package com.legou.order.service;
    import com.lxs.legou.order.po.OrderItem;
    import java.util.List;
    /**
    * @Des 新职课商城项⽬
    * @Author 雪松
    * @Date 2020/11/30 14:44
    */
    public interface CartService {
     
     /**
     * 添加购物⻋
     * @param id sku的ID
     * @param num 购买的数量
     * @param username 购买的商品的⽤户名
     */
     void add(Long id, Integer num, String username);
     
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    业务层接⼝实现类
    legou-order/legou-order-service/src/main/java/com/legou/order/service/impl/CartServiceImpl.java

    package com.legou.order.service.impl;
    import com.legou.order.client.SkuClient;
    import com.legou.order.client.SpuClient;
    import com.legou.order.service.CartService;
    import com.lxs.legou.item.po.Sku;
    import com.lxs.legou.item.po.Spu;
    import com.lxs.legou.order.po.OrderItem;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.stereotype.Service;
    import java.util.List;
    /**
    * @Des 新职课商城项⽬
    * @Author 雪松
    * @Date 2020/11/30 16:29
    */
    @Service
    public class CartServiceImpl implements CartService {
     @Autowired
     private SkuClient skuFeign;
     @Autowired
     private SpuClient spuFeign;
     @Autowired
     private RedisTemplate redisTemplate;
     @Override
     public void add(Long id, Integer num, String username) {
     //1.根据商品的SKU的ID 获取sku的数据
     Sku data = skuFeign.edit(id);
     if (data != null) {
     //2.根据sku的数据对象 获取 该SKU对应的SPU的数据
     Long spuId = data.getSpuId();
     Spu spu = spuFeign.edit(spuId);
     //3.将数据存储到 购物⻋对象(order_item)中
     OrderItem orderItem = new OrderItem();
     orderItem.setCategoryId1(spu.getCid1());
     orderItem.setCategoryId2(spu.getCid2());
     orderItem.setCategoryId3(spu.getCid3());
     orderItem.setSpuId(spu.getId());
     orderItem.setSkuId(id);
     orderItem.setName(data.getTitle());//商品的名称 sku的名称
     orderItem.setPrice(data.getPrice());//sku的单价
     orderItem.setNum(num);//购买的数量
     orderItem.setPayMoney(orderItem.getNum() * orderItem.getPrice());//
    单价* 数量
     orderItem.setImage(data.getImages());//商品的图⽚dizhi
     //4.数据添加到redis中 key:⽤户名 field:sku的ID value:购物⻋数据
    (order_item)
     redisTemplate.boundHashOps("Cart_" + username).put(id,
    orderItem);// hset key field value hget key field
    legou-order/legou-order-service/src/main/java/com/legou/order/controller/CartController.java
     }
     }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    4. 控制层

    legou-order/legou-order-service/src/main/java/com/legou/order/controller/CartController.java

    package com.legou.order.controller;
    import com.legou.order.config.TokenDecode;
    import com.legou.order.service.CartService;
    import com.lxs.legou.order.po.OrderItem;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.CrossOrigin;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import java.io.IOException;
    import java.util.List;
    /**
    * @Des 新职课商城项⽬
    * @Author 雪松
    * @Date 2020/11/30 18:04
    */
    @RestController
    @RequestMapping("/cart")
    @CrossOrigin
    public class CartController {
     @Autowired
     private CartService cartService;
     @Autowired
     private TokenDecode tokenDecode;
    	 /**
    	 * 添加购物⻋
    	 *
    	 * @param id 要购买的商品的SKU的ID
    	 * @param num 要购买的数量
    	 * @return
    	测试添加购物⻋,效果如下:
    	请求地址 http://localhost:9009/cart/add?num=6&id=2868393
    	Redis缓存中的数据
    	 */
     @RequestMapping("/add")
     public ResponseEntity add(Long id, Integer num) throws IOException {
     //springsecurity 获取当前的⽤户名 传递service
     String username = "lxs";
    // Map<String, String> userInfo = tokenDecode.getUserInfo();
    // String username = userInfo.get("username");
     System.out.println("⽤户名:"+username);
     cartService.add(id, num, username);
     return ResponseEntity.ok("添加成功");
     }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    测试添加购物⻋,效果如下:
    请求地址 http://localhost:9009/cart/add?num=6&id=2868393
    在这里插入图片描述

    5 feign调⽤异常

    可以使⽤fallbackFactory打印feign调⽤异常
    产⽣FallbackFactory组件
    在FeignClient注解中通过fallbackFactory属性指定配置上⾯的组件

    package com.legou.order.client;
    import com.lxs.legou.item.api.SkuApi;
    import com.lxs.legou.item.po.Sku;
    import feign.hystrix.FallbackFactory;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.stereotype.Component;
    import org.springframework.web.bind.annotation.RequestMapping;
    import java.util.List;
    @FeignClient(name = "item-service", /*fallback =
    SkuClient.SkuClientFallback.class*/ fallbackFactory =
    SkuClient.SkuClientFallbackFactory.class)
    public interface SkuClient extends SkuApi {
    
     @Component
     @RequestMapping("/sku-fallback")
     //这个可以避免容器中requestMapping重复
     class SkuClientFallback implements SkuClient {
     private static final Logger LOGGER =
    LoggerFactory.getLogger(SkuClientFallback.class);
     @Override
     public List<Sku> selectSkusBySpuId(Long spuId) {
     LOGGER.error("异常发⽣,进⼊fallback⽅法");
     return null;
     }
     @Override
     public Sku edit(Long id) {
     LOGGER.error("异常发⽣,进⼊fallback⽅法");
     return null;
     }
     }
     @Component
     @RequestMapping("/sku-fallback-factory")
     class SkuClientFallbackFactory implements FallbackFactory<SkuClient> {
     Logger logger =
    LoggerFactory.getLogger(SkuClientFallbackFactory.class);
     @Override
     public SkuClient create(Throwable throwable) {
     throwable.printStackTrace();
     logger.error(throwable.getMessage());
     return new SkuClient() {
     @Override
     public List<Sku> selectSkusBySpuId(Long spuId) {
     return null;
     }
     @Override
     public Sku edit(Long id) {
     return null;
     }
     };
     }
     }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    6.1.4 购物⻋列表

    (1)思路分析

    在这里插入图片描述

    (2)代码实现

    1.业务层

    业务层接⼝

    /**
     * 从redis中获取当前的⽤户的购物⻋的列表数据
     * @param username
     * @return
     */
     List<OrderItem> list(String username);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    业务层接⼝实现类

    @Override
     public List<OrderItem> list(String username) {
     List<OrderItem> orderItemList = redisTemplate.boundHashOps("Cart_" +
    username).values();
     return orderItemList;
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    2.控制层
    @RequestMapping("/list")
     public ResponseEntity<List<OrderItem>> list() throws IOException {
     String username = "lxs";
    // Map<String, String> userInfo = tokenDecode.getUserInfo();
    // String username = userInfo.get("username");
     System.out.println("哇塞::⽤户名:"+username);
     List<OrderItem> orderItemList = cartService.list(username);
     return new ResponseEntity<>(orderItemList, HttpStatus.OK);
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    3.测试

    使⽤Postman访问 GET http://localhost:9009/cart/list ,效果如下:
    在这里插入图片描述

    (3)问题处理

    1.删除商品购物⻋在这里插入图片描述

    我们发现个问题,就是⽤户将商品加⼊购物⻋,⽆论数量是正负,都会执⾏添加购物⻋,如果数量如果<=0,应该移除该商品的。
    修改changgou-service-order的com.changgou.order.service.impl.CartServiceImpl的add⽅法,添加如下代码:
    在这里插入图片描述

    if(num<=0){
     //删除掉原来的商品
     redisTemplate.boundHashOps("Cart_" + username).delete(id);
     return;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    6.2 微服务之间认证

    在这里插入图片描述

    6.2.1 传递管理员令牌

    使⽤场景:我们在授权中⼼微服务调⽤⽤户微服务时可以直接⽣成管理员令牌,通过header传递到⽤户微服务,之前没有实现是因为为了测试⽅便我们在⽤户为服务中设置了未认证也可以访问⽤户微服务。
    在这里插入图片描述
    开启认证访问后,处理逻辑应该为:
    在这里插入图片描述
    代码实现:
    产⽣管理员令牌的⼯具⽅法:
    auth-center/src/main/java/com/service/auth/serviceauth/utils/AdminToken.java

    package com.service.auth.serviceauth.utils;
    
    /**
     * @author
     * @version 1.0
     * @description
     * @createDate 2022/6/22 10:21
     **/
    
    import org.codehaus.jackson.map.ObjectMapper;
    import org.springframework.core.io.ClassPathResource;
    import org.springframework.security.jwt.Jwt;
    import org.springframework.security.jwt.JwtHelper;
    import org.springframework.security.jwt.crypto.sign.RsaSigner;
    import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;
    
    import java.io.IOException;
    import java.security.KeyPair;
    import java.security.interfaces.RSAPrivateKey;
    import java.util.HashMap;
    import java.util.Map;
    
    public class AdminToken {
        public static String adminToken() throws IOException {
            //证书⽂件
            String key_location = "mickey.jks";
            //密钥库密码
            String keystore_password = "mickey";
            //访问证书路径
            ClassPathResource resource = new ClassPathResource(key_location);
            //密钥⼯⼚
            KeyStoreKeyFactory keyStoreKeyFactory = new
                    KeyStoreKeyFactory(resource, keystore_password.toCharArray());
            //密钥的密码,此密码和别名要匹配
            String keypassword = "mickey";
            //密钥别名
            String alias = "mickey";
            //密钥对(密钥和公钥)
            KeyPair keyPair = keyStoreKeyFactory.getKeyPair(alias,
                    keypassword.toCharArray());
            //私钥
            RSAPrivateKey aPrivate = (RSAPrivateKey) keyPair.getPrivate();
            //定义payload信息
            Map<String, Object> tokenMap = new HashMap<String, Object>();
            tokenMap.put("user_name", "admin");
            tokenMap.put("client_id", "client");
            tokenMap.put("authorities", new String[] {"ROLE_ADMIN"});
            //⽣成jwt令牌
            Jwt jwt = JwtHelper.encode(new
                    ObjectMapper().writeValueAsString(tokenMap), new RsaSigner(aPrivate));
            //取出jwt令牌
            String token = jwt.getEncoded();
            return token;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    Feign拦截器
    auth-center/src/main/java/com/service/auth/serviceauth/interceptor/TokenRequestInterceptor.java

    package com.service.auth.serviceauth.interceptor;
    
    import com.service.auth.serviceauth.utils.AdminToken;
    import feign.RequestInterceptor;
    import feign.RequestTemplate;
    import org.springframework.stereotype.Component;
    
    import java.io.IOException;
    
    /**
     * @Des 新职课商城项目 -拦截器
     * @Author 雪松
     * @Date 2020/12/2 15:57
     */
    @Component
    public class TokenRequestInterceptor implements RequestInterceptor {
    
        @Override
        public void apply(RequestTemplate requestTemplate) {
            String token = null;
            try {
                token = AdminToken.adminToken();
            } catch (IOException e) {
                e.printStackTrace();
            }
            requestTemplate.header("Authorization", "Bearer " + token);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    6.2.2 传递当前⽤户令牌

    使⽤场景:购物⻋功能已经做完了,但⽤户我们都是硬编码写死的。⽤户要想将商品加⼊购物⻋,必须得先登录授权,然后通过header传递令牌到购物⻋微服务,购物⻋微服务通过Feign拦截器添加令牌传递到商品微服务,如下图所示:
    在这里插入图片描述

    1.创建拦截器

    legou-order/legou-order-service/src/main/java/com/legou/order/interceptor/MyFeignInterceptor.java

    package com.legou.order.interceptor;
    
    import feign.RequestInterceptor;
    import feign.RequestTemplate;
    import org.springframework.stereotype.Component;
    import org.springframework.web.context.request.RequestContextHolder;
    import org.springframework.web.context.request.ServletRequestAttributes;
    import javax.servlet.http.HttpServletRequest;
    import java.util.Enumeration;
    
    /**
     * @Des 新职课商城项⽬
     * @Author 雪松
     * @Date 2020/12/1 10:19
     */
    @Component
    public class MyFeignInterceptor implements RequestInterceptor {
        @Override
        public void apply(RequestTemplate requestTemplate) {
            // 获取当前请求头,传递给商品微服务,获取的是当前线程的request信息。这时候如果使用线程隔离,需要采用信号量隔离方案。不然会报错
            ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (requestAttributes != null) {
                //1.获取请求对象
                HttpServletRequest request = requestAttributes.getRequest();
                Enumeration<String> headerNames = request.getHeaderNames();
                if (headerNames != null) {
                    //2.获取请求对象中的所有的头信息(请求传递过来的)
                    while (headerNames.hasMoreElements()) {
                        String name = headerNames.nextElement();//头的名称
                        String value = request.getHeader(name);//头名称对应的值
                        System.out.println("name:" + name + "::::::::value:" +
                                value);
                        //3.将头信息传递给fegin (restTemplate)
                        requestTemplate.header(name,value);
                    }
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    2.测试

    在这里插入图片描述
    我们发现这块的ServletRequestAttributes始终为空RequestContextHolder.getRequestAttributes()
    该⽅法是从ThreadLocal变量⾥⾯取得相应信息的,当hystrix断路器的隔离策略为THREAD时,是⽆法取得ThreadLocal中的值。
    解决⽅案:hystrix隔离策略换为SEMAPHORE
    config-repo/application.yml
    在这里插入图片描述
    再次测试,效果如下:
    在这里插入图片描述

    6.2.3 获取⽤户数据

    (1) 数据分析

    ⽤户登录后,数据会封装到 SecurityContextHolder.getContext().getAuthentication() ⾥⾯,
    我们可以将数据从这⾥⾯取出,然后转换成 OAuth2AuthenticationDetails ,在这⾥⾯可以获取到令牌信息、令牌类型等,代码如下:
    在这里插入图片描述
    这⾥的tokenValue是加密之后的令牌数据,remoteAddress是⽤户的IP信息,tokenType是令牌类型。我们可以获取令牌加密数据后,使⽤公钥对它进⾏解密,如果能解密说明说句⽆误,如果不能解密⽤户
    也没法执⾏到这⼀步。解密后可以从明⽂中获取⽤户信息。

    代码实现
    1.⼯具类

    legou-order/legou-order-service/src/main/java/com/legou/order/config/TokenDecode.java

    package com.legou.order.config;
    
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.core.io.ClassPathResource;
    import org.springframework.core.io.Resource;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.security.jwt.Jwt;
    import org.springframework.security.jwt.JwtHelper;
    import org.springframework.security.jwt.crypto.sign.RsaVerifier;
    import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails;
    import org.springframework.stereotype.Component;
    
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.util.Map;
    import java.util.stream.Collectors;
    
    /**
     * @Des 新职课商城项目
     * @Author 雪松
     * @Date 2020/11/30 18:06
     */
    
    @Component
    public class TokenDecode {
    
        private static final String PUBLIC_KEY = "mickey_public.key";
    
        @Autowired
        private ObjectMapper objectMapper;
    
        // 获取令牌
        public String getToken() {
            OAuth2AuthenticationDetails authentication = (OAuth2AuthenticationDetails) SecurityContextHolder.getContext().getAuthentication().getDetails();
    
            String tokenValue = authentication.getTokenValue();
    
            return tokenValue;
        }
    
    
        /**
         * 获取当前的登录的用户的用户信息
         *
         * @return
         */
        public Map<String, String> getUserInfo() throws IOException {
            //1.获取令牌
            String token = getToken();
    
            //2.解析令牌  公钥
            String pubKey = getPubKey();
    
            Jwt jwt = JwtHelper.decodeAndVerify(token, new RsaVerifier(pubKey));
            String claims = jwt.getClaims();//{}
    
    
            System.out.println(claims);
            //3.返回
    //        Map<String,String> map = JSON.parseObject(claims, Map.class);
            Map<String, String> map = objectMapper.readValue(claims, Map.class);
            return map;
        }
    
        private String getPubKey() {
            Resource resource = new ClassPathResource(PUBLIC_KEY);
            try {
                InputStreamReader inputStreamReader = new InputStreamReader(resource.getInputStream());
                BufferedReader br = new BufferedReader(inputStreamReader);
                return br.lines().collect(Collectors.joining("\n"));
            } catch (IOException ioe) {
                return null;
            }
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    2.控制器

    在这里插入图片描述

    3.测试

    使⽤postman携带令牌访问
    在这里插入图片描述

    6.3 订单结⻚

    6.3.1 收件地址分析

    ⽤户从购物⻋⻚⾯点击结算,跳转到订单结算⻚,结算⻚需要加载⽤户对应的收件地址,如下图:
    在这里插入图片描述
    表结构分析:

    CREATE TABLE `address_` (
     `id_` int(11) NOT NULL AUTO_INCREMENT,
     `username_` varchar(50) DEFAULT NULL COMMENT '⽤户名',
     `province_` varchar(20) DEFAULT NULL COMMENT '省',
     `city_` varchar(20) DEFAULT NULL COMMENT '市',
     `area_` varchar(20) DEFAULT NULL COMMENT '县/区',
     `phone_` varchar(20) DEFAULT NULL COMMENT '电话',
     `address_` varchar(200) DEFAULT NULL COMMENT '详细地址',
     `contact_` varchar(50) DEFAULT NULL COMMENT '联系⼈',
     `is_default_` varchar(1) DEFAULT NULL COMMENT '是否是默认 1默认 0否',
     `alias_` varchar(50) DEFAULT NULL COMMENT '别名',
     PRIMARY KEY (`id_`)
    ) ENGINE=InnoDB AUTO_INCREMENT=66 DEFAULT CHARSET=utf8;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    我们可以根据⽤户登录名去address_表中查询对应的数据。

    6.3.2实现⽤户收件地址查询

    (1)代码实现

    实体类
    在⽤户微服务⼯程中增加Address
    legou-security/legou-securityinstance/src/main/java/com/lxs/legou/security/po/Address.java

    package com.lxs.legou.order.po;
    
    import com.baomidou.mybatisplus.annotation.TableField;
    import com.baomidou.mybatisplus.annotation.TableName;
    import com.lxs.legou.core.po.BaseEntity;
    import lombok.Data;
    
    /**
     * @author
     * @version 1.0
     * @description 收货地址
     * @createDate 2022/6/22 20:41
     **/
    @Data
    @TableName("address_")
    public class Address extends BaseEntity {
        @TableField("username_")
        private String username;//⽤户名
        @TableField("province_")
        private String provinceid;//省
        @TableField("city_")
        private String cityid;//市
        @TableField("area_")
        private String areaid;//县/区
        @TableField("phone_")
        private String phone;//电话
        @TableField("address_")
        private String address;//详细地址
        @TableField("contact_")
        private String contact;//联系⼈
        @TableField("is_default_")
        private String isDefault;//是否是默认 1默认 0否
        @TableField("alias_")
        private String alias;//别名
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    dao
    legou-security/legou-security-service/src/main/java/com/lxs/legou/security/dao/AddressDao.java

    package com.lxs.legou.security.dao;
    import com.lxs.legou.core.dao.ICrudDao;
    import com.lxs.legou.security.po.Address;
    /**
     * @Des 新职课商城项⽬
     * @Author 雪松
     * @Date 2020/12/3 11:38
     */
    public interface AddressDao extends ICrudDao<Address> {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    service
    legou-security/legou-security-service/src/main/java/com/lxs/legou/security/service/IAddressService.java

    package com.lxs.legou.security.service;
    import com.lxs.legou.core.service.ICrudService;
    import com.lxs.legou.security.po.Address;
    /**
     * @Des 新职课商城项⽬
     * @Author 雪松
     * @Date 2020/12/3 11:38
     */
    public interface IAddressService extends ICrudService<Address> {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    legou-security/legou-securityservice/src/main/java/com/lxs/legou/security/service/impl/AddressServiceImpl.java

    package com.legou.order.service.impl;
    
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.baomidou.mybatisplus.core.toolkit.Wrappers;
    import com.legou.order.service.IAddressService;
    import com.lxs.legou.core.service.impl.CrudServiceImpl;
    import com.lxs.legou.order.po.Address;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.stereotype.Service;
    import java.util.List;
    
    /**
     * @author
     * @version 1.0
     * @description
     * @createDate 2022/6/22 20:48
     **/
    @Service
    public class AddressServiceImpl extends CrudServiceImpl<Address> implements IAddressService {
    
        @Override
        public List<Address> list(Address entity) {
            //根据⽤户名查询⽤户收货地址
            QueryWrapper<Address> queryWrapper = Wrappers.<Address>query();
            if (StringUtils.isNotEmpty(entity.getUsername())) {
                queryWrapper.eq("username_", entity.getUsername());
            }
            return getBaseMapper().selectList(queryWrapper);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    legou-security/legou-securityservice/src/main/java/com/lxs/legou/security/controller/AddressController.java

    package com.legou.order.CartController;
    
    import com.legou.order.config.TokenDecode;
    import com.legou.order.service.IAddressService;
    import com.lxs.legou.core.controller.BaseController;
    import com.lxs.legou.order.po.Address;
    import io.swagger.annotations.ApiOperation;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RestController;
    import java.io.IOException;
    import java.util.List;
    import java.util.Map;
    
    /**
     * @Des 新职课商城项⽬
     * @Author 雪松
     * @Date 2020/12/3 11:50
     */
    @RestController
    @RequestMapping(value = "/address")
    public class AddressController extends BaseController<IAddressService, Address>
    {
        @Autowired
        private TokenDecode tokenDecode;
    
    
        @Override
        @ApiOperation(value="查询", notes="根据实体条件查询")
        @RequestMapping(value = "/list", method = {RequestMethod.POST, RequestMethod.GET})
        public List<Address> list(Address entity) {
            Map<String, String> user = null;
            try {
                user = tokenDecode.getUserInfo();
            } catch (IOException e) {
                e.printStackTrace();
            }
            String username = user.get("user_name");
            entity.setUsername(username);
            // 根据当前用户查询收件人地址
            return service.list(entity);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    (2)测试

    使⽤postman携带令牌访问
    在这里插入图片描述

    (3)运送清单

    在这里插入图片描述
    运送清单其实就是购物⻋列表,直接查询之前的购物⻋列表即可,这⾥不做说明了。'”

    6.4 下单

    6.4.1 业务分析

    点击结算⻚提交订单的时候,会⽴即创建订单数据,创建订单数据会将数据存⼊到2张表中,分别是订表和订单明细表,此处还需要修改商品对应的库存数量,改变⽤户的积分,然后删除应该的购物⻋数据。
    在这里插入图片描述
    订单表结构如下:

    package com.lxs.legou.order.po;
    
    import com.baomidou.mybatisplus.annotation.IdType;
    import com.baomidou.mybatisplus.annotation.TableField;
    import com.baomidou.mybatisplus.annotation.TableId;
    import com.baomidou.mybatisplus.annotation.TableName;
    import com.lxs.legou.core.po.BaseEntity;
    import lombok.Data;
    import java.util.Date;
    
    /**
     * @Des 新职课商城项⽬ 订单主表
     * @Author 雪松
     * @Date 2020/11/30 14:20
     */
    @Data
    @TableName("order_")
    public class Order extends BaseEntity {
    
        /**
         * 采用雪花算法,获得主键
         */
        @TableId(value = "id_", type = IdType.INPUT)
        private Long id;
    
        @TableField("total_num_")
        private Long totalNum;//数量合计
        @TableField("total_money_")
        private Long totalMoney;//⾦额合计
        @TableField("pre_money_")
        private Long preMoney;//优惠⾦额
        @TableField("post_fee_")
        private Long postFee;//邮费
        @TableField("pay_money_")
        private Long payMoney;//实付⾦额
        @TableField("pay_type_")
        private String payType;//⽀付类型,1、在线⽀付、0 货到付款
        @TableField("create_time_")
        private Date createTime;//订单创建时间
        @TableField("update_time_")
        private Date updateTime;//订单更新时间
        @TableField("pay_time_")
        private Date payTime;//付款时间
        @TableField("consign_time_")
        private Date consignTime;//发货时间
        @TableField("end_time_")
        private Date endTime;//交易完成时间
        @TableField("close_time_")
        private Date closeTime;//交易关闭时间
        @TableField("shipping_name_")
        private String shippingName;//物流名称订单明细
        @TableField("shipping_code_")
        private String shippingCode;//物流单号
        @TableField("username_")
        private String username;//⽤户名称
        @TableField("buyer_message_")
        private String buyerMessage;//买家留⾔
        @TableField("buyer_rate_")
        private String buyerRate;//是否评价
        @TableField("receiver_contact_")
        private String receiverContact;//收货⼈
        @TableField("receiver_mobile_")
        private String receiverMobile;//收货⼈⼿机
        @TableField("receiver_address_")
        private String receiverAddress;//收货⼈地址
        @TableField("source_type_")
        private String sourceType;//订单来源:1:web,2:app,3:微信公众号,4:微信⼩程序 5H5⼿机⻚⾯
        @TableField("transaction_id_")
        private String transactionId;//交易流⽔号
        @TableField("order_status_")
        private String orderStatus;//订单状态,0:未完成,1:已完成,2:已退货
        @TableField("pay_status_")
        private String payStatus;//⽀付状态,0:未⽀付,1:已⽀付,2:⽀付失败
        @TableField("consign_status_")
        private String consignStatus;//发货状态,0:未发货,1:已发货,2:已收货
        @TableField("is_delete_")
        private String isDelete;//是否删除
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79

    订单明细表结构如下:

    package com.lxs.legou.order.po;
    
    import com.baomidou.mybatisplus.annotation.IdType;
    import com.baomidou.mybatisplus.annotation.TableField;
    import com.baomidou.mybatisplus.annotation.TableId;
    import com.baomidou.mybatisplus.annotation.TableName;
    import com.lxs.legou.core.po.BaseEntity;
    import lombok.Data;
    
    /**
     * @author
     * @version 1.0
     * @description 订单明细
     * @createDate 2022/6/21 16:19
     **/
    @Data
    @TableName("order_item_")
    public class OrderItem extends BaseEntity {
    
        /**
         * 采用雪花算法,获得主键
         */
        @TableId(value = "id_", type = IdType.INPUT)
        private Long id;
    
        @TableField("category_id1_")
        private Long categoryId1;//1级分类
        @TableField("category_id2_")
        private Long categoryId2;//2级分类
        @TableField("category_id3_")
        private Long categoryId3;//3级分类
        @TableField("spu_id_")
        private Long spuId;//SPU_ID
        @TableField("sku_id_")
        private Long skuId;//SKU_ID
        @TableField("order_id_")
        private long orderId;//订单ID
        @TableField("name_")
        private String name;//商品名称
        @TableField("price_")
        private Long price;//单价
        @TableField("num_")
        private Integer num;//数量
        @TableField("money_")
        private Long money;//总⾦额
        @TableField("pay_money_")
        private Long payMoney;//实付⾦额
        @TableField("image_")
        private String image;//图⽚地址
        @TableField("weight_")
        private Integer weight;//重量
        @TableField("post_fee_")
        private Integer postFee;//运费
        @TableField("is_return_")
        private String isReturn;//是否退货,0:未退货,1:已退货
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    6.4.2 下单实现

    下单的时候,先添加订单往order表中增加数据,再添加订单明细,往order_item表中增加数据。

    (1) ⽣成订单号

    分布式系统中,有⼀些需要使⽤全局唯⼀ID的场景,这种时候为了防⽌ID冲突可以使⽤36位的UUID,但是UUID有⼀些缺点,⾸先他相对⽐较⻓,另外UUID⼀般是⽆序的。有些时候我们希望能使⽤⼀种简单⼀些的ID,并且希望ID能够按照时间有序⽣成。⽽twitter的SnowFlake解决了这种需求,最初Twitter把存储系统从MySQL迁移到Cassandra,因为Cassandra没有顺序ID⽣成机制,所以开发了这样⼀套全局唯⼀ID⽣成服务。
    在这里插入图片描述
    所有⽣成的id按时间趋势递增
    分布式系统内不会产⽣重复id(因为有datacenterId和workerId来做区分)
    ⼯具类legou-common/src/main/java/com/lxs/legou/common/utils/IdWorker.java

    package com.lxs.legou.common.utils;
    
    import java.lang.management.ManagementFactory;
    import java.net.InetAddress;
    import java.net.NetworkInterface;
    
    /**
     * <p>名称:IdWorker.java</p>
     * <p>描述:分布式自增长ID</p>
     * <pre>
     *     Twitter的 Snowflake JAVA实现方案
     * </pre>
     * 核心代码为其IdWorker这个类实现,其原理结构如下,我分别用一个0表示一位,用—分割开部分的作用:
     * 1||0---0000000000 0000000000 0000000000 0000000000 0 --- 00000 ---00000 ---000000000000
     * 在上面的字符串中,第一位为未使用(实际上也可作为long的符号位),接下来的41位为毫秒级时间,
     * 然后5位datacenter标识位,5位机器ID(并不算标识符,实际是为线程标识),
     * 然后12位该毫秒内的当前毫秒内的计数,加起来刚好64位,为一个Long型。
     * 这样的好处是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由datacenter和机器ID作区分),
     * 并且效率较高,经测试,snowflake每秒能够产生26万ID左右,完全满足需要。
     * <p>
     * 64位ID (42(毫秒)+5(机器ID)+5(业务编码)+12(重复累加))
     *
     * @author Polim
     */
    public class IdWorker {
        // 时间起始标记点,作为基准,一般取系统的最近时间(一旦确定不能变动)
        private final static long twepoch = 1288834974657L;
        // 机器标识位数
        private final static long workerIdBits = 5L;
        // 数据中心标识位数
        private final static long datacenterIdBits = 5L;
        // 机器ID最大值
        private final static long maxWorkerId = -1L ^ (-1L << workerIdBits);
        // 数据中心ID最大值
        private final static long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
        // 毫秒内自增位
        private final static long sequenceBits = 12L;
        // 机器ID偏左移12位
        private final static long workerIdShift = sequenceBits;
        // 数据中心ID左移17位
        private final static long datacenterIdShift = sequenceBits + workerIdBits;
        // 时间毫秒左移22位
        private final static long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
    
        private final static long sequenceMask = -1L ^ (-1L << sequenceBits);
        /* 上次生产id时间戳 */
        private static long lastTimestamp = -1L;
        // 0,并发控制
        private long sequence = 0L;
    
        private final long workerId;
        // 数据标识id部分
        private final long datacenterId;
    
        public IdWorker(){
            this.datacenterId = getDatacenterId(maxDatacenterId);
            this.workerId = getMaxWorkerId(datacenterId, maxWorkerId);
        }
        /**
         * @param workerId
         *            工作机器ID
         * @param datacenterId
         *            序列号
         */
        public IdWorker(long workerId, long datacenterId) {
            if (workerId > maxWorkerId || workerId < 0) {
                throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
            }
            if (datacenterId > maxDatacenterId || datacenterId < 0) {
                throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
            }
            this.workerId = workerId;
            this.datacenterId = datacenterId;
        }
        /**
         * 获取下一个ID
         *
         * @return
         */
        public synchronized long nextId() {
            long timestamp = timeGen();
            if (timestamp < lastTimestamp) {
                throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
            }
    
            if (lastTimestamp == timestamp) {
                // 当前毫秒内,则+1
                sequence = (sequence + 1) & sequenceMask;
                if (sequence == 0) {
                    // 当前毫秒内计数满了,则等待下一秒
                    timestamp = tilNextMillis(lastTimestamp);
                }
            } else {
                sequence = 0L;
            }
            lastTimestamp = timestamp;
            // ID偏移组合生成最终的ID,并返回ID
            long nextId = ((timestamp - twepoch) << timestampLeftShift)
                    | (datacenterId << datacenterIdShift)
                    | (workerId << workerIdShift) | sequence;
    
            return nextId;
        }
    
        private long tilNextMillis(final long lastTimestamp) {
            long timestamp = this.timeGen();
            while (timestamp <= lastTimestamp) {
                timestamp = this.timeGen();
            }
            return timestamp;
        }
    
        private long timeGen() {
            return System.currentTimeMillis();
        }
    
        /**
         * <p>
         * 获取 maxWorkerId
         * </p>
         */
        protected static long getMaxWorkerId(long datacenterId, long maxWorkerId) {
            StringBuffer mpid = new StringBuffer();
            mpid.append(datacenterId);
            String name = ManagementFactory.getRuntimeMXBean().getName();
            if (!name.isEmpty()) {
             /*
              * GET jvmPid
              */
                mpid.append(name.split("@")[0]);
            }
          /*
           * MAC + PID 的 hashcode 获取16个低位
           */
            return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
        }
    
        /**
         * <p>
         * 数据标识id部分
         * </p>
         */
        protected static long getDatacenterId(long maxDatacenterId) {
            long id = 0L;
            try {
                InetAddress ip = InetAddress.getLocalHost();
                NetworkInterface network = NetworkInterface.getByInetAddress(ip);
                if (network == null) {
                    id = 1L;
                } else {
                    byte[] mac = network.getHardwareAddress();
                    id = ((0x000000FF & (long) mac[mac.length - 1])
                            | (0x0000FF00 & (((long) mac[mac.length - 2]) << 8))) >> 6;
                    id = id % (maxDatacenterId + 1);
                }
            } catch (Exception e) {
                System.out.println(" getDatacenterId: " + e.getMessage());
            }
            return id;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161

    (2)Dao

    legou-order/legou-order-service/src/main/java/com/lxs/legou/order/dao/OrderDao.java

    package com.lxs.legou.order.dao;
    import com.lxs.legou.core.dao.ICrudDao;
    import com.lxs.legou.order.po.Order;
    public interface OrderDao extends ICrudDao<Order> {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    mybatis\order\OrderDao.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.lxs.legou.order.dao.OrderDao">
     <select id="selectByPage" resultType="Order">
     select
     *
     from
     order_
     <where>
     <if test="username != null and username != ''">
     username_ = #{username}
     </if>
     </where>
     </select>
    </mapper>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    com.lxs.legou.order.dao.OrderItemDao

    package com.lxs.legou.order.dao;
    import com.lxs.legou.core.dao.ICrudDao;
    import com.lxs.legou.order.po.OrderItem;
    public interface OrderItemDao extends ICrudDao<OrderItem> {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    (3)Service

    com.lxs.legou.order.service.OrderService

    package com.lxs.legou.order.service;
    import com.lxs.legou.core.service.ICrudService;
    import com.lxs.legou.order.po.Order;
    public interface OrderService extends ICrudService<Order> {
     /**
     * 增加订单
     * @param order
     */
     public void add(Order order);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    com.lxs.legou.order.service.impl.OrderServiceImpl

    package com.legou.order.service.impl;
    
    import com.legou.order.client.SkuClient;
    import com.legou.order.client.UserClient;
    import com.legou.order.dao.IOrderItemDao;
    import com.legou.order.service.IOrderService;
    import com.lxs.legou.common.utils.IdWorker;
    import com.lxs.legou.core.service.impl.CrudServiceImpl;
    import com.lxs.legou.order.po.Order;
    import com.lxs.legou.order.po.OrderItem;
    import com.netflix.discovery.converters.Auto;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.stereotype.Service;
    
    import java.util.Date;
    import java.util.List;
    
    @Service
    public class OrderServiceImpl extends CrudServiceImpl<Order> implements IOrderService {
    
        @Autowired
        private IdWorker idWorker;
    
        @Autowired
        private RedisTemplate redisTemplate;
    
        @Autowired
        private IOrderItemDao orderItemDao;
    
        @Autowired
        private SkuClient skuClient;
    
        @Autowired
        private UserClient userClient;
    
        @Override
        public void add(Order order) {
            //1 添加订单主表(Order)数据
            order.setId(idWorker.nextId());
    
            //2 循环购物车数据,添加订单明细(OrderItem)数据
            List<OrderItem> cartList = redisTemplate.boundHashOps("Cart_" + order.getUsername()).values();
    
            Long totalNum =0l; //订单总数量
            long totalMoney = 0l; //订单总金额
            for (OrderItem orderItem : cartList) {
                totalNum += orderItem.getNum();
                totalMoney += orderItem.getPayMoney();
    
                orderItem.setId(idWorker.nextId());//订单选项的iD
                orderItem.setOrderId(order.getId());//订单的iD
                orderItem.setIsReturn("0");//未退货
                orderItemDao.insert(orderItem);
    
                //3 调用商品微服务,减库存
                skuClient.decrCount(orderItem.getNum(), orderItem.getSkuId());
            }
    
            order.setTotalNum(totalNum);//设置总数量
            order.setTotalMoney(totalMoney);//设置总金额
            order.setPayMoney(totalMoney);//设置实付金额
            order.setCreateTime(new Date());
            order.setUpdateTime(order.getCreateTime());
            order.setOrderStatus("0");//0:未完成
            order.setPayStatus("0");//未支付
            order.setConsignStatus("0");//未发货
            order.setIsDelete("0");//未删除
    
            getBaseMapper().insert(order);
    
            //4 增加用户积分
            userClient.addPoint(10l, order.getUsername());
    
            //5 删除redis中的购物车数据
            redisTemplate.delete("Cart_" + order.getUsername());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78

    (4)Controller

    com.lxs.legou.order.controller.OrderController
    package com.lxs.legou.order.controller;
    import com.lxs.legou.order.config.TokenDecode;
    import com.lxs.legou.order.service.OrderService;
    import com.lxs.legou.core.controller.BaseController;
    import com.lxs.legou.order.po.Order;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import java.io.IOException;
    @RestController
    @RequestMapping("/order")
    public class OrderController extends BaseController<OrderService, Order> {
     @Autowired
     private TokenDecode tokenDecode;
     /**
     * 添加订单
     * @param order
     * @return
     * @throws IOException
     */
     @PostMapping("/add")
     public ResponseEntity add(@RequestBody Order order) throws IOException {
     order.setUsername(tokenDecode.getUserInfo().get("user_name"));
     service.add(order);
     return ResponseEntity.ok("添加成功");
     }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    (5)测试

    在这里插入图片描述

    6.4.3 库存变更

    (1)业务分析

    上⾯操作只实现了下单操作,但对应的库存还没跟着⼀起减少,我们在下单之后,应该调⽤商品微服务,将下单的商品库存减少,销量增加。每次订单微服务只需要将⽤户名传到商品微服务,商品微服务通过⽤户名到Redis中查询对应的购物⻋数据,然后执⾏库存减少,库存减少需要控制当前商品库存>=销售数量.
    在这里插入图片描述
    如何控制库存数量>=销售数量呢?其实可以通过SQL语句实现,每次减少数量的时候,加个条件判断。where num>=#{num} 即可。

    (2)代码实现

    Dao
    修改legou-item/legou-item-service/src/main/java/com/lxs/legou/item/dao/SkuDao.java

    @Update(value="update sku_ set stock_ = stock_ - #{num} where id_ =#{skuId}
    and stock_ >= #{num}")
     public int decrCount(@Param("num") Integer num, @Param("skuId") Long
    skuId);
    
    • 1
    • 2
    • 3
    • 4

    service
    修改legou-item/legou-item-service/src/main/java/com/lxs/legou/item/service/ISkuService.java

    /**
     * 减库存
     * @param num
     * @param skuId
     */
     public void decrCount(Integer num, Long skuId);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    修改legou-item/legou-item-service/src/main/java/com/lxs/legou/item/service/impl/SkuServiceImpl.java

    @Override
     public void decrCount(Integer num, Long skuId) {
     ((SkuDao) getBaseMapper()).decrCount(num, skuId);
     }
    
    • 1
    • 2
    • 3
    • 4

    controller
    修改legou-item/legou-item-service/src/main/java/com/lxs/legou/item/controller/SkuController.java

    /**
     * 减库存
     * @param num
     * @param skuId
     */
     @PostMapping(value = "/decr-count")
     public void decrCount(@RequestParam("num") Integer num,
    @RequestParam("skuId") Long skuId) {
     service.decrCount(num, skuId);
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    注意:通过Feign调⽤Controller如果有多个参数,必须写@RequestParam,否则抛出下⾯异常

    Caused by: java.lang.IllegalStateException: Method has too many Body
    parameters: public abstract void
    com.lxs.legou.item.api.SkuApi.decrCount(java.lang.Integer,java.lang.Long)
    
    • 1
    • 2
    • 3

    Feign Client
    legou-item/legou-item-instance/src/main/java/com/lxs/legou/item/api/SkuApi.java

    package com.lxs.legou.item.api;
    import com.lxs.legou.item.po.Sku;
    import io.swagger.annotations.ApiOperation;
    import org.springframework.web.bind.annotation.*;
    import java.util.List;
    @RequestMapping(value = "/sku")
    public interface SkuApi {
     @ApiOperation(value="查询spu对应的sku", notes="根据spuId查询sku集合")
     @GetMapping("/select-skus-by-spuid/{id}")
     public List<Sku> selectSkusBySpuId(@PathVariable("id") Long spuId);
     @ApiOperation(value="加载", notes="根据ID加载")
     @GetMapping("/edit/{id}")
     public Sku edit(@PathVariable Long id);
     @PostMapping(value = "/decr-count")
     public void decrCount(@RequestParam("num") Integer num,
    @RequestParam("skuId") Long skuId);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    legou-order/legou-order-service/src/main/java/com/lxs/legou/order/client/SkuClient.java

    package com.lxs.legou.order.client;
    import com.lxs.legou.item.api.SkuApi;
    import com.lxs.legou.item.po.Sku;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.stereotype.Component;
    import org.springframework.web.bind.annotation.RequestMapping;
    import java.util.List;
    @FeignClient(name = "item-service", fallback =
    SkuClient.SkuClientFallback.class)
    public interface SkuClient extends SkuApi {
    
     @Component
     @RequestMapping("/sku-fallback")
     //这个可以避免容器中requestMapping重复
     class SkuClientFallback implements SkuClient {
     private static final Logger LOGGER =
    	
    LoggerFactory.getLogger(SkuClientFallback.class);
    
     @Override
     public List<Sku> selectSkusBySpuId(Long spuId) {
     LOGGER.error("异常发⽣,进⼊fallback⽅法");
     return null;
     }
     @Override
     public Sku edit(Long id) {
     LOGGER.error("异常发⽣,进⼊fallback⽅法");
     return null;
     }
     @Override
     public void decrCount(Integer num, Long skuId) {
     LOGGER.error("异常发⽣,进⼊fallback⽅法");
     }
     }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    (3)调⽤库存递减

    在这里插入图片描述

    (4)测试

    下单后库存递减
    在这里插入图片描述

    (5)超卖问题

    如果库存递减使⽤下⾯的伪代码会产⽣超卖现象,超卖现象的本质都是多线程数据同步问题,因为时分布式系统,不能单纯的加锁处理,如果处理下⾯的逻辑需要使⽤分布式锁,关于分布式锁,后⾯内容讲解,因为我们的代码直接执⾏语句,有数据库⾏级锁,不会产⽣超卖问题
    在这里插入图片描述

    6.4.4 增加积分

    ⽐如每次下单完成之后,给⽤户增加10个积分,⽀付完成后赠送优惠券,优惠券可⽤于⽀付时再次抵扣。我们先完成增加积分功能。如下表:point表示⽤户积分

    (1)代码实现

    dao
    修改legou-security/legou-security-service/src/main/java/com/lxs/legou/security/dao/UserDao.java

    /**
     * 增加积分
     * @param point
     * @param userName
     * @return
     */
     @Update(value="update user_ set point_ = point_ + #{point} where user_name_ =
    #{userName}")
     public int addPoint(@Param(value="point") Long point
    ,@Param(value="userName") String userName);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    service
    修改legou-security/legou-securityservice/src/main/java/com/lxs/legou/security/service/IUserService.java

    /**
     * 增加会员积分
     */
     public void addPoint(Long point, String username);
    
    • 1
    • 2
    • 3
    • 4

    修改legou-security/legou-securityservice/src/main/java/com/lxs/legou/security/service/impl/UserServiceImpl.java

    @Override
     public void addPoint(Long point, String username) {
     ((UserDao) getBaseMapper()).addPoint(point, username);
     }
    
    • 1
    • 2
    • 3
    • 4

    控制层
    修改legou-security/legou-securityservice/src/main/java/com/lxs/legou/security/controller/UserController.java

    /**
     * 增加积分
     * @param point
     * @param username
     */
     @GetMapping(value = "/add-point")
     public void addPoint(@RequestParam("point") Long point,
    @RequestParam("username") String username) {
     service.addPoint(point, username);
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    Feign添加
    修改legou-security/legou-security-instance/src/main/java/com/lxs/legou/security/api/UserApi.java

    @GetMapping(value = "/add-point")
     public void addPoint(@RequestParam("point") Long point,
    @RequestParam("username") String username);
    
    • 1
    • 2
    • 3
    (2)增加积分调⽤

    legou-order/legou-orderservice/src/main/java/com/lxs/legou/order/service/impl/OrderServiceImpl.java

    //4.增加积分 调⽤⽤户微服务的userfeign 增加积分
    userClient.addPoint(10l, order.getUsername())
    
    • 1
    • 2
    (3)测试

    在这里插入图片描述

  • 相关阅读:
    本地存储WebStorage
    轻舟程序创建的centos7.x磁盘合并到根的操作方法
    帝国cms改目录后打不开,帝国cms改目录生成后还是404
    怎样生成分布式的流水ID
    文心一言 VS 讯飞星火 VS chatgpt (177)-- 算法导论13.3 6题
    【面试题】JS使用parseInt()、正则截取字符串中数字
    基于钉钉的宜搭创建付款流程
    编辑视频无需第三方软件,在iPhone上也可以轻松编辑视频
    一个循环问题以及两个循环问题,其中两个循环需要两种不同方式实现
    HyperLynx(三)传输线类型及相关设置
  • 原文地址:https://blog.csdn.net/weixin_42688085/article/details/125414387