java电商项目(九)

本文涉及的产品
实时计算 Flink 版,5000CU*H 3个月
检索分析服务 Elasticsearch 版,2核4GB开发者规格 1个月
实时数仓Hologres,5000CU*H 100GB 3个月
简介: 本文介绍了购物车功能的实现过程,包括用户登录后将商品添加至购物车、购物车列表展示及微服务之间的认证机制。具体步骤如下:1. **购物车功能**: - 用户选择商品并点击“加入购物车”,系统将商品信息存储到Redis中。2. **微服务之间认证**: - **传递管理员令牌**:在授权中心微服务调用用户微服务时,生成管理员令牌并通过Header传递。 - **传递当前用户令牌**:用户登录后,通过Feign拦截器将用户令牌传递到其他微服务。 - **获取用户数据**:通过`SecurityContextHolder`获取用户信息,并使用公钥解密令牌以验证用户

[TOC]

1 购物车

用户选择商品放入购物车,如果未登录,跳转到登录页先登录。然后将商品放入购物车。

1.1 购物车分析

(1)需求分析

用户在商品详细页点击加入购物车,提交商品SKU编号和购买数量,添加到购物车。购物车展示页面如下:

1558235718643

(2)购物车实现思路

1558259384800

我们实现的是用户登录后的购物车,用户将商品加入购物车的时候,直接将要加入购物车的详情存入到Redis即可。每次查看购物车的时候直接从Redis中获取。

(3)表结构分析

用户登录后将商品加入购物车,需要存储商品详情以及购买数量,购物车详情表如下:

数据中orderitem表:

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;

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

1.2 搭建订单购物车微服务

因为购物车功能比较简单,这里我们把订单和购物车微服务放在一个工程下

工程结构

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>

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>

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.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

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 的验签

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;

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@EnableCircuitBreaker
public class OrderApplication {

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

}

1.2.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;


@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();
    }

}

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;

@Configuration
public class JwtConfig {

    public static final String public_cert = "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("lxsong"); //设置证书签名密码,否则报错

        return converter;
    }
}

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;

@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.3 添加购物车

1.3.1 思路分析

1558235343539

用户添加购物车,将要加入购物车的商品存入到Redis中即可。一个用户可以将多件商品加入购物车,存储到Redis中的数据可以采用Hash类型。

选Hash类型可以将用户的用户名作为namespace,将指定商品加入购物车,则往对应的namespace中增加一个key和value,key是商品ID,value是加入购物车的商品详情,如下图:

1558260256362

1.3.2 代码实现

1.3.0 实体类

订单主表

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;


@Data
@TableName("order_")
public 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:微信小程序  5 H5手机页面

    @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;//是否删除

}

订单明细

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;

@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.3.2.1 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 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;
        }
    }

}

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.3.2.2 业务层

业务层接口

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;


public interface CartService {
   

    /**
     * 添加购物车
     * @param id  sku的ID
     * @param num 购买的数量
     * @param username  购买的商品的用户名
     */
    void add(Long id, Integer num, String username);

}

业务层接口实现类

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;


@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
        }

    }

}
1.3.2.3 控制层

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;


@RestController
@RequestMapping("/cart")
@CrossOrigin
public class CartController {
   

    @Autowired
    private CartService cartService;


    @Autowired
    private TokenDecode tokenDecode;

    /**
     * 添加购物车
     *
     * @param id  要购买的商品的SKU的ID
     * @param num 要购买的数量
     * @return
     */
    @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("添加成功");

    }

}

测试添加购物车,效果如下:

请求地址http://localhost:9009/cart/add?num=6&id=2868393

1562754547292

Redis缓存中的数据

1562754574692

1.3.2.4 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.4 购物车列表

1.4.1 思路分析

1558260759149

接着我们实现一次购物车列表操作。因为存的时候是根据用户名往Redis中存储用户的购物车数据的,所以我们这里可以将用户的名字作为key去Redis中查询对应的数据。

1.4.2 代码实现

(1)业务层

业务层接口

    /**
     * 从redis中获取当前的用户的购物车的列表数据
     * @param username
     * @return
     */
    List<OrderItem> list(String username);

业务层接口实现类

    @Override
    public List<OrderItem> list(String username) {
   
        List<OrderItem> orderItemList = redisTemplate.boundHashOps("Cart_" + username).values();
        return orderItemList;
    }

(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);
    }

(3)测试

