Chiriri's blog Chiriri's blog
首页
  • Java

    • JavaSE
    • JavaEE
    • 设计模式
  • Python

    • Python
    • Python模块
    • 机器学习
  • Golang

    • Golang
    • gRPC
  • 服务器

    • Linux
    • MySQL
    • NoSQL
    • Kubernetes
  • 项目

    • 传智健康
    • 畅购商城
  • Hadoop生态

    • Hadoop
    • Zookeeper
    • Hive
    • Flume
    • Kafka
    • Azkaban
    • Hbase
    • Scala
    • Spark
    • Flink
  • 大数据项目

    • 离线数仓
  • 青训营

    • 第四届青训营
  • HTML

    • HTML
    • JavaScript
  • Vue

    • Vue2
    • TypeScript
    • Vue3
    • Uni-APP
  • 数据结构与算法
  • C语言
  • 考研数据结构
  • 计算机组成原理
  • 计算机操作系统
  • Java基础

    • Java基础
    • Java集合
    • JUC
    • JVM
  • 框架

    • Spring
    • Dubbo
    • Spring Cloud
  • 数据库

    • MySQL
    • Redis
    • Elasticesearch
  • 消息队列

    • RabbitMQ
    • RocketMQ
  • 408

    • 计算机网络
    • 操作系统
    • 算法
  • 分类
  • 标签
  • 归档
  • 导航站
GitHub (opens new window)

Iekr

苦逼后端开发
首页
  • Java

    • JavaSE
    • JavaEE
    • 设计模式
  • Python

    • Python
    • Python模块
    • 机器学习
  • Golang

    • Golang
    • gRPC
  • 服务器

    • Linux
    • MySQL
    • NoSQL
    • Kubernetes
  • 项目

    • 传智健康
    • 畅购商城
  • Hadoop生态

    • Hadoop
    • Zookeeper
    • Hive
    • Flume
    • Kafka
    • Azkaban
    • Hbase
    • Scala
    • Spark
    • Flink
  • 大数据项目

    • 离线数仓
  • 青训营

    • 第四届青训营
  • HTML

    • HTML
    • JavaScript
  • Vue

    • Vue2
    • TypeScript
    • Vue3
    • Uni-APP
  • 数据结构与算法
  • C语言
  • 考研数据结构
  • 计算机组成原理
  • 计算机操作系统
  • Java基础

    • Java基础
    • Java集合
    • JUC
    • JVM
  • 框架

    • Spring
    • Dubbo
    • Spring Cloud
  • 数据库

    • MySQL
    • Redis
    • Elasticesearch
  • 消息队列

    • RabbitMQ
    • RocketMQ
  • 408

    • 计算机网络
    • 操作系统
    • 算法
  • 分类
  • 标签
  • 归档
  • 导航站
GitHub (opens new window)
  • JavaSE

  • JavaEE

  • Linux

  • MySQL

  • NoSQL

  • Python

  • Python模块

  • 机器学习

  • 设计模式

  • 传智健康

  • 畅购商城

    • Day01 项目搭建
    • Day02 FastDFS
    • Day03 微服务鉴权
    • Day04 新增和修改商品
    • Day05 广告缓存
    • Day06 监听数据库更新广告缓存
    • Day07 ES搜索
    • Day08 Thymeleaf
    • Day09 Oauth2
    • Day10 购物车渲染
      • SpringSecurity权限控制
        • 角色权限控制
      • 购物车
        • 添加购物车
      • 购物车渲染
        • 购物车列表渲染
        • 前端页面
        • 购物车渲染服务、订单服务对接网关
        • 如果商品数小于0则删除商品
      • 订单服务对接oauth
        • 微服务间认证
      • 动态获取当前登陆人
      • 页面配置
        • 未登录时登录跳转
        • 登录成功跳转原地址
    • Day11 订单结算
    • Day12 分布式事务解决方案
    • Day13 微信支付
    • Day14 订单处理
    • Day15 秒杀前端
    • Day16 秒杀后端
  • 博客项目

  • JVM

  • JUC

  • Golang

  • Kubernetes

  • 硅谷课堂

  • C

  • 源码

  • 神领物流

  • RocketMQ

  • 短链平台

  • 后端
  • 畅购商城
Iekr
2021-11-23
目录

Day10 购物车渲染

# Day10 购物车渲染

# SpringSecurity 权限控制

1562729873478

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

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

