# 一、七牛云

到七牛云的官网网站进行注册账号并登录,然后先绑定邮箱才能进行实名认证

下面我们去获取一下 access_key 和 secret_key 还有 url 后面操作需要

image-20240322191501201

image-20240322191519463

access_key 我们可以直接看到并且 复制,但是 secret_key 我们看不到而却也并不能直接复制,我们需要点击输入框右边的眼睛进行验证码的验证才能看到这里就不展示了。

进行下面的操作

image-20240322191730551

image-20240322191805639

创建一个空间,选择图片中红色箭头指向的地方

image-20240322191908886

创建好空间后就可以获取我们上面说到的 url 了

image-20240322192112178

我们成功获取了重要的三个参数:

access_key: Zc1ktIEYgIhsY-MuNAT5W=
secret_key: Zc1ktIejiscnsY-MuasdasdT5Wwdsdxxx
url: saqvg00o7.hb-bkt.clouddn.com

下面是对七牛云的一些介绍:

计费方面:开通七牛云对象存储服务的实名认证用户,每月可享受一定量的标准存储免费存储空间,限制 10GB 空间

# 七牛云的响应状态码表

http://78re52.com0.z0.glb.qiniucdn.com/docs/v6/api/reference/codes.html

七牛云的优点

  1. 支持各种尺寸的图片缩放;
  2. 支持图片自动压缩;
  3. 支持水印添加:图片水印、文字水印两种模式;
  4. 图片防盗链,限制访问来源;
  5. 设置 ip 黑白名单,防止恶意盗刷、攻击;
  6. 自定义图片域名,看起来更具有归属性;
  7. 统计图片的各种访问数据;
  8. 支持上传日志文件,可保存 30 天,便于排除程序问题;

# 1.1 开发指南:

注意:

  • 上传文件的名称中,不支持 \0 字符。
  • 若文件名中存在 \0 字符,则会返回 400 Bad Request 和 error message “key must not contain null byte”。
  • 上传文件名 utf-8 编码字符,长度不超过 750 字节 。
  • 若有特殊需求,请联系技术支持。

表单上传

表单上传是指在一个单一的 HTTP POST 请求中完成一个文件的上传,比较适合简单的应用场景和尺寸较小的文件。

# 1.1.1 分片上传

分片上传是将一个文件分为多个小数据块,每个小数据块以一个独立的 HTTP 请求分别上传。所有小数据块都上传完成后,再发送一个请求给服务端将这些小数据块组织成一个逻辑资源,以完成上传过程。

相比表单上传,分片上传的优势:

  • 适合尺寸较大的文件传输,通过分片来避免单个 HTTP 数据量过大而导致连接超时的现象。
  • 在网络条件较差的环境下,较小尺寸的文件可以有较高的上传成功率,从而避免无休止的失败重试。
  • 支持断点续传。

然而,相比表单上传,分片上传需要多次 HTTP 请求才能完成上传过程,会有额外的成本开销。另外也增加了代码的复杂度,因此选择是否使用分片上传时应谨慎评估使用的必要性。

分片上传的使用细节请参考分片上传 v1 版分片上传 v2 版,两种分片上传不同见详情。

上传后续动作

在上传时开发者可以指定上传完成后服务端的后续动作,例如回调通知 (callback)、自定义响应内容、303 重定向等。可设置的后续动作与表单上传中完全一致。

这里需要明确的是,虽然后续动作在生成上传凭证时已经指定,但这些后续动作只在服务端处理完创建文件 (mkfile) 请求后才会发生,而且也只有 mkfile 请求的内容可以包含变量

分片上传分为 v1 和 v2 版本,如下:

# 1.1.2 分片上传 v1 版本

分片上传支持将一个文件切割为一系列特定大小的数据片,分别将这些小数据片上传到服务端,全部上传完后再在服务端将这些数据片合并成为一个资源。

分片上传引入了两个概念:(Block)和(Chunk)。每个由一到多个组成,而一个资源则由一到多个组成。他们之间的关系可以用下图表述:

资源、块、片的关系

是上传过程中作为临时存储的单位。服务端会以约七天为单位的周期清除上传后未被合并为块 (文件) 的数据片 (块)。

与分片上传相关的 API 有:创建块 (mkblk)上传片 (bput)创建文件 (mkfile)。一个完整的分片上传流程可用下图表示:

分片上传流程

其中的关键点如下:

  • 将待上传的文件按预定义块大小切分为若干个块(每块大小不大于 4MB)。如果这个文件小于 4MB,就只有一个块,如果文件大于 4MB, 除最后一块外,其余块大小固定为 4MB。
  • 将每个块再按预定义的片大小切分为若干个片,先在服务端创建一个相应块(通过调用 mkblk,并带上第一个片的内容),然后再循环将所有剩下的片全部上传(通过调用 bput,从而完成一个块的上传)
  • 在所有块上传完成后,通过调用 mkfile 将这些上传完成的块信息再严格的按顺序组装出一个逻辑资源的元信息,从而完成整个资源的分片上传过程。

# 1.1.3 分片上传 v2 版本