使用Postman访问 GET http://localhost:9009/cart/list ,效果如下:

1562796534059

1.4.3 问题处理

(1)删除商品购物车

1562835328074

我们发现个问题,就是用户将商品加入购物车,无论数量是正负,都会执行添加购物车,如果数量如果<=0,应该移除该商品的。

修改changgou-service-order的com.changgou.order.service.impl.CartServiceImpl的add方法,添加如下代码:

1562835767995

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

2 微服务之间认证

2.1 传递管理员令牌

使用场景:我们在授权中心微服务调用用户微服务时可以直接生成管理员令牌,通过header传递到用户微服务,之前没有实现是因为为了测试方便我们在用户为服务中设置了未认证也可以访问用户微服务

开启认证访问后,处理逻辑应该为:

代码实现:

产生管理员令牌的工具方法:

auth-center/src/main/java/com/service/auth/serviceauth/utils/AdminToken.java

package com.service.auth.serviceauth.utils;

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 = "lxsong.jks";
        //密钥库密码
        String keystore_password = "lxsong";
        //访问证书路径
        ClassPathResource resource = new ClassPathResource(key_location);
        //密钥工厂
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(resource, keystore_password.toCharArray());
        //密钥的密码,此密码和别名要匹配
        String keypassword = "lxsong";
        //密钥别名
        String alias = "lxsong";
        //密钥对(密钥和公钥)
        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;
    }

}

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;


@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);
    }
}

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;


@Component
public class MyFeignInterceptor implements RequestInterceptor {
   
    @Override
    public void apply(RequestTemplate requestTemplate) {
   
        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);
                }
            }
        }


    }
}

(3)测试

1562839507997

我们发现这块的ServletRequestAttributes始终为空,RequestContextHolder.getRequestAttributes()该方法是从ThreadLocal变量里面取得相应信息的,当hystrix断路器的隔离策略为THREAD时,是无法取得ThreadLocal中的值。

解决方案:hystrix隔离策略换为SEMAPHORE

config-repo/application.yml

feign:
  hystrix:
    enabled: true #开启熔断

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 60000 #熔断超时时间
          strategy: SEMAPHORE

再次测试,效果如下:

1562839848861

2.3 获取用户数据

2.3.1 数据分析

用户登录后,数据会封装到SecurityContextHolder.getContext().getAuthentication()里面,我们可以将数据从这里面取出,然后转换成OAuth2AuthenticationDetails,在这里面可以获取到令牌信息、令牌类型等,代码如下:

1562801050157

这里的tokenValue是加密之后的令牌数据,remoteAddress是用户的IP信息,tokenType是令牌类型。

我们可以获取令牌加密数据后,使用公钥对它进行解密,如果能解密说明说句无误,如果不能解密用户也没法执行到这一步。解密后可以从明文中获取用户信息。

2.3.2 代码实现

(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;



@Component
public class TokenDecode {

    private static final String PUBLIC_KEY = "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;
        }
    }

}

(2) 控制器

(5)测试

使用postman携带令牌访问

1562801825256

