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

簽名機(jī)制

更新時(shí)間:

為保證API的安全調(diào)用,在調(diào)用API時(shí)阿里云會(huì)對(duì)每個(gè)API請(qǐng)求通過簽名(Signature)進(jìn)行身份驗(yàn)證。無論使用HTTP還是HTTPS協(xié)議提交請(qǐng)求,都需要在請(qǐng)求中包含簽名信息。此文檔描述了如何計(jì)算簽名,提供了Java、Python、Go的代碼示例。

概述

RPC API要按以下格式在API請(qǐng)求的Query中增加簽名(Signature)。

https://Endpoint/?SignatureVersion=1.0&SignatureMethod=HMAC-SHA1&SignatureNonce=3ee8c1b8-83d3-44af-a94f-4e0ad82fd6cf&Signature=CT9X0VtwR86fNWSnsc6v8YGOjuE%3D&

其中

  • SignatureMethod:簽名方式,目前支持HMAC-SHA1。

  • SignatureVersion:簽名算法版本,目前版本是 1.0。

  • SignatureNonce:唯一隨機(jī)數(shù),用于防止網(wǎng)絡(luò)重放攻擊。用戶在不同請(qǐng)求間要使用不同的隨機(jī)數(shù)值,建議使用通用唯一識(shí)別碼(Universally Unique Identifier, UUID)。

  • Signature:使用AccessKey Secret對(duì)請(qǐng)求進(jìn)行對(duì)稱加密后生成的簽名。

簽名算法遵循RFC2104 HMAC-SHA1規(guī)范,使用AccessSecret對(duì)編碼、排序后的整個(gè)請(qǐng)求串計(jì)算HMAC值作為簽名。簽名的元素是請(qǐng)求自身的一些參數(shù),由于每個(gè)API請(qǐng)求內(nèi)容不同,所以簽名的結(jié)果也不盡相同??蓞⒖急疚牡牟僮鞑襟E,計(jì)算簽名值。(詳細(xì)簽名規(guī)則請(qǐng)參考簽名機(jī)制說明

String signature = Base64(HMAC_SHA1(AccessSecret + "&", UTF_8_Encoding_Of(stringToSign)))

步驟一:構(gòu)造待簽名字符串

  1. 使用請(qǐng)求參數(shù)構(gòu)造規(guī)范化的請(qǐng)求字符串(CanonicalizedQueryString)。

    1. 按照參數(shù)名稱的字典順序?qū)φ?qǐng)求中所有的請(qǐng)求參數(shù)(包括公共請(qǐng)求參數(shù)和接口的自定義參數(shù),但不包括公共請(qǐng)求參數(shù)中的Signature參數(shù))進(jìn)行排序。

      說明

      這些參數(shù)是請(qǐng)求URI中的參數(shù)部分,即URI中“?”之后由“&”連接的部分。詳見示例部分。

    2. 對(duì)排序之后的請(qǐng)求參數(shù)的名稱和值分別用UTF-8字符集進(jìn)行URL編碼。編碼規(guī)則請(qǐng)參考下表。

      字符

      編碼方式

      A-Z、a-z和0-9以及“-”、“_”、“.”和“~”

      不編碼

      其它字符

      編碼成 %XY 的格式,其中 XY 是字符對(duì)應(yīng)ASCII碼的16進(jìn)制表示。例如英文的雙引號(hào)(””)對(duì)應(yīng)的編碼為 %22

      擴(kuò)展的UTF-8字符

      編碼成 %XY%ZA…的格式

      英文空格

      編碼成 %20,而不是加號(hào)(+)。

      注:該編碼方式和一般采用的 application/x-www-form-urlencoded MIME格式編碼算法(例如Java標(biāo)準(zhǔn)庫(kù)中的 java.net.URLEncoder的實(shí)現(xiàn))存在區(qū)別。編碼時(shí)可以先用標(biāo)準(zhǔn)庫(kù)的方式進(jìn)行編碼,然后把編碼后的字符串中的加號(hào)(+)替換成 %20,星號(hào)(*)替換成 %2A,%7E替換回波浪號(hào)(~),即可得到上述規(guī)則描述的編碼字符串。本算法可以用下面的percentEncode方法來實(shí)現(xiàn):

      private static String percentEncode(String value) throws UnsupportedEncodingException {
          return value != null ? URLEncoder.encode(value, "UTF-8").replace("+", "%20").replace("*", "%2A").replace("%7E", "~") : null;
      }
    3. 將編碼后的參數(shù)名稱和值用英文等號(hào)(=)進(jìn)行連接。

    4. 將等號(hào)連接得到的參數(shù)組合按步驟 1 排好的順序依次使用“&”符號(hào)連接,即得到規(guī)范化請(qǐng)求字符串。

  2. 將第一步構(gòu)造的規(guī)范化字符串按照下面的規(guī)則構(gòu)造成待簽名的字符串。

    String StringToSign = HTTPMethod + "&" +
                          percentEncode(“/”) + "&" +
                          percentEncode(CanonicalizedQueryString)

    其中:

    1. HttpMethod 是提交請(qǐng)求用的HTTP方法,例如GET。注意:如果計(jì)算簽名時(shí)使用GET方法,最后請(qǐng)求時(shí)也需要使用GET方法,否則會(huì)導(dǎo)致簽名錯(cuò)誤。

    2. percentEncode("/") 是對(duì)字符 “/” 進(jìn)行編碼得到的值,即 %2F。

    3. percentEncode(CanonicalizedQueryString) 是對(duì)步驟1中構(gòu)造的規(guī)范化請(qǐng)求字符串(CanonicalizedQueryString)按照步驟1.b描述的編碼規(guī)則編碼后得到的字符串。

