在Koa.js中实现文件上传的接口(服务端签名直传)

发布于:2024-12-09 ⋅ 阅读:(194) ⋅ 点赞:(0)

目录

为什么客户端直传

方案概览

步骤一:配置OSS

一、创建Bucket

二、配置CORS规则

步骤二:配置服务端

一、配置用户权限

二 、在访问控制为RAM用户授予AliyunOSSFullAccess的权限

步骤三 .服务端获取访问凭证 & 配置Web端

服务端生成Post签名和Post Policy

客户端示例代码


 

为什么客户端直传

在典型的服务端和客户端架构下,常见的文件上传方式是服务端代理上传:客户端将文件上传到业务服务器,然后业务服务器将文件上传到OSS。在这个过程中,一份数据需要在网络上传输两次,会造成网络资源的浪费,增加服务端的资源开销。为了解决这一问题,您可以在客户端直连OSS来完成文件上传,无需经过业务服务器中转。

 

服务端生成签名以实现Web端直传,使用户能够通过Web浏览器直接使用PostObject接口上传文件到OSS。此过程通过签名机制确保上传的安全性。同时,根据具体业务需求,您可以在服务端生成上传策略(Policy),以限制上传操作。

方案概览

服务端生成签名实现Web端直传的过程如下:


 

要实现服务端签名直传,只需3步:

说明

对于需要限制上传文件属性的场景,您可以在服务端生成PostObject所需的Post签名、PostPolicy等信息,然后客户端可以凭借这些信息,在一定的限制下不依赖OSS SDK直接上传文件。您可以借助服务端生成的PostPolicy限制客户端上传的文件,例如限制文件大小、文件类型。此方案适用于通过HTML表单上传的方式上传文件。需要注意的是,此方案不支持基于分片上传大文件、基于分片断点续传的场景。

  1. 配置OSS:配置OSS,在控制台创建一个Bucket,用于存储用户上传的文件。同时,为 Bucket 配置跨域资源共享(CORS) 规则,以允许来自服务端的跨域请求。

  2. 配置服务端:配置服务端,服务端生成PostObject所需的签名和Post Policy,然后使用访问凭证和服务端预设的上传策略(如Bucket名称、目录路径、过期时间等)生成签名授权用户在一定时间内进行文件上传。

  3. 配置Web端:配置Web端,构造HTML表单请求,通过表单提交使用签名将文件上传到OSS。

步骤一:配置OSS

一、创建Bucket

创建一个OSS Bucket,用于存储Web应用在浏览器环境中直接上传的文件。

  1. 登录OSS管理控制台

  2. 在左侧导航栏,单击Bucket 列表然后单击创建 Bucket

  3. 创建 Bucket面板,选择快捷创建,按如下说明配置各项参数。

    参数

    示例值

    Bucket名称

    web-direct-upload

    地域

    华东1(杭州)

  4. 点击完成创建

二、配置CORS规则

为创建的OSS Bucket配置CORS规则。

  1. 访问Bucket列表,然后单击目标Bucket名称。

  2. 跨域设置页面,单击创建规则

  3. 创建跨域规则面板,按以下说明设置跨域规则。

    参数

    示例值

    来源

    *

    允许Methods

    POST、PUT、GET

    允许Headers

    *

  1. 单击确定

步骤二:配置服务端

一、配置用户权限

说明

为了确保部署完成后不会因为操作未授权而导致文件上传到OSS失败,建议您先按照以下步骤创建RAM用户并配置相应的权限。

在访问控制创建RAM用户

首先,创建一个调用方式为OpenAPI调用的RAM用户,并获取对应的访问密钥,作为业务服务器的应用程序的长期身份凭证。

  1. 使用云账号或账号管理员登录RAM控制台

  2. 在左侧导航栏,选择身份管理 > 用户

  3. 单击创建用户

  4. 输入登录名称显示名称

  5. 调用方式区域下,选择OpenAPI调用,然后单击确定

重要

RAM用户的AccessKey Secret只在创建时显示,后续不支持查看,请妥善保管。

单击操作下的复制,保存调用密钥(AccessKey ID和AccessKey Secret)。

二 、在访问控制为RAM用户授予AliyunOSSFullAccess的权限

创建RAM用户后,需要授予RAM用户AliyunOSSFullAccess的权限,使其可以通过扮演RAM角色来获取管理对象存储服务(OSS)权限

  1. 在左侧导航栏,选择身份管理 > 用户

  2. 用户页面,找到目标RAM用户,然后单击RAM用户右侧的添加权限

  3. 新增授权页面,选择AliyunOSSFullAccess系统策略。

  4. 单击确认新增授权

步骤三 .服务端获取访问凭证 & 配置Web端

您可以通过OSS控制台的PostObject Policy签名工具为通过HTML表单上传生成请求签名。通过PostObject Policy签名工具填入指定参数后,会自动生成请求签名,并校验请求签名的正确性。

  1. 参数

    是否必选

    示例值

    说明

    AccessKeyId

    LTAI********

    填写阿里云账号或RAM用户的访问密钥AccessKey,包括AccessKey ID和AccessKey Secret。

    AccessKeySecret

    KZo1********

    过期时间

    2023-01-09T07:36:58.086Z

    请求过期时间,必须为GMT格式。下拉选择过期时间后会自定填充到Policy。

    Policy

    { "expiration": "2014-12-01T12:00:00.000Z", "conditions": [ {"bucket": "johnsmith" }, ["content-length-range", 1, 10], ["eq", "$success_action_status", "201"], ["starts-with", "$key", "user/eric/"], ["in", "$content-type", ["image/jpg", "image/png"]], ["not-in", "$cache-control", ["no-cache"]] ]}

    PostObject请求的Policy表单域,用于验证请求的合法性。Policy为一段经过UTF-8和Base64编码的JSON文本,声明了PostObject请求必须满足的条件。

    重要

    对于向公共读写的Bucket执行表单上传时,Policy表单域为可选项,但强烈建议使用该域来限制PostObject请求。

    关于Post Policy的更多信息,请参见附录:Post Policy