相关实践学习
部署Stable Diffusion玩转AI绘画(GPU云服务器)
本实验通过在ECS上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。
目录
相关文章
|
2天前
|
安全 NoSQL Java
java电商项目(十)
本文介绍了电商系统中订单结算和下单流程的实现。主要包括: 1. **订单结页**: - **收件地址分析**:用户从购物车页面跳转到订单结算页,加载用户收件地址。地址信息存储在 `address_` 表中。 - **实现用户收件地址查询**:通过用户登录名查询收件地址,涉及实体类、DAO、Service 和 Controller 的实现。 2. **下单**: - **业务分析**:下单时创建订单数据,包括订单表 `order_` 和订单明细表 `order_item_`,同时修改商品库存、增加用户积分并删除购物车数据。
13 3
|
2天前
|
消息中间件 安全 Java
java电商项目(十一)
本文接续前几个文章的项目进行讲解!
8 1
|
2天前
|
缓存 NoSQL Java
java电商项目(十二)
本文接续前几个文章的项目进行讲解
11 1
|
2天前
|
监控 算法 Java
java电商项目(七)
微服务网关作为系统唯一对外的入口,位于客户端和服务端之间,处理非业务功能,如路由请求、鉴权、监控、缓存、限流等。它解决了客户端直接调用多个微服务带来的复杂性、跨域请求、认证复杂、难以重构等问题。常用的微服务网关技术有Nginx、Zuul和Spring Cloud Gateway。Spring Cloud Gateway因其集成断路器、路径重写和较好的性能而被广泛使用。本文介绍了如何使用Spring Cloud Gateway搭建后台网关系统,包括引入依赖、配置文件、跨域配置、路由过滤配置、负载均衡、限流等。此外,还详细讲解了RBAC权限数据管理、组织机构管理单点登录(SSO)及JWT鉴权的实现
8 1
|
2天前
|
canal 监控 JavaScript
java电商项目(六)
Thymeleaf 是一个类似于 FreeMarker 的模板引擎,能够完全替代 JSP。它支持动静结合,无网络时显示静态内容,有网络时用后台数据替换静态内容,并且与 Spring Boot 完美整合。本文介绍了如何使用 Thymeleaf 生成商品详情页的静态页面。具体步骤包括创建商品静态化微服务、配置项目依赖、创建 Controller 和 Service、生成静态页面、模板填充、静态资源过滤以及启动测试。此外,还介绍了如何通过 Canal 监听商品数据变化,自动触发静态页面的生成或删除。
8 1
|
2天前
|
SQL 自然语言处理 Java
java电商项目(五)
本文介绍了如何构建一个基于Elasticsearch的商品搜索微服务,主要包括以下几个部分: 1. **数据导入ES**: - 搭建搜索工程,创建`legou-search`项目,提供搜索服务和索引数据更新操作。 - 配置`pom.xml`文件,引入必要的依赖。 - 创建启动器和配置文件,配置Elasticsearch连接信息。 - 分析索引库数据格式,确定需要存储的数据字段。 - 实现商品微服务接口,调用其他微服务获取所需数据。 - 创建索引并导入数据,将SPU和SKU数据转换为索引库中的Goods对象。 2. **商品搜索*
10 1
|
2天前
|
canal NoSQL 关系型数据库
java电商项目(四)
本章介绍了如何通过Lua、OpenResty、Nginx限流及Canal的使用,实现电商门户首页的高并发解决方案。主要内容包括: 1. **商城门户搭建**:使用Vue和iView构建前端门户项目,介绍如何展示商品分类和广告数据,并通过Redis缓存提升访问速度。 2. **Lua基础**:介绍Lua的基本概念、特性、应用场景及安装步骤,并通过示例展示了Lua的基本语法和常用功能。 3. **OpenResty介绍**:详细说明OpenResty的特性和优势,包括如何安装OpenResty和配置Nginx,以及如何使用Lua脚本操作Nginx缓存和数据库。
8 1
|
2天前
|
存储 前端开发 JavaScript
java电商项目(二)
本文档详细介绍了商品分类和规格参数的实现过程。商品分类分为三级管理,主要用于首页商品导航和后台商品管理,采用树状结构存储。规格参数则用于描述商品的具体属性,包括SPU和SKU的定义,规格参数与分类绑定,支持搜索过滤。文档涵盖了表结构设计、实体类、持久层、业务层、控制层的实现,并提供了前端组件的示例代码,确保前后端无缝对接。
10 1
|
2天前
|
存储 安全 Java
java电商项目(八)
OAuth 2.0 是一种开放标准,允许用户授权第三方应用访问其在某一网站上的私密资源,而无需提供用户名和密码。它通过提供一个令牌(token)来实现这一功能。OAuth 2.0 主要包括四种授权模式:授权码模式、简化模式、密码模式和客户端模式。授权码模式是最常用的一种,适用于第三方平台登录功能。Spring Security OAuth 2.0 提供了强大的工具来实现授权服务器和资源服务器的集成,支持多种授权模式和令牌存储方式,如内存、数据库、JWT 和
10 0
|
2天前
|
前端开发 算法 JavaScript
java电商项目(三)
本文介绍了乐购商城的商品数据分析和管理功能。首先解释了SPU(标准产品单位)和SKU(库存量单位)的概念,以及它们在商品管理和销售中的作用。接着详细分析了SPU、SPU详情和SKU三个表的结构及其关系。文章还介绍了商品管理的需求分析、实现思路和后台代码,包括实体类、持久层、业务层和控制层的实现。最后,文章讲解了前端组件的设计和实现,包括列表组件、添加修改组件、商品描述、通用规格、SKU特有规格和SKU列表的处理。通过这些内容,读者可以全面了解乐购商城的商品管理和数据分析系统。
7 0