步驟二:計(jì)算簽名值

  1. 根據(jù)RFC2104的定義,計(jì)算待簽名字符串(StringToSign)的HMAC值。

    說明

    計(jì)算簽名時(shí)使用的Key就是您持有的AccessKey Secret并加上一個(gè) “&” 字符(ASCII:38), 使用的哈希算法是SHA1。

  2. 按照Base64編碼規(guī)則把上面的HMAC值編碼成字符串,即得到簽名值(Signature)。

  3. 將得到的簽名值作為Signature參數(shù)添加到請(qǐng)求參數(shù)中。

    說明

    得到的簽名值在作為最后的請(qǐng)求參數(shù)值提交時(shí),需要按照RFC3986的規(guī)則進(jìn)行URL編碼。

示例

DescribeRegions API 為例,假設(shè)使用的AccessKey Id 為 testid, AccessKey Secret為 testsecret。 簽名前的請(qǐng)求URL如下:

http://ecs.aliyuncs.com/?Timestamp=2016-02-23T12:46:24Z&Format=XML&AccessKeyId=testid&Action=DescribeRegions&SignatureMethod=HMAC-SHA1&SignatureNonce=3ee8c1b8-83d3-44af-a94f-4e0ad82fd6cf&Version=2014-05-26&SignatureVersion=1.0

請(qǐng)求參數(shù)按照參數(shù)名稱的字典順序排序后為:

AccessKeyId=testid&Action=DescribeRegions&Format=XML&SignatureMethod=HMAC-SHA1&SignatureNonce=3ee8c1b8-83d3-44af-a94f-4e0ad82fd6cf&SignatureVersion=1.0&Timestamp=2016-02-23T12:46:24Z&Version=2014-05-26

根據(jù)步驟一第1部分描述,對(duì)排序之后的請(qǐng)求參數(shù)的名稱和值分別用UTF-8字符集進(jìn)行URL編碼后為:

AccessKeyId=testid&Action=DescribeRegions&Format=XML&SignatureMethod=HMAC-SHA1&SignatureNonce=3ee8c1b8-83d3-44af-a94f-4e0ad82fd6cf&SignatureVersion=1.0&Timestamp=2016-02-23T12%3A46%3A24Z&Version=2014-05-26

根據(jù)步驟一第2部分描述,構(gòu)造的待簽名字符串(stringToSign) 為(使用GET方法):

GET&%2F&AccessKeyId%3Dtestid%26Action%3DDescribeRegions%26Format%3DXML%26SignatureMethod%3DHMAC-SHA1%26SignatureNonce%3D3ee8c1b8-83d3-44af-a94f-4e0ad82fd6cf%26SignatureVersion%3D1.0%26Timestamp%3D2016-02-23T12%253A46%253A24Z%26Version%3D2014-05-26

按照步驟二中的方法得到的簽名(Signature) 為:

