OBS鑒權實現的寶典秘籍,速拿!


摘要:OBS提供了REST(Representational State Transfer)風格API,支持您通過HTTP/HTTPS請求調用。本文將帶你了解OBS API鑒權實現的寶典秘籍。

OBS提供了REST(Representational State Transfer)風格API,支持您通過HTTP/HTTPS請求調用。在調用OBS的API前,需要了解OBS的鑒權認證方式。

簽名計算篇

本文就將帶您了解OBS的兩種常見的鑒權方式——Header攜帶簽名和URL攜帶簽名。

1、在Header中攜帶簽名計算

官網鏈接:

1.1、簽名的計算原理和計算方法

原理圖示

計算方法

1.構造請求字符串(StringToSign)。

請求字符串的構造方法如下:

StringToSign =

HTTP-Verb + "\n" +

Content-MD5 + "\n" +

Content-Type + "\n" +

Date + "\n" +

CanonicalizedHeaders + CanonicalizedResource

2.對第一步的結果進行UTF-8編碼。

3.使用SK對第二步的結果進行HMAC-SHA1簽名計算。

4.對第三步的結果進行Base64編碼,得到簽名。

簽名如以下形式(28位長度的BASE64編碼的字符串):

JONydLd9zpf+Eu3IYiUjNmukHN0= 

計算示例

例:需要獲取桶”obs-test”下的對象log.conf的對象ACL,如何構造請求並計算簽名?

1、首先明確StringToSign的各字段:

請求方法:GET;

請求MD5:空

Content-Type:空

請求時間:Tue, 28 Jul 2020 06:29:47 GMT(即北京時間2020年7月28日14:29:47)

自定義頭域(CanonicalizedHeaders):空

規范化資源(CanonicalizedResource):/obs-test/log.conf?acl

2、構造請求字符串StringToSign如下:

StringToSign = ‘’’GET

Tue, 28 Jul 2020 06:29:47 GMT

/obs-test/log.conf?acl’’’

3、根據簽名算法,將StringToSign進行HMAC-SHA1計算后進行BASE64編碼獲得簽名結果:xYlcrwT9jSaCtY0OnBE01OBR+aA=

1.2、簽名計算的實現方式

以Python計算簽名代碼為例,供參考:

1.  import hashlib  
2.  import hmac  
3.  import binascii  
4.  from datetime import datetime  
5.    
6.  # 驗證信息  
7.  AK = '您的access_key_id'  
8.  SK = '您的secret_access_key_id'  
9.    
10. # 指定HTTP方法,可選GET/PUT/DELETE/POST/OPTIONS  
11. httpMethod = "GET"  
12.   
13. # 指定請求的Header:Content-Type和Content-MD5  
14. contentType = ""  
15. conten**5 = ""  
16.   
17. # 使用datetime庫生成時間,如果需要自定義請求時間請保持格式一致  
18. date = datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT')  
19.   
20. # 填寫canonicalizedHeaders  
21. # canonicalizedHeaders = "x-obs-acl:public-read"  
22. # canonicalizedHeaders = "x-obs-acl:public-read\n"+'x-obs-storage-class:WARM\n'  
23. canonicalizedHeaders = ""  
24.   
25. # 填寫CanonicalizedResource  
26. # CanonicalizedResource = "/BucketName/ObjectName"  
27. # CanonicalizedResource = "/BucketName/ObjectName?acl"  
28. # CanonicalizedResource = "/"
29. CanonicalizedResource = "/BucketName/"  
30.   
31. # 生成StringToSign  
32. canonical_string = httpMethod + "\n" + conten**5 + "\n" + contentType + "\n" + date + "\n" + canonicalizedHeaders + CanonicalizedResource  
33.   
34. # 計算簽名並進行BASE64編碼  
35. hashed = hmac.new(SK.encode('UTF-8'), canonical_string.encode('UTF-8'), hashlib.sha1)  
36. encode_canonical = binascii.b2a_base64(hashed.digest())[:-1].decode('UTF-8')  
37.   
38. # 打印StringToSign以便出現問題時進行驗證  
39. print(canonical_string)  
40.   
41. # 打印簽名  
42. print(encode_canonical)  

C語言簽名算法示例:

請參考,下載C語言簽名計算代碼樣例,其中:

計算簽名的接口包含在sign.h頭文件中。

