1. Day10 购物车渲染

2. SpringSecurity权限控制

10. Day10 购物车渲染 - 图1

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

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

2.1. 角色权限控制

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

  1. 开启@PreAuthorize
    changgou-user-serviceResourceServerConfig类上添加@EnableGlobalMethodSecurity注解,用于开启@PreAuthorize的支持,
    10. Day10 购物车渲染 - 图2

  2. 方法权限控制
    changgoug-service-user微服务的com.changgou.user.controller.UserController类的delete()方法上添加权限控制注解@PreAuthorize
    10. Day10 购物车渲染 - 图3

  3. 测试接口 由于我们令牌用户不是admin无法进行服务访问
    10. Day10 购物车渲染 - 图4

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

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

3. 购物车

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

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

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

10. Day10 购物车渲染 - 图5

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

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

changgou_order数据中tb_order_item表:

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

3.1. 添加购物车

10. Day10 购物车渲染 - 图6

  1. 定义feign接口
    在changou_service_goods_api下的feign包的SkuFeign添加方法
    1. @GetMapping("/sku/{id}")
    2. public Result findById(@PathVariable String id);
  1. 订单服务添加依赖 changgou_service_order添加依赖
    1. <dependency>
    2. <groupId>com.changgou</groupId>
    3. <artifactId>changgou_service_goods_api</artifactId>
    4. <version>1.0-SNAPSHOT</version>
    5. </dependency>
  1. 在 changgou_service_order启动类添加开启feign扫描
    1. @EnableFeignClients(basePackages = "com.changgou.goods.feign")
  1. 订单服务新建cartService 实现添加购物车 ```java package com.changgou.order.service;

import java.util.Map;

public interface CartService {

  1. //添加购物车
  2. void addCart(String skuId, Integer num, String username);
  3. //查询购物车数据
  4. Map list(String username);

}

  1. 5.
  2. impl
  3. ```java
  4. package com.changgou.order.service.impl;
  5. import com.changgou.goods.feign.SkuFeign;
  6. import com.changgou.goods.feign.SpuFeign;
  7. import com.changgou.goods.pojo.Sku;
  8. import com.changgou.goods.pojo.Spu;
  9. import com.changgou.order.pojo.OrderItem;
  10. import com.changgou.order.service.CartService;
  11. import org.springframework.beans.factory.annotation.Autowired;
  12. import org.springframework.data.redis.core.RedisTemplate;
  13. import org.springframework.stereotype.Service;
  14. import java.util.HashMap;
  15. import java.util.List;
  16. import java.util.Map;
  17. @Service
  18. public class CartServiceImpl implements CartService {
  19. private static final String CART = "cart_";
  20. @Autowired
  21. private RedisTemplate redisTemplate;
  22. @Autowired
  23. private SkuFeign skuFeign;
  24. @Autowired
  25. private SpuFeign spuFeign;
  26. @Override
  27. public void addCart(String skuId, Integer num, String username) {
  28. //1.查询redis中相对应的商品信息
  29. OrderItem orderItem = (OrderItem) redisTemplate.boundHashOps(CART + username).get(skuId);
  30. if (orderItem != null) {
  31. //2.如果当前商品在redis中存在 则更新商品的数量和价格
  32. orderItem.setNum(orderItem.getNum() + num); //更新数量
  33. orderItem.setMoney(orderItem.getNum() * orderItem.getPrice()); //总价
  34. orderItem.setPayMoney(orderItem.getNum() * orderItem.getPrice()); //实付金额
  35. } else {
  36. //3.如果当前商品在redis中不存在 将商品添加到redis中
  37. Sku sku = skuFeign.findById(skuId).getData();
  38. Spu spu = spuFeign.findSpuById(sku.getSpuId()).getData();
  39. //封装orderItem
  40. orderItem = this.sku2OrderItem(sku, spu, num);
  41. }
  42. //3.将orderItem添加到redis中
  43. redisTemplate.boundHashOps(CART + username).put(skuId, orderItem);
  44. }
  45. //查询购物车列表数据
  46. @Override
  47. public Map list(String username) {
  48. Map map = new HashMap();
  49. List<OrderItem> orderItemList = redisTemplate.boundHashOps(CART + username).values();
  50. map.put("orderItemList", orderItemList);
  51. //商品的总数量和总价格
  52. Integer totalNum = 0;
  53. Integer totalMoney = 0;
  54. for (OrderItem orderItem : orderItemList) {
  55. totalMoney += orderItem.getMoney();
  56. totalNum += orderItem.getNum();
  57. }
  58. map.put("totalNum",totalNum);
  59. map.put("totalMoney",totalMoney);
  60. return map;
  61. }
  62. private OrderItem sku2OrderItem(Sku sku, Spu spu, Integer num) {
  63. OrderItem orderItem = new OrderItem();
  64. orderItem.setCategoryId1(spu.getCategory1Id());
  65. orderItem.setCategoryId2(spu.getCategory2Id());
  66. orderItem.setCategoryId3(spu.getCategory3Id());
  67. orderItem.setSpuId(spu.getId());
  68. orderItem.setSkuId(sku.getId());
  69. orderItem.setName(sku.getName());
  70. orderItem.setPrice(sku.getPrice());
  71. orderItem.setNum(num);
  72. orderItem.setMoney(orderItem.getPrice() * num);
  73. orderItem.setPayMoney(orderItem.getPrice() * num);
  74. orderItem.setImage(sku.getImage());
  75. orderItem.setWeight(sku.getWeight() * num);
  76. return orderItem;
  77. }
  78. }
  1. controller ```java 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 {

  1. @Autowired
  2. private CartService cartService;
  3. @GetMapping("/addCart")
  4. public Result addCart(@RequestParam("skuId") String skuId, @RequestParam("num") Integer num) {
  5. //动态获取当前人信息
  6. String username = "itheima";
  7. cartService.addCart(skuId, num, username);
  8. return new Result(true, StatusCode.OK, "加入购物车成功");
  9. }
  10. @GetMapping("/list")
  11. public Map list() {
  12. //动态获取当前用户信息
  13. String username = "itheima";
  14. Map map = cartService.list(username);
  15. return map;
  16. }

}

  1. <a name="d2a9589e"></a>
  2. # 4. 购物车渲染
  3. ![](https://cdn.jsdelivr.net/gh/Iekrwh/images/md-images/1558260759149.png#alt=1558260759149)
  4. 接着我们实现一次购物车列表操作。因为存的时候是根据用户名往Redis中存储用户的购物车数据的,所以我们这里可以将用户的名字作为key去Redis中查询对应的数据。
  5. 在changgou_web中搭建订单购物车微服务工程`changgou_web_order`,该工程主要实现购物车和订单的渲染操作。 pom.xml依赖
  6. ```xml
  7. <dependencies>
  8. <dependency>
  9. <groupId>com.changgou</groupId>
  10. <artifactId>changgou_service_order_api</artifactId>
  11. <version>1.0-SNAPSHOT</version>
  12. </dependency>
  13. <dependency>
  14. <groupId>com.changgou</groupId>
  15. <artifactId>changgou_common</artifactId>
  16. <version>1.0-SNAPSHOT</version>
  17. </dependency>
  18. <dependency>
  19. <groupId>org.springframework.boot</groupId>
  20. <artifactId>spring-boot-starter-thymeleaf</artifactId>
  21. </dependency>
  22. </dependencies>