OLeaidS1JvxuMvnyHOwuJ+uX5qY=

最后按照RFC3986規(guī)則編碼的簽名(Signature)參數(shù)加入到URL請(qǐng)求中,得到的URL為:

http://ecs.aliyuncs.com/?Timestamp=2016-02-23T12:46:24Z&Format=XML&AccessKeyId=testid&Action=DescribeRegions&SignatureMethod=HMAC-SHA1&SignatureNonce=3ee8c1b8-83d3-44af-a94f-4e0ad82fd6cf&Version=2014-05-26&SignatureVersion=1.0&Signature=OLeaidS1JvxuMvnyHOwuJ%2BuX5qY%3D

代碼示例

您可以參考下方的代碼示例構(gòu)造最終的請(qǐng)求鏈接(不需要安裝第三方庫(kù))。

重要

下面的代碼僅作為示例,請(qǐng)勿在生產(chǎn)環(huán)境中使用。

package demo;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
import java.time.temporal.ChronoField;
import java.util.Arrays;
import java.util.Base64;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;

import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

public class Demo {

    private static String percentEncode(String value) {
        try {
            return value != null ? URLEncoder.encode(value, "UTF-8")
                    .replace("+", "%20")
                    .replace("*", "%2A")
                    .replace("%7E", "~") : null;
        } catch (UnsupportedEncodingException ignore) {
            return value;
        }
    }

    private static String urlDecode(String value) {
        try {
            return URLDecoder.decode(value, "UTF-8");
        } catch (UnsupportedEncodingException ignore) {
            return value;
        }
    }