計算簽名的示例代碼在main.c文件中。

可視化簽名計算工具:

您也可以通過OBS提供的可視化簽名計算工具來計算簽名。

工具鏈接:

說明:

1. canonicalizedHeaders:表示HTTP請求頭域中的OBS請求頭字段,即以“x-obs-”作為前輟的頭域,如“x-obs-date,x-obs-acl,x-obs-meta-*”;

a.請求頭字段中關鍵字的的所有字符要轉為小寫,需要添加多個字段時,要將所有字段按照關鍵字的字典序從小到大進行排序;

b.在添加請求頭字段時,如果有重名的字段,則需要進行合並。如:x-obs-meta-name:name1和x-obs-meta-name:name2,則需要先將重名字段的值(這里是name1和name2)以逗號分隔,合並成x-obs-meta-name:name1,name2;

c.頭域中的請求頭字段中的關鍵字不允許含有非ASCII碼或不可識別字符;請求頭字段中的值也不建議使用非ASCII碼或不可識別字符,如果一定要使用非ASCII碼或不可識別字符,需要客戶端自行做編解碼處理,可以采用URL編碼或者Base64編碼,服務端不會做解碼處理;

d.當請求頭字段中含有無意義空格或table鍵時,需要摒棄。例如:x-obs-meta-name: name(name前帶有一個無意義空格),需要轉換為:x-obs-meta-name:name;

e.每一個請求頭字段最后都需要另起新行。

2. canonicalizedResource表示HTTP請求所指定的OBS資源,構造方式如下:

<桶名+對象名>+[子資源] …

a.通過桶綁定的自定義域名訪問OBS,桶名由自定義域名表示,則為"/",其中“obs.ccc.com”為桶綁定的自定義域名。如果沒有對象名,如列舉桶,則為"/";

b.不是通過桶綁定的自定義域名訪問OBS的場景,則為"/bucket/object",如果沒有對象名,如列舉桶,則為"/bucket/"。如果桶名也沒有,則為“/”;

c.如果有子資源,則將子資源添加進來,例如?acl,?logging。

3. 如需要使用臨時AK/SK+SecurityToken的方式計算簽名,計算簽名的方法保持一致,但需要在頭域中添加“x-obs-security-token:…”字段。

4.計算Content-MD5的方法見文末的說明。

5.其他語言計算簽名的代碼可詳見對應語言的SDK,詳見:

1.3、常見問題

1.訪問OBS時報錯:Signature Does Not Match

簽名不匹配的情況主要有以下兩種可能:

a.您沒有使用正確的AK/SK,您可以檢查您計算簽名使用的SK和發送請求時所攜帶的AK是否正確且匹配;

b.您計算簽名時構造的StringToSign和服務端根據接收到的HTTP請求所計算的StringToSign不匹配,您可以檢查服務端返回的StringToSign,並與本地計算簽名所使用的StringToSign進行對比。

如下圖是服務端返回的由接收到HTTP請求所還原的StringToSign,您可以通過對比您本地的StringToSign和您發送到服務端的HTTP請求,來分析您簽名計算失敗的原因。

2.訪問OBS時報錯:Request has expired

此類情況請您檢查您攜帶的Date是否正確,為保證請求的時效性,您所攜帶的Date頭域必須與服務端的時間相差在15分鍾以內(服務端為UTC時間),如您攜帶了x-obs-date頭域,需檢查x-obs-date的時間是否與服務端時間相差15分鍾以內。

2、在URL中攜帶簽名

OBS服務支持用戶構造一個特定操作的URL,這個URL中會包含用戶AK、簽名、有效期、資源等信息,任何拿到這個URL的人均可執行這個操作,OBS服務收到這個請求后認為該請求就是簽發URL用戶自己在執行操作。例如構造一個攜帶簽名信息的下載對象的URL,拿到相應URL的人能下載這個對象,但該URL只在Expires指定的失效時間內有效。URL中攜帶簽名主要用於在不提供給其他人Secret Access Key的情況下,讓其他人能用預簽發的URL來進行身份認證,並執行預定義的操作。

官網鏈接

2.1、簽名的計算原理和計算方法

原理圖示

計算方法

1.構造請求字符串(StringToSign)。

請求字符串的構造方法如下:

StringToSign =