# 角色权限控制

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

  1. 开启 **@PreAuthorize**

    在 changgou-user-service 的 ResourceServerConfig 类上添加 @EnableGlobalMethodSecurity 注解,用于开启 @PreAuthorize 的支持,

    1562736198378

  2. 方法权限控制

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

    1562736271600

  3. 测试接口 由于我们令牌用户不是 admin 无法进行服务访问

    1564821856401

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

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

# 购物车

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

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

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

1558259384800

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

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

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

# 添加购物车

img

  1. 定义 feign 接口

    在 changou_service_goods_api 下的 feign 包的 SkuFeign 添加方法

    @GetMapping("/sku/{id}")
    public Result findById(@PathVariable String id);
    
    1
    2
  2. 订单服务添加依赖 changgou_service_order 添加依赖

    <dependency>
        <groupId>com.changgou</groupId>
        <artifactId>changgou_service_goods_api</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    
    1
    2
    3
    4
    5
  3. 在 changgou_service_order 启动类添加开启 feign 扫描

    @EnableFeignClients(basePackages = "com.changgou.goods.feign")
    
    1
  4. 订单服务新建 cartService 实现添加购物车

    package com.changgou.order.service;
    
    import java.util.Map;
    
    public interface CartService {
    
        //添加购物车
        void addCart(String skuId, Integer num, String username);
    
        //查询购物车数据
        Map list(String username);
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
  5. impl

    package com.changgou.order.service.impl;
    
    import com.changgou.goods.feign.SkuFeign;
    import com.changgou.goods.feign.SpuFeign;
    import com.changgou.goods.pojo.Sku;
    import com.changgou.goods.pojo.Spu;
    import com.changgou.order.pojo.OrderItem;
    import com.changgou.order.service.CartService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.stereotype.Service;
    
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    @Service
    public class CartServiceImpl implements CartService {
    
        private static final String CART = "cart_";
    
        @Autowired
        private RedisTemplate redisTemplate;
    
        @Autowired
        private SkuFeign skuFeign;
    
        @Autowired
        private SpuFeign spuFeign;
    
        @Override
        public void addCart(String skuId, Integer num, String username) {
            //1.查询redis中相对应的商品信息
            OrderItem orderItem = (OrderItem) redisTemplate.boundHashOps(CART + username).get(skuId);
            if (orderItem != null) {
                //2.如果当前商品在redis中存在 则更新商品的数量和价格
                orderItem.setNum(orderItem.getNum() + num); //更新数量
                orderItem.setMoney(orderItem.getNum() * orderItem.getPrice()); //总价
                orderItem.setPayMoney(orderItem.getNum() * orderItem.getPrice()); //实付金额
            } else {
                //3.如果当前商品在redis中不存在 将商品添加到redis中
                Sku sku = skuFeign.findById(skuId).getData();
                Spu spu = spuFeign.findSpuById(sku.getSpuId()).getData();
    
                //封装orderItem
                orderItem = this.sku2OrderItem(sku, spu, num);
            }
            //3.将orderItem添加到redis中
            redisTemplate.boundHashOps(CART + username).put(skuId, orderItem);
        }
    
        //查询购物车列表数据
        @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 totalMoney = 0;
    
            for (OrderItem orderItem : orderItemList) {
                totalMoney += orderItem.getMoney();
                totalNum += orderItem.getNum();
            }
    
            map.put("totalNum",totalNum);
            map.put("totalMoney",totalMoney);
            return map;
        }
    
        private OrderItem sku2OrderItem(Sku sku, Spu spu, Integer num) {
            OrderItem orderItem = new OrderItem();
            orderItem.setCategoryId1(spu.getCategory1Id());
            orderItem.setCategoryId2(spu.getCategory2Id());
            orderItem.setCategoryId3(spu.getCategory3Id());
            orderItem.setSpuId(spu.getId());
            orderItem.setSkuId(sku.getId());
            orderItem.setName(sku.getName());
            orderItem.setPrice(sku.getPrice());
            orderItem.setNum(num);
            orderItem.setMoney(orderItem.getPrice() * num);
            orderItem.setPayMoney(orderItem.getPrice() * num);
            orderItem.setImage(sku.getImage());
            orderItem.setWeight(sku.getWeight() * num);
            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
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
  6. controller

    package com.changgou.order.controller;
    
    
    import com.changgou.entity.Result;
    import com.changgou.entity.StatusCode;
    import com.changgou.order.service.CartService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.*;
    
    import java.util.Map;
    
    @RestController
    @RequestMapping("/cart")
    public class CartController {
    
        @Autowired
        private CartService cartService;
    
        @GetMapping("/addCart")
        public Result addCart(@RequestParam("skuId") String skuId, @RequestParam("num") Integer num) {
            //动态获取当前人信息
    
            String username = "itheima";
            cartService.addCart(skuId, num, username);
            return new Result(true, StatusCode.OK, "加入购物车成功");
        }
    
        @GetMapping("/list")
        public Map list() {
            //动态获取当前用户信息
            String username = "itheima";
            Map map = cartService.list(username);
            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
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35

# 购物车渲染

1558260759149

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

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

<dependencies>
    <dependency>
        <groupId>com.changgou</groupId>
        <artifactId>changgou_service_order_api</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
        <groupId>com.changgou</groupId>
        <artifactId>changgou_common</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
</dependencies>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

application

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

启动类 OrderWebApplication

package com.changgou.web.order;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients(basePackages = {"com.changgou.order.feign"})
public class OrderWebApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderWebApplication.class,args);
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

静态资源拷贝

1562833792176

# 购物车列表渲染

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

package com.changgou.order.feign;

import com.changgou.entity.Result;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.Map;

@FeignClient(name = "order")
public interface CartFeign {
    @GetMapping("/cart/addCart")
    Result addCart(@RequestParam("skuId") String skuId, @RequestParam("num") Integer num);

    @GetMapping("/cart/list")
    public Map list();

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

在 changgou_web_order 中创建 com.changgou.order.controller.CartController

package com.changgou.web.order.controller;


import com.changgou.entity.Result;
import com.changgou.entity.StatusCode;
import com.changgou.order.feign.CartFeign;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@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.addCart(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
31
32
33
34
35
36
37
38
39
40
41
42

# 前端页面

第 126 行

            <!-- vue loadlist -->
            <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"  />
        <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.00</i>
                </span>
        </div>
        <div class="sumbtn">
            <a class="sum-btn" href="getOrderInfo.html" target="_blank">结算</a>
        </div>
    </div>
</div>
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

vue 实例

<script th:inline="javascript">
    var app = new Vue({
        el: '#app',
        data() {
            return {
                items: [[${items}]]
            }
        },
        methods:{
            add:function (skuId, num) {
                axios.get("/api/wcart/add?id="+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

# 购物车渲染服务、订单服务对接网关

添加两个微服务路由

#订单微服务
- 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

# 如果商品数小于 0 则删除商品

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

修改 changgou-service-order 的 com.changgou.order.service.impl.CartServiceImpl 的 add 方法

添加以下逻辑代码

    if (orderItem.getNum() <= 0) {
        //删除改商品
        redisTemplate.boundHashOps(CART + username).delete(skuId);
        return;
    }
1
2
3
4
5

方法完整代码

@Override
public void addCart(String skuId, Integer num, String username) {
    //1.查询redis中相对应的商品信息
    OrderItem orderItem = (OrderItem) redisTemplate.boundHashOps(CART + username).get(skuId);
    if (orderItem != null) {
        //2.如果当前商品在redis中存在 则更新商品的数量和价格
        orderItem.setNum(orderItem.getNum() + num); //更新数量
        if (orderItem.getNum() <= 0) {
            //删除改商品
            redisTemplate.boundHashOps(CART + username).delete(skuId);
            return;
        }
        orderItem.setMoney(orderItem.getNum() * orderItem.getPrice()); //总价
        orderItem.setPayMoney(orderItem.getNum() * orderItem.getPrice()); //实付金额
    } else {
        //3.如果当前商品在redis中不存在 将商品添加到redis中
        Sku sku = skuFeign.findById(skuId).getData();
        Spu spu = spuFeign.findSpuById(sku.getSpuId()).getData();

        //封装orderItem
        orderItem = this.sku2OrderItem(sku, spu, num);
    }
    //3.将orderItem添加到redis中
    redisTemplate.boundHashOps(CART + username).put(skuId, 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

# 订单服务对接 oauth

将公钥复制到 changgou_service_order

添加依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
1
2
3
4

在 order 包下新建 config 包 创建 ResourceServerConfig

package com.changgou.order.config;

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.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.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.stream.Collectors;

@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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76

# 微服务间认证

img

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

1564849663409

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

package com.changgou.interceptor;

import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
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 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<String> headerNames = request.getHeaderNames();
                while (headerNames.hasMoreElements()) {
                    String headerName = headerNames.nextElement();
                    if ("authorization".equals(headerName)) {
                        String headerValue = request.getHeader(headerName); //jwt令牌

                        //传递令牌
                        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
27
28
29
30
31
32
33
34
35
36
37
38
39

更改 changgou_order_web 启动类,添加拦截器声明

@Bean
public FeignInterceptor feignInterceptor() {
    return new FeignInterceptor();
}
1
2
3
4

# 动态获取当前登陆人

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

<!--鉴权-->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.0</version>
    <scope>provided</scope>
</dependency>
1
2
3
4
5
6
7

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

package com.changgou.order.config;

import com.alibaba.fastjson.JSON;
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.util.StringUtils;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Map;
import java.util.stream.Collectors;

public class TokenDecode {
    //公钥
    private static final String PUBLIC_KEY = "public.key";

    private static String publickey="";

    /***
     * 获取用户信息
     * @return
     */
    public Map<String,String> getUserInfo(){
        //获取授权信息
        OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) SecurityContextHolder.getContext().getAuthentication().getDetails();
        //令牌解码
        return dcodeToken(details.getTokenValue());
    }

    /***
     * 读取令牌数据
     */
    public Map<String,String> dcodeToken(String token){
        //校验Jwt
        Jwt jwt = JwtHelper.decodeAndVerify(token, new RsaVerifier(getPubKey()));

        //获取Jwt原始内容
        String claims = jwt.getClaims();
        return JSON.parseObject(claims,Map.class);
    }


    /**
     * 获取非对称加密公钥 Key
     * @return 公钥 Key
     */
    public String getPubKey() {
        if(!StringUtils.isEmpty(publickey)){
            return publickey;
        }
        Resource resource = new ClassPathResource(PUBLIC_KEY);
        try {
            InputStreamReader inputStreamReader = new InputStreamReader(resource.getInputStream());
            BufferedReader br = new BufferedReader(inputStreamReader);
            publickey = br.lines().collect(Collectors.joining("\n"));
            return publickey;
        } 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

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

package com.changgou;

import com.changgou.order.config.TokenDecode;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import tk.mybatis.spring.annotation.MapperScan;

@SpringBootApplication
@EnableEurekaClient
@MapperScan(basePackages = {"com.changgou.order.dao"})
@EnableFeignClients(basePackages = "com.changgou.goods.feign")
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run( OrderApplication.class);
    }

    @Bean
    public TokenDecode tokenDecode(){
        return new TokenDecode();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

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

注入 TokenDecode:

@Autowired
private TokenDecode tokenDecode;
1
2
    @GetMapping("/addCart")
    public Result addCart(@RequestParam("skuId") String skuId, @RequestParam("num") Integer num) {
        //动态获取当前人信息

//        String username = "itheima";
        String username = tokenDecode.getUserInfo().get("username");
        cartService.addCart(skuId, num, username);
        return new Result(true, StatusCode.OK, "加入购物车成功");
    }

    @GetMapping("/list")
    public Map list() {
        //动态获取当前用户信息
//        String username = "itheima";
        String username = tokenDecode.getUserInfo().get("username");
        Map map = cartService.list(username);
        return map;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 页面配置

# 未登录时登录跳转

在用户没有登录的情况下,直接访问购物车页面,返回的只是个错误状态码,这个毫无意义,我们应该重定向到登录页面,让用户登录,我们可以修改网关的头文件,让用户每次没登录的时候,都跳转到登录页面。

修改 changgou-gateway-web 的 com.changgou.filter.AuthorizeFilter

private static final String LOGIN_URL = "http://localhost:8001/api/oauth/toLogin";

 //跳转登陆页面
    private Mono<Void> toLoginPage(String loginUrl, ServerWebExchange exchange) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.SEE_OTHER); //302
        response.getHeaders().set("Location", loginUrl);
        return response.setComplete();
    }
1
2
3
4
5
6
7
8
9

完整代码

package com.changgou.web.gateway.filter;

import com.changgou.web.gateway.service.AuthService;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component
public class AuthFilter implements GlobalFilter, Ordered {

    private static final String LOGIN_URL = "http://localhost:8001/api/oauth/toLogin";

    @Autowired
    private AuthService authService;


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        //1.判断请求路径是否为登陆请求 如果是 则直接放行
        String path = request.getURI().getPath();
        if ("/api/oauth/login".equals(path) || !URLFilter.hasAuthorize(path)) {
            //直接放行
            return chain.filter(exchange);
        }
        //2.不是登陆请求 从cookie中获取jti的值 如果值不存在 拒绝本次访问
        String jti = authService.getJtiFromCookie(request);
        if (StringUtils.isEmpty(jti)) {
            //为空 拒绝访问
//            response.setStatusCode(HttpStatus.UNAUTHORIZED);
//            return response.setComplete();
            //跳转到登陆页面
            return this.toLoginPage(LOGIN_URL, exchange);
        }


        //3.如果cookie中有jti的值 从redis中获取jwt的值 如果值不存在 拒绝本次访问
        String jwt = authService.getJwtFromRedis(jti);
        if (StringUtils.isEmpty(jwt)) {
            //过期或不存在 拒绝访问
//            response.setStatusCode(HttpStatus.UNAUTHORIZED);
//            return response.setComplete();
            //跳转到登陆页面
            return this.toLoginPage(LOGIN_URL, exchange);
        }

        //对当前的请求对象进行增强 让它携带令牌的信息
        request.mutate().header("Authorization", "Bearer " + jwt);
        return chain.filter(exchange);
    }

    //跳转登陆页面
    private Mono<Void> toLoginPage(String loginUrl, ServerWebExchange exchange) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.SEE_OTHER); //302
        response.getHeaders().set("Location", loginUrl);
        return response.setComplete();
    }

    @Override
    public int getOrder() {
        return 0;
    }
}
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

# 登录成功跳转原地址

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

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

修改网关携带当前访问 URI

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

1564908926330

package com.changgou.web.gateway.filter;

import com.changgou.web.gateway.service.AuthService;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component
public class AuthFilter implements GlobalFilter, Ordered {

    private static final String LOGIN_URL = "http://localhost:8001/api/oauth/toLogin";

    @Autowired
    private AuthService authService;


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        //1.判断请求路径是否为登陆请求 如果是 则直接放行
        String path = request.getURI().getPath();
        if ("/api/oauth/login".equals(path) || !URLFilter.hasAuthorize(path)) {
            //直接放行
            return chain.filter(exchange);
        }
        //2.不是登陆请求 从cookie中获取jti的值 如果值不存在 拒绝本次访问
        String jti = authService.getJtiFromCookie(request);
        if (StringUtils.isEmpty(jti)) {
            //为空 拒绝访问
//            response.setStatusCode(HttpStatus.UNAUTHORIZED);
//            return response.setComplete();
            //跳转到登陆页面
            return this.toLoginPage(LOGIN_URL+"?FROM="+request.getURI().getPath(), exchange);
        }


        //3.如果cookie中有jti的值 从redis中获取jwt的值 如果值不存在 拒绝本次访问
        String jwt = authService.getJwtFromRedis(jti);
        if (StringUtils.isEmpty(jwt)) {
            //过期或不存在 拒绝访问
//            response.setStatusCode(HttpStatus.UNAUTHORIZED);
//            return response.setComplete();
            //跳转到登陆页面
            return this.toLoginPage(LOGIN_URL, exchange);
        }

        //对当前的请求对象进行增强 让它携带令牌的信息
        request.mutate().header("Authorization", "Bearer " + jwt);
        return chain.filter(exchange);
    }

    //跳转登陆页面
    private Mono<Void> toLoginPage(String loginUrl, ServerWebExchange exchange) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.SEE_OTHER); //302
        response.getHeaders().set("Location", loginUrl);
        return response.setComplete();
    }

    @Override
    public int getOrder() {
        return 0;
    }
}
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

登录控制器获取参数

修改 changgou-user-oauth 的 com.changgou.oauth.controller.LoginRedirect 记录访问来源页

1564909939107

@RequestMapping("/toLogin")
public String toLogin(@RequestParam(value = "FROM",required = false,defaultValue = "") String from, Model model){
    model.addAttribute("from",from);
    return "login";
}
1
2
3
4
5

修改页面,获取来源页信息,并存到 from 变量中,登录成功后跳转到该地址

1564909961213

<script th:inline="javascript">
    var app = new Vue({
        el: "#app",
        data: {
            username: "",
            password: "",
            msg: "",
            from: [[${from}]],
        },
        methods: {
            login: function () {
                app.msg = "正在登录";
                axios.post("/api/oauth/login?username=" + app.username + "&password=" + app.password).then(function (response) {
                    if (response.data.flag) {
                        app.msg = "登录成功";
                        //跳转原地址
                        location.href = app.from
                    } else {
                        app.msg = "登录失败";
                    }
                })
            }
        }
    })
</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
编辑 (opens new window)
上次更新: 2023/12/06, 01:31:48
Day09 Oauth2
Day11 订单结算

← Day09 Oauth2 Day11 订单结算→

最近更新
01
k8s
06-06
02
进程与线程
03-04
03
计算机操作系统概述
02-26
更多文章>
Theme by Vdoing | Copyright © 2022-2025 Iekr | Blog
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式