    private static String readFromInputStream(InputStream source) {
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(source))) {
            return reader.lines().collect(Collectors.joining("\n"));
        } catch (IOException ignore) {
            return "";
        }
    }

    private static String doPost(String url, String fileName) {
        HttpURLConnection urlConnection = null;
        try {
            URL postUrl = new URL(url);
            urlConnection = (HttpURLConnection) postUrl.openConnection();
            urlConnection.setDoOutput(true);
            urlConnection.setDoInput(true);
            urlConnection.setRequestMethod("POST");
            urlConnection.setUseCaches(false);
            urlConnection.setRequestProperty("Content-Type", "application/octet-stream");
            Path filePath = Paths.get(fileName);
            if (!filePath.toFile().isFile()) {
                throw new FileNotFoundException(String.format("file:%s not found", fileName));
            }
            Files.copy(filePath, urlConnection.getOutputStream());
            return readFromInputStream(urlConnection.getInputStream());
        } catch (FileNotFoundException e) {
            return e.getMessage();
        } catch (IOException e) {
            if (urlConnection != null) {
                return readFromInputStream(urlConnection.getErrorStream());
            }
            return e.getMessage();
        }
    }

    private static String doGet(String url) {
        HttpURLConnection urlConnection = null;
        try {
            URL postUrl = new URL(url);
            urlConnection = (HttpURLConnection) postUrl.openConnection();
            urlConnection.setDoOutput(true);
            urlConnection.setDoInput(true);
            urlConnection.setRequestMethod("GET");
            urlConnection.setUseCaches(false);
            return readFromInputStream(urlConnection.getInputStream());
        } catch (IOException e) {
            if (urlConnection != null) {
                return readFromInputStream(urlConnection.getErrorStream());
            }
            return e.getMessage();
        }

    }

    public static String getSignature(String url, String secret, String httpMethod) throws Exception {
        // 解析url中的參數(shù)部分
        URL u = new URL(url);
        String query = u.getQuery();
        // 獲取范化的請(qǐng)求字符串
        String canonicalString = Arrays.stream(query.split("&"))
                .map(s -> s.split("="))
                // 去掉不合法的空參數(shù)(例如: https://example?Url=&AccessKeyId=)
                .filter(arr -> arr != null && arr.length > 1)
                // 根據(jù)請(qǐng)求參數(shù)的字典順序排序
                .sorted(Comparator.comparing(arr -> arr[0]))
                // 按照 RFC3986 規(guī)則編碼參數(shù)名稱、參數(shù)值
                .map(arr -> String.format("%s=%s", percentEncode(arr[0]), percentEncode(urlDecode(arr[1]))))
                // 用"&"拼接起來編碼后的參數(shù)
                .reduce((s1, s2) -> s1 + "&" + s2)
                .orElse("");
        // 將規(guī)范化字符串拼接成待簽名的字符串
        String stringToSign = httpMethod + "&" + percentEncode("/") + "&" + percentEncode(canonicalString);
        // 把 AccessKeySecret加上"&"構(gòu)成 HMAC-SHA1 算法的key
        secret += "&";
        // HMAC-SHA1 編碼后的bytes
        SecretKey secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA1");
        Mac mac = Mac.getInstance("HmacSHA1");
        mac.init(secretKey);
        byte[] hashBytes = mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8));
        // 按照 base64 編碼規(guī)則生成最后的簽名字符串
        String signature = Base64.getEncoder().encodeToString(hashBytes);
        return signature;
    }

    /**
     * 獲取公共請(qǐng)求參數(shù)。公共請(qǐng)求參數(shù)詳見文檔:http://bestwisewords.com/document_detail/145074.html
     *
     * @param accessKeyId 您的AccessKeyId。如何獲取AccessKeyId請(qǐng)參考:http://bestwisewords.com/document_detail/116401.htm?spm=a2c4g.11186623.0.0.6e6027b5fXEVz0
     * @return 公共請(qǐng)求參數(shù)組成的字典
     */
    private static Map<String, String> getCommonParameters(String accessKeyId) {
        return new HashMap<String, String>() {
            {
                put("Action", "RecognizeGeneral"); // 調(diào)用的接口名稱,此處以 RecognizeGeneral 為例
                put("Version", "2021-07-07"); // API版本。OCR的固定值:2021-07-07
                put("Format", "JSON"); // 指定接口返回?cái)?shù)據(jù)的格式,可以選擇 JSON 或者 XML
                put("AccessKeyId", accessKeyId); // 您的AccessKeyId
                put("SignatureNonce", UUID.randomUUID().toString()); // 簽名唯一隨機(jī)數(shù),每次調(diào)用不可重復(fù)
                put("Timestamp", Instant.now().with(ChronoField.NANO_OF_SECOND, 0).toString()); // 需要Java8及以上版本。如果您需要使用更低版本Java,請(qǐng)更換獲取時(shí)間戳的方法
                put("SignatureMethod", "HMAC-SHA1"); // 簽名方式。目前為固定值 HMAC-SHA1
                put("SignatureVersion", "1.0"); // 簽名方式。目前為固定值 1.0
            }
        };
    }

    /**
     * 使用傳圖片URL方法請(qǐng)求接口示例。以 RecognizeGeneral 接口為例。
     */
    private static void getDemo() throws Exception {
        String endpoint = "ocr-api.cn-hangzhou.aliyuncs.com";
        // 如何獲取AccessKeyId、AccessKeySecret請(qǐng)參考文檔:http://bestwisewords.com/document_detail/116401.htm?spm=a2c4g.11186623.0.0.6e6027b5GvMJ25
        String accessKeyId = "";
        String accessKeySecret = "";
        // 獲取公共請(qǐng)求參數(shù)
        Map<String, String> parametersMap = getCommonParameters(accessKeyId);
        // 添加業(yè)務(wù)參數(shù)(不同的接口參數(shù)有差異,此處以RecognizeGeneral為例,Url參數(shù)為圖片鏈接)
        parametersMap.put("Url", "https://example.png");
        // 初始化請(qǐng)求URL
        StringBuilder urlBuilder = new StringBuilder("https://" + endpoint + "/?");
        // 把業(yè)務(wù)參數(shù)拼接到請(qǐng)求鏈接中
        for (Map.Entry<String, String> entry : parametersMap.entrySet()) {
            // entry.getValue() 可能出現(xiàn)"&"等符號(hào)。 需要encode
            urlBuilder.append(String.format("%s=%s", entry.getKey(), URLEncoder.encode(entry.getValue(), "UTF-8")))
                    .append('&');
        }
        // 去掉最后的"&"
        String url = urlBuilder.substring(0, urlBuilder.length() - 1);
        // 獲取簽名
        String signature = getSignature(url, accessKeySecret, "GET");
        // 按照 RFC3986 規(guī)則編碼簽名,并添加到最終的請(qǐng)求鏈接上
        url += String.format("&Signature=%s", percentEncode(signature));
        // 通過GET方式請(qǐng)求接口并打印識(shí)別結(jié)果,以 RecognizeGeneral 為例
        String result = doGet(url);
        System.out.println(result);
    }

    /**
     * 識(shí)別本地文件代碼示例。以 RecognizeGeneral 接口為例。
     */
    private static void postDemo() throws Exception {
        String endpoint = "ocr-api.cn-hangzhou.aliyuncs.com";
        // 如何獲取AccessKeyId、AccessKeySecret請(qǐng)參考文檔:http://bestwisewords.com/document_detail/116401.htm?spm=a2c4g.11186623.0.0.6e6027b5GvMJ25
        String accessKeyId = "";
        String accessKeySecret = "";
        // 獲取公共請(qǐng)求參數(shù)
        Map<String, String> parametersMap = getCommonParameters(accessKeyId);
        // 初始化請(qǐng)求URL
        StringBuilder urlBuilder = new StringBuilder("https://" + endpoint + "/?");
        // 把業(yè)務(wù)參數(shù)拼接到請(qǐng)求鏈接中
        for (Map.Entry<String, String> entry : parametersMap.entrySet()) {
            // entry.getValue() 可能出現(xiàn)"&"等符號(hào)。 需要encode
            urlBuilder.append(String.format("%s=%s", entry.getKey(), URLEncoder.encode(entry.getValue(), "UTF-8")))
                    .append('&');
        }
        // 去掉最后的"&"
        String url = urlBuilder.substring(0, urlBuilder.length() - 1);
        // 獲取簽名
        String signature = getSignature(url, accessKeySecret, "POST");
        // 按照 RFC3986 規(guī)則編碼簽名,并添加到最終的請(qǐng)求鏈接上
        url += String.format("&Signature=%s", percentEncode(signature));
        // 本地文件
        String fileName = "/home/example.png";
        // 通過POST請(qǐng)求接口并打印識(shí)別結(jié)果,以 RecognizeGeneral 為例
        String result = doPost(url, fileName);
        System.out.println(result);
    }

    public static void main(String[] args) throws Exception {
        // getDemo();
        // postDemo();
    }
}
# encoding=utf-8
try:
    from urllib.parse import quote, urlparse, parse_qs