application

  1. server:
  2. port: 9011
  3. spring:
  4. application:
  5. name: order-web
  6. main:
  7. allow-bean-definition-overriding: true #当遇到同样名字的时候,是否允许覆盖注册
  8. thymeleaf:
  9. cache: false
  10. eureka:
  11. client:
  12. service-url:
  13. defaultZone: http://127.0.0.1:6868/eureka
  14. instance:
  15. prefer-ip-address: true
  16. feign:
  17. hystrix:
  18. enabled: true
  19. client:
  20. config:
  21. default: #配置全局的feign的调用超时时间 如果 有指定的服务配置 默认的配置不会生效
  22. connectTimeout: 60000 # 指定的是 消费者 连接服务提供者的连接超时时间 是否能连接 单位是毫秒
  23. readTimeout: 80000 # 指定的是调用服务提供者的 服务 的超时时间() 单位是毫秒
  24. #hystrix 配置
  25. hystrix:
  26. command:
  27. default:
  28. execution:
  29. timeout:
  30. #如果enabled设置为false,则请求超时交给ribbon控制
  31. enabled: true
  32. isolation:
  33. strategy: SEMAPHORE
  34. thread:
  35. # 熔断器超时时间,默认:1000/毫秒
  36. timeoutInMilliseconds: 80000
  37. #请求处理的超时时间
  38. ribbon:
  39. ReadTimeout: 4000
  40. #请求连接的超时时间
  41. ConnectTimeout: 3000

