关于微服务项目的文件存储(其一)

在之前普通的 SpringBoot 项目中,我们上传的文件大部分选择存储在那个单一的服务器上,因为整个项目只有一个服务,不涉及微服务的多服务。

但是当进入到了微服务项目阶段,我们的一个微服务中,可能要部署多台服务器,服务器不止一个,那现在,服务就不能单纯的上传到某一台服务器上面了,这样就会导致其他的服务器没有这个资源而导致不同步。那这样怎么解决呢?非常简单,我们有两个选择:

  1. 自建服务器

    自己在建立一个服务器,专门用于存储要长传的文件。但是缺点是搭建复杂、维护成本高。

  2. 云存储

    租用第三方,比如阿里云这样的云存储,不是自己的服务器。好处是即开即用,无需维护,按量收费。因此,大部分都会选择云存储。

接下来就以使用 阿里云 云存储服务来开始讲解。我们具体使用的是阿里云的对象存储(OSS)。首先要了解一下对象存储的一些专业术语。

  1. 存储空间(Bucket)

    存储空间是存储对象(Object)的容器,所有的对象都必须隶属于某个存储空间。我们一般一个项目创建一个 Bucket。

  2. 对象/文件(Object)

    对象是 OSS 存储数据的基本单元,也被称为 OSS 的文件。对象由以下几部分组成:

    • 元信息(Object Meta)

    • 用户数据(Data)

    • 文件名(Key)

    对象由存储空间内部唯一的 Key 来标识。

  3. 地域(Region)

    地域表示 OSS 的数据中心所在的物理位置,可根据费用、请求来源等综合选择数据存储的地域。

  4. 访问域名(Endpoint)

    访问域名表示 OSS 对外服务的访问域名。OSS 以 HTTP RESTful API 的形式对外提供服务,当访问不同地域的时候,需要不同的域名。通过内网和外网访问同一个地域所需要的域名也是不同的。

  5. 访问密钥(AccessKey)

    访问密钥,即 AccessKey ,简称 AK,指的是访问身份验证中用到的 AccessKeyId 和 AccessKeySecret。OSS 通过使用 AccessKeyId 和 AccessKeySecret 对称加密的方法来验证某个请求的发送者身份。AccessKeyId 用于标识用户,AccessKeySecret 是用户用于加密前面字符串和 OSS 用来

创建我们的 Bucket 步骤:

  1. 开通阿里云的OSS服务(免费)

  2. OSS管理控制台创建自己的 Bucket

    image-20251107194548902.png

    如上图所示,为自己的 Bucket 创建一个名字,选择标准存储(正式上线阶段使用)或者低频存储(测试、学习阶段使用),然后就可创建自己的 Bucket 了。

  3. 直接上传文件

    直接按照网址的提示,手动上传文件,即可将文件进行云存储

  4. 访问文件

    直接复制对应文件的访问 url ,即可进行浏览。

当然了,我们今后上传文件,肯定不是手动上传文件的,而是通过后台项目,自动上传的,并且得到文件对应的 url 。

今后我们自动上传主要有两种方式:

  1. 用户提交图片后,将上传请求,提交到网关,然后,交给具体处理的微服务,这个微服务拿到这个文件的图片流,将这个图片流数据,传给 OSS 并拿到对应 url。

  2. 让服务端签名后直传。即用户提交图片后,将上传请求,提交到网关,然后,交给具体处理的微服务,但是这个微服务不直接提交这个文件流,而是生成一个 Policy,这个 Policy 包含了加密后的账号密码,而阿里云端可以验证这个 Policy。微服务生成的这个 Policy 再传给前端,前端直接将 Policy 及文件,上传给 OSS 并返回对应 url。

但我们一般都是使用第二种方法。虽然第一种方法中,可以在通过在我们的服务器中,用自己的账号密码上传,更加安全,但上传图片还要过一遍我们自己的服务器,完全没有必要,在大量用户下会带来瓶颈。而第二种方法中,文件就不需要在我们的服务中过一遍了。