except:
    from urllib import quote
    from urlparse import urlparse, parse_qs
import hmac
import base64
import hashlib
import uuid
from datetime import datetime, tzinfo, timedelta


class UTC(tzinfo):
    """表示UTC時(shí)間
    """

    def tzname(self, dt):
        return "UTC"

    def utcoffset(self, dt):
        return timedelta(0)

    def dst(self, dt):
        return timedelta(0)


def percentEncode(s):
    return quote(s.encode('utf-8'), safe='~')


def get_signature(url, secret, http_method):
    # 解析url中的參數(shù)部分
    queries = parse_qs(urlparse(url).query)
    # 按照請(qǐng)求參數(shù)字典順序排序
    keys = sorted(queries.keys())
    # 初始化規(guī)范化的請(qǐng)求字符串
    canonicalized_query_string = ""
    # 生成 規(guī)范化的請(qǐng)求字符串
    for k in keys:
        # 按照 RFC3986 規(guī)則編碼參數(shù)名稱
        quoted_param = percentEncode(k)
        # 按照 RFC3986 規(guī)則編碼參數(shù)值
        quoted_value = percentEncode(queries[k][0])
        # 把編碼后的參數(shù)名稱和值用英文等號(hào)(=)拼接起來,然后用 "&" 連接
        canonicalized_query_string += '&%s=%s' % (quoted_param, quoted_value)
    # 去掉開頭的 "&"
    canonicalized_query_string = canonicalized_query_string[1:]
    # 將規(guī)范化字符串拼接成待簽名的字符串
    string_to_sign = http_method + '&' + percentEncode('/') + '&' + percentEncode(canonicalized_query_string)
    # 把 AccessKeySecret加上"&"構(gòu)成 HMAC-SHA1 算法的key
    secret += '&'
    # HMAC-SHA1 編碼后的bytes
    hash_bytes = hmac.new(secret.encode('utf-8'), string_to_sign.encode('utf-8'), digestmod=hashlib.sha1).digest()
    # 按照 base64 編碼規(guī)則生成最后的簽名字符串
    signature = base64.b64encode(hash_bytes).decode('utf-8')
    return signature