启动类 OrderWebApplication

  1. package com.changgou.web.order;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
  5. import org.springframework.cloud.openfeign.EnableFeignClients;
  6. @SpringBootApplication
  7. @EnableEurekaClient
  8. @EnableFeignClients(basePackages = {"com.changgou.order.feign"})
  9. public class OrderWebApplication {
  10. public static void main(String[] args) {
  11. SpringApplication.run(OrderWebApplication.class,args);
  12. }
  13. }

静态资源拷贝

10. Day10 购物车渲染 - 图7

4.1. 购物车列表渲染

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

  1. package com.changgou.order.feign;
  2. import com.changgou.entity.Result;
  3. import org.springframework.cloud.openfeign.FeignClient;
  4. import org.springframework.web.bind.annotation.GetMapping;
  5. import org.springframework.web.bind.annotation.RequestParam;
  6. import java.util.Map;
  7. @FeignClient(name = "order")
  8. public interface CartFeign {
  9. @GetMapping("/cart/addCart")
  10. Result addCart(@RequestParam("skuId") String skuId, @RequestParam("num") Integer num);
  11. @GetMapping("/cart/list")
  12. public Map list();
  13. }

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

  1. package com.changgou.web.order.controller;
  2. import com.changgou.entity.Result;
  3. import com.changgou.entity.StatusCode;
  4. import com.changgou.order.feign.CartFeign;
  5. import org.springframework.beans.factory.annotation.Autowired;
  6. import org.springframework.stereotype.Controller;
  7. import org.springframework.ui.Model;
  8. import org.springframework.web.bind.annotation.GetMapping;
  9. import org.springframework.web.bind.annotation.RequestMapping;
  10. import org.springframework.web.bind.annotation.ResponseBody;
  11. import org.springframework.web.bind.annotation.RestController;
  12. import java.util.HashMap;
  13. import java.util.Map;
  14. @Controller
  15. @RequestMapping("/wcart")
  16. public class CartController {
  17. @Autowired
  18. private CartFeign cartFeign;
  19. //查询购物车
  20. @GetMapping("/list")
  21. public String list(Model model) {
  22. Map map = cartFeign.list();
  23. model.addAttribute("items", map);
  24. return "cart";
  25. }
  26. //添加购物车
  27. @GetMapping("/add")
  28. @ResponseBody
  29. public Result<Map> add(String id, Integer num) {
  30. cartFeign.addCart(id, num);
  31. Map map = cartFeign.list();
  32. return new Result<>(true, StatusCode.OK, "添加购物车成功", map);
  33. }
  34. }

4.2. 前端页面