HTTP-Verb + "\n" +

Content-MD5 + "\n" +

Content-Type + "\n" +

Date + "\n" +

CanonicalizedHeaders + CanonicalizedResource

2.對第一步的結果進行UTF-8編碼。

3.使用SK對第二步的結果進行HMAC-SHA1簽名計算。

4.對第三步的結果進行Base64編碼,得到簽名。

簽名如以下形式(28位長度的BASE64編碼的字符串):

JONydLd9zpf+Eu3IYiUjNmukHN0=

URL中攜帶的簽名計算方法同Header中攜帶簽名的簽名計算方法,但是需要將Date更換為UNIX時間戳。

攜帶簽名的URL形式如下:

其對應的StringToSign為

GET

1575452568

/obs-ycytest/

URL中攜帶的參數具體含義見下表:

計算示例

例:需要獲取桶”obs-test”下的對象log.conf的對象ACL,如何構造請求並計算簽名?

1、首先明確StringToSign的各字段:

請求方法:GET;

請求MD5:空

Content-Type:空

請求時間:1595918661(即北京時間2020年7月28日14:44:21)

自定義頭域(CanonicalizedHeaders):空

規范化資源(CanonicalizedResource):/obs-test/log.conf?acl

2、構造請求字符串StringToSign如下:

StringToSign = ‘’’GET

1595918661

/obs-test/log.conf?acl’’’

3、根據簽名算法,將StringToSign進行HMAC-SHA1計算后進行BASE64編碼獲得簽名結果:lLcYw1fFMJv5m+MS0XenNrqJlag=

根據計算的結果,將URL拼接起來即可生成攜帶簽名的URL如下:

 lLcYw1fFMJv5m+MS0XenNrqJlag=

2.2、簽名計算的實現方式

在URL中攜帶簽名時,只需將Date替換為UNIX時間戳即可計算對應的簽名,因此對應的代碼不再贅述。

如需要使用臨時AK/SK+SecurityToken的方式計算簽名,計算簽名的方法保持一致,但需要在對應的CanonicalizedResource中添加“?x-obs-security-token=…”字段,且計算得到的簽名必須要進行URL編碼。使用臨時AK/SK+SecurityToken計算簽名的代碼如下:

1.  import hashlib  
2.  import hmac  
3.  import binascii  
4.  import urllib.request  
5.    
6.  AK = 'Input Your AccessKeyId'  
7.  SK = 'Input Your SecretKeyId'  
8.  Token = 'Input Your SecurityToken'  
9.    
10. httpMethod = "GET"  
11. contentType = ""  
12. Conten**5 = ''  
13. date = '1594972984'  
14. canonicalizedHeaders = ''  
15. CanonicalizedResource = "/messageflow/flowengine.tar.gz" + "?x-obs-security-token=" + Token  
16. canonical_string = httpMethod + "\n" + Conten**5 + "\n" + contentType + "\n" + date + "\n" + canonicalizedHeaders + CanonicalizedResource  
17. hashed = hmac.new(SK.encode('UTF-8'), canonical_string.encode('UTF-8'), hashlib.sha1)  
18. encode_canonical = binascii.b2a_base64(hashed.digest())[:-1].decode('UTF-8')  
19.   
20. url= 'messageflow.obs.myhuaweicloud.com/flowengine.tar.gz?x-obs-security-token={}&Expires={}&AccessKeyId={}&Signature={}'.format(Token, date, AK, urllib.request.quote(encode_canonical))  
21. print(url)  

說明:

1.在計算簽名時,Date表示的是一個UNIX時間戳;

2.如果想要在瀏覽器中使用URL中攜帶簽名生成的預定於URL,則計算簽名時不要使用“Content-MD5”、“Content-Type”、“CanonicalizedHeaders”計算簽名,否則瀏覽器不能攜帶這些參數,請求發送到服務端之后,會提示簽名錯誤。

3、Content-MD5的計算方式

3.1、Content-MD5的計算方法

以消息內容“0123456789”為例,以下詳細說明計算該字符串的Content-MD5的方法。

1.先計算MD5加密的二進制數組(128位)。

2.對這個二進制數組進行base64編碼(而不是對32位字符串編碼)。

以Python為例:

>>> import base64,hashlib

>>> hash = hashlib.md5()

>>> hash.update("0123456789".encode(‘utf-8’))

