谷粒商城分布式基础(四)—— 分布式组件SpringCloud & SpringCloud Alibaba

五、分布式组件SpringCloud Alibaba

1、SpringCloud Alibaba 简介

1、简介
    Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用 微服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布 式应用服务。

    依托 Spring Cloud Alibaba,您只需要添加一些注解和少量配置,就可以将 Spring Cloud 应用 接入阿里微服务解决方案,通过阿里中间件来迅速搭建分布式应用系统。

    https://github.com/alibaba/spring-cloud-alibaba
2、为什么使用
 1)eureka、hystrix等组件停止维护


2)SpringCloud 的几大痛点     
    SpringCloud 部分组件停止维护和更新,给开发带来不便;     
    SpringCloud 部分环境搭建复杂,没有完善的可视化界面,我们需要大量的二次开发和定制     
    SpringCloud 配置复杂,难以上手,部分配置差别难以区分和合理应用
3)SpringCloud Alibaba 的优势     阿里使用过的组件经历了考验,性能强悍,设计合理,现在开源出来大家用     成套的产品搭配完善的可视化界面给开发运维带来极大的便利     搭建简单,学习曲线低。

4)结合 SpringCloud Alibaba 我们最终的技术搭配方案:
    SpringCloud Alibaba - Nacos:注册中心(服务发现/注册)
    SpringCloud Alibaba - Nacos:配置中心(动态配置管理)
    SpringCloud - Ribbon:负载均衡
    SpringCloud - Feign:声明式 HTTP 客户端(调用远程服务)
    SpringCloud Alibaba - Sentinel:服务容错(限流、降级、熔断)
    SpringCloud - Gateway:API 网关(webflux 编程模式)
    SpringCloud - Sleuth:调用链监控
    SpringCloud Alibaba - Seata:原 Fescar,即分布式事务解决方案
3、版本选择
    由于 Spring Boot 1 和 Spring Boot 2 在 Actuator 模块的接口和注解有很大的变更,且 spring-cloud-commons 从 1.x.x 版本升级到 2.0.0 版本也有较大的变更,因此我们采取跟 SpringBoot 版本号一致的版本: 
     1.5.x 版本适用于 Spring Boot 1.5.x
     2.0.x 版本适用于 Spring Boot 2.0.x
     2.1.x 版本适用于 Spring Boot 2.1.x
4、搭配环境
 1)统一一下springboot的版本
    分别修改gulimall-coupon/member/order/product/ware 的pom.xml中springboot的版本号如下所示

 (2)引入springcloud的依赖

    修改gulimall-common的pom.xml文件,添加如下依赖

<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

 2、SpringCloud Alibaba-Nacos 【作为注册中心】

  Nacos 是阿里巴巴开源的一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。他是使用 java 编写。需要依赖 java 环境
  Nacos 文档地址: https://nacos.io/zh-cn/docs/quick-start.html
1、下载 nacos-server的Windows版本
  https://github.com/alibaba/nacos/releases
  nacos-server-1.1.3.zip:https://download.csdn.net/download/wu2374633583/12028032
  下载完成之后,直接解压缩
2、启动 nacos-server
 (1)双击 bin 中的 startup.cmd 文件
 (2)访问 http://localhost:8848/nacos/
 (3)使用默认的 nacos/nacos 进行登录

3、将微服务注册到 nacos 中
 
1)修改gulimall-common的pom.xml,引入 Nacos Discovery Starter
<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

(2)在gulimall-coupon(优惠券服务的)/src/main/resources/application.properties 配置文件中配置 Nacos Server 地址
  spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848

  (3)使用@EnableDiscoveryClient 开启服务注册发现功能

  (4)给服务起名称,启动服务,观察 nacos 服务列表是否已经注册上服务
    注意:每一个应用都应该有名字,这样才能注册上去。修改 application.properties 文件
    spring.application.name=gulimall-coupon

  启动后查看 nacos-server ,发现gulimall-coupon已经注册进服务列表了

  (5)总结nacos服务注册使用三步

    1、导包 nacos-discovery
    2、写配置,指定 nacos 地址,指定应用的名字
    3、开启服务注册发现功能@EnableDiscoveryClient 

  (6)注册更多的服务上去

    按照上述步骤将gulimall-member也注册到nacos中

 3、SpringCloud-OpenFeign【声明式远程调用】

  想要获取当前会员领取到的所有优惠券。先去注册中心找优惠券服务, 注册中心调一台优惠券服务器给会员,会员服务器发送请求给这台优 惠券服务器,然后对方响应。

1、简介
    Feign 是一个声明式的 HTTP 客户端,它的目的就是让远程调用更加简单。Feign 提供了 HTTP 请求的模板,
通过编写简单的接口和插入注解,就可以定义好 HTTP 请求的参数、格式、地 址等信息。
    Feign 整合了 Ribbon(负载均衡)和 Hystrix(服务熔断),可以让我们不再需要显式地使用这 两个组件。
    SpringCloudFeign 在 NetflixFeign 的基础上扩展了对 SpringMVC 注解的支持,在其实现下,
我们只需创建一个接口并用注解的方式来配置它,即可完成对服务提供方的接口绑定。简化了 SpringCloudRibbon 自行封装服务调用客户端的开发量
2、原理
3、OpenFeign测试远程调用
    会员服务想要远程调用优惠券服务,只需要给会员服务里引入openfeign依赖,他就有了远程调用其他服务的能力。
  Feign 使用三步
    1、导包 openfeign
    2、开启@EnableFeignClients 功能
    3、编写接口,进行远程调用
 (1)gulimall-member(用户服务)的pom.xml导包引入依赖,这样会员服务就有了远程调用其他服务的能力
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

 (2)gulimall-coupon(优惠券服务)创建被远程调用的方法
  在gulimall-coupon(优惠券服务)的 CouponController 中修改如下内容
@RequestMapping("/member/list")
public R memberCoupons(){//全系统的所有返回都返回R
// 应该去数据库查用户对于的优惠券,但这个我们简化了,不去数据库查了,构造了一个优惠券给他返回
CouponEntity couponEntity = new CouponEntity();
couponEntity.setCouponName("满100减10");//优惠券的名字
return R.ok().put("coupons", Arrays.asList(couponEntity));
}
  这样我们准备好了优惠券的调用内容 
 (3)给gulimall-member(用户服务)的主启动类开启 @EnableFeignClients 功能,并且设置远程调用扫描的包

 (4)编写接口,进行远程调用
    那么要调用什么东西呢?就是我们刚才写的优惠券的功能
    在gulimall-member(用户服务)的 com.atguigu.gulimall.member.feign 包下新建优惠券远程调用的feign接口
package com.atguigu.gulimall.member.feign;

import com.atguigu.common.utils.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;

@Component
@FeignClient("gulimall-coupon")//告诉spring cloud这个接口是一个远程客户端,要调用coupon服务,再去调用coupon服务/coupon/coupon/member/list对应的方法
public interface CouponFeignService {

@RequestMapping("/coupon/coupon/member/list")
public R membercoupons();
}
 (5)编写测试请求
    在gulimall-member(用户服务)的控制层MemberController 写一个测试请求
@RequestMapping("/coupons")
public R test(){
MemberEntity memberEntity = new MemberEntity();
memberEntity.setNickname("张三");
R membercoupons = couponFeignService.membercoupons(); //假设张三去数据库查了后返回了张三的优惠券信息
// 打印会员和优惠券信息
return R.ok().put("member",memberEntity).put("coupons",membercoupons.get("coupons"));
}

  (6) 重新启动服务

    http://localhost:8000/member/member/coupons

{"msg":"success","code":0,"coupons":[{"id":null,"couponType":null,"couponImg":null,"couponName":"满100减10","num":null,"amount":null,"perLimit":null,"minPoint":null,"startTime":null,"endTime":null,"useType":null,"note":null,"publishCount":null,"useCount":null,"receiveCount":null,"enableStartTime":null,"enableEndTime":null,"code":null,"memberLevel":null,"publish":null}],"member":{"id":null,"levelId":null,"username":null,"password":null,"nickname":"张三","mobile":null,"email":null,"header":null,"gender":null,"birth":null,"city":null,"job":null,"sign":null,"sourceType":null,"integration":null,"growth":null,"status":null,"createTime":null}}

  (7)若远程服务 gulimall-coupon 掉线宕机会咋样?

    停掉gulimall-coupon(优惠券服务),观察nacos-server和服务列表和接口响应

  nacos-server的gulimall-coupon健康实例数为0了,之后会被自动剔除。并且接口响应超时了

  我们重新启动gulimall-coupon,优惠券服务重新上线之后,又完全恢复了

 4、SpringCloud Alibaba-Nacos 【作为配置中心】

   我们还可以用nacos作为配置中心。配置中心的意思是不在application.properties 等文件中配置了,而是放到nacos配置中心公用,这样无需每台机器都改。

1、使用步骤
 (1)导入包依赖
  修改gulimall-common的pom.xml,引入 Nacos Config Starter
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>
 (2)在gulimall-coupons项目中创建/src/main/resources/bootstrap.properties ,这个文件是springboot里规定的,他优先级别application.properties高
      spring.application.name=gulimall-coupon
      spring.cloud.nacos.config.server-addr=127.0.0.1:8848
    在gulimall-coupons项目中创建/src/main/resources/application.properties
      coupon.user.name=zhangsan
      coupon.user.age=18
 (3)在gulimall-coupon(优惠券服务)的 CouponController 中添加如下内容
@Value("${coupon.user.name}")//从application.properties中获取//不要写user.name,他是环境里的变量
private String name;
@Value("${coupon.user.age}")
private Integer age;

@RequestMapping("/test")
public R test(){
return R.ok().put("name",name).put("age",age);
}
 (4)重启gulimall-coupon主启动程序
      浏览器访问 http://localhost:7000/coupon/coupon/test
      {"msg":"success","code":0,"name":"zhangsan","age":18}
    读取到了application.properties文件的内容
2、存在问题
    修改配置怎么办?实际生产中是不能修改application.properties重启应用的
解决办法:
    把配置交给配置中心nacos,之后只需要修改配置中心nacos即可
    我们发现项目启动的时候默认去加载nacos资源文件gulimall-coupon.properties,这说明如果我们的配置中心中有这么一个配置,那么里面的配置也是可以获取到的

    下面,我们来解决这个问题

  (1)nacos-server 浏览器设置nacos配置列表

    浏览器去nacos里的配置列表,点击+号,data ID:gulimall-coupon.properties,配置gulimall-coupon.properties,然后点击发布

  (2)gulimall-coupon对应控制层加上动态刷新注解 @RefreshScope

  (3)重启gulimall-coupon,在nacos浏览器里修改配置,修改就可以观察到能动态修改了

    注意:nacos的配置内容优先于项目本地的配置内容。

3、总结:如何使用nacos作为配置中心统一管理配置

1)引入依赖
    <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>
2创建一个bootstrap.properties
    spring.application.name=gulimall-coupon
    spring.cloud.nacos.config.server-addr=127.0.0.1:8848
  
(3)nacos-server需要给配置中心默认添加一个叫 数据集(Data Id)gulimall-coupon.properties。默认规则:应用名.properties,给应用名.properties添加任何配置 
(4)动态获取配置
@RefreshScope:动态获取并刷新配置
@Value("${配置项名}"):获取到配置
之后每次修改浏览器nacos-server配置列表中配置文件的信息,不需要重启项目,直接可动态获取到nacos中修改后的最新的配置信息
如果配置中心和当前应用的配置文件中都配置了相同的项,优先使用配置中心的配置
4、核心概念
 (1)命名空间:配置隔离
    默认:public(保留空间);默认新增的所有配置都在public空间
   (a)开发、测试、生产:利用命名空间来做环境隔离
      注意:需要在bootstrap.properties 配置上,需要使用哪个命名空间下的配置,若不配置,默认使用的是public
      spring.cloud.nacos.config.namespace=534e9b53-1a82-4c30-a258-5be306f7aa94

     (b)每一个微服务之间互相隔离配置,每一个微服务都创建自己的命名空间,只加载自己命名空间下的所有配置

 (2)配置集

   一组相关或者不相关的配置项的集合称为配置集。在系统中,一个配置文件通常就是一个配置集,包含了系统各个方面的配置。例如,一个配置集可能包含了数据源、线程池、日志级别等配置项。 

 (3)配置集ID:类似文件名

  Nacos 中的某个配置集的 ID。配置集 ID 是组织划分配置的维度之一。Data ID 通常用于组织划分系统的配置集。一个系统或者应用可以包含多个配置集,每个配置集都可以被一个有意义的名称标识。Data ID 通常采用类 Java 包(如 com.taobao.tc.refund.log.level)的命名规则保证全局唯一性。此命名规则非强制。 

 (4)配置分组

  默认所有的配置集都属于:DEFAULT_GROUP

  每个微服务创建自己的命名空间,使用配置分组区分环境,dev,test,prod

5、加载多配置集
  (1)微服务任何配置信息,任何配置文件都可以放在配置中心中
  (2)只需要在bootstrap.properties中说明加载配置中心中哪些配置文件即可
  (3)@Value、@
ConfigurationProperties
    以前SpringBoot任何方法从配置文件中获取值,都能使用
    配置中心有的优先使用配置中心中的值
      我们尝试将application.yml拆分成多个配置集,在nacos中配置多个yml,这个一拆分之后,其实之前的三个配置文件只用保留bootstrap.properties文件了


5、SpringCloud-OpenFeign【网关】

  发送请求需要知道商品服务的地址,如果商品服务器有100服务器,1号掉线后, 还得改,所以需要网关动态地管理,他能从注册中心中实时地感知某个服务上 线还是下线。

  请求也要加上询问权限,看用户有没有权限访问这个请求,也需要网关。

  所以我们使用spring cloud的gateway组件做网关功能。

1、简介
(1)基本介绍 网关作为流量的入口,常用功能包括路由转发、权限校验、限流控制等。而 springcloud gateway 作为 SpringCloud 官方推出的第二代网关框架,取代了 Zuul 网关。
  网关提供 API 全托管服务,丰富的 API 管理功能,辅助企业管理大规模的 API,以降低管理成本和安全风险,包括协议适配、协议转发、安全策略、防刷、流量、监控日志等功能。
  Spring Cloud Gateway 旨在提供一种简单而有效的方式来对 API 进行路由,并为他们提供切面,例如:安全性,监控/指标 和弹性等。
  官方文档地址: https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.1.3.RELEASE/single/spring-cloud-gateway.html

 (2)Spring Cloud Gateway特点

    基于 Spring5,支持响应式编程和 SpringBoot2.0

    支持使用任何请求属性进行路由匹配

    特定于路由的断言和过滤器

    集成 Hystrix 进行断路保护

    集成服务发现功能

    易于编写 Predicates 和 Filters

    支持请求速率限制

    支持路径重写 

 (3)为什么使用 API 网关

    API 网关出现的原因是微服务架构的出现,不同的微服务一般会有不同的网络地址,而外部客户端可能需要调用多个服务的接口才能完成一个业务需求,如果让客户端直接与各个微服务通信,会有以下的问题:

    (a)客户端会多次请求不同的微服务,增加了客户端的复杂性

    (b)存在跨域请求,在一定场景下处理相对复杂。

    (c)认证复杂,每个服务都需要独立认证。

     (d)难以重构,随着项目的迭代,可能需要重新划分微服务。例如,可能将多个服务合并成一个或者将一个服务拆分成多个。如果客户端直接与微服务通信,那么重构将会很难实施。

    (e)某些微服务可能使用了防火墙 / 浏览器不友好的协议,直接访问会有一定的困难。

  以上这些问题可以借助 API 网关解决。API 网关是介于客户端和服务器端之间的中间层,所有的外部请求都会先经过 API 网关这一层。也就是说,API 的实现方面更多的考虑业务逻辑,而安全、性能、监控可以交由 API 网关来做,这样既提高业务灵活性又不缺安全性:使用 API 网关后的优点如下:
    (a)易于监控。可以在网关收集监控数据并将其推送到外部系统进行分析。
    (b)易于认证。可以在网关上进行认证,然后再将请求转发到后端的微服务,而无须在每个微服务中进行认证。
    (c)减少了客户端与各个微服务之间的交互次数。 
2、三大核心概念
 1)路由
    路由是网关最基础的部分,路由信息有一个 ID、一个目的 URL、一组断言和一组 Filter 组成。如果断言路由为真,则说明请求的 URL 和配置匹配
 (2)断言
   Java8 中的断言函数。Spring Cloud Gateway 中的断言函数输入类型是 Spring5.0 框架中的 ServerWebExchange。Spring Cloud Gateway 中的断言函数允许开发者去定义匹配
来自于 http request 中的任何信息,比如请求头和参数等。
 (3)过滤器
  一个标准的 Spring webFilter。Spring cloud gateway 中的 filter 分为两种类型的Filter,分别是 Gateway Filter 和 Global Filter。
过滤器 Filter 将会对请求和响应进行修改处理 

工作原理:

  客户端发送请求给网关,弯管 HandlerMapping 判断是否请求满足某个路由,满足就发给网关的 WebHandler。这个 WebHandler 将请求交给一个过滤器链,请求到达目标服务之前,会执行所有过滤器的 pre 方法。请求到达目标服务处理之后再依次执行所有过滤器的 post 方法。

  一句话:满足某些断言(predicates)就路由到指定的地址(uri),使用指定的过滤器(filter)

3、使用
 目标:访问http://localhost:88/?url=baidu 跳转到百度, 访问http://localhost:88/?url=qq 跳转到qq
 (1)创建新Module:gulimall-gateway,选择Spring Initializr

 (2) 我们修改一下pom.xml,版本环境保持一致

 (3)引入公共依赖 gulimall-common

  gulimll-common中已经引入了服务的注册发现和配置中心

<dependency>
<groupId>com.atguigu.gulimall</groupId>
<artifactId>gulimall-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>

(4)@EnableDiscoveryClient注解开启服务注册发现
注意:由于引用了gulimall-common公共依赖,而common里面引入了mybatis-plus,所以启动后会报错找不到数据源,这里我们使用exclude排除掉即可
  @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
(5)配置nacos注册中心地址application.properties
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
spring.application.name=gulimall-gateway
server.port=88
  注意:nacos-server要启动

(6)nacos-server里创建命名空间gateway,然后在gateway命名空间里创建文件gulimall-gateway.yml

 (7)bootstrap.properties 填写配置中心地址

spring.application.name=gulimall-gateway
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.namespace=dd69061f-64b7-4d5c-9ce7-0106b856c70d

 

 (8)在项目里创建application.yml配置网关路由信息

spring:
cloud:
gateway:
routes:
- id: test_route
uri: https://www.baidu.com
predicates:
- Query=url,baidu
- id: qq_route
uri: https://www.qq.com
predicates:
- Query=url,qq

 

 (9)启动项目测试

    http://localhost:88/?url=baidu   #跳转到百度页面

    http://localhost:88/?url=qq      #跳转到qq页面

6、SpringCloud Alibaba-OSS【文件存储】 

  在商品服务API的品牌管理里面需要上传文件,这里我们使用 SpringCloud Alibaba-OSS来实现

1、简介
    对象存储服务(Object Storage Service,OSS)是一种海量、安全、低成本、高可靠的云存储 服务,适合存放任意类型的文件。容量和处理能力弹性扩展,多种存储类型供选择,全面优 化存储成本。
2、开通阿里云对象对象存储服务
(1)开通对象存储OSS
  https://www.aliyun.com/product/oss

  开通后,控制台“常用入口”查看API文档

  (2)创建 Bucket 存储空间

  (3)平台上传测试

  这种方式是手动上传图片,实际上我们可以在程序中设置自动上传图片到阿里云 对象存储。

3、上传方式

  (1)普通上传方式

  (2)服务端签名后直传

  上传的账号信息存储在应用服务器

  上传先找应用服务器要一个policy上传策略,生成防伪签名

4、使用原生sdk进行文件上传操作
  我们使用“服务端签名后直传”的方式上传文件到阿里云
  查看阿里云关于文件上传的帮助: https://help.aliyun.com/document_detail/32009.html?spm=a2c4g.11186623.6.768.549d59aaWuZMGJ
 
(1)gulimall-product 添加依赖包
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.5.0</version>
</dependency>

 (2)找到对象存储 gulimall-hr 的EndPoint(地域节点)

  我们选择“oss-cn-beijing.aliyuncs.com” 

 (3)开通AcessKey管理

  给子账号分配权限

  复制保存AcessKey ID 和 AcessKey Secret,一会要用到

 (5)创建测试用例