分片上传( Multipart Upload ),可以将要上传的文件分成多个数据块( 又称之为 Part )来分别上传,上传完成之后再调用接口将这些 Part 组合成一个 Object。

与分片上传相关的 API 有:初始化 (initiateMultipartUpload)分块上传数据 (uploadPart) 完成文件上传 (completeMultipartUpload) 终止上传 ( abortMultipartUpload )列举已上传分片 (listParts)。一个完整的分片上传流程可用下图表示:

image-20240322193237381

其中的关键点如下:

  • Part 大小要求,除最后一个 Part 外,每个 Part 大小在 1MB - 1GB 之间。
  • 每个 Multipart Upload 任务,最多 10000 个 Part, 编号 PartNumber 在 1 - 10000 (含)之间。
  • 要上传的文件切分成 Part 之后,实现并发上传。具体的并发个数并不是越多速度越快,要结合自身情况考虑。网络情况较好时,建议增大 Part 大小。反之,减小 Part 大小。
  • 在所有 Part 上传完成后,通过调用 completeMultipartUpload 将这些上传完成的 Part 信息严格的按编号 PartNumber 顺序(编号可以不连续,但必须是升序)组装出一个逻辑资源的元信息,从而完成整个资源的分片上传过程

# 1.2 上传文件代码演示:

# 1.2.1 引入 jar 包

<dependency>
   <groupId>com.qiniu</groupId>
   <artifactId>qiniu-java-sdk</artifactId>
   <version>7.7.0</version>
</dependency>

配置文件信息:

server:
  port: 8081
qiniu:
  accessKey: Zc1ktIEYgIhsY-MuNAT5Wf8WTowk1AeV30SVKBNQ
  secretKey: -ofjyul-TpbriHRx8gJZWospjWArayyTjLZCBkL9
  bucket: demo-demo-demo-demo
  path: http://saqvg00o7.hb-bkt.clouddn.com/

代码编写

@SpringBootTest
class QiniuDemoApplicationTests
{
    @Value("${qiniu.accessKey}")
    private String accessKey;
    @Value("${qiniu.secretKey}")
    private String secretKey;
    @Value("${qiniu.bucket}")
    private String bucket;
    @Value("${qiniu.path}")
    private String path;
    @Test
    void upload()
    {
        // 本地文件
        File file = new File("E:\\demo\\基础.mp4");
        // 获取文件后缀名
        String fileName = file.getName().substring(file.getName().lastIndexOf("."));
        // 实例带 Region.autoRegion () 的配置对象
        Configuration configuration = new Configuration(Region.autoRegion());
        // 指定分片上传的版本为 V2 版本
        configuration.resumableUploadAPIVersion = Configuration.ResumableUploadAPIVersion.V2;
        // 实例化上传对象并传入配置信息
        UploadManager uploadManager = new UploadManager(configuration);
        // 创建时间对象
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        // 序列化当前时间
        String format = simpleDateFormat.format(new Date());
        // 通过 UUID + 当前时间 + 文件后缀名 创建出一个不容易碰撞的文件名
        String randomName = UUID.randomUUID().toString().replace("-", "") + format + fileName;
        // 将 access_key 和 secret_key 传入上传策略中
        Auth auth = Auth.create(accessKey, secretKey);
        // 上传时指定桶的名称
        String uploadToken = auth.uploadToken(bucket);
        try
        {
            // 上传分片文件,最后一个参数说明:true,断点续传,false,没有断点续传(默认)
            uploadManager.put(file, randomName, uploadToken, null, null, true);
        } catch (QiniuException e)
        {
            System.out.println("上传失败, 原因:" + e.getMessage());
        }
        System.out.println("上传路径:" + path + "/" + randomName);
    }
    @Test
    void download()
    {
    }
}

执行结果:

// 返回的路径可以进行访问地址源就是我们上传的视频而已
上传路径:saqvg00o7.hb-bkt.clouddn.com/0a79c8333bb94d768ebb2f6cac84aa482024-03-22 19:42:18.mp4

查看七牛云的空间内容:

image-20240322194310969

可以看到视频上传成功了并且点击详情可以进行查看

# 1.3 下载代码演示:

下载资源分为:公开资源下载,私有资源下载;

# 1.3.1 公开资源下载

公开资源下载通过 HTTP GET 的方式访问资源 URL 即可。资源 URL 的构成如下:

http://<domain>/<key>

其中 <domain> 有两种形态:七牛子域名和自定义域名。

七牛子域名是创建空间时默认分配的域名,开发者请前往七牛开发者平台,选择一个空间后查看测试域名即七牛子域名。子域名形式类似于 78re52.com1.z0.glb.clouddn.com,用户可以通过以下 URL 下载名为 resource/flower.jpg 的资源:

http://78re52.com1.z0.glb.clouddn.com/resource/flower.jpg

您也可以为某特定空间,申请绑定自定义域名,通过这个自定义域名访问资源,例如绑定的自定义域名为 i.example.com,您就可以通过以下 URL 访问同样的资源:

http://i.example.com/resource/flower.jpg