下面就来详细说一下如何自动上传,以及它的详细步骤。官方帮助文档里详细说明了如何上传,具体可查阅官方文档,我就简单介绍下流程步骤。

  1. 添加依赖


    <dependency>
       <groupId>com.aliyun.ossgroupId>
       <artifactId>aliyun-sdk-ossartifactId>
       <version>3.18.3version>
    dependency>
  2. 配置访问凭证

    首先要在RAM 控制台中,创建使用永久 AccessKey 访问的 RAM 用户,保存 AccessKey,然后为该用户授予 AliyunOSSFullAccess 权限,如下边两张图。

    image-20251107204806376.png

    image-20251107205827654.png

    这个步骤完成后,会获取三个值:

    1. AccessKey ID

      一串字符串,要自己记住

    2. AccessKey Secret

      一串字符串,要自己记住

    3. 用户登录名称

      刚刚注册的名称,即为:sanguimall@1109771891991402.onaliyun.com

    接着就是为 AccessKey 配置环境变量(Windows):

    setx OSS_ACCESS_KEY_ID "这里写你的 AccessKey ID"
    setx OSS_ACCESS_KEY_SECRET "这里写你的 AccessKey Secret"

    此时,主要最好重启一下你的 idea,不然可能识别不了最新的环境变量。

  3. 编写原生程序

    按照自己的实际信息填写。

    /**
    * 测试初始化
    */
    @Test
    public void testInitOss(){
       // 从环境变量获取访问凭证
       String accessKeyId = System.getenv("OSS_ACCESS_KEY_ID");
       System.out.println(accessKeyId);
       String accessKeySecret = System.getenv("OSS_ACCESS_KEY_SECRET");
       System.out.println(accessKeySecret);

       // 设置OSS地域和Endpoint
       String region = "cn-beijing";
       String endpoint = "oss-cn-beijing.aliyuncs.com";

       // 创建凭证提供者
       DefaultCredentialProvider provider = new DefaultCredentialProvider(accessKeyId, accessKeySecret);

       // 配置客户端参数
       ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
       // 显式声明使用V4签名算法
       clientBuilderConfiguration.setSignatureVersion(SignVersion.V4);

       // 初始化OSS客户端
       OSS ossClient = OSSClientBuilder.create()
              .credentialsProvider(provider)
              .clientConfiguration(clientBuilderConfiguration)
              .region(region)
              .endpoint(endpoint)
              .build();

       // 列出当前用户的所有Bucket
       List<Bucket> buckets = ossClient.listBuckets();
       System.out.println("成功连接到 OSS 服务,当前账号下的 Bucket 列表:");

       if (buckets.isEmpty()) {
           System.out.println("当前账号下暂无 Bucket");
      } else {
           for (Bucket bucket : buckets) {
               System.out.println("- " + bucket.getName());
          }
      }

       // 释放资源
       ossClient.shutdown();
       System.out.println("OSS 客户端已关闭");
    }

    /**
    * 测试上传功能
    */
    @Test
    public void testUpload() throws Exception {
       // Endpoint 外网域名
       String endpoint = "oss-cn-beijing.aliyuncs.com";
       // 从环境变量中获取访问凭证。
       EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
       // 填写 Bucket 名称
       String bucketName = "sanguimall-test";
       // 填写 Object 完整路径,完整路径中不能包含 Bucket 名称
       String objectName = "test/testImage.jpg";
       // 填写本地文件的完整路径,
       // 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件流。
       String filePath= "D:\\01-TempFiles\\2025-11-07-upload\\testImag.jpg";
       // 填写 Bucket 所在地域
       String region = "cn-beijing";

       // 创建 OSSClient 实例。
       // 当 OSSClient 实例不再使用时,调用 shutdown 方法以释放资源。
       ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
       clientBuilderConfiguration.setSignatureVersion(SignVersion.V4);
       OSS ossClient = OSSClientBuilder.create()
              .endpoint(endpoint)
              .credentialsProvider(credentialsProvider)
              .clientConfiguration(clientBuilderConfiguration)
              .region(region)
              .build();

       try {
           InputStream inputStream = new FileInputStream(filePath);
           // 创建 PutObjectRequest 对象。
           PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, inputStream);
           // 创建 PutObject 请求。
           PutObjectResult result = ossClient.putObject(putObjectRequest);
      } catch (OSSException oe) {
           System.out.println("Caught an OSSException, which means your request made it to OSS, "
                   + "but was rejected with an error response for some reason.");
           System.out.println("Error Message:" + oe.getErrorMessage());
           System.out.println("Error Code:" + oe.getErrorCode());
           System.out.println("Request ID:" + oe.getRequestId());
           System.out.println("Host ID:" + oe.getHostId());
      } catch (ClientException ce) {
           System.out.println("Caught an ClientException, which means the client encountered "
                   + "a serious internal problem while trying to communicate with OSS, "
                   + "such as not being able to access the network.");
           System.out.println("Error Message:" + ce.getMessage());
      } finally {
           if (ossClient != null) {
               ossClient.shutdown();
          }
      }
    }
  4. 编写基于SpringCloud Alibaba 的 OSS 代码

    之前的原生代码,类似于使用 JDBC 来连接数据库,今后会使用 SpringCloud Alibaba-OSS 中框架的继承代码来使用。当然使用框架也需要先执行之前的置访问凭证。

    • 添加依赖


      <dependency>
         <groupId>com.alibaba.cloudgroupId>
         <artifactId>aliyun-oss-spring-boot-starterartifactId>
      dependency>

      注意:若 Maven 显示该依赖爆红,则可以在 标签同级处,在下方添加如下版本空值的内容即可:

      <dependencyManagement>
         <dependencies>
             <dependency>
                 <groupId>com.alibaba.cloudgroupId>
                 <artifactId>aliyun-spring-boot-dependenciesartifactId>
                 <version>1.0.0version>
                 <type>pomtype>
                 <scope>importscope>
             dependency>
         dependencies>
      dependencyManagement>
    • 修改 yaml 文件

      alibaba:
      cloud:
        oss:
          endpoint: 你自己的 endpoint
        access-key: ${OSS_ACCESS_KEY_ID}
        secret-key: ${OSS_ACCESS_KEY_SECRET}

      注意两个 key 是配置在本机的用户变量里的

    • 添加配置文件

      package com.sangui.sanguimall.product.config;


      import org.springframework.beans.factory.annotation.Value;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;

      /**
      * @Author: sangui
      * @CreateTime: 2025-11-08
      * @Description:
      * @Version: 1.0
      */
      @Configuration
      public class OssConfig {

         @Bean
         public com.aliyun.oss.OSS oss(
                 @Value("${alibaba.cloud.oss.endpoint}") String endpoint,
                 @Value("${alibaba.cloud.access-key}") String ak,
                 @Value("${alibaba.cloud.secret-key}") String sk) {
             // 注意:使用 OSSClientBuilder(适用于新版 SDK)
             return new com.aliyun.oss.OSSClientBuilder().build(endpoint, ak, sk);
        }
      }
    • 测试程序

      @SpringBootTest
      public class OssTest {
         @Resource
         private OSSClient ossClient;

         @Test
         public void testPutObjectByResourceInject() throws FileNotFoundException {
             String bucketName = "sanguimall-test";
             String objectName = "test4/testImage2.jpg";
             FileInputStream fileInputStream = new FileInputStream("D:\\01-TempFiles\\2025-11-07-upload\\testImag.jpg");
             ossClient.putObject(bucketName,objectName,fileInputStream);
        }
      }

    至此,程序流程就结束了,这种方式就是我们之前说的,将图片文件传给微服务端,再进行上传。但是,这种方式其实是不被推荐的,上传的人一多,就会有瓶颈。我们应该直接使用浏览器将图片文件提交给 OSS,而微服务端只需要提供签名数据(Policy)就行了。

我会在未来这几天,尽力更新完使用浏览器将图片直接提交给 OSS 的一系列的程序及讲解。

  • 微信
  • 赶快加我聊天吧
  • QQ
  • 赶快加我聊天吧
  • weinxin
三桂

发表评论 取消回复 您未登录,登录后才能评论,前往登录