• 畅购商城_第10章_购物车


    畅购商城_第10章_购物车

    第10章 购物车

    学习目标

    • 能够通过SpringSecurity进行权限控制
    • 掌握购物车流程
    • 掌握购物车渲染
    • 微服务之间的认证访问

    1 SpringSecurity权限控制

    在这里插入图片描述

    用户每次访问微服务的时候,先去oauth2.0服务登录,登录后再访问微服务网关,微服务网关将请求转发给其他微服务处理。

    由于我们项目使用了微服务,任何用户都有可能使用任意微服务,此时我们需要控制相关权限,例如:普通用户角色不能使用用户的删除操作,只有管理员才可以使用,那么这个时候就需要使用到SpringSecurity的权限控制功能了。

    1.1 角色权限加载

    在changgou-user-oauth服务中,com.changgou.oauth.config.UserDetailsServiceImpl该类实现了加载用户相关信息,如下代码:

    在这里插入图片描述

    上述代码给登录用户定义了三个角色,分别为salesman,accountant,user,这一块我们目前使用的是硬编码方式将角色写死了。

    1.2 角色权限控制

    在每个微服务中,需要获取用户的角色,然后根据角色识别是否允许操作指定的方法,Spring Security中定义了四个支持权限控制的表达式注解,分别是@PreAuthorize@PostAuthorize@PreFilter@PostFilter。其中前两者可以用来在方法调用前或者调用后进行权限检查,后两者可以用来对集合类型的参数或者返回值进行过滤。在需要控制权限的方法上,我们可以添加@PreAuthorize注解,用于方法执行前进行权限检查,校验用户当前角色是否能访问该方法。

    (1)开启@PreAuthorize

    changgou-user-serviceResourceServerConfig类上添加@EnableGlobalMethodSecurity注解,用于开启@PreAuthorize的支持,代码如下:

    在这里插入图片描述

    (2)方法权限控制

    changgoug-service-user微服务的com.changgou.user.controller.UserController类的delete()方法上添加权限控制注解@PreAuthorize,代码如下:

    在这里插入图片描述

    (3)测试

    我们使用Postman测试,先创建令牌,然后将令牌数存放到头文件中访问微服务网关来调用user微服务的search方法,效果如下:

    地址:http://localhost:8001/api/user/search/1/10 提交方式:GET

    在这里插入图片描述

    发现上面无法访问,因为用户登录的时候,角色不包含admin角色,而search方法需要admin角色,所以被拦截了。

    我们再测试其他方法,其他方法没有配置拦截,所以用户登录后就会放行。

    访问http://localhost:8001/api/user

    效果如下:

    在这里插入图片描述

    1.3 小结

    如果希望一个方法能被多个角色访问,配置:@PreAuthorize("hasAnyAuthority('admin','user')")

    如果希望一个类都能被多个角色访问,在类上配置:@PreAuthorize("hasAnyAuthority('admin','user')")

    2 购物车

    购物车分为用户登录购物车和未登录购物车操作,国内知名电商京东用户登录和不登录都可以操作购物车,如果用户不登录,操作购物车可以将数据存储到Cookie,用户登录后购物车数据可以存储到Redis中,再将之前未登录加入的购物车合并到Redis中即可。

    淘宝天猫则采用了另外一种实现方案,用户要想将商品加入购物车,必须先登录才能操作购物车。

    我们今天实现的购物车是天猫解决方案,即用户必须先登录才能使用购物车功能。

    2.1 购物车业务分析

    (1)需求分析

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

    在这里插入图片描述

    (2)购物车实现思路

    在这里插入图片描述

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

    (3)表结构分析

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

    changgou_order数据中tb_order_item表:

    CREATE TABLE `tb_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

    2.2 添加购物车

    在这里插入图片描述

    2.2.1 获取sku数据

    goods服务中定义根据id查询sku对象实现

    @RestController
    @CrossOrigin
    @RequestMapping("/sku")
    public class SkuController {
    
    
        @Autowired
        private SkuService skuService;
    
         @GetMapping("/{id}")
        public Result<Sku> findById(@PathVariable("id") String id){
            Sku sku = skuService.findById(id);
            return new Result(true,StatusCode.OK,"查询成功",sku);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    2.2.2 定义feign接口

    goods-api工程中定义skuFeign接口,并定义查询方法

     /**
         * 根据id查询sku信息
         * @param id
         * @return
         */
        @GetMapping("/{id}")
        public Result<Sku> findById(@PathVariable("id") String id);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    2.2.3 订单服务添加依赖
    <dependency>
        <groupId>com.changgougroupId>
        <artifactId>changgou_service_goods_apiartifactId>
        <version>1.0-SNAPSHOTversion>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    2.2.4 订单服务启动类添加feign接口扫描
    @EnableFeignClients(basePackages = "com.changgou.goods.feign")
    
    • 1
    2.2.5 订单服务新建CartController
    @RestController
    @CrossOrigin
    @RequestMapping("/cart")
    public class CartController {
    
        @Autowired
        private CartService cartService;
    
        /**
         * 添加购物车
         * @param skuId 
         * @param num
         * @return
         */
        @GetMapping("/add")
        public Result add(@RequestParam("skuId") String skuId, @RequestParam("num") Integer num){
    
            //暂时静态,后续动态获取
            String username = "itcast";
            cartService.add(skuId,num,username);
    
            return new Result(true, StatusCode.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
    2.2.6 订单服务添加cartService,实现添加购物车

    代码如下:

    @Service
    public class CartServiceImpl implements CartService {
    
        private static final String CART="Cart_";
    
        @Autowired
        private RedisTemplate redisTemplate;
    
        @Autowired
        private SkuFeign skuFeign;
    
        @Autowired
        private SpuFeign spuFeign;
    
        /**
         * 添加购物车
         * @param skuId
         * @param num
         */
        @Override
        public void add(String skuId, Integer num,String username) {
    
            /**
             * 1)查询redis中的数据
             * 2)如果redis中已经有了,则追加数量,重新计算金额
             * 3)如果没有,将商品添加到缓存
             */
            OrderItem orderItem = (OrderItem) redisTemplate.boundHashOps(CART+username).get(skuId);
            if (orderItem != null){
                //存在,刷新购物车
                orderItem.setNum(orderItem.getNum()+num);
                orderItem.setMoney(orderItem.getNum()*orderItem.getPrice());
                orderItem.setPayMoney(orderItem.getNum()*orderItem.getPrice());
            }else{
                //不存在,新增购物车
                Result<Sku> skuResult = skuFeign.findById(skuId);
                Sku sku = skuResult.getData();
                Spu spu = spuFeign.findByspuId(sku.getSpuId());
    
                //将SKU转换成OrderItem
                orderItem = this.sku2OrderItem(sku,spu,num);
            }
    
            //存入redis
            redisTemplate.boundHashOps(CART+username).put(skuId,orderItem);
    
    
        }
    
        //sku转换为orderItem
        private OrderItem sku2OrderItem(Sku sku, Spu spu, Integer num) {
            OrderItem orderItem = new OrderItem();
            orderItem.setSpuId(sku.getSpuId());
            orderItem.setSkuId(sku.getId());
            orderItem.setName(sku.getName());
            orderItem.setPrice(sku.getPrice());
            orderItem.setNum(num);
            orderItem.setMoney(num*orderItem.getPrice());       //单价*数量
            orderItem.setPayMoney(num*orderItem.getPrice());    //实付金额
            orderItem.setImage(sku.getImage());
            orderItem.setWeight(sku.getWeight()*num);           //重量=单个重量*数量
    
            //分类ID设置
            orderItem.setCategoryId1(spu.getCategory1Id());
            orderItem.setCategoryId2(spu.getCategory2Id());
            orderItem.setCategoryId3(spu.getCategory3Id());
            return orderItem;
        }
    }
    
    • 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

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

    请求地址localhost:9004/cart/add?skuId=100000022652&num=1

    在这里插入图片描述

    Redis缓存中已经有商品了

    在这里插入图片描述

    2.4 购物车列表

    2.4.1 思路分析

    在这里插入图片描述

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

    3.4.2 代码实现

    (1)控制层

    com.changgou.order.controller.CartController类,添加购物车列表查询方法,代码如下:

    /***
     * 查询用户购物车列表
     * @return
    */
    @GetMapping(value = "/list")
    public Map list(){
        //暂时静态,后续修改
        String username = "itcast";
        return cartService.list(username);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    (2)业务层

    业务层接口

    com.changgou.order.service.CartService接口,添加购物车列表方法,代码如下:

    /***
      * 查询用户的购物车数据
      * @return
      */
     Map list(String username);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    业务层接口实现类

    com.changgou.order.service.impl.CartServiceImpl类,添加购物车列表实现方法,代码如下:

    /**
         * 获取购物车列表数据
         * @param username
         * @return
         */
        @Override
        public Map list(String username) {
            Map map = new HashMap();
    
            List<OrderItem> orderItemList = redisTemplate.boundHashOps(CART+username).values();
    
            map.put("orderItemList",orderItemList);
    
            //商品数量与总价格
            Integer totalNum = 0;
            Integer totalPrice = 0;
    
            for (OrderItem orderItem : orderItemList) {
                totalNum +=orderItem.getNum();
                totalPrice+=orderItem.getMoney();
            }
            map.put("totalNum",totalNum);
            map.put("totalPrice",totalPrice);
            return map;
        }
    
    • 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

    (3)测试

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

    在这里插入图片描述

    3 购物车渲染

    在这里插入图片描述

    如上图所示,用户每次将商品加入购物车,或者点击购物车列表的时候,先经过订单购物车后端渲染服务,再通过feign调用购物车订单微服务来实现购物车的操作,例如:加入购物车、购物车列表。

    3.1 购物车渲染服务搭建

    在changgou_web中搭建订单购物车微服务工程changgou_web_order,该工程主要实现购物车和订单的渲染操作。

    (1) pom.xml依赖

    <dependencies>
        <dependency>
            <groupId>com.changgougroupId>
            <artifactId>changgou_service_order_apiartifactId>
            <version>1.0-SNAPSHOTversion>
        dependency>
        <dependency>
            <groupId>com.changgougroupId>
            <artifactId>changgou_commonartifactId>
            <version>1.0-SNAPSHOTversion>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-thymeleafartifactId>
        dependency>
    dependencies>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    (2)application.yml配置

    server:
      port: 9011
    spring:
      application:
        name: order-web
      main:
        allow-bean-definition-overriding: true   #当遇到同样名字的时候,是否允许覆盖注册
      thymeleaf:
        cache: false
    eureka:
      client:
        service-url:
          defaultZone: http://127.0.0.1:6868/eureka
      instance:
        prefer-ip-address: true
    feign:
      hystrix:
        enabled: true
      client:
        config:
          default:   #配置全局的feign的调用超时时间  如果 有指定的服务配置 默认的配置不会生效
            connectTimeout: 60000 # 指定的是 消费者 连接服务提供者的连接超时时间 是否能连接  单位是毫秒
            readTimeout: 80000  # 指定的是调用服务提供者的 服务 的超时时间()  单位是毫秒
    #hystrix 配置
    hystrix:
      command:
        default:
          execution:
            timeout:
              #如果enabled设置为false,则请求超时交给ribbon控制
              enabled: true
            isolation:
              strategy: SEMAPHORE
              thread:
                # 熔断器超时时间,默认:1000/毫秒
                timeoutInMilliseconds: 80000
    #请求处理的超时时间
    ribbon:
      ReadTimeout: 4000
      #请求连接的超时时间
      ConnectTimeout: 3000
    
    • 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

    (3)创建启动类

    创建com.changgou.OrderWebApplication启动类,代码如下:

    @SpringBootApplication
    @EnableEurekaClient
    public class OrderWebApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(OrderWebApplication.class,args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    (4)静态资源拷贝

    资源\成品页面\cart.html页面拷贝到工程中,如下图:

    在这里插入图片描述

    3.2 购物车列表渲染

    3.2.1 Feign创建

    在changgou_service_order_api中添加CartFeign接口,并在接口中创建添加购物车和查询购物车列表,代码如下:

    @FeignClient(name="order")
    public interface CartFeign {
    
        /**
         * 添加购物车
         * @param skuId
         * @param num
         * @return
         */
        @GetMapping("/cart/add")
        public Result add(@RequestParam("skuId") String skuId, @RequestParam("num") Integer num);
    
        /***
         * 查询用户购物车列表
         * @return
         */
        @GetMapping(value = "/cart/list")
        public Map list();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    3.2.2 后台代码

    在changgou_web_order中创建com.changgou.order.controller.CartController,并添加查询购物车集合方法和添加购物车方法,代码如下:

    @Controller
    @RequestMapping("/wcart")
    public class CartController {
    
        @Autowired
        private CartFeign cartFeign;
    
        /**
         * 查询
         */
        @GetMapping("/list")
        public String list(Model model){
            Map map = cartFeign.list();
            model.addAttribute("items",map);
            return "cart";
        }
    
        /**
         * 添加购物车
         */
        @GetMapping("/add")
        @ResponseBody
        public Result<Map> add(String id, Integer num){
    
            cartFeign.add(id, num);
    
            Map map = cartFeign.list();
            return new Result<>(true, StatusCode.OK,"添加购物车成功",map);
        }
    }
    
    • 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
    3.2.3 前端页面

    (1)加载列表数据

    我们可以先在网页下面添加一个js脚本,用于加载购物车数据,并用一个对象存储,代码如下:

    
                            <div class="cart-list" v-for="item in items.orderItemList" :key="item.index">
                                <ul class="goods-list yui3-g">
                                    <li class="yui3-u-1-24">
                                        <input type="checkbox" name="chk_list" id="" value="" />
                                    li>
                                    <li class="yui3-u-6-24">
                                        <div class="good-item">
                                            <div class="item-img">
                                                <img :src="item.image" />
                                            div>
                                            <div class="item-msg">div>
                                        div>
                                    li>
                                    <li class="yui3-u-5-24">
                                        <div class="item-txt">{{item.name}}div>
                                    li>
                                    <li class="yui3-u-1-8">
                                        <span class="price">{{item.price}}span>
                                    li>
                                    <li class="yui3-u-1-8">
                                        <a href="javascript:void(0)" @click="add(item.skuId,-1)" class="increment mins">-a>
                                        <input autocomplete="off" type="text" v-model="item.num" @blur="add(item.skuId,item.num)" value="1" minnum="1" class="itxt" />
                                        <a href="javascript:void(0)" @click="add(item.skuId,1)" class="increment plus">+a>
                                    li>
                                    <li class="yui3-u-1-8">
                                        <span class="sum">{{item.num*item.price}}span>
                                    li>
                                    <li class="yui3-u-1-8">
                                        <a href="#none">删除a>
                                        <br />
                                        <a href="#none">移到收藏a>
                                    li>
                                ul>
                            div>
    
                        div>
                    div>
                div>
                <div class="cart-tool">
                    <div class="select-all">
                        <input class="chooseAll" type="checkbox" name="" id="" value="" />
                        <span>全选span>
                    div>
                    <div class="option">
                        <a href="#none">删除选中的商品a>
                        <a href="#none">移到我的关注a>
                        <a href="#none">清除下柜商品a>
                    div>
                    <div class="money-box">
                        <div class="chosed">已选择
                            <span>{{items.totalNum}}span>件商品div>
                        <div class="sumprice">
                            <span>
                                <em>总价(不含运费) :em>
                                <i class="summoney">¥{{items.totalMoney}}i>
                            span>
                            <span>
                                <em>已节省:em>
                                <i>-¥20.00i>
                            span>
                        div>
                        <div class="sumbtn">
                            <a class="sum-btn" href="getOrderInfo.html" target="_blank">结算a>
                        div>
                    div>
                div>
    <script th:inline="javascript">
            var app = new Vue({
                el: '#app',
                data() {
                    return {
                        items: [[${items}]]
                    }
                },
                methods:{
                    add:function (skuId, num) {
                        axios.get("/wcart/add?skuId="+skuId+"&num="+num).then(function (response) {
                            if (response.data.flag){
                                app.items=response.data.data
                            }
                        })
                    }
                }
            })
        script>
    
    • 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
    3.2.4 购物车渲染服务、订单服务对接网关
    1. 修改微服务网关changgou-gateway-web的application.yml配置文件,添加order的路由过滤配置,配置如下:
                #订单微服务
                - id: changgou_order_route
                  uri: lb://order
                  predicates:
                  - Path=/api/cart/**,/api/categoryReport/**,/api/orderConfig/**,/api/order/**,/api/orderItem/**,/api/orderLog/**,/api/preferential/**,/api/returnCause/**,/api/returnOrder/**,/api/returnOrderItem/**
                  filters:
                  - StripPrefix=1
                #购物车订单渲染微服务
                - id: changgou_order_web_route
                  uri: lb://order-web
                  predicates:
                  - Path=/api/wcart/**,/api/worder/**
                  filters:
                  - StripPrefix=1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    运行之后,效果如下:

    在这里插入图片描述

    3.3 商品数量变更

    用户可以点击+号或者-号,或者手动输入一个数字,然后更新购物车列表,我们可以给-+号一个点击事件,给数字框一个失去焦点事件,然后调用后台,实现购物车的更新。

    请求后台方法:

    在js里面创建一个请求后台的方法,代码如下:

    在这里插入图片描述

    添加事件:

    在±号和数字框那里添加点击事件和失去焦点事件,然后调用上面的add方法,代码如下:
    在这里插入图片描述

    3.4 删除商品购物车

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

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

    在这里插入图片描述

    3.5 订单服务对接oauth

    1)配置公钥

    2)oauth依赖

    <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-starter-oauth2artifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    1. 添加配置类
    @Configuration
    @EnableResourceServer
    //开启方法上的PreAuthorize注解
    @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
    public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    
        //公钥
        private static final String PUBLIC_KEY = "public.key";
    
        /***
         * 定义JwtTokenStore
         * @param jwtAccessTokenConverter
         * @return
         */
        @Bean
        public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
            return new JwtTokenStore(jwtAccessTokenConverter);
        }
    
        /***
         * 定义JJwtAccessTokenConverter
         * @return
         */
        @Bean
        public JwtAccessTokenConverter jwtAccessTokenConverter() {
            JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
            converter.setVerifierKey(getPubKey());
            return converter;
        }
        /**
         * 获取非对称加密公钥 Key
         * @return 公钥 Key
         */
        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;
            }
        }
    
        /***
         * Http安全配置,对每个到达系统的http请求链接进行校验
         * @param http
         * @throws Exception
         */
        @Override
        public void configure(HttpSecurity http) throws Exception {
            //所有请求必须认证通过
            http.authorizeRequests()
                    .anyRequest().
                    authenticated();    //其他地址需要认证授权
        }
    }
    
    • 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

    3.6 微服务间认证

    在这里插入图片描述

    如上图:因为微服务之间并没有传递头文件,所以我们可以定义一个拦截器,每次微服务调用之前都先检查下头文件,将请求的头文件中的令牌数据再放入到header中,再调用其他微服务即可。

    测试访问购物车,产生如下效果:

    在这里插入图片描述

    3.6.1 feign拦截器实现微服务间认证

    (1)创建拦截器

    在changgou_common服务中创建一个com.changgou.interceptor.FeignInterceptor拦截器,并将所有头文件数据再次加入到Feign请求的微服务头文件中,代码如下:

    @Component
    public class FeignInterceptor implements RequestInterceptor {
    
        @Override
        public void apply(RequestTemplate requestTemplate) {
    
            RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
    
            if (requestAttributes!=null){
    
                HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
                if (request!=null){
                    Enumeration headerNames = request.getHeaderNames();
                    if (headerNames!=null){
                        while (headerNames.hasMoreElements()){
                            String headerName = headerNames.nextElement();
                            if (headerName.equals("authorization")){
                                String headerValue = request.getHeader(headerName);
                                requestTemplate.header(headerName,headerValue);
                            }
                        }
                    }
                }
            }
            }
    }
    
    • 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
    1. 更改changgou_order_web启动类,添加拦截器声明
    @Bean
    public FeignInterceptor feignInterceptor(){
        return new FeignInterceptor();
    }
    
    • 1
    • 2
    • 3
    • 4

    3.8 动态获取当前登陆人

    3.8.1 数据分析

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

    在这里插入图片描述

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

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

    5.4.2 代码实现

    因为该类在很多微服务中都会被使用到,所以需要将该类添加到common工程中

    (1)在changgou-common工程中引入鉴权包

    
    
        io.jsonwebtoken
        jjwt
        0.9.0
        provided
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    (2)添加资源中的TokenDecode工具类到changgou-service-order微服务config包下,用于解密令牌信息

    代码如下:

    在这里插入图片描述

    (3)将该工具类以bean的形式声明到order服务中

    在这里插入图片描述

    (4)控制层获取用户数据

    在CartController中注入TokenDecode,并调用TokenDecode的getUserInfo方法获取用户信息,代码如下:

    注入TokenDecode:

    @Autowired
    private TokenDecode tokenDecode;
    
    • 1
    • 2

    获取用户名:

    在这里插入图片描述

    3.9 页面配置

    3.9.1 未登录时登录跳转

    在用户没有登录的情况下,直接访问购物车页面,效果如下:

    在这里插入图片描述

    我们可以发现,返回的只是个错误状态码,这个毫无意义,我们应该重定向到登录页面,让用户登录,我们可以修改网关的头文件,让用户每次没登录的时候,都跳转到登录页面。

    修改changgou-gateway-web的com.changgou.filter.AuthorizeFilter,代码如下:

    在这里插入图片描述

    此时再测试,就可以跳转到登录页面了。

    访问:http://localhost:8001/api/wcart/list

    3.9.2 登录成功跳转原地址

    刚才已经实现了未登录时跳转登录页,但是当登录成功后,并没有跳转到用户本来要访问的页面。

    要实现这个功能的话,可以将用户要访问的页面作为参数传递到登录控制器,由登录控制器根据参数完成路径跳转

    1. 修改网关携带当前访问URI

    修改changgou-gateway-web的com.changgou.filter.AuthorizeFilter,在之前的URL后面添加FROM参数以及FROM参数的值为request.getURI(),代码如下:

    在这里插入图片描述

    2)登录控制器获取参数

    修改changgou-user-oauth的com.changgou.oauth.controller.LoginRedirect记录访问来源页,代码如下:

    在这里插入图片描述

    1. 修改页面,获取来源页信息,并存到from变量中,登录成功后跳转到该地址。
      在这里插入图片描述

    此时再测试,就可以识别未登录用户,跳转到登录页,然后根据登录状态,如果登录成功,则跳转到来源页。

  • 相关阅读:
    07-JVM内存逃逸分析与实践
    vue+express、gitee pm2部署轻量服务器(20230923)
    CSS 注释语法与应用
    为啥https比http慢
    机器学习3-岭回归,Lasso,变量选择技术
    【图像增强】基于麻雀搜索算法与双伽马校正的图像自适应增强算法Matlab代码
    JVM ZGC垃圾收集器
    Vue3 实现文件预览 Word Excel pdf 图片 视频等格式 大全!!!!
    ASPICE风险管理与合规性策略:确保汽车软件开发的稳健与高效
    sign鉴权前置到网关,看看shenyu怎么处理
  • 原文地址:https://blog.csdn.net/guan1843036360/article/details/127643520