# 1.3.2 私有资源下载

当您将空间设置成私有时,必须获得授权,才能对空间内的资源进行访问。

私有资源下载是通过 HTTP GET 的方式访问特定的 URL。私有资源 URL 与公开资源 URL 相比只是增加了两个参数 etoken ,分别表示过期时间和下载凭证。一个完整的私有资源 URL 如下所示:

http://<domain>/<key>?e=<deadline>&token=<downloadToken>

参数 e 表示 URL 的过期时间,采用 Unix 时间戳,单位为秒。超时的访问将返回 401 错误。参数 token 表示下载凭证。下载凭证是对资源访问的授权,不带下载凭证或下载凭证不合法都会导致 401 错误,表示验证失败。

注意:

  • 如果请求方的时钟未校准,可能会造成有效期验证不正常,例如直接认为已过期。因此需要进行时钟校准。
  • 由于开发者无法保证客户端的时间都校准,所以应该在业务服务器上创建时间戳,并周期性校准业务服务器时钟。
    • token 必须放在请求的最后,token 之后的参数会被忽略。以请求 http://test.cinem.net/aaaa.jpg?e=1778754963&token=sQvk4AXf0rEkzcytkr...XjI0M:zwvwiM0wsMBRj46xcby05U=&attname=geral_TS-PFS3010-8ET 为例,此时 attname=geral_TS-PFS3010-8ET 会被忽略,并不生效

# 1.4 下载资源代码演示:

@Test
void download()
{
   // 要下载到的路径
   String loadPath = "E:\\";
   // 下载资源 url
   String downloadUrl = "http://saqvg00o7.hb-bkt.clouddn.com/0a79c8333bb94d768ebb2f6cac84aa482024-03-22%2019:42:18.mp4";
   // 获取文件的名称
   String substring = downloadUrl.substring(path.lastIndexOf("/") + 1);
   // 实例化 HttpClient 对象
   CloseableHttpClient httpClient = HttpClientBuilder.create().build();
   // 实例化请求对象传入请求 url
   HttpGet httpGet = new HttpGet(downloadUrl);
   CloseableHttpResponse response = null;
   InputStream content = null;
   FileOutputStream os = null;
   try
   {
      // 执行请求并返回响应对象
      response = httpClient.execute(httpGet);
      // 获取响应实体
      HttpEntity entity = response.getEntity();
      // 判断实体是否为空
      if (entity != null)
      {
         // 创建对应的目录 上面的文件名有 % : 这个是不允许的会创建文件 / 目录失败所以先进行转换下次上传时注意格式就行
         File file = new File(loadPath + substring.replace(":", "-").replace("%", "-"));
         // 通过 content 返回输入流对象
         content = entity.getContent();
         // 创建输出流写出文件数据到指定位置
         os = new FileOutputStream(file);
         int i = 0;
         byte[] bytes = new byte[1024];
         while (((i = content.read(bytes)) != -1))
         {
            os.write(bytes, 0, i);
         }
      }
   } catch (IOException e)
   {
      throw new RuntimeException(e);
   } finally
   {
      // 关闭流释放资源
      try
      {
         if (os != null)
         {
            os.close();
         }
         if (content != null)
         {
            content.close();
         }
      } catch (IOException e)
      {
         throw new RuntimeException(e);
      }
   }
}

下载结果:

image-20240322214724480

# 1.5 删除指定资源:

注意:这里被坑了 10 分钟,因为文件名中带 % (空格自转的) 还有 - 导致删除返回状态码 298 (部分执行成功) 文件并没有被删除

@Test
void delete() {
   // 资源 url 这里就需要注意了,上面的文件名 带 %(是空格自转的) 带 - 的会导致删除部分成功 也就是会返回 298 状态码 然后 文件并没有删除 就是这样 我就把文件名改了才删除成功的
   String fileName = "http://saqvg00o7.hb-bkt.clouddn.com/0a7.mp4";
   // 获取文件名
   String substring = fileName.substring(path.lastIndexOf("/") + 1);
   System.out.println(substring);
   // 实例带 Region.autoRegion () 的配置对象
   Configuration configuration = new Configuration(Region.region0());
   // 将 access_key 和 secret_key 传入策略中
   Auth auth = Auth.create(accessKey, secretKey);
   // 实例化资源管理对象
   BucketManager bucketManager = new BucketManager(auth, configuration);
   // 批量请求文件数量不能超 1000
   BucketManager.BatchOperations batchOperations = new BucketManager.BatchOperations();
   // 批量删除
   batchOperations.addDeleteOp(bucket, substring);
   try
   {
      // 请求批量删除
      Response batch = bucketManager.batch(batchOperations);
      // 获取请求的状态码
      int statusCode = batch.statusCode;
      System.out.println(statusCode);
      // 判断状态码
      //todo 298 记 这一个被文件名坑的一次
      if(statusCode == 200)
      {
         System.out.println("删除成功");
      } else
      {
         System.out.println("删除失败");
      }
   } catch (QiniuException e)
   {
      throw new RuntimeException(e);
   }
}

image-20240322222622298