谷粒商城-全栈-23 商品服务-阿里云存储 (OSS) 整合到第三方微服务

一、创建第三方微服务

由于OSS业务属于第三方服务,后期可能还会有短信、邮件服务,所以,为了方便管理,将OSS文件上传业务整合到第三方微服务,以后相关的功能都在这里扩展。

1、创建第三方微服务

右键File->Module->使用 Spring Intitializr 创建模块

  • Project Metadata
    • Group: com.atguigu.gulimall
    • Artifact: gulimall-third-party
    • Package: com.atguigu.gulimall.thirdparty
  • Dependencies添加依赖
    • spring web
    • Spring Cloud openfeign

2、添加依赖

修改gulimall-third-party/pom.xml添加依赖

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

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

        <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>${spring-cloud.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>

      <!-- Aliyun Spring Boot dependencies -->
      <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>aliyun-spring-boot-dependencies</artifactId>
        <version>1.0.0</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

完整的 pom.xml 配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.16.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>
  <groupId>com.atguigu.gulimall</groupId>
  <artifactId>gulimall-third-party</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>gulimall-third-party</name>
  <description>第三方服务</description>

  <properties>
    <java.version>1.8</java.version>
    <spring-cloud.version>Greenwich.SR6</spring-cloud.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>com.atguigu.gulimall</groupId>
      <artifactId>gulimall-common</artifactId>
      <version>0.0.1-SNAPSHOT</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>aliyun-oss-spring-boot-starter</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
      <exclusions>
        <exclusion>
          <groupId>org.junit.vintage</groupId>
          <artifactId>junit-vintage-engine</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
  </dependencies>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>${spring-cloud.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>

      <!-- Aliyun Spring Boot dependencies -->
      <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>aliyun-spring-boot-dependencies</artifactId>
        <version>1.0.0</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>

</project>

如果加载不到 aliyun-spring-boot-dependencies 的相关依赖,需要检查 spring-cloud.version 配置和 Spring boot版本号是否匹配。

3、注册到注册中心

将gulimall-third-party注册到注册中心

在nacos后台创建第三方微服务的命名空间,名称:third-party
在Nacos注册中心创建配置文件 oss.yml

alibaba:    
    cloud:   
      access-key: LTAI4Fq***************2aTCJ
      secret-key: Ueb6c******************ZtPXUqi
      oss:
        endpoint: oss-cn-heyuan.aliyuncs.com

新建文件:gulimall-third-party/src/main/resources/bootstrap.properties

spring.application.name=gulimall-third-party

spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.namespace=510ff354-cad2-4807-8040-ad7c03c02e62

spring.cloud.nacos.config.ext-config[0].data-id=oss.yml
spring.cloud.nacos.config.ext-config[0].group=DEFAULT_GROUP
# 动态刷新,改了配置之后会自动刷新
spring.cloud.nacos.config.ext-config[0].refresh=true    

创建 gulimall-third-party/src/main/resources/application.yml

spring:
  #  配置nacos注册中心
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
  application:
    name: gulimall-third-party
server:
  port: 30000

修改gulimall-third-party/pom.xml 文件,排除掉 mybatis-plus 的引入依赖

<dependency>
      <groupId>com.atguigu.gulimall</groupId>
      <artifactId>gulimall-common</artifactId>
      <version>0.0.1-SNAPSHOT</version>
      <exclusions>
        <!-- 由于third-party暂时不需要连接数据库服务,所以,在导入common微服务的时候,直接排除掉mybatis-plus,否则会报错[20200819] -->
        <exclusion>
          <groupId>com.baomidou</groupId>
          <artifactId>mybatis-plus-boot-starter</artifactId>
        </exclusion>
      </exclusions>
    </dependency>

启动文件添加注册发现注解:
gulimall/thirdparty/GulimallThirdPartyApplication.java

@EnableDiscoveryClient // 添加注册发现功能
@SpringBootApplication
public class GulimallThirdPartyApplication {

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

然后启动 gulimall-third-party 微服务,在Nacos看是否已经注册成功。

file

我们可以看到,已经成功启动并且注入到Nacos注册中心了 ^_^

4、单元测试

上边已经添加到注册中心了,我们在单元测试调用OSS接口上传到阿里云:
thirdparty/GulimallThirdPartyApplicationTests.java

package com.atguigu.gulimall.thirdparty;

import com.aliyun.oss.OSS;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.io.FileInputStream;

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

  @Autowired
  public OSS ossClient;

  @Test
  public void contextLoads() {
  }

  @Test
  public void ossTest(){
    try {
      String bucket_name = "gulimall-corwien";
     FileInputStream inputStream =  new FileInputStream("/Users/kaiyiwang/Desktop/toutiao/unnamed.jpg");
      ossClient.putObject(bucket_name, "oss-upload-test.jpg", inputStream);
    }
    catch (Exception e) {
      e.printStackTrace();
      System.out.println("upload fail: " + e.getMessage());
    }

    System.out.println("upload success ");
  }
}

修改Nacos 配置中心下的 gulimall-third-partyoss.yml 配置文件,去掉 endpoint 连接的 bucket_name,这个name 需要我们动态传递:

alibaba:    
    cloud:   
      access-key: LTAI4Fq***********y2aTCJ
      secret-key: Ueb6cWiG********5ZtPXUqi
      oss:
        endpoint: oss-cn-heyuan.aliyuncs.com

# 代码调用上传时不需要bucket_name 前缀,代码里边会动态加入
# gulimall-corwien.oss-cn-heyuan.aliyuncs.com

然后重新发布配置,再运行ossTest()测试方法,控制台运行成功,我们登录到阿里云OSS后台,看是否有上传,可以看到,川建国同志已经上传成功了^_^

file

二、OSS获取服务端签名

服务端签名直传请参考官方文档:最佳实践-服务端签名后直传

为了便于调试,把OSS配置写在本地 gulimall-third-party/src/main/resources/application.yml 文件:

spring:
  #  配置nacos注册中心
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
  application:
    name: gulimall-third-party
server:
  port: 30000
alibaba:
  cloud:
    access-key: LTAI4FqLgxxxxxxxxxxxAApy2aTCJ
    secret-key: Ueb6cWiGxxxxxxxxx68f5ZtPXUqi
    oss:
      endpoint: oss-cn-heyuan.aliyuncs.com
      bucket: gulimall-corwien

新建文件:
gulimall-third-party/src/main/java/com/atguigu/gulimall/thirdparty/controller/OssController.java

package com.atguigu.gulimall.thirdparty.controller;

import com.alibaba.fastjson.JSONObject;
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.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @author: kaiyi
 * @create: 2020-08-20 02:02
 */
@RestController
@RequestMapping("thirdparty")
public class OssController {

  @Autowired
  OSS ossClient;

  @Value("${alibaba.cloud.oss.endpoint}")
  private String endpoint;

  @Value("${alibaba.cloud.oss.bucket}")
  private String bucket;

  @Value("${alibaba.cloud.access-key}")
  private String accessId;

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

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

    // callbackUrl为 上传回调服务器的URL,请将下面的IP和Port配置为您自己的真实信息。
    String callbackUrl = "http://88.88.88.88:8888";

    // 用户上传文件时指定的前缀,也就是目录
    String format = new SimpleDateFormat("YYYY-MM-dd").format(new Date());
    String dir = format;

    // 创建OSSClient实例,引入自动依赖注入,这里不需要再new了
    // OSS ossClient = new OSSClientBuilder().build(endpoint, accessId, accessKey);

    Map<String, String> respMap = null;

    try {
      long expireTime = 30;
      long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
      Date expiration = new Date(expireEndTime);
      // PostObject请求最大可支持的文件大小为5 GB,即CONTENT_LENGTH_RANGE为5*1024*1024*1024。
      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));
      // respMap.put("expire", formatISO8601Date(expiration));

      /*
      JSONObject jasonCallback = new JSONObject();
      jasonCallback.put("callbackUrl", callbackUrl);
      jasonCallback.put("callbackBody",
          "filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}");
      jasonCallback.put("callbackBodyType", "application/x-www-form-urlencoded");
      String base64CallbackBody = BinaryUtil.toBase64String(jasonCallback.toString().getBytes());
      respMap.put("callback", base64CallbackBody);
       */

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

    return respMap;

  }

}