>>> base64.b64encode(hash.digest())

'eB5eJF1ptWaXm4bijSPyxw=='

注:hash.digest(),計算出二進制數組(128位)。

>>> hash.digest()

'x\x1e^$]i\xb5f\x97\x9b\x86\xe2\x8d#\xf2\xc7'

3.2、Content-MD5計算的實現

以Python計算文件MD5代碼為例,供參考:

1.  import os  
2.  import base64  
3.  import hashlib  
4.    
5.    
6.  def md5_file_encode_by_size_offset(file_path=None, size=None, offset=None, chuckSize=None):  
7.      if file_path is not None and size is not None and offset is not None:  
8.          m = hashlib.md5()  
9.          with open(file_path, 'rb') as fp:  
10.             CHUNKSIZE = 65536 if chuckSize is None else chuckSize  
11.             fp.seek(offset)  
12.             read_count = 0  
13.             while read_count < size:  
14.                 read_size = CHUNKSIZE if size - read_count >= CHUNKSIZE else size - read_count  
15.                 data = fp.read(read_size)  
16.                 read_count_once = len(data)  
17.                 if read_count_once <= 0:  
18.                     break 
19.                 m.update(data)  
20.                 read_count += read_count_once  
21.         return base64.b64encode(m.digest()).decode()  
22.   
23.   
24. file_path = r'Input Your File Path'  
25. size = os.path.getsize(file_path)  
26. Conten**5 = md5_file_encode_by_size_offset(file_path=file_path, size=size, offset=0)  
27. print(Conten**5)  

3.3、常見問題

常見錯誤是直接對計算出的32位字符串進行base64編碼。

# hash.hexdigest(),計算得到可見的32位字符串編碼。

>>> import base64,hashlib

>>> hash = hashlib.md5()

>>> hash.update("0123456789".encode(‘utf-8’))

>>> hash.hexdigest()

'781e5e245d69b566979b86e28d23f2c7'

# 錯誤的MD5值進行base64編碼后的結果:

>>> base64.b64encode(hash.hexdigest())

'NzgxZTVlMjQ1ZDY5YjU2Njk3OWI4NmUyOGQyM2YyYzc='

POST簽名計算

OBS服務支持基於瀏覽器的POST上傳對象請求,此類請求的簽名信息通過表單的方式上傳。POST上傳對象的流程主要如下:

首先,創建一個安全策略,指定請求中需要滿足的條件,比如:桶名、對象名前綴;

然后,創建一個基於此策略的簽名,需要簽名的請求表單中必須包含有效的signature和policy;

最后,創建一個表單將對象上傳到桶中。

1、基於瀏覽器上傳的表單中攜帶簽名

官網鏈接:

1.1、簽名的計算原理和計算方法

原理圖示

計算方法

1.構造請求Policy:

例如一個最簡單的請求Policy如下:

Policy = {
  "expiration": "2020-12-21T12:00:00.000Z",
  "conditions": [
    {
      "bucket": "obs-test"
    },
    [
      "eq",
      "$key",
      "post.txt"
    ],
  ]
}

2.對請求Policy進行UTF-8編碼。

3.對第二步的結果進行Base64編碼。

4.使用SK對第三步的結果進行HMAC-SHA1簽名計算。

5.對第四步的結果進行Base64編碼,得到簽名。

簽名如以下形式(28位長度的BASE64編碼的字符串):

CVs7GTY6n8Gdhc74Gj+QhpbxtT4=

即:StringToSign = Base64( UTF-8-Encoding-Of( policy ) )

Signature = Base64( HMAC-SHA1( YourSecretAccessKeyID, StringToSign ) )

計算示例

例:需要通過瀏覽器表單上傳對象”post.txt”到桶”obs-test”下,同時設置對象ACL為公共讀,如何構造請求並計算簽名?

1、首先構造請求Policy:

設置請求過期時間:2020-12-21T12:00:00.000Z

桶名:obs-test

對象ACL:public-read

匹配條件:對象名=post.txt

2、構造Policy如下:

{
  "expiration": "2020-12-21T12:00:00.000Z",
  "conditions": [
    {
      "bucket": "obs-test"
    },
    {
    "x-obs-acl": "public-read"
    },
    [
      "eq",
      "$key",
      "post.txt"
    ],
  ]
}