def get_common_parameters(access_key_id):
    """獲取公共請(qǐng)求參數(shù)。公共請(qǐng)求參數(shù)詳見文檔:http://bestwisewords.com/document_detail/145074.html
    Args:
        您的AccessKeyId。如何獲取AccessKeyId請(qǐng)參考:http://bestwisewords.com/document_detail/116401.htm?spm=a2c4g.11186623.0.0.6e6027b5fXEVz0
    Returns:
        公共請(qǐng)求參數(shù)組成的字典
    """
    return {
        "Action": "RecognizeGeneral",    # 調(diào)用的接口名稱,此處以 RecognizeGeneral 為例
        "Version": "2021-07-07",    # API版本。OCR的固定值:2021-07-07
        "Format": "JSON",    # 指定接口返回?cái)?shù)據(jù)的格式,可以選擇 JSON 或者 XML
        "AccessKeyId": access_key_id,    # 您的AccessKeyId
        "SignatureNonce": uuid.uuid4(),    # 簽名唯一隨機(jī)數(shù)
        "Timestamp": datetime.utcnow().replace(tzinfo=UTC()).strftime('%Y-%m-%dT%H:%M:%SZ'),    # 請(qǐng)求的時(shí)間戳。按照ISO8601標(biāo)準(zhǔn)表示,并需要使用UTC時(shí)間,格式為yyyy-MM-ddTHH:mm:ssZ
        "SignatureMethod": "HMAC-SHA1",    # 簽名方式。目前為固定值 HMAC-SHA1
        "SignatureVersion": "1.0"    # 簽名方式。目前為固定值 1.0
    }


def get_request_url():
    """獲取完整的請(qǐng)求URL。
    """
    endpoint = "ocr-api.cn-hangzhou.aliyuncs.com"
    # 如何獲取AccessKeyId、AccessKeySecret請(qǐng)參考文檔:http://bestwisewords.com/document_detail/116401.htm?spm=a2c4g.11186623.0.0.6e6027b5GvMJ25
    access_key_id = ""        # 您的AccessKeyId
    access_key_secret = ""    # 您的AccessKeySecret
    # 獲取公共請(qǐng)求參數(shù)
    parameters = get_common_parameters(access_key_id)
    # 添加業(yè)務(wù)參數(shù)(不同的接口參數(shù)有差異,此處以RecognizeGeneral為例,Url參數(shù)為圖片鏈接)
    parameters['Url'] = "https://example.png"
    # 把業(yè)務(wù)參數(shù)拼接到請(qǐng)求鏈接中
    url = "https://%s/?%s" % (endpoint, '&'.join('%s=%s' % (k, v) for k, v in parameters.items()))
    # 獲取簽名(GET方法)
    signature = get_signature(url, access_key_secret, 'GET')
    # 按照 RFC3986 規(guī)則編碼簽名,并添加到最終的請(qǐng)求鏈接上
    url += "&Signature=" + percentEncode(signature)
    return url


if __name__ == '__main__':
    request_url = get_request_url()
    print(request_url)
package main

import (
	"crypto/hmac"
	"crypto/rand"
	"crypto/sha1"
	"encoding/base64"
	"fmt"
	"net/url"
	"sort"
	"strings"
	"time"
)

