日本熟妇hd丰满老熟妇,中文字幕一区二区三区在线不卡 ,亚洲成片在线观看,免费女同在线一区二区

鴻蒙環(huán)境服務(wù)端簽名直傳

更新時(shí)間:

通過服務(wù)端簽名實(shí)現(xiàn)鴻蒙應(yīng)用直傳OSS使用戶可以通過鴻蒙端直接使用PutObject接口上傳文件到OSS,此過程通過在服務(wù)端實(shí)現(xiàn)URL簽名機(jī)制確保上傳的安全性。

方案概覽

使用鴻蒙環(huán)境實(shí)現(xiàn)文件上傳的過程如下:

image

要實(shí)現(xiàn)鴻蒙應(yīng)用直傳OSS,只需3步:

  1. 配置OSS:在OSS控制臺(tái)上創(chuàng)建一個(gè)Bucket,用于存儲(chǔ)用戶上傳的文件。

  2. 配置服務(wù)端:在服務(wù)端創(chuàng)建一個(gè)實(shí)例,用于從STS服務(wù)獲取一個(gè)臨時(shí)訪問憑證,然后使用臨時(shí)訪問憑證生成簽名 URL用于授權(quán)用戶在一定時(shí)間內(nèi)進(jìn)行文件上傳。

  3. 配置鴻蒙客戶端:在鴻蒙客戶端,實(shí)現(xiàn)從ECS獲取簽名并構(gòu)造PutObject請(qǐng)求,將文件上傳到OSS。

示例代碼

以下代碼示例只給出關(guān)鍵邏輯的代碼片段,完整示例工程請(qǐng)參見:oss-js-sdk-harmony-demo.zip

服務(wù)端代碼示例

服務(wù)端生成簽名URL:

const express = require("express");
const mime = require("mime");
const OSS = require("ali-oss");
const app = express();
const port = 3000; // 監(jiān)聽端口

app.use(express.json());

app.use(express.urlencoded({ extended: false }));