第126行

  1. <!-- vue loadlist -->
  2. <div class="cart-list" v-for="item in items.orderItemList" :key="item.index">
  3. <ul class="goods-list yui3-g">
  4. <li class="yui3-u-1-24">
  5. <input type="checkbox" name="chk_list" id="" value="" />
  6. </li>
  7. <li class="yui3-u-6-24">
  8. <div class="good-item">
  9. <div class="item-img">
  10. <img :src="item.image" />
  11. </div>
  12. <div class="item-msg"></div>
  13. </div>
  14. </li>
  15. <li class="yui3-u-5-24">
  16. <div class="item-txt">{{item.name}}</div>
  17. </li>
  18. <li class="yui3-u-1-8">
  19. <span class="price">{{item.price}}</span>
  20. </li>
  21. <li class="yui3-u-1-8">
  22. <a href="javascript:void(0)" @click="add(item.skuId,-1)" class="increment mins">-</a>
  23. <input autocomplete="off" type="text" v-model="item.num" @blur="add(item.skuId,item.num)" value="1" minnum="1" class="itxt" />
  24. <a href="javascript:void(0)" @click="add(item.skuId,1)" class="increment plus">+</a>
  25. </li>
  26. <li class="yui3-u-1-8">
  27. <span class="sum">{{item.num*item.price}}</span>
  28. </li>
  29. <li class="yui3-u-1-8">
  30. <a href="#none">删除</a>
  31. <br />
  32. <a href="#none">移到收藏</a>
  33. </li>
  34. </ul>
  35. </div>
  36. </div>
  37. </div>
  38. </div>
  39. <div class="cart-tool">
  40. <div class="select-all">
  41. <input class="chooseAll" type="checkbox" />
  42. <span>全选</span>
  43. </div>
  44. <div class="option">
  45. <a href="#none">删除选中的商品</a>
  46. <a href="#none">移到我的关注</a>
  47. <a href="#none">清除下柜商品</a>
  48. </div>
  49. <div class="money-box">
  50. <div class="chosed">已选择
  51. <span>{{items.totalNum}}</span>件商品</div>
  52. <div class="sumprice">
  53. <span>
  54. <em>总价(不含运费) :</em>
  55. <i class="summoney">¥{{items.totalMoney}}</i>
  56. </span>
  57. <span>
  58. <em>已节省:</em>
  59. <i>-¥20.00</i>
  60. </span>
  61. </div>
  62. <div class="sumbtn">
  63. <a class="sum-btn" href="getOrderInfo.html" target="_blank">结算</a>
  64. </div>
  65. </div>
  66. </div>

vue实例

  1. <script th:inline="javascript">
  2. var app = new Vue({
  3. el: '#app',
  4. data() {
  5. return {
  6. items: [[${items}]]
  7. }
  8. },
  9. methods:{
  10. add:function (skuId, num) {
  11. axios.get("/api/wcart/add?id="+skuId+"&num="+num).then(function (response) {
  12. if (response.data.flag){
  13. app.items=response.data.data
  14. }
  15. })
  16. }
  17. }
  18. })
  19. </script>

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