// uuid 生成隨機(jī)字符串,作為 SignatureNonce
func uuid() string {
	b := make([]byte, 16)
	_, err := rand.Read(b)
	if err != nil {
		panic(err)
	}
	return fmt.Sprintf("%X-%X-%X-%X-%X", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
}

func percentEncode(s string) string {
	s = url.QueryEscape(s)
	s = strings.ReplaceAll(s, "+", "%20")
	s = strings.ReplaceAll(s, "*", "%2A")
	s = strings.ReplaceAll(s, "%7E", "~")
	return s
}

// getCommonParameters 獲取公共請(qǐng)求參數(shù)。公共請(qǐng)求參數(shù)詳見文檔:http://bestwisewords.com/document_detail/145074.html
func getCommonParameters(accessKeyId string) map[string]string {
	return map[string]string{
		"Action":           "RecognizeGeneral",                                  // 調(diào)用的接口名稱,此處以 RecognizeGeneral 為例
		"Version":          "2021-07-07",                                        // API版本。OCR的固定值:2021-07-07
		"Format":           "JSON",                                              // 指定接口返回?cái)?shù)據(jù)的格式,可以選擇 JSON 或者 XML
		"AccessKeyId":      accessKeyId,                                         // 您的AccessKeyId
		"SignatureNonce":   uuid(),                                              // 簽名唯一隨機(jī)數(shù)
		"Timestamp":        time.Now().UTC().Format("2006-01-02T15:04:05.000Z"), //請(qǐng)求的時(shí)間戳。按照ISO8601標(biāo)準(zhǔn)表示,并需要使用UTC時(shí)間,格式為yyyy-MM-ddTHH:mm:ssZ。示例:2018-01-01T12:00:00Z表示北京時(shí)間2018年01月01日20點(diǎn)00分00秒。
		"SignatureMethod":  "HMAC-SHA1",                                         // 簽名方式。目前為固定值 HMAC-SHA1
		"SignatureVersion": "1.0",                                               // 簽名方式。目前為固定值 1.0
	}
}

// getSignature 獲取簽名
func getSignature(urlString, secret, httpMethod string) string {
	// 解析url中的參數(shù)部分
	u, err := url.Parse(urlString)
	if err != nil {
		panic(err)
	}
	rawQuery := u.RawQuery
	// 把url的請(qǐng)求參數(shù)名稱記錄到 slice 中
	queryMap, err := url.ParseQuery(rawQuery)
	if err != nil {
		panic(err)
	}
	keys := make([]string, 0)
	for k := range queryMap {
		keys = append(keys, k)
	}
	// 按照請(qǐng)求參數(shù)字典順序排序
	sort.Strings(keys)
	// 初始化規(guī)范化的請(qǐng)求字符串
	canonicalString := ""
	for i, k := range keys {
		canonicalString += fmt.Sprintf("%s=%s", percentEncode(k), percentEncode(queryMap[k][0]))
		if i < len(keys)-1 {
			canonicalString += "&"
		}
	}
	// 將規(guī)范化字符串拼接成待簽名的字符串
	stringToSign := httpMethod + "&" + percentEncode("/") + "&" + percentEncode(canonicalString)
	// 把 AccessKeySecret加上"&"構(gòu)成 HMAC-SHA1 算法的key
	secret += "&"
	// HMAC-SHA1 編碼后的bytes
	h := hmac.New(sha1.New, []byte(secret))
	_, err = h.Write([]byte(stringToSign))
	if err != nil {
		panic(err)
	}
	b := h.Sum(nil)
	// 按照 base64 編碼規(guī)則生成最后的簽名字符串
	signature := base64.StdEncoding.EncodeToString(b)
	return signature
}

// getRequestUrl 獲取完整的請(qǐng)求URL
func getRequestUrl() string {
	endpoint := "ocr-api.cn-hangzhou.aliyuncs.com"
	// 如何獲取AccessKeyId、AccessKeySecret請(qǐng)參考文檔:http://bestwisewords.com/document_detail/116401.htm?spm=a2c4g.11186623.0.0.6e6027b5GvMJ25
	accessKeyId := ""     // 您的AccessKeyId
	accessKeySecret := "" // 您的AccessKeySecret
	// 獲取公共請(qǐng)求參數(shù)
	parameters := getCommonParameters(accessKeyId)
	// 添加業(yè)務(wù)參數(shù)(不同的接口參數(shù)有差異,此處以RecognizeGeneral為例,Url參數(shù)為圖片鏈接)
	parameters["Url"] = "https://example.png"
	// 把業(yè)務(wù)參數(shù)拼接到請(qǐng)求鏈接中
	url := "https://" + endpoint + "/?"
	for k, v := range parameters {
		url += fmt.Sprintf("%s=%s&", k, v)
	}
  // 去掉url最后的"&"
  url = url[:len(url)-1]
	// 獲取簽名
	signature := getSignature(url, accessKeySecret, "GET")
	// 按照 RFC3986 規(guī)則編碼簽名,并添加到最終的請(qǐng)求鏈接上
	url += "&Signature=" + percentEncode(signature)
	return url
}

func main() {
	url := getRequestUrl()
	fmt.Println(url)
}
const url = require("url");
const crypto = require("crypto");

const percentEncode = (s) => {
  return encodeURIComponent(s)
    .replace(/\+/g, "%20")
    .replace(/\*/g, "%2A")
    .replace(/%7E/g, "~");
};

// 獲取公共請(qǐng)求參數(shù)。公共請(qǐng)求參數(shù)詳見文檔:http://bestwisewords.com/document_detail/145074.html
const getCommonParameters = (accessKeyId) => {
  return {
    Action: "RecognizeGeneral", // 調(diào)用的接口名稱,此處以 RecognizeGeneral 為例
    Version: "2021-07-07", // API版本。OCR的固定值:2021-07-07
    Format: "JSON", // 指定接口返回?cái)?shù)據(jù)的格式,可以選擇 JSON 或者 XML
    AccessKeyId: accessKeyId, // 您的AccessKeyId
    SignatureNonce: crypto.randomBytes(16).toString("hex"), // 簽名唯一隨機(jī)數(shù)
    Timestamp: new Date().toISOString(), // 請(qǐng)求的時(shí)間戳。按照ISO8601標(biāo)準(zhǔn)表示,并需要使用UTC時(shí)間,格式為yyyy-MM-ddTHH:mm:ssZ
    SignatureMethod: "HMAC-SHA1", // 簽名方式。目前為固定值 HMAC-SHA1
    SignatureVersion: "1.0", // 簽名方式。目前為固定值 1.0
  };
};

// 獲取簽名
const getSignature = (urlString, secret, httpMethod) => {
  // 解析url中的參數(shù)部分
  const query = url.parse(urlString, true).query;
  // 按照請(qǐng)求參數(shù)字典順序排序
  keys = Object.keys(query).sort();
  // 初始化規(guī)范化的請(qǐng)求字符串
  canonicalString = "";
  for (const k of keys) {
    // 按照 RFC3986 規(guī)則編碼參數(shù)名稱
    let encodedParam = percentEncode(k);
    // 按照 RFC3986 規(guī)則編碼參數(shù)值
    let encodedValue = percentEncode(query[k]);
    // 把編碼后的參數(shù)名稱和值用英文等號(hào)(=)拼接起來,然后用 "&" 連接
    canonicalString += `&${encodedParam}=${encodedValue}`;
  }
  // 去掉開頭的 "&"
  canonicalString = canonicalString.substring(1);
  // 將規(guī)范化字符串拼接成待簽名的字符串
  let stringToSign =
    httpMethod +
    "&" +
    percentEncode("/") +
    "&" +
    percentEncode(canonicalString);
  // 把 AccessKeySecret加上"&"構(gòu)成 HMAC-SHA1 算法的key
  secret += "&";
  // HMAC-SHA1 編碼后的bytes
  let res = crypto.createHmac("sha1", secret).update(stringToSign).digest();
  // 按照 base64 編碼規(guī)則生成最后的簽名字符串
  let signature = res.toString("base64");
  return signature;
};

// 獲取完整的請(qǐng)求URL
const getRequestUrl = () => {
  const endpoint = "ocr-api.cn-hangzhou.aliyuncs.com";
  // 如何獲取AccessKeyId、AccessKeySecret請(qǐng)參考文檔:http://bestwisewords.com/document_detail/116401.htm?spm=a2c4g.11186623.0.0.6e6027b5GvMJ25
  const accessKeyId = "";        // 您的AccessKeyId
  const accessKeySecret = "";    // 您的AccessKeySecret
  // 獲取公共請(qǐng)求參數(shù)
  let parameters = getCommonParameters(accessKeyId);
  // 添加業(yè)務(wù)參數(shù)(不同的接口參數(shù)有差異,此處以RecognizeGeneral為例,Url參數(shù)為圖片鏈接)
  parameters["Url"] = "https://example.png";
  // 把業(yè)務(wù)參數(shù)拼接到請(qǐng)求鏈接中
  let requestUrl = `https://${endpoint}/?`;
  for (const key in parameters) {
    requestUrl += `${key}=${parameters[key]}&`;
  }
  requestUrl = requestUrl.substring(0, requestUrl.length - 1);
  // 獲取簽名
  const signature = getSignature(requestUrl, accessKeySecret, "GET");
  // 按照 RFC3986 規(guī)則編碼簽名,并添加到最終的請(qǐng)求鏈接上
  requestUrl += `&Signature=${percentEncode(signature)}`;
  return requestUrl;
};

const res = getRequestUrl();
console.log(res);