app.post("/get_sign_url", async (req, res) => {
  const {
    fileName,
    method,
    headers = {},
    queries = {},
    additionalHeaders = [],
  } = req.body; // 從body中解析出數(shù)據(jù)
  const client = new OSS({
    region: "yourRegion",
    accessKeyId: "yourstsAccessKey",
    accessKeySecret: "yourstsAccessKeySecret",
    stsToken: "yourSTSToken",
    bucket: "yourBucket",
    authorizationV4: true,
  });

  const reqHeaders = {
    ...headers,
  };

  // 處理一下content-type
  if (fileName && method === "PUT") {
    const fileNameSplit = fileName.split(".");

    reqHeaders["content-type"] = mime.getType(
      fileNameSplit.length > 1 ? fileNameSplit[fileNameSplit.length - 1] : ""
    );
  }

  // 生成V4簽名URL
  const url = await client.signatureUrlV4(
    method,
    300,
    {
      headers: reqHeaders,
      queries,
    },
    fileName,
    additionalHeaders
  );

  res.json({
    url,
    contentType: reqHeaders["content-type"],
  });
});

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`);
});

鴻蒙客戶端代碼示例

在客戶端獲取簽名并使用簽名URL上傳文件:

import { http } from '@kit.NetworkKit';
import fs from '@ohos.file.fs';
import { request } from './request';

const serverUrl = 'http://x.x.x.x:3000/get_sign_url'; // 獲取簽名URL的服務(wù)器URL

/**
 * getSignUrl返回?cái)?shù)據(jù)
 */
export interface ISignUrlResult {
  /** 簽名URL */
  url: string;
  /** content-type */
  contentType?: string;
}

/**
 * 獲取簽名URL
 * @param fileName 文件名稱
 * @param req 用于生成V4簽名URL的請(qǐng)求信息
 * @param req.method 請(qǐng)求方式
 * @param [req.headers] 請(qǐng)求頭
 * @param [req.queries] 請(qǐng)求查詢參數(shù)
 * @param [req.additionalHeaders] 加簽的請(qǐng)求頭
 */
const getSignUrl = async (fileName: string, req: {
  method: 'GET' | 'POST' | 'PUT';
  headers?: Record<string, string | number>;
  queries?: Record<string, string>;
  additionalHeaders?: string[];
}): Promise<ISignUrlResult> => {
  console.info('in getSignUrl');

  try {
    const response = await request(serverUrl, {
      method: http.RequestMethod.POST,
      header: {
        'Content-Type': 'application/json'
      },
      extraData: {
        fileName,
        method: req.method,
        headers: req.headers,
        queries: req.queries,
        additionalHeaders: req.additionalHeaders
      },
      expectDataType: http.HttpDataType.OBJECT
    }, 200);
    const result = response.result as ISignUrlResult;

    console.info('success getSignUrl');

    return result;
  } catch (err) {
    console.info('getSignUrl request error: ' + JSON.stringify(err));

    throw err;
  }
};

/**
 * PutObject
 * @param fileUri 文件URI
 */
const putObject = async (fileUri: string): Promise<void> => {
  console.info('in putObject');

  const fileInfo = await fs.open(fileUri, fs.OpenMode.READ_ONLY);
  const fileStat = await fs.stat(fileInfo.fd);
  let signUrlResult: ISignUrlResult;

  console.info('file name: ', fileInfo.name);

  try {
    // 獲取PutObject的簽名URL
    signUrlResult = await getSignUrl(fileInfo.name, {
      method: 'PUT',
      headers: {
        'Content-Length': fileStat.size
      },
      additionalHeaders: ['Content-Length']
    });
  } catch (e) {
    await fs.close(fileInfo.fd);

    throw e;
  }

  const data = new ArrayBuffer(fileStat.size);

  await fs.read(fileInfo.fd, data);
  await fs.close(fileInfo.fd);

  try {
    // 使用PutObject方法上傳文件
    await request(signUrlResult.url, {
      method: http.RequestMethod.PUT,
      header: {
        'Content-Length': fileStat.size,
        'Content-Type': signUrlResult.contentType
      },
      extraData: data
    }, 200);

    console.info('success putObject');
  } catch (err) {
    console.info('putObject request error: ' + JSON.stringify(err));

    throw err;
  }
};

export {
  getSignUrl,
  putObject
};

操作步驟

步驟一:配置OSS

創(chuàng)建Bucket

創(chuàng)建一個(gè)OSS Bucket,用于存儲(chǔ)Web應(yīng)用在瀏覽器環(huán)境中直接上傳的文件。

  1. 登錄OSS管理控制臺(tái)

  2. 在左側(cè)導(dǎo)航欄,單擊Bucket 列表,然后單擊創(chuàng)建 Bucket

  3. 創(chuàng)建 Bucket面板,選擇快捷創(chuàng)建,按如下說明配置各項(xiàng)參數(shù)。

    參數(shù)

    示例值

    Bucket名稱

    web-direct-upload

    地域

    華東1(杭州)

  4. 點(diǎn)擊完成創(chuàng)建。

步驟二:配置服務(wù)端

、創(chuàng)建一臺(tái)ECS并綁定角色

操作一:創(chuàng)建ECS實(shí)例

請(qǐng)您進(jìn)入自定義購買頁面,并根據(jù)如下各模塊的內(nèi)容,創(chuàng)建或選擇購買ECS實(shí)例所需的基礎(chǔ)資源。

  1. 選擇地域 & 付費(fèi)類型

    1. 根據(jù)業(yè)務(wù)需求,選擇合適的付費(fèi)類型。本文選擇按量付費(fèi)模式,此模式操作相對(duì)靈活。

    2. 基于業(yè)務(wù)場景對(duì)時(shí)延的要求,選擇地域。通常來說離ECS實(shí)例的物理距離越近,網(wǎng)絡(luò)時(shí)延越低,訪問速度越快。本文以選擇華東1(杭州)為例。

      image

  1. 創(chuàng)建專有網(wǎng)絡(luò)VPC & 交換機(jī)

    創(chuàng)建VPC時(shí),請(qǐng)您選擇和ECS相同的地域,并根據(jù)業(yè)務(wù)需求規(guī)劃網(wǎng)段。本文以創(chuàng)建華東1(杭州)地域的VPC和交換機(jī)為例。創(chuàng)建完畢后返回ECS購買頁,刷新并選擇VPC及交換機(jī)。

    說明

    創(chuàng)建VPC時(shí),可同時(shí)創(chuàng)建交換機(jī)。

    image

    image

    image

  1. 選擇規(guī)格 & 鏡像

    選擇實(shí)例的規(guī)格及鏡像,鏡像為實(shí)例確定安裝的操作系統(tǒng)及版本。本文選擇的實(shí)例規(guī)格為ecs.e-c1m1.large,在滿足測試需求的同時(shí),價(jià)格較為實(shí)惠。鏡像為公共鏡像Alibaba Cloud Linux 3.2104 LTS 64。

    image

  1. 選擇存儲(chǔ)

    ECS實(shí)例選擇系統(tǒng)盤,并按需選擇數(shù)據(jù)盤。本文實(shí)現(xiàn)簡單Web系統(tǒng)搭建,只需要系統(tǒng)盤存儲(chǔ)操作系統(tǒng),無需數(shù)據(jù)盤。

    image

  1. 綁定公網(wǎng)IP

    本實(shí)例需要支持公網(wǎng)訪問。為了簡化操作,本文選擇直接為實(shí)例分配公網(wǎng)IP。您也可以在創(chuàng)建實(shí)例后,為實(shí)例綁定彈性公網(wǎng)IP,具體操作,請(qǐng)參見EIP綁定至ECS實(shí)例

    說明
    • 若未綁定公網(wǎng)IP,將無法使用SSHRDP通過公網(wǎng)直接訪問實(shí)例,也無法通過公網(wǎng)驗(yàn)證實(shí)例中Web服務(wù)的搭建。

    • 本文選擇按使用流量的帶寬計(jì)費(fèi)模式。此模式只需為所消耗的公網(wǎng)流量付費(fèi)。更多信息,請(qǐng)參見公網(wǎng)帶寬計(jì)費(fèi)。

    image

  1. 創(chuàng)建安全組

    為實(shí)例創(chuàng)建安全組。安全組是一種虛擬網(wǎng)絡(luò)防火墻,能夠控制ECS實(shí)例的出入流量。創(chuàng)建時(shí),需要設(shè)置放行以下指定端口,便于后續(xù)訪問ECS實(shí)例。

    端口范圍:SSH(22)、RDP(3389)、HTTP(80)、HTTPS(443)。

    說明
    • 端口范圍處選中的是ECS實(shí)例上運(yùn)行的應(yīng)用需開放的端口。

    • 此處創(chuàng)建的安全組默認(rèn)設(shè)置0.0.0.0/0作為源的規(guī)則。0.0.0.0/0表示允許全網(wǎng)段設(shè)備訪問指定的端口,如果您知道請(qǐng)求端的IP地址,建議后續(xù)設(shè)置為具體的IP范圍。具體操作,請(qǐng)參見修改安全組規(guī)則。

    image

  1. 創(chuàng)建密鑰對(duì)

    1. 密鑰對(duì)可作為登錄時(shí)證明個(gè)人身份的安全憑證,創(chuàng)建完成后,必須下載私鑰,以供后續(xù)連接ECS實(shí)例時(shí)使用。創(chuàng)建完畢后返回ECS購買頁,刷新并選擇密鑰對(duì)。

    2. root具有操作系統(tǒng)的最高權(quán)限,使用root作為登錄名可能會(huì)導(dǎo)致安全風(fēng)險(xiǎn),建議您選擇ecs-user作為登錄名。

      說明

      創(chuàng)建密鑰對(duì)后,私鑰會(huì)自動(dòng)下載,請(qǐng)您關(guān)注瀏覽器的下載記錄,保存.pem格式的私鑰文件。

      image

  1. 創(chuàng)建并查看ECS實(shí)例

    創(chuàng)建或選擇好ECS實(shí)例所需的基礎(chǔ)資源后,勾選《云服務(wù)器ECS服務(wù)條款》、《云服務(wù)器ECS退訂說明》,單擊確認(rèn)下單。在提示成功的對(duì)話框中,單擊管理控制臺(tái),即可在控制臺(tái)查看到創(chuàng)建好的ECS實(shí)例。請(qǐng)您保存以下數(shù)據(jù),以便在后續(xù)操作中使用。

    • 實(shí)例ID:便于在實(shí)例列表中查詢到該實(shí)例。

    • 地域:便于在實(shí)例列表中查詢到該實(shí)例。

    • 公網(wǎng)IP地址:便于在后續(xù)使用ECS實(shí)例時(shí),做Web服務(wù)的部署結(jié)果驗(yàn)證。

    imageimage

操作二:連接ECS實(shí)例

  1. 云服務(wù)器ECS控制臺(tái)實(shí)例列表頁面,根據(jù)地域、實(shí)例ID找到創(chuàng)建好的ECS實(shí)例,單擊操作列下的遠(yuǎn)程連接。image

  2. 遠(yuǎn)程連接對(duì)話框中,單擊通過Workbench遠(yuǎn)程連接對(duì)應(yīng)的立即登錄image

  3. 登錄實(shí)例對(duì)話框中,選擇認(rèn)證方式SSH密鑰認(rèn)證,用戶名為root,輸入或上傳創(chuàng)建密鑰對(duì)時(shí)下載的私鑰文件,單擊確定,即可登錄ECS實(shí)例。

    說明

    私鑰文件在創(chuàng)建密鑰對(duì)時(shí)自動(dòng)下載到本地,請(qǐng)您關(guān)注瀏覽器的下載記錄,查找.pem格式的私鑰文件。

    image

  4. 顯示如下頁面后,即說明您已成功登錄ECS實(shí)例。image

操作三:在訪問控制中創(chuàng)建RAM角色

  1. 進(jìn)入RAM訪問控制的創(chuàng)建角色頁面。

  2. 在創(chuàng)建角色頁面勾選阿里云服務(wù),單擊下一步。

    image

  3. 勾選普通服務(wù)角色,填寫角色名稱如oss-web-upload。并選擇受信服務(wù)為云服務(wù)器后,單擊完成,即可完成創(chuàng)建。

    image

  4. 單擊復(fù)制,保存角色的ARN。

    1.png

操作四:在訪問控制中創(chuàng)建上傳文件的權(quán)限策略

  1. 權(quán)限策略頁面,單擊創(chuàng)建權(quán)限策略。

  2. 創(chuàng)建權(quán)限策略頁面,單擊腳本編輯,將以下腳本中的<Bucket名稱>替換為準(zhǔn)備工作中創(chuàng)建的Bucket名稱web-direct-upload。

    {
      "Version": "1",
      "Statement": [
        {
          "Effect": "Allow",
          "Action": "oss:PutObject",
          "Resource": "acs:oss:*:*:<Bucket名稱>/*"
        }
      ]
    }
  3. 然后單擊確定,填寫策略名稱。

操作五:在訪問控制為RAM角色授予權(quán)限

  1. 角色頁面,找到目標(biāo)RAM角色,然后單擊RAM角色右側(cè)的新增授權(quán)。

  2. 新增授權(quán)頁面下的自定義策略頁簽,選擇已創(chuàng)建的自定義權(quán)限策略。

  3. 單擊確定

操作六:為ECS綁定RAM角色

  1. 進(jìn)入云服務(wù)器ECS實(shí)例頁面,在頁面上方選擇ECS實(shí)例所處地域,然后在實(shí)例頁面單擊目標(biāo)實(shí)例右側(cè)image按鈕,單擊授予/收回RAM角色

    image

  2. 授予/收回RAM角色彈出框選擇目標(biāo)RAM角色,完成ECS綁定RAM角色。

    image

說明

生成的臨時(shí)身份憑證用于下一步生成簽名URL,如果您已經(jīng)擁有臨時(shí)身份憑證,可以直接跳轉(zhuǎn)到下一步三、服務(wù)端生成URL簽名。

二、服務(wù)端生成臨時(shí)訪問憑證

操作一、ECS服務(wù)端配置依賴

請(qǐng)執(zhí)行以下命令安裝獲取臨時(shí)訪問憑證所需要的依賴項(xiàng)。

Python

  1. 安裝Python3。

  2. 執(zhí)行以下命令,安裝Credentials工具。

sudo pip install oss2
sudo pip install alibabacloud_credentials

Java

Maven項(xiàng)目中,導(dǎo)入以下依賴。

<!-- https://mvnrepository.com/artifact/com.aliyun/credentials-java -->
<dependency>
    <groupId>com.aliyun</groupId>
    <artifactId>credentials-java</artifactId>
    <version>0.3.4</version>
</dependency>

<dependency>
    <groupId>com.aliyun.kms</groupId>
    <artifactId>kms-transfer-client</artifactId>
    <version>0.1.0</version>
</dependency>

<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>3.17.4</version>
</dependency>

操作二、在ECS服務(wù)端獲取臨時(shí)身份憑證

通過在業(yè)務(wù)服務(wù)器集成STS SDK,實(shí)現(xiàn)獲取臨時(shí)STS身份憑證,并將其返回給請(qǐng)求者。

Python

from alibabacloud_credentials.client import Client as CredClient
from alibabacloud_credentials.models import Config as CredConfig

def main():
    # 配置ECSRAMRole作為訪問憑證。
    credentialConfig = CredConfig(
        type='ecs_ram_role',
        role_name='ecs_role_name'       # 請(qǐng)?zhí)顚慐CS扮演的角色名稱。
    )
    credentialsClient = CredClient(credentialConfig)
    credential = credentialsClient.get_credential()

    accesskeyid = credential.access_key_id            # 獲取accesskeyid。
    accesskeysecret = credential.access_key_secret    # 獲取accesskeysecret。
    security_token = credential.security_token        # 獲取security_token。

    print("stsToken:", security_token)
    print("accesskeyid:", accesskeyid)
    print("accesskeysecret:", accesskeysecret)

if __name__ == "__main__":
    main()

Java

import com.aliyun.credentials.models.CredentialModel;
import com.aliyun.oss.common.auth.Credentials;
import com.aliyun.oss.common.auth.CredentialsProvider;
import com.aliyun.oss.common.auth.DefaultCredentials;
import com.aliyun.oss.common.utils.BinaryUtil;

public class vxDemo {
    public static void main(String[] args) {
    
        // 配置ECSRAMRole作為訪問憑證。
        com.aliyun.credentials.models.Config config = new com.aliyun.credentials.models.Config();
        config.setType("ecs_ram_role");
        config.setRoleName("ecs_role_name");   // 請(qǐng)?zhí)顚慐CS扮演的角色名稱。
        final com.aliyun.credentials.Client credentialsClient = new com.aliyun.credentials.Client(config);
        CredentialsProvider credentialsProvider = new CredentialsProvider() {
            @Override
            public void setCredentials(Credentials credentials) {
            }

            @Override
            public Credentials getCredentials() {
                CredentialModel credential = credentialsClient.getCredential();
                return new DefaultCredentials(credential.getAccessKeyId(), credential.getAccessKeySecret(), credential.getSecurityToken());
            }
        };
        String accessKeyId = credentialsProvider.getCredentials().getAccessKeyId();             //獲取accessKeyId。
        String secretAccessKey = credentialsProvider.getCredentials().getSecretAccessKey();     //獲取secretAccessKey。
        String securityToken = credentialsProvider.getCredentials().getSecurityToken();         //獲取securityToken。

        // 打印臨時(shí)訪問憑證信息。
        System.out.println("stsToken:" + securityToken);
        System.out.println("accessKeyId:" + accessKeyId);
        System.out.println("accesskeySecret:"+ secretAccessKey);
    }
}

生成的臨時(shí)訪問憑證accessKeyId、accessKeySecret、stsToken用于在下一步中填入生成簽名URL。

三、服務(wù)端生成URL簽名

客戶端向服務(wù)端發(fā)送一個(gè) POST 請(qǐng)求,包含文件名、HTTP 方法、請(qǐng)求頭、查詢參數(shù)等。服務(wù)端接收到請(qǐng)求后,通過OSS Client生成一個(gè)簽名URL,該URL允許用戶上傳文件到OSS。

app.post('/get_sign_url', async (req, res) => {
    const {
        fileName,
        method,
        headers = {},
        queries = {},
        additionalHeaders = []
    } = req.body; // 從body中解析出數(shù)據(jù)
    const client = new OSS({
        region: 'yourRegion',
        // 填入步驟二中返回的臨時(shí)訪問憑證accessKeyId、accessKeySecret、stsToken
        accessKeyId: 'yourstsAccessKey',
        accessKeySecret: 'yourstsAccessKeySecret',
        stsToken: 'yourSTSToken',
        bucket: 'yourBucket',
        authorizationV4: true
    });

    const reqHeaders = {
        ...headers
    };

    // 處理一下content-type
    if (fileName && method === 'PUT') {
        const fileNameSplit = fileName.split('.');

        reqHeaders['content-type'] = mime.getType(fileNameSplit.length > 1 ? fileNameSplit[fileNameSplit.length-1] : '');
    }

    // 生成V4簽名URL
    const url = await client.signatureUrlV4(method, 300, {
        headers: reqHeaders,
        queries
    }, fileName, additionalHeaders);

    res.json({
        url,
        contentType: reqHeaders['content-type']
    });
});

步驟三:配置鴻蒙客戶端

一、構(gòu)建HTTP數(shù)據(jù)請(qǐng)求

使用 @kit.NetworkKit 庫的 http 模塊來發(fā)送 HTTP 請(qǐng)求的異步函數(shù) request。

import { http } from '@kit.NetworkKit';

const request = async (url: string, options: http.HttpRequestOptions, successCode: number[] | number) => {
  const httpRequest = http.createHttp();

  try {
    const httpResponse = await httpRequest.request(url, {
      ...options,
      priority: 1,
      connectTimeout: 60000,
      readTimeout: 60000,
      usingProtocol: http.HttpProtocol.HTTP1_1
    });

    if ((Array.isArray(successCode) && successCode.includes(httpResponse.responseCode)) || httpResponse.responseCode === successCode) {
      const requestID = httpResponse.header['x-oss-request-id'];

      console.info(`request success${requestID ? ', oss request ID: ' + requestID : ''}`);

      return httpResponse;
    } else {
      throw {
        code: httpResponse.responseCode,
        result: httpResponse.result.toString(),
        requestID: httpResponse.header['x-oss-request-id']
      };
    }
  } catch (err) {
    console.info('request error: ' + JSON.stringify(err));

    throw err;
  } finally {
    httpRequest.destroy();
  }
};

export {
  request
};

二、從本地獲取要上傳的文件

import { common } from '@kit.AbilityKit';
import abilityAccessCtrl from '@ohos.abilityAccessCtrl';
import picker from '@ohos.file.picker';

// 選擇文件
const fileSelect = async (context: common.Context) => {
  const documentSelectOptions = new picker.DocumentSelectOptions();
  const documentViewPicker = new picker.DocumentViewPicker(context);

  documentSelectOptions.maxSelectNumber = 5;


  const documentSelectResult = await documentViewPicker.select(documentSelectOptions);

  return documentSelectResult;
};

export {
  fileSelect
};

三、客戶端發(fā)送請(qǐng)求生成簽名URL

客戶端發(fā)起請(qǐng)求到服務(wù)器請(qǐng)求生成簽名 URL。請(qǐng)求體包含了文件名、請(qǐng)求方法、頭部信息等。成功響應(yīng)后,返回簽名 URL 和 Content-Type。如果請(qǐng)求失敗,捕獲并拋出錯(cuò)誤。

const getSignUrl = async (fileName: string, req: {
  method: 'GET' | 'POST' | 'PUT';
  headers?: Record<string, string | number>;
  queries?: Record<string, string>;
  additionalHeaders?: string[];
}): Promise<ISignUrlResult> => {
  console.info('in getSignUrl');

  try {
    const response = await request(serverUrl, {
      method: http.RequestMethod.POST,
      header: {
        'Content-Type': 'application/json'
      },
      extraData: {
        fileName,
        method: req.method,
        headers: req.headers,
        queries: req.queries,
        additionalHeaders: req.additionalHeaders
      },
      expectDataType: http.HttpDataType.OBJECT
    }, 200);
    const result = response.result as ISignUrlResult;

    console.info('success getSignUrl');

    return result;
  } catch (err) {
    console.info('getSignUrl request error: ' + JSON.stringify(err));

    throw err;
  }
};

四、上傳文件

獲取在服務(wù)端生成的簽名URL,然后使用PutObject的方法上傳文件。

const putObject = async (fileUri: string): Promise<void> => {
  console.info('in putObject');

  const fileInfo = await fs.open(fileUri, fs.OpenMode.READ_ONLY);
  const fileStat = await fs.stat(fileInfo.fd);
  let signUrlResult: ISignUrlResult;

  console.info('file name: ', fileInfo.name);

  try {
    // 獲取PutObject的簽名URL
    signUrlResult = await getSignUrl(fileInfo.name, {
      method: 'PUT',
      headers: {
        'Content-Length': fileStat.size
      },
      additionalHeaders: ['Content-Length']
    });
  } catch (e) {
    await fs.close(fileInfo.fd);

    throw e;
  }

  const data = new ArrayBuffer(fileStat.size);

  await fs.read(fileInfo.fd, data);
  await fs.close(fileInfo.fd);

  try {
    // 使用PutObject方法上傳文件
    await request(signUrlResult.url, {
      method: http.RequestMethod.PUT,
      header: {
        'Content-Length': fileStat.size,
        'Content-Type': signUrlResult.contentType
      },
      extraData: data
    }, 200);

    console.info('success putObject');
  } catch (err) {
    console.info('putObject request error: ' + JSON.stringify(err));

    throw err;
  }
};

結(jié)果驗(yàn)證

以上步驟部署完成后,您可以鴻蒙環(huán)境中實(shí)現(xiàn)上傳文件到OSS的功能,效果示例如下:

  1. 點(diǎn)擊Upload File按鈕選擇上傳的文件。

    image

  2. Bucket列表頁面,選擇您之前創(chuàng)建的用來存放用戶上傳文件的Bucket并打開。您可以在上傳列表看到用戶在鴻蒙端上傳的文件。

    image

清理資源

在本方案中,您創(chuàng)建了1臺(tái)ECS實(shí)例、1個(gè)OSS Bucket、和1個(gè)RAM角色。測試完方案后,您可以參考以下規(guī)則處理對(duì)應(yīng)產(chǎn)品的資源,避免繼續(xù)產(chǎn)生費(fèi)用或產(chǎn)生安全風(fēng)險(xiǎn)。

釋放ECS實(shí)例

如果您不再需要這臺(tái)實(shí)例,可以將其釋放。釋放后,實(shí)例停止計(jì)費(fèi),數(shù)據(jù)不可恢復(fù)。具體操作如下:

  1. 返回云服務(wù)器ECS控制臺(tái)實(shí)例列表頁面,根據(jù)地域、實(shí)例ID找到目標(biāo)ECS實(shí)例,單擊操作列下的image。

  2. 選擇釋放。image

  3. 確認(rèn)實(shí)例無誤后,選擇立即釋放,單擊下一步。

  4. 確認(rèn)即將釋放的關(guān)聯(lián)資源,并了解相關(guān)數(shù)據(jù)風(fēng)險(xiǎn)后,單擊確認(rèn),即可完成ECS實(shí)例的釋放。

說明
  • 系統(tǒng)盤、分配的公網(wǎng)IP將隨實(shí)例釋放。

  • 安全組、交換機(jī)和VPC不會(huì)隨實(shí)例釋放,但它們均為免費(fèi)資源,您可根據(jù)實(shí)際的業(yè)務(wù)需要選擇性刪除。

  • 彈性公網(wǎng)IP不會(huì)隨實(shí)例釋放,且不是免費(fèi)資源,您可根據(jù)實(shí)際的業(yè)務(wù)需要選擇性刪除。

刪除Bucket

  1. 登錄OSS管理控制臺(tái)。

  2. 單擊Bucket 列表,然后單擊目標(biāo)Bucket名稱。

  3. 刪除Bucket的所有文件(Object)。

  4. 在左側(cè)導(dǎo)航欄,單擊刪除Bucket,然后按照頁面指引完成刪除操作。

刪除RAM角色

  1. 使用RAM管理員登錄RAM控制臺(tái)。

  2. 在左側(cè)導(dǎo)航欄,選擇身份管理 > 角色。

  3. 角色頁面,單擊目標(biāo)RAM角色操作列的刪除角色。

  4. 刪除角色對(duì)話框,輸入RAM角色名稱,然后點(diǎn)擊刪除角色。

    說明

    如果RAM角色被授予了權(quán)限策略,刪除角色時(shí),會(huì)同時(shí)解除授權(quán)。

常見問題

如何實(shí)現(xiàn)分片上傳?

當(dāng)您希望使用簽名URL以分片上傳的方式上傳大文件到OSS時(shí),您需要先初始化分片上傳,然后為每一個(gè)分片生成一個(gè)對(duì)應(yīng)的上傳簽名URL,并返回給客戶端??蛻舳丝梢允褂眠@些簽名URL上傳所有的分片信息,然后合并分片來達(dá)到通過簽名URL實(shí)現(xiàn)分片上傳的目的。具體代碼實(shí)現(xiàn)可參考:

import { http } from '@kit.NetworkKit';
import fs from '@ohos.file.fs';
import { getSignUrl } from './upload';
import { request } from './request';
import { xmlToObj } from './xml';

type TPart = {
  partNum: number;
  etag: string;
};

type TTodoPart = {
  partLength: number;
  partNum: number;
}

/**
 * InitiateMultipartUpload
 * @param fileName 文件名
 */
const initiateMultipartUpload = async (fileName: string) => {
  console.info('in initiateMultipartUpload');

  // 獲取InitiateMultipartUpload簽名URL
  const signUrlResult = await getSignUrl(fileName, {
    method: 'POST',
    queries: {
      uploads: null
    }
  });

  try {
    // 通過InitiateMultipartUpload接口來通知OSS初始化一個(gè)Multipart Upload事件
    const response = await request(signUrlResult.url, {
      method: http.RequestMethod.POST,
      expectDataType: http.HttpDataType.STRING
    }, 200);
    const result = response.result as string;

    console.info('success initiateMultipartUpload');

    const res = xmlToObj(result) as {
      InitiateMultipartUploadResult: {
        Bucket: string;
        Key: string;
        UploadId: string;
        EncodingType?: string;
      }
    };

    return res.InitiateMultipartUploadResult;
  } catch (err) {
    console.info('initiateMultipartUpload request error: ' + JSON.stringify(err));

    throw err;
  }
};

/**
 * UploadPart
 * @param uploadId 分片上傳的uploadId
 * @param partNum 分片上傳的partNumber
 * @param file 上傳的文件
 * @param length 分片大小
 * @param offset 文件讀取位置
 */
const uploadPart = async (uploadId: string, partNum: number, file: fs.File, length: number, offset: number = 0) => {
  console.info('in uploadPart');

  // 獲取UploadPart簽名URL
  const signUrlResult = await getSignUrl(file.name, {
    method: 'PUT',
    headers: {
      'Content-Length': length
    },
    queries: {
      uploadId,
      partNumber: partNum.toString()
    },
    additionalHeaders: ['Content-Length']
  });

  const data = new ArrayBuffer(length);

  await fs.read(file.fd, data, {
    length,
    offset
  });

  try {
    const response = await request(signUrlResult.url, {
      method: http.RequestMethod.PUT,
      header: {
        'Content-Length': length,
        'Content-Type': signUrlResult.contentType
      },
      extraData: data
    }, 200);

    console.info('success uploadPart');

    return response.header['etag'] as string;
  } catch (err) {
    console.info('uploadPart request error: ' + JSON.stringify(err));

    throw err;
  }
};

/**
 * CompleteMultipartUpload
 * @param fileName 文件名
 * @param uploadId 分片上傳的uploadId
 * @param completeAll 指定是否列舉當(dāng)前UploadId已上傳的所有Part
 * @param [parts] CompleteMultipartUpload所需的Part列表
 */
const completeMultipartUpload = async (fileName: string, uploadId: string, completeAll: boolean = false, parts?: TPart[]) => {
  console.info('in completeMultipartUpload');

  if (!completeAll && !parts) {
    throw new Error('completeMultipartUpload needs to pass in parameter parts.');
  }

  const signUrlResult = await getSignUrl(fileName, {
    method: 'POST',
    headers: completeAll ? {
      'x-oss-complete-all': 'yes'
    } : {
      'Content-Type': 'application/xml'
    },
    queries: {
      uploadId
    }
  });

  let xml: string;

  if (!completeAll) {
    const completeParts = parts.concat().sort((a, b) => a.partNum - b.partNum)
      .filter((item, index, arr) => !index || item.partNum !== arr[index - 1].partNum);
    xml = '<?xml version="1.0" encoding="UTF-8"?>\n<CompleteMultipartUpload>\n';

    completeParts.forEach(item => {
      xml += `<Part>\n<PartNumber>${item.partNum}</PartNumber>\n<ETag>${item.etag}</ETag>\n</Part>\n`
    });
    xml += '</CompleteMultipartUpload>';
  }

  try {
    const result  = await request(signUrlResult.url, {
      method: http.RequestMethod.POST,
      header: completeAll ? {
        'x-oss-complete-all': 'yes'
      } : {
        'Content-Type': 'application/xml'
      },
      extraData: !completeAll ? xml : undefined
    }, 200);
    console.info('success completeMultipartUpload');

    return result;
  } catch (err) {
    console.info('completeMultipartUpload request error: ' + JSON.stringify(err));

    throw err;
  }
};

/**
 * 分片上傳信息
 */
interface ICheckpoint {
  /** 分片上傳的uploadId */
  uploadId: string;
  /** 文件URI */
  fileUri: string;
  /** 分片大小 */
  partSize: number;
  /** 已經(jīng)上傳完成的分片 */
  doneParts: TPart[];
}

/**
 * 分片上傳
 */
export class MultipartUpload {
  /** 分片上傳信息 */
  private checkpoint: ICheckpoint;
  /** 上傳文件 */
  private file: fs.File;
  /** 文件詳細(xì)屬性信息 */
  private fileStat: fs.Stat;
  /** 取消上傳標(biāo)識(shí) */
  private cancelFlag = true;
  /** 并發(fā)上傳數(shù) */
  private parallel = 5;
  /** 上傳隊(duì)列 */
  private uploadQueue: TTodoPart[] = [];
  /** 當(dāng)前正在上傳數(shù) */
  private uploadingCount = 0;
  /** 上傳失敗分片信息 */
  private uploadErrors: {
    partNum: number;
    uploadError: Error;
  }[] = [];

  /**
   * 創(chuàng)建MultipartUpload實(shí)例
   * @param [fileUri] 文件URI
   * @param [checkpoint] 分片上傳信息
   */
  constructor(fileUri?: string, checkpoint?: ICheckpoint) {
    if (checkpoint) {
      this.checkpoint = checkpoint;
      this.file = fs.openSync(checkpoint.fileUri, fs.OpenMode.READ_ONLY);
    } else {
      if (!fileUri) {
        throw Error('MultipartUpload need fileUri or checkpoint.');
      }

      this.file = fs.openSync(fileUri, fs.OpenMode.READ_ONLY);
      this.checkpoint = {
        uploadId: '',
        fileUri,
        partSize: 2 ** 20,
        doneParts: []
      };
    }

    this.fileStat = fs.statSync(this.file.fd);
  }

  private async uploadPart(part: TTodoPart, resolve: () => void) {
    this.uploadingCount++;

    const {
      partLength,
      partNum
    } = part;

    try {
      const result = await uploadPart(this.checkpoint.uploadId, partNum, this.file, partLength, (partNum - 1) * this.checkpoint.partSize);

      this.checkpoint.doneParts.push({
        partNum: partNum,
        etag: result
      });
      this.uploadingCount--;

      if(this.uploadErrors.length < 1) {
        if (this.uploadQueue.length < 1 && this.uploadingCount < 1) {
          resolve();
        } else {
          this.next(resolve);
        }
      }
    } catch (e) {
      this.uploadingCount--;
      this.uploadErrors.push({
        partNum: partNum,
        uploadError: e
      });
      resolve();
    }
  }

  private next(resolve: () => void) {
    if (this.cancelFlag) {
      resolve();
    }

    if (this.uploadQueue.length > 0 && this.uploadingCount < this.parallel && this.uploadErrors.length < 1) {
      this.uploadPart(this.uploadQueue.shift(), resolve);
    }
  }

  /**
   * 執(zhí)行分片上傳
   */
  async multipartUpload() {
    this.cancelFlag = false;
    this.uploadQueue = [];
    this.uploadErrors = [];

    if (this.checkpoint.uploadId === '') {
      const initResult = await initiateMultipartUpload(this.file.name);

      this.checkpoint.uploadId = initResult.UploadId;
    }

    const partsSum = Math.ceil(this.fileStat.size / this.checkpoint.partSize);

    for (let i = 0; i < partsSum; i++) {
      if (this.checkpoint.doneParts.findIndex(v => v.partNum === i + 1) === -1) {
        this.uploadQueue.push({
          partLength: i + 1 === partsSum ? this.fileStat.size % this.checkpoint.partSize : this.checkpoint.partSize,
          partNum: i + 1
        });
      }
    }

    const tempCount = Math.min(this.parallel, this.uploadQueue.length);

    await new Promise<void>((resolve) => {
      for (let i = 0; i < tempCount; i++) {
        this.next(resolve);
      }
    });

    if (this.cancelFlag) {
      throw new Error('MultipartUpload cancel');
    }

    if (this.uploadErrors.length) {
      throw new Error('Upload failed parts: ' + this.uploadErrors.map(i => i.partNum).join(','));
    }

    return await completeMultipartUpload(this.file.name, this.checkpoint.uploadId, false, this.checkpoint.doneParts);
  }

  cancel() {
    this.cancelFlag = true;
  }
};

export {
  initiateMultipartUpload,
  uploadPart,
  completeMultipartUpload
};