添加两个微服务路由

  1. #订单微服务
  2. - id: changgou_order_route
  3. uri: lb://order
  4. predicates:
  5. - Path=/api/cart/**,/api/categoryReport/**,/api/orderConfig/**,/api/order/**,/api/orderItem/**,/api/orderLog/**,/api/preferential/**,/api/returnCause/**,/api/returnOrder/**,/api/returnOrderItem/**
  6. filters:
  7. - StripPrefix=1
  8. #购物车订单渲染微服务
  9. - id: changgou_order_web_route
  10. uri: lb://order-web
  11. predicates:
  12. - Path=/api/wcart/**,/api/worder/**
  13. filters:
  14. - StripPrefix=1

4.4. 如果商品数小于0则删除商品

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

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

添加以下逻辑代码

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

方法完整代码

  1. @Override
  2. public void addCart(String skuId, Integer num, String username) {
  3. //1.查询redis中相对应的商品信息
  4. OrderItem orderItem = (OrderItem) redisTemplate.boundHashOps(CART + username).get(skuId);
  5. if (orderItem != null) {
  6. //2.如果当前商品在redis中存在 则更新商品的数量和价格
  7. orderItem.setNum(orderItem.getNum() + num); //更新数量
  8. if (orderItem.getNum() <= 0) {
  9. //删除改商品
  10. redisTemplate.boundHashOps(CART + username).delete(skuId);
  11. return;
  12. }
  13. orderItem.setMoney(orderItem.getNum() * orderItem.getPrice()); //总价
  14. orderItem.setPayMoney(orderItem.getNum() * orderItem.getPrice()); //实付金额
  15. } else {
  16. //3.如果当前商品在redis中不存在 将商品添加到redis中
  17. Sku sku = skuFeign.findById(skuId).getData();
  18. Spu spu = spuFeign.findSpuById(sku.getSpuId()).getData();
  19. //封装orderItem
  20. orderItem = this.sku2OrderItem(sku, spu, num);
  21. }
  22. //3.将orderItem添加到redis中
  23. redisTemplate.boundHashOps(CART + username).put(skuId, orderItem);
  24. }

5. 订单服务对接oauth

将公钥复制到 changgou_service_order

添加依赖

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

在order包下新建config包 创建 ResourceServerConfig

  1. package com.changgou.order.config;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Configuration;
  4. import org.springframework.core.io.ClassPathResource;
  5. import org.springframework.core.io.Resource;
  6. import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
  7. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
  8. import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
  9. import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
  10. import org.springframework.security.oauth2.provider.token.TokenStore;
  11. import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
  12. import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
  13. import java.io.BufferedReader;
  14. import java.io.IOException;
  15. import java.io.InputStreamReader;
  16. import java.util.stream.Collectors;
  17. @Configuration
  18. @EnableResourceServer
  19. //开启方法上的PreAuthorize注解
  20. @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
  21. public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
  22. //公钥
  23. private static final String PUBLIC_KEY = "public.key";
  24. /**** 定义JwtTokenStore
  25. * @param jwtAccessTokenConverter
  26. * @return
  27. */
  28. @Bean
  29. public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
  30. return new JwtTokenStore(jwtAccessTokenConverter);
  31. }
  32. /***
  33. * 定义JJwtAccessTokenConverter
  34. * @return
  35. */
  36. @Bean
  37. public JwtAccessTokenConverter jwtAccessTokenConverter() {
  38. JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
  39. converter.setVerifierKey(getPubKey());
  40. return converter;
  41. }
  42. /**
  43. * 获取非对称加密公钥 Key
  44. * @return 公钥 Key
  45. */
  46. private String getPubKey() {
  47. Resource resource = new ClassPathResource(PUBLIC_KEY);
  48. try {
  49. InputStreamReader inputStreamReader = new InputStreamReader(resource.getInputStream());
  50. BufferedReader br = new BufferedReader(inputStreamReader);
  51. return br.lines().collect(Collectors.joining("\n"));
  52. } catch (IOException ioe) {
  53. return null;
  54. }
  55. }
  56. /***
  57. * Http安全配置,对每个到达系统的http请求链接进行校验
  58. * @param http
  59. * @throws Exception
  60. */
  61. @Override
  62. public void configure(HttpSecurity http) throws Exception {
  63. //所有请求必须认证通过
  64. http.authorizeRequests()
  65. .anyRequest().
  66. authenticated(); //其他地址需要认证授权
  67. }
  68. }

5.1. 微服务间认证

10. Day10 购物车渲染 - 图8

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

10. Day10 购物车渲染 - 图9

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

  1. package com.changgou.interceptor;
  2. import feign.RequestInterceptor;
  3. import feign.RequestTemplate;
  4. import org.springframework.stereotype.Component;
  5. import org.springframework.web.context.request.RequestAttributes;
  6. import org.springframework.web.context.request.RequestContextHolder;
  7. import org.springframework.web.context.request.ServletRequestAttributes;
  8. import javax.servlet.http.HttpServletRequest;
  9. import java.util.Enumeration;
  10. @Component
  11. public class FeignInterceptor implements RequestInterceptor {
  12. @Override
  13. public void apply(RequestTemplate requestTemplate) {
  14. //传递令牌
  15. RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
  16. if (requestAttributes != null) {
  17. HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
  18. if (request != null) {
  19. Enumeration<String> headerNames = request.getHeaderNames();
  20. while (headerNames.hasMoreElements()) {
  21. String headerName = headerNames.nextElement();
  22. if ("authorization".equals(headerName)) {
  23. String headerValue = request.getHeader(headerName); //jwt令牌
  24. //传递令牌
  25. requestTemplate.header(headerName, headerValue);
  26. }
  27. }
  28. }
  29. }
  30. }
  31. }

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

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

6. 动态获取当前登陆人

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

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

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

  1. package com.changgou.order.config;
  2. import com.alibaba.fastjson.JSON;
  3. import org.springframework.core.io.ClassPathResource;
  4. import org.springframework.core.io.Resource;
  5. import org.springframework.security.core.context.SecurityContextHolder;
  6. import org.springframework.security.jwt.Jwt;
  7. import org.springframework.security.jwt.JwtHelper;
  8. import org.springframework.security.jwt.crypto.sign.RsaVerifier;
  9. import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails;
  10. import org.springframework.util.StringUtils;
  11. import java.io.BufferedReader;
  12. import java.io.IOException;
  13. import java.io.InputStreamReader;
  14. import java.util.Map;
  15. import java.util.stream.Collectors;
  16. public class TokenDecode {
  17. //公钥
  18. private static final String PUBLIC_KEY = "public.key";
  19. private static String publickey="";
  20. /***
  21. * 获取用户信息
  22. * @return
  23. */
  24. public Map<String,String> getUserInfo(){
  25. //获取授权信息
  26. OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) SecurityContextHolder.getContext().getAuthentication().getDetails();
  27. //令牌解码
  28. return dcodeToken(details.getTokenValue());
  29. }
  30. /***
  31. * 读取令牌数据
  32. */
  33. public Map<String,String> dcodeToken(String token){
  34. //校验Jwt
  35. Jwt jwt = JwtHelper.decodeAndVerify(token, new RsaVerifier(getPubKey()));
  36. //获取Jwt原始内容
  37. String claims = jwt.getClaims();
  38. return JSON.parseObject(claims,Map.class);
  39. }
  40. /**
  41. * 获取非对称加密公钥 Key
  42. * @return 公钥 Key
  43. */
  44. public String getPubKey() {
  45. if(!StringUtils.isEmpty(publickey)){
  46. return publickey;
  47. }
  48. Resource resource = new ClassPathResource(PUBLIC_KEY);
  49. try {
  50. InputStreamReader inputStreamReader = new InputStreamReader(resource.getInputStream());
  51. BufferedReader br = new BufferedReader(inputStreamReader);
  52. publickey = br.lines().collect(Collectors.joining("\n"));
  53. return publickey;
  54. } catch (IOException ioe) {
  55. return null;
  56. }
  57. }
  58. }

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

  1. package com.changgou;
  2. import com.changgou.order.config.TokenDecode;
  3. import org.springframework.boot.SpringApplication;
  4. import org.springframework.boot.autoconfigure.SpringBootApplication;
  5. import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
  6. import org.springframework.cloud.openfeign.EnableFeignClients;
  7. import org.springframework.context.annotation.Bean;
  8. import tk.mybatis.spring.annotation.MapperScan;
  9. @SpringBootApplication
  10. @EnableEurekaClient
  11. @MapperScan(basePackages = {"com.changgou.order.dao"})
  12. @EnableFeignClients(basePackages = "com.changgou.goods.feign")
  13. public class OrderApplication {
  14. public static void main(String[] args) {
  15. SpringApplication.run( OrderApplication.class);
  16. }
  17. @Bean
  18. public TokenDecode tokenDecode(){
  19. return new TokenDecode();
  20. }
  21. }

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