3、根據簽名算法,將Policy進行UTF-8編碼后再進行BASE64編碼,再進行HMAC-SHA1計算后獲得簽名結果:odouyqpyXcYlKQz7G1/EaUNfJUE=

1.2、簽名計算的實現方式

以Python計算簽名代碼為例,供參考:

1.  import hashlib  
2.  import hmac  
3.  import binascii  
4.    
5.  # 驗證信息  
6.  SK = '您的secret_access_key_id'  
7.    
8.  # Policy  
9.  canonical_string = '''''{ 
10.   "expiration": "2020-12-21T12:00:00.000Z", 
11.   "conditions": [ 
12.     { 
13.       "bucket": "obs-test" 
14.     }, 
15.     { 
16.     "x-obs-acl": "public-read"  
17.     }, 
18.     [ 
19.       "eq", 
20.       "$key", 
21.       "post.txt" 
22.     ], 
23.   ] 
24. }'''  
25.   
26. policybase64 = binascii.b2a_base64(canonical_string.encode('utf-8'))  
27. policybase64 = policybase64[:-1].decode('UTF-8')  
28. # Policy的Base64編碼  
29. print(policybase64)  
30. hashed = hmac.new(SK.encode('UTF-8'), policybase64.encode('UTF-8'), hashlib.sha1)  
31. encode_canonical = binascii.b2a_base64(hashed.digest())[:-1].decode('UTF-8')  
32. # 簽名  
33. print(encode_canonical)  

說明:

1. policy使用json格式,conditions可以支持 { } 和 [ ] 兩種方式,{ }中包含表單元素的key和value兩項,以冒號分隔;[ ]中包含條件類型、key、value三項,以逗號分隔,元素key之前使用$字符表示變量;

2.下表內的字符都必須進行轉義:

1.3、基於瀏覽器表單上傳的Policy與其他方式的StringToSign的異同

基於瀏覽器表單上傳時,匹配條件可以設置為“Starts-With”,即前綴匹配而非精確匹配,因此在上傳時適用於更廣的場景。

OBS API實戰篇

OBS提供了REST(Representational State Transfer)風格API,支持您通過HTTP/HTTPS請求調用。

通過前述的簽名計算方法,想必大家對OBS的API鑒權方式已經有所了解,下文將通過Postman、cURL、編碼等方法對OBS的API進行實戰調用。

1、OBS支持的API列表

詳見官網鏈接:

2、構造請求

本節主要介紹OBS RESTful 請求的構成

2.1、請求URI

OBS根據桶和對象以及所對應的子資源參數來確定具體的URI,當需要進行資源操作時,可以使用這個URI地址。

URI的一般格式為(方括號內為可選項):

protocol://[bucket.]domain[:port][/object][?param]

URI中參數的具體含義如下:

2.2、請求方法

HTTP方法(也稱為操作或動詞),它告訴服務你正在請求什么類型的操作。

華為雲對象存儲支持以下的REST請求方法:

2.3、請求消息頭

請求消息頭是可選的附加請求頭字段,如指定的URI和HTTP方法所要求的字段。以下是OBS的公共請求消息頭:

2.4、請求消息體(可選)

請求消息體通常以結構化格式(如JSON或XML)發出,與請求消息頭中Content-type對應,傳遞除請求消息頭之外的內容。若請求消息體中參數支持中文,則中文字符必須為UTF-8編碼。

每個接口的請求消息體內容不同,也並不是每個接口都需要有請求消息體(或者說消息體為空),GET、DELETE操作類型的接口就不需要消息體,消息體具體內容需要根據具體接口而定。

3、發起請求

本文以列舉桶內對象為例,說明各種客戶端發起請求調用OBS API的流程。

3.1、構造請求

1、按照上述2.1構建請求URI,本例中的URI即為“bucketname.obs.cn-north-4.myhuaweicloud.com”;

2.按照上述2.2選擇對應的請求方法,本例使用“GET”請求方法;

3.按照2.3選擇對應的請求消息頭,由於本例的請求為GET請求,因此Content-Type、Content-Length、Content-MD5都設置為空字符串;

其中“Authorization”請求頭中的簽名需要根據前文所述的簽名計算方法進行計算:

StringToSign=GET

Thu, 03 Sep 2020 01:57:08 GMT