public void testUpload() throws FileNotFoundException {
// yourEndpoint填写Bucket所在地域对应的Endpoint。以华东1(杭州)为例,Endpoint填写为https://oss-cn-hangzhou.aliyuncs.com。
String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
// 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
String accessKeyId = "yourAccessKeyId";
String accessKeySecret = "yourAccessKeySecret";

// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

// 填写本地文件的完整路径。如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件流。
InputStream inputStream = new FileInputStream("D:\\localpath\\examplefile.txt");
// 依次填写Bucket名称(例如examplebucket)和Object完整路径(例如exampledir/exampleobject.txt)。Object完整路径中不能包含Bucket名称。
ossClient.putObject("examplebucket", "exampledir/exampleobject.txt", inputStream);

// 关闭OSSClient。
ossClient.shutdown();
}

   运行后

  查看阿里云平台文件管理 

5、使用SpringCloud Alibaba-OSS 上传
  更为简单的使用方式,是使用SpringCloud Alibaba

  (1) gulimall-common 导入依赖包

<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alicloud-oss</artifactId>
</dependency>

  (2)创建“AccessKey ID”和“AccessKeySecret”

    前面已经提到过,这里就不再讲述了

  (3)gulimall-product 修改application.yml

 (4)注释掉之前gulimall-product 的pom.xml中引入的sdk

  这里我们不再用sdk方式了,改用 gulimall-common中引入的springcloud包的模式

 (5)修改测试案例

 (6)总结

    1、引入oss-starter

    2、配置key、endpoint相关信息即可

    3、使用OSSClient 进行相关操作

6、创建第三方模块 gulimall-third
  但是这样来做还是比较麻烦,如果以后的上传任务都交给gulimall-product来完成,显然耦合度高。最好单独新建一个Module来完成文件上传任务。
 (1)New Module,选择Spring Initiializr

  选择 Web——Spring Web

     Spring Cloud Routing——OpenFeign

  (2)我们修改一下pom.xml,版本环境保持一致

  (3)引入公共依赖 gulimall-common

  gulimll-common中已经引入了服务的注册发现和配置中心

<dependency>
<groupId>com.atguigu.gulimall</groupId>
<artifactId>gulimall-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>

(4)将gulimall-common的对象存储的依赖和springcloud的依赖管理全部移动到gulimall-third-party

 (5)注释掉 gulimall-common的oss对象存储依赖

 (6)第三方服务gulimall-third-party整个微服务也需要注册到注册中心

  (a)nacos-server创建命名空间

   (b)在空间third-party新建配置文件 oss.yml,内容为之前os存储的配置信息

  (c)新增配置bootstrap.properties

spring.application.name=gulimall-third-party

spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.namespace=75bbcd29-424c-44ac-b741-e21161ae90da

spring.cloud.nacos.config.ext-config[0].dataId=oss.yml
spring.cloud.nacos.config.extension-configs[0].group=DEFAULT_GROUP
spring.cloud.nacos.config.extension-configs[0].refresh=true

  (d)新增配置文件 application.yml

server:
port: 30000

spring:
application:
name: gulimall-third-party
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848

logging:
level:
com.yxj.gulimall.product: debug

  (e)为了防止启动报错,修改pom.xml中依赖的gulimall-common,去掉mybatis-plus的依赖,因为我们这里不需要数据源依赖,也没配置,如果不去掉的话,启动会报错 

  (f)主启动类加上注解@EnableDiscoveryClient,开启服务注册和发现

  (g)启动gulimall-third-party,查看nacos-server

  至此,第三方服务gulimall-third-party 已经注册到 nacos 注册中心

 (7)编写测试类

  其实就是之前gulimall-product测试类的代码剪切过来的,gulimall-product就不需要了

package com.atguigu.gulimall.thirdparty;