注入TokenDecode:

  1. @Autowired
  2. private TokenDecode tokenDecode;
  1. @GetMapping("/addCart")
  2. public Result addCart(@RequestParam("skuId") String skuId, @RequestParam("num") Integer num) {
  3. //动态获取当前人信息
  4. // String username = "itheima";
  5. String username = tokenDecode.getUserInfo().get("username");
  6. cartService.addCart(skuId, num, username);
  7. return new Result(true, StatusCode.OK, "加入购物车成功");
  8. }
  9. @GetMapping("/list")
  10. public Map list() {
  11. //动态获取当前用户信息
  12. // String username = "itheima";
  13. String username = tokenDecode.getUserInfo().get("username");
  14. Map map = cartService.list(username);
  15. return map;
  16. }

7. 页面配置

7.1. 未登录时登录跳转

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

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

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

完整代码

  1. package com.changgou.web.gateway.filter;
  2. import com.changgou.web.gateway.service.AuthService;
  3. import org.apache.commons.lang.StringUtils;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.cloud.gateway.filter.GatewayFilterChain;
  6. import org.springframework.cloud.gateway.filter.GlobalFilter;
  7. import org.springframework.core.Ordered;
  8. import org.springframework.http.HttpStatus;
  9. import org.springframework.http.server.reactive.ServerHttpRequest;
  10. import org.springframework.http.server.reactive.ServerHttpResponse;
  11. import org.springframework.stereotype.Component;
  12. import org.springframework.web.server.ServerWebExchange;
  13. import reactor.core.publisher.Mono;
  14. @Component
  15. public class AuthFilter implements GlobalFilter, Ordered {
  16. private static final String LOGIN_URL = "http://localhost:8001/api/oauth/toLogin";
  17. @Autowired
  18. private AuthService authService;
  19. @Override
  20. public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
  21. ServerHttpRequest request = exchange.getRequest();
  22. ServerHttpResponse response = exchange.getResponse();
  23. //1.判断请求路径是否为登陆请求 如果是 则直接放行
  24. String path = request.getURI().getPath();
  25. if ("/api/oauth/login".equals(path) || !URLFilter.hasAuthorize(path)) {
  26. //直接放行
  27. return chain.filter(exchange);
  28. }
  29. //2.不是登陆请求 从cookie中获取jti的值 如果值不存在 拒绝本次访问
  30. String jti = authService.getJtiFromCookie(request);
  31. if (StringUtils.isEmpty(jti)) {
  32. //为空 拒绝访问
  33. // response.setStatusCode(HttpStatus.UNAUTHORIZED);
  34. // return response.setComplete();
  35. //跳转到登陆页面
  36. return this.toLoginPage(LOGIN_URL, exchange);
  37. }
  38. //3.如果cookie中有jti的值 从redis中获取jwt的值 如果值不存在 拒绝本次访问
  39. String jwt = authService.getJwtFromRedis(jti);
  40. if (StringUtils.isEmpty(jwt)) {
  41. //过期或不存在 拒绝访问
  42. // response.setStatusCode(HttpStatus.UNAUTHORIZED);
  43. // return response.setComplete();
  44. //跳转到登陆页面
  45. return this.toLoginPage(LOGIN_URL, exchange);
  46. }
  47. //对当前的请求对象进行增强 让它携带令牌的信息
  48. request.mutate().header("Authorization", "Bearer " + jwt);
  49. return chain.filter(exchange);
  50. }
  51. //跳转登陆页面
  52. private Mono<Void> toLoginPage(String loginUrl, ServerWebExchange exchange) {
  53. ServerHttpResponse response = exchange.getResponse();
  54. response.setStatusCode(HttpStatus.SEE_OTHER); //302
  55. response.getHeaders().set("Location", loginUrl);
  56. return response.setComplete();
  57. }
  58. @Override
  59. public int getOrder() {
  60. return 0;
  61. }
  62. }

7.1.1. 登录成功跳转原地址

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

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

修改网关携带当前访问URI

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