然后启动服务,访问 http://localhost:30000/thirdparty/oss/policy,获取OSS的签名校验:

{
    "accessid": "LTAI4FqLgSsw8YKAApy2aTCJ",
    "policy": "eyJleHBpcmF0aW9uIjoiMjAyMC0wOC0yMFQwMzoyMDozMy4wMjRaIiwiY29uZGl0aW9ucyI6W1siY29udGVudC1sZW5ndGgtcmFuZ2UiLDAsMTA0ODU3NjAwMF0sWyJzdGFydHMtd2l0aCIsIiRrZXkiLCIyMDIwLTA4LTIwIl1dfQ==",
    "signature": "J/m1D3d0pa9dsbQzg1VEDUL9AO8=",
    "dir": "2020-08-20",
    "host": "https://gulimall-corwien.oss-cn-heyuan.aliyuncs.com",
    "expire": "1597893633"
}

我们可以看到,OSS返回这样的签名校验结果,有上传的host,上传的目录,过期时间等。

三、将第三方微服务(OSS)请求加入网关

将请求第三方微服务加入到 gateway 网关服务,以后可以直接通过网关请求。
修改 gulimall-gateway/src/main/resources/application.yml

spring:
  cloud:
    gateway:
      routes:
        - id: product_route
          uri: lb://gulimall-product
          predicates:
            - Path=/api/product/**
          filters:
            - RewritePath=/api/(?<segment>.*),/$\{segment}
        # 新增加第三方网关路由
        - id: third_party_route
          uri: lb://gulimall-third-party
          predicates:
            - Path=/api/thirdparty/**
          filters:
            - RewritePath=/api/(?<segment>.*),/$\{segment}
        - id: admin_route
          uri: lb://renren-fast
          predicates:
            - Path=/api/**
          filters:
            - RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment}
## 前端项定义规则,都带 /api 前缀, lb 表示负载均衡到哪个注册器
## http://localhost:8888/api/captcha.jpg  需要通过注册中心网关8888端口转发到renren-fast 8080端口服务
## http://localhost:8080/renren-fast/captcha.jpg:
## filters 路径重写 /api/ -> /renren-fast/

重启网关服务,然后通过路由访问测试:

通过路由访问一样也可以拿到签名校验结果。
file

为者常成,行者常至