import com.aliyun.oss.OSSClient;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import javax.annotation.Resource;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class GulimallThirdPartyApplicationTests {

@Resource
OSSClient ossClient;

@Test
public void testUpload() throws FileNotFoundException {
/*// yourEndpoint填写Bucket所在地域对应的Endpoint。以华东1(杭州)为例,Endpoint填写为https://oss-cn-hangzhou.aliyuncs.com。
String endpoint = "https://oss-cn-beijing.aliyuncs.com";
// 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
String accessKeyId = "LTAI5tQuKKQEkCaQRfST5eaE";
String accessKeySecret = "qyOdtmn9pht9DhTuI5ZE0l7Z1phfCe";

// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);*/

// 填写本地文件的完整路径。如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件流。
InputStream inputStream = new FileInputStream("C:\\Users\\Administrator\\Pictures\\花草.jpg");
// 依次填写Bucket名称(例如examplebucket)和Object完整路径(例如exampledir/exampleobject.txt)。Object完整路径中不能包含Bucket名称。
ossClient.putObject("gulimall-hr", "花草.jpg", inputStream);

// 关闭OSSClient。
ossClient.shutdown();

System.out.println("上传成功");
}
}
  测试结果

 (8)改进成为服务端签名后直传

  上面模块搭建直到第(7)步,已经完成了上传功能,但是这个操作是先将文件传入到服务端,通过服务端上传文件。

  如果我们想直接从客户端前端上传的话,采用JavaScript客户端直接签名(参见JavaScript客户端签名直传)时,AccessKeyID和AcessKeySecret会暴露在前端页面,因此存在严重的安全隐患。因此,OSS提供了服务端签名后直传的方案。

  服务端签名后直传的原理如下:

  (1)用户发送上传Policy请求到应用服务器。
  (2)应用服务器返回上传Policy和签名给用户。
  (3)用户直接上传数据到OSS。

 (a)修改application.yml,新增相关配置,供系统直接访问

 (b)编写上传controller方法

package com.atguigu.gulimall.thirdparty.controller;

import com.aliyun.oss.OSS;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.MatchMode;
import com.aliyun.oss.model.PolicyConditions;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;

@RestController
public class OssController {

@Resource
OSS ossClient;

@Value ("${spring.cloud.alicloud.oss.endpoint}")
String endpoint ;
@Value("${spring.cloud.alicloud.oss.bucket}")
String bucket ;
@Value("${spring.cloud.alicloud.access-key}")
String accessId ;
@Value("${spring.cloud.alicloud.secret-key}")
String accessKey ;


@RequestMapping("/oss/policy")
public Map<String, String> policy(){

String host = "https://" + bucket + "." + endpoint; // host的格式为 bucketname.endpoint

String format = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
String dir = format; // 用户上传文件时指定的前缀。

Map<String, String> respMap=null;
try {
long expireTime = 30;
long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
Date expiration = new Date(expireEndTime);
PolicyConditions policyConds = new PolicyConditions();
policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);

String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
byte[] binaryData = postPolicy.getBytes("utf-8");
String encodedPolicy = BinaryUtil.toBase64String(binaryData);
String postSignature = ossClient.calculatePostSignature(postPolicy);

respMap= new LinkedHashMap<String, String>();
respMap.put("accessid", accessId);
respMap.put("policy", encodedPolicy);
respMap.put("signature", postSignature);
respMap.put("dir", dir);
respMap.put("host", host);
respMap.put("expire", String.valueOf(expireEndTime / 1000));

} catch (Exception e) {
// Assert.fail(e.getMessage());
System.out.println(e.getMessage());
} finally {
ossClient.shutdown();
}
return respMap;
}
}

 (c)启动gulimall-third-party,测试:http://localhost:30000/oss/policy,返回

{
  "accessid": "LTAI5tQuKKQEkCaQRfST5eaE",
  "policy": "eyJleHBpcmF0aW9uIjoiMjAyMS0xMi0xMVQxMToxNTo1MC40MTJaIiwiY29uZGl0aW9ucyI6W1siY29udGVudC1sZW5ndGgtcmFuZ2UiLDAsMTA0ODU3NjAwMF0sWyJzdGFydHMtd2l0aCIsIiRrZXkiLCIyMDIxLTEyLTExIl1dfQ==",
  "signature": "C4RNL+2qQh9Ji7dR988fU1f2ZB4=",
  "dir": "2021-12-11",
  "host": "https://gulimall-third-party.oss-cn-beijing.aliyuncs.com",
  "expire": "1639221350"
}

 (d)配置gulimall-gateway中的路由规则

   以后在上传文件时的访问路径为“ http://localhost:88/api/thirdparty/oss/policy”

   在“gulimall-gateway”中配置路由规则:

- id: third_party_route
uri: lb://gulimall-third-party
predicates:
- Path=/api/thirdparty/**
filters:
- RewritePath=/api/thirdparty/(?<segment>/?.*),/$\{segment}

  重启gulimall-gateway, 测试是否能够正常跳转: http://localhost:88/api/thirdparty/oss/policy

  ok,第三方模块的上传功能已经处理完毕

 

 

 

 

 

 

 

posted @ 2021-12-08 18:42  沧海一粟hr  阅读(1030)  评论(0编辑  收藏  举报