10. Day10 购物车渲染 - 图10

  1. package com.changgou.web.gateway.filter;
  2. import com.changgou.web.gateway.service.AuthService;
  3. import org.apache.commons.lang.StringUtils;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.cloud.gateway.filter.GatewayFilterChain;
  6. import org.springframework.cloud.gateway.filter.GlobalFilter;
  7. import org.springframework.core.Ordered;
  8. import org.springframework.http.HttpStatus;
  9. import org.springframework.http.server.reactive.ServerHttpRequest;
  10. import org.springframework.http.server.reactive.ServerHttpResponse;
  11. import org.springframework.stereotype.Component;
  12. import org.springframework.web.server.ServerWebExchange;
  13. import reactor.core.publisher.Mono;
  14. @Component
  15. public class AuthFilter implements GlobalFilter, Ordered {
  16. private static final String LOGIN_URL = "http://localhost:8001/api/oauth/toLogin";
  17. @Autowired
  18. private AuthService authService;
  19. @Override
  20. public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
  21. ServerHttpRequest request = exchange.getRequest();
  22. ServerHttpResponse response = exchange.getResponse();
  23. //1.判断请求路径是否为登陆请求 如果是 则直接放行
  24. String path = request.getURI().getPath();
  25. if ("/api/oauth/login".equals(path) || !URLFilter.hasAuthorize(path)) {
  26. //直接放行
  27. return chain.filter(exchange);
  28. }
  29. //2.不是登陆请求 从cookie中获取jti的值 如果值不存在 拒绝本次访问
  30. String jti = authService.getJtiFromCookie(request);
  31. if (StringUtils.isEmpty(jti)) {
  32. //为空 拒绝访问
  33. // response.setStatusCode(HttpStatus.UNAUTHORIZED);
  34. // return response.setComplete();
  35. //跳转到登陆页面
  36. return this.toLoginPage(LOGIN_URL+"?FROM="+request.getURI().getPath(), exchange);
  37. }
  38. //3.如果cookie中有jti的值 从redis中获取jwt的值 如果值不存在 拒绝本次访问
  39. String jwt = authService.getJwtFromRedis(jti);
  40. if (StringUtils.isEmpty(jwt)) {
  41. //过期或不存在 拒绝访问
  42. // response.setStatusCode(HttpStatus.UNAUTHORIZED);
  43. // return response.setComplete();
  44. //跳转到登陆页面
  45. return this.toLoginPage(LOGIN_URL, exchange);
  46. }
  47. //对当前的请求对象进行增强 让它携带令牌的信息
  48. request.mutate().header("Authorization", "Bearer " + jwt);
  49. return chain.filter(exchange);
  50. }
  51. //跳转登陆页面
  52. private Mono<Void> toLoginPage(String loginUrl, ServerWebExchange exchange) {
  53. ServerHttpResponse response = exchange.getResponse();
  54. response.setStatusCode(HttpStatus.SEE_OTHER); //302
  55. response.getHeaders().set("Location", loginUrl);
  56. return response.setComplete();
  57. }
  58. @Override
  59. public int getOrder() {
  60. return 0;
  61. }
  62. }

登录控制器获取参数

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

10. Day10 购物车渲染 - 图11

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

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

10. Day10 购物车渲染 - 图12

  1. <script th:inline="javascript">
  2. var app = new Vue({
  3. el: "#app",
  4. data: {
  5. username: "",
  6. password: "",
  7. msg: "",
  8. from: [[${from}]],
  9. },
  10. methods: {
  11. login: function () {
  12. app.msg = "正在登录";
  13. axios.post("/api/oauth/login?username=" + app.username + "&password=" + app.password).then(function (response) {
  14. if (response.data.flag) {
  15. app.msg = "登录成功";
  16. //跳转原地址
  17. location.href = app.from
  18. } else {
  19. app.msg = "登录失败";
  20. }
  21. })
  22. }
  23. }
  24. })
  25. </script>