服务端生成Post签名和Post Policy

 

服务端生成Post签名和Post Policy等信息的示例代码如下:

  ALI_ACCESS_KEY_ID,
  ALI_ACCESS_KEY_SECRET,
  ALI_OSS_BUCKET,
  ALI_OSS_REGION,
} = require("../config/config.default");
const OSS = require("ali-oss");
const moment = require("moment");
const path = require("path");
// 验证配置项是否存在
if (
  !ALI_ACCESS_KEY_ID ||
  !ALI_ACCESS_KEY_SECRET ||
  !ALI_OSS_BUCKET ||
  !ALI_OSS_REGION
) {
  throw new Error("Missing required configuration for Ali OSS");
}

/**
 * 初始化 OSS 客户端
 */
const store = new OSS({
  region: ALI_OSS_REGION, // yourRegion填写Bucket所在地域。以华东1(杭州)为例,Region填写为oss-cn-hangzhou。
  accessKeyId: ALI_ACCESS_KEY_ID,
  accessKeySecret: ALI_ACCESS_KEY_SECRET,
  bucket: ALI_OSS_BUCKET, // yourBucketName填写Bucket名称。
});

/**
 * 计算 Post 签名
 * @param {string} directory - 上传目录
 * @param {string} name - 文件名
 * @returns {Promise<Object>} - 签名结果
 */
async function calculatePostSignature(directory, name) {
  const date = moment().add(1, "days");
  const key = `${directory}/${name}`;
  const policy = {
    expiration: date.toISOString(), // 过期时间
    // 条件
    conditions: [
      ["content-length-range", 0, 5 * 1024 * 1024], // 文件大小限制
      { bucket: ALI_OSS_BUCKET }, // 存储空间
      ["eq", "$key", key], // 文件名 eq:等于
      [
        "in",
        "$content-type",
        ["image/jpeg", "image/png", "image/gif", "image/bmp", "image/webp"],
      ], // 文件类型限制 in: 包含
    ],
  };
  const formData = store.calculatePostSignature(policy);
  // 获取主机
  let host = await store.getBucketLocation();
  // 构建 URL
  let url = `http://${ALI_OSS_BUCKET}.${host.location}.aliyuncs.com`.toString();
  return {
    policy: formData.policy, // 策略
    signature: formData.Signature, // 签名
    ossAccessKeyId: formData.OSSAccessKeyId, // 访问凭证
    host: url, // 主机
    dir: directory, // 目录
    key: key, // 文件名
  };
}

注册使用

 // 获取 oss 上传的 token
  async getOssToken(ctx, next) {
    let { filename } = ctx.request.query;
    console.log(filename);

    const directory = "uploads/images";
    //  生成唯一文件名 (唯一id+文件扩展名)
    const uniqueFileName = `${uuidv4()}.${filename.split(".")[1]}`;
    const res = await calculatePostSignature(directory, uniqueFileName);
    ctx.body = {
      code: 0,
      message: "获取oss上传token成功",
      result: res,
    };
  }

客户端示例代码

Web端使用临时访问凭证上传文件到OSS的示例代码如下:

1.调用接口获取签名等信息
2 调用后端 返回的上传oss的地址
注意 要使用后的返回的文件名进行上传,这样可以保证唯一性,并且 政策上有检验如果不是也会上传失败

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<form>
    <input type="file" name="file" id="file" />
    <button type="submit">上传</button>
</form>

<body>

</body>
<script>
    const form = document.querySelector("form");
    const fileInput = document.querySelector("#file");
    form.addEventListener("submit", (event) => {
        event.preventDefault(); // 阻止表单提交的默认行为
        const file = fileInput.files[0];
        const filename = fileInput.files[0].name;
        let type = fileInput.files[0].type;


        fetch("http://localhost:9999/goods/oss/token?filename=" + filename, { method: "GET" })
            .then((response) => {
                if (!response.ok) {
                    throw new Error("获取签名失败");
                }

                return response.json();
            })
            .then((res) => {
                let data = res.result;
                const formData = new FormData();
                formData.append("name", filename);
                formData.append("policy", data.policy); // 策略
                formData.append("OSSAccessKeyId", data.ossAccessKeyId);// 阿里云的key
                formData.append("success_action_status", "200");  // 200 表示上传成功后,返回200状态码
                formData.append("signature", data.signature); // 签名
                formData.append("key", data.key); // 文件名
                formData.append("file", file); // 文件

                return fetch(data.host, { method: "POST", body: formData });
            })
            .then((response) => {
                if (response.ok) {
                    console.log("上传成功");
                    alert("文件已上传");
                } else {
                    console.log("上传失败", response);
                    alert("上传失败,请稍后再试");
                }
            })
            .catch((error) => {

                console.error("发生错误:", error);
            });
    });
</script>

</html>


网站公告

今日签到

点亮在社区的每一天
去签到