/bucketname/

將其進行加密並進行編碼后,獲得Authorization頭域為:OBS Y5IBJTPVZBXYXVLKVOSZ:L0h7P/XDJltLkT/arekZy3Ysh68=

4.傳入對應的請求消息體,本例中不涉及。

3.2、使用PostMan發起請求

Postman是一種網頁調試與發送網頁http請求的chrome插件。我們可以用來很方便的模擬get或者post或者其他方式的請求來調試接口。

由於Postman具有可視化的操作界面,因此調試接口過程不再贅述。

注意事項:

當在請求中不手動指定“Content-Type”等頭域時,Postman會自動生成頭域,可能會導致簽名不匹配的情況,可通過點擊紅框中的按鈕將隱藏頭域顯示以便定位此類問題;

3.3、使用cURL發起請求

cURL是利用URL語法在命令行方式下工作的文件傳輸工具。利用cURL可以調試OBS提供的接口。在使用curl調試OBS API時,需要用到以下的語法:

-H HTTP請求頭

-X HTTP方法

調用OBS提供的列舉對象接口時,在Linux終端下執行以下命令:

curl -X GET  -H "Content-Type:" -H "Authorization:OBS Y5IBJTPVZBXYXVLKVOSZ:*****" -H "Date:Tue, 25 Aug 2020 03:16:39 GMT" –kv

即可獲取到接口返回的信息。

注意事項:

curl命令所攜帶的 –k參數允許curl使用非安全的ssl連接並且傳輸數據(證書不受信),而-v參數能輸出更詳細的參數便於debug。

3.4、使用HTTP請求庫發起請求

通過編碼發起請求,以Python常見的HTTP請求庫requests庫為例,代碼如下:(示例代碼中已集成簽名計算)

1.  import hashlib  
2.  import hmac  
3.  import binascii  
4.  from datetime import datetime  
5.  import requests  
6.  import time  
7.    
8.  AK = "Input Your Access Key Id"  
9.  SK = "Input Your Secret Access Key Id"  
10.   
11.   
12. time = time.time()  
13. time = int(time)  
14. time = str(time)  
15.   
16. # 計算簽名  
17. httpMethod = 'GET'  
18. contentType = ''  
19. Conten**5 = ''  
20. date = datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT')  
21. canonicalizedHeaders = ''  
22. canonicalizedResource = '/obs-yuchenyu/'  
23. canonical_string = httpMethod + "\n" + Conten**5 + "\n" + contentType + "\n" + date + "\n" + canonicalizedHeaders + canonicalizedResource  
24. hashed = hmac.new(SK.encode('UTF-8'), canonical_string.encode('UTF-8'), hashlib.sha1)  
25. encode_canonical = binascii.b2a_base64(hashed.digest())[:-1].decode('UTF-8')  
26.   
27. url = 'https://obs-yuchenyu.obs.cn-north-4.myhuaweicloud.com/'  
28.   
29. # 生成請求頭  
30. headers = {  
31.     'Date': date,  
32.     'Authorization': 'OBS {}:{}'.format(AK, encode_canonical),  
33.     'Content-Type': '',  
34.     'Content-MD5': ''  
35. }  
36.   
37.   
38. # 發送請求  
39. resp = requests.get(url, headers=headers, verify=False)  
40. print(resp.status_code)  
41. print(resp.headers)  
42. print(resp.content.decode('utf-8'))  
返回結果如下:
"C:\Program Files\Python37\python.exe" "D:/OBS PythonSDK/OBS_API.py"
200
{'via': 'proxy A', 'Date': 'Tue, 25 Aug 2020 03:25:59 GMT', 'Server': 'OBS', 'Connection': 'Keep-Alive', 'x-obs-id-2': '32AAAQAAEAABAAAQAAEAABAAAQAAEAABCTDP5fqiYrOrqBlcJ91P0KqhCGTp+5Pl', 'Content-Type': 'application/xml', 'Content-Length': '28217', 'x-obs-request-id': '0000017423A6A7AA44CB43873F3DBCD6', 'x-obs-bucket-location': 'cn-north-4'}
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><ListBucketResult xmlns="http://obs.myhwclouds.com/doc/2015-06-30/">……
Process finished with exit code 0

 

點擊關注,第一時間了解華為雲新鮮技術~

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM