开发者社区> 问答> 正文

RESTful API是什么?



1. 简介


短语音识别REST API支持以POST方式整段上传长度不多于 一分钟的语音文件。识别结果将以 JSON格式在请求响应中 一次性返回,开发者需保证在识别结果返回前连接不被中断。

2. 选取语音模型和编码格式


开发者需要根据自身应用场景选取合适的语音模型。不同的语音模型对应不同的编码与数据格式,并在特定的场景下获得更高的识别准确度。

语音模型语言采样率适用场景
customer-service-8k中文8KHz客服对话
customer-service中文16KHz客服对话
chat中文16KHz社交聊天
entertainment中文16KHz家庭娱乐
shopping中文16KHz电商购物
english英文16KHz英文转写


3. 上传语音文件

  • 短语识别请求实例
<divre style='background: rgb(246, 246, 246); font: 12px/1.6 "YaHei Consolas Hybrid", Consolas, "Meiryo UI", "Malgun Gothic", "Segoe UI", "Trebuchet MS", Helvetica, monospace, monospace; margin: 0px 0px 16px; padding: 10px; outline: 0px; border-radius: 3px; border: 1px solid rgb(221, 221, 221); overflow: auto; white-space: pre-wrap; word-wrap: break-word; box-sizing: border-box; font-size-adjust: none; font-stretch: normal;' prettyprinted?="" linenums="">
  1. POST https://nlsapi.aliyun.com/recognize?model=chat
  2. Authorization: Dataplus *****
  3. Content-type: audio/pcm; samplerate=16000
  4. Accept: application/json
  5. Date: Sat, 11 Mar 2017 08:33:32 GMT
  6. Content-Length: *
  7. [audio data]

一个完整的语音识别请求需包含以下要素:

3.1 URL

协议URL方法
HTTPShttps://nlsapi.aliyun.com/recognizePOST


3.2 输入参数

名称类型需求描述
versionString必填目前的版本号是”2.0”
modelString必填语音模型,详见 2


3.3 HTTP Header Field

名称类型需求描述
AuthorizationString必填鉴权, 详见 3.3.1
Content-typeString必填必须以audio/开头,标明用户音频数据编码类型与采样率,详见 3.3.2
AcceptString必填识别结果返回格式,仅支持application/json
DateString必填鉴权,HTTP 1.1协议中规定的GMT时间,例如:Wed, 05 Sep. 2012 23:00:00 GMT
Content-LengthInteger必填语音文件长度


3.3.1 Authorization Header


调用阿里巴巴智能语音交互平台的任何功能前都需经过严格的鉴权验证。在处理用户请求前,服务端会校验Authorization Header以确保用户请求在传输过程中没有被恶意篡改或替换。<divre style='background: rgb(246, 246, 246); font: 12px/1.6 "YaHei Consolas Hybrid", Consolas, "Meiryo UI", "Malgun Gothic", "Segoe UI", "Trebuchet MS", Helvetica, monospace, monospace; margin: 0px 0px 16px; padding: 10px; outline: 0px; border-radius: 3px; border: 1px solid rgb(221, 221, 221); overflow: auto; white-space: pre-wrap; word-wrap: break-word; box-sizing: border-box; font-size-adjust: none; font-stretch: normal;' prettyprinted?="" linenums="">
  1. Authorization: Dataplus access_id:signature

Authorization以固定字符串Dataplus开头,开发者需要将从阿里云申请到的access_id和经过计算的signature以:分隔并以 Base64编码后加入Header。

3.3.1.1 signature的计算


阿里云标准校验规则略有区别,计算语音服务的signature需要首先对语音文件进行 两次MD5和Base64编码,然后将编码结果与Reqeust Method,Accept,Content-Type和Date Header合并产生特征值,最后用阿里云取得的access_key对特征值进行HMAC-SHA1加密生成signature。<divre style='background: rgb(246, 246, 246); font: 12px/1.6 "YaHei Consolas Hybrid", Consolas, "Meiryo UI", "Malgun Gothic", "Segoe UI", "Trebuchet MS", Helvetica, monospace, monospace; margin: 0px 0px 16px; padding: 10px; outline: 0px; border-radius: 3px; border: 1px solid rgb(221, 221, 221); overflow: auto; white-space: pre-wrap; word-wrap: break-word; box-sizing: border-box; font-size-adjust: none; font-stretch: normal;' prettyprinted?="" linenums="">
  1. // 1.对body做两次MD5+BASE64加密
  2. String bodyMd5 = MD5Base64(MD5Base64(body));
  3. // 2.特征值
  4. String feature = method + "\n" + accept + "\n" + bodyMd5 + "\n" + content_type + "\n" + date;
  5. // 2.对特征值HMAC-SHA1加密
  6. String signature = HMACSha1(feature, access_secret);


3.3.1.2 计算 MD5+BASE64

<pre style='background: rgb(246, 246, 246); font: 12px/1.6 "YaHei Consolas Hybrid", Consolas, "Meiryo UI", "Malgun Gothic", "Segoe UI", "Trebuchet MS", Helvetica, monospace, monospace; margin: 0px 0px 16px; padding: 10px; outline: 0px; border-radius: 3px; border: 1px solid rgb(221, 221, 221); overflow: auto; white-space: pre-wrap; word-wrap: break-word; box-sizing: border-box; font-size-adjust: none; font-stretch: normal;' prettyprinted?="" linenums="">
  1. public static String MD5Base64(String s) throws UnsupportedEncodingException {
  2.     if (s == null)
  3.         return null;
  4.     String encodeStr = "";
  5.     //string 编码必须为utf-8
  6.     byte[] utfBytes = s.getBytes("UTF-8");
  7.     MessageDigest mdTemp;
  8.     try {
  9.         mdTemp = MessageDigest.getInstance("MD5");
  10.         mdTemp.update(utfBytes);
  11.         byte[] md5Bytes = mdTemp.digest();
  12.         BASE64Encoder b64Encoder = new BASE64Encoder();
  13.         encodeStr = b64Encoder.encode(md5Bytes);
  14.     } catch (Exception e) {
  15.         throw new Error("Failed to generate MD5 : " + e.getMessage());
  16.     }
  17.     return encodeStr;
  18. }


3.3.1.3 计算 HMAC-SHA1

<pre style='background: rgb(246, 246, 246); font: 12px/1.6 "YaHei Consolas Hybrid", Consolas, "Meiryo UI", "Malgun Gothic", "Segoe UI", "Trebuchet MS", Helvetica, monospace, monospace; margin: 0px 0px 16px; padding: 10px; outline: 0px; border-radius: 3px; border: 1px solid rgb(221, 221, 221); overflow: auto; white-space: pre-wrap; word-wrap: break-word; box-sizing: border-box; font-size-adjust: none; font-stretch: normal;' prettyprinted?="" linenums="">
  1. public static String HMACSha1(String data, String key) {
  2.     String result;
  3.     try {
  4.         SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), "HmacSHA1");
  5.         Mac mac = Mac.getInstance("HmacSHA1");
  6.         mac.init(signingKey);
  7.         byte[] rawHmac = mac.doFinal(data.getBytes());
  8.         result = (new BASE64Encoder()).encode(rawHmac);
  9.     } catch (Exception e) {
  10.         throw new Error("Failed to generate HMAC : " + e.getMessage());
  11.     }
  12.     return result;
  13. }


3.3.2 Content-Type Header


用来表明用户上传的音频数据格式和采样率,例如:<divre style='background: rgb(246, 246, 246); font: 12px/1.6 "YaHei Consolas Hybrid", Consolas, "Meiryo UI", "Malgun Gothic", "Segoe UI", "Trebuchet MS", Helvetica, monospace, monospace; margin: 0px 0px 16px; padding: 10px; outline: 0px; border-radius: 3px; border: 1px solid rgb(221, 221, 221); overflow: auto; white-space: pre-wrap; word-wrap: break-word; box-sizing: border-box; font-size-adjust: none; font-stretch: normal;' prettyprinted?="" linenums="">
  1. Content-Type: audio/wav; samplerate=8000

如果没有提供samplerate字段,采样率默认为16K。目前支持的音频编码格式如下:
语音格式编码采样率
pcmpcm16K/8K
wavpcm16K/8K
opu阿里优化opus16K
opus标准opus16K
speexspeex16K


4. 识别结果返回


HTTP状态码200表示识别成功,请求结果以application/json格式在Response Body中返回;其他的HTTP错误码表示识别失败,具体的错误消息以application/json格式在Response Body中返回。

4.1 识别成功与识别失败

名称类型描述
request_idString识别请求id
resultStringASR识别结果
  • 识别成功实例
<pre style='background: rgb(246, 246, 246); font: 12px/1.6 "YaHei Consolas Hybrid", Consolas, "Meiryo UI", "Malgun Gothic", "Segoe UI", "Trebuchet MS", Helvetica, monospace, monospace; margin: 0px 0px 16px; padding: 10px; outline: 0px; border-radius: 3px; border: 1px solid rgb(221, 221, 221); overflow: auto; white-space: pre-wrap; word-wrap: break-word; box-sizing: border-box; font-size-adjust: none; font-stretch: normal;' prettyprinted?="" linenums="">
  1. {
  2.     "request_id":"5552717cb25e4f64a180feecbe478889",
  3.     "result":"测试五秒钟长度的语音"
  4. }


4.2 识别失败

名称类型描述
request_idString识别请求id
error_codeString具体的错误代码
error_messageString具体的错误消息
  • 识别失败实例
<pre style='background: rgb(246, 246, 246); font: 12px/1.6 "YaHei Consolas Hybrid", Consolas, "Meiryo UI", "Malgun Gothic", "Segoe UI", "Trebuchet MS", Helvetica, monospace, monospace; margin: 0px 0px 16px; padding: 10px; outline: 0px; border-radius: 3px; border: 1px solid rgb(221, 221, 221); overflow: auto; white-space: pre-wrap; word-wrap: break-word; box-sizing: border-box; font-size-adjust: none; font-stretch: normal;' prettyprinted?="" linenums="">
  1. {
  2.     "request_id":"5552717cb25e4f64a180feecbe478889",
  3.     "error_code":80103,
  4.     "error_message":"Failed to invoke auth service!"
  5. }


4.3 错误码定义

HTTP状态status_codeHTTP语义
请求格式有误400错误请求
鉴权失败403鉴权验证失败
无法接受406检查Accept类型,必须为application/json
请求超时408处理请求超时
超出最大并发量429太多请求
处理出错500服务器内部错误
服务不可用503服务不可用


5. 代码示例



5.1 请求DEMO

<pre style='background: rgb(246, 246, 246); font: 12px/1.6 "YaHei Consolas Hybrid", Consolas, "Meiryo UI", "Malgun Gothic", "Segoe UI", "Trebuchet MS", Helvetica, monospace, monospace; margin: 0px 0px 16px; padding: 10px; outline: 0px; border-radius: 3px; border: 1px solid rgb(221, 221, 221); overflow: auto; white-space: pre-wrap; word-wrap: break-word; box-sizing: border-box; font-size-adjust: none; font-stretch: normal;' prettyprinted?="" linenums="">
  1. package com.alibaba.idst.nls;
  2. import java.io.File;
  3. import java.io.IOException;
  4. import java.io.UnsupportedEncodingException;
  5. import java.nio.file.FileSystem;
  6. import java.nio.file.FileSystems;
  7. import java.nio.file.Files;
  8. import java.nio.file.Path;
  9. import com.alibaba.fastjson.JSON;
  10. import com.alibaba.idst.nls.response.HttpResponse;
  11. import com.alibaba.idst.nls.utils.HttpUtil;
  12. import org.slf4j.Logger;
  13. import org.slf4j.LoggerFactory;
  14. public class HttpAsrDemo {
  15.     private static  Logger logger = LoggerFactory.getLogger(HttpAsrDemo.class);
  16.     private static String url = "http://nlsapi.aliyun.com/recognize?";
  17.    public static void main(String[] args) throws IOException {
  18.        //请使用https://ak-console.aliyun.com/ 页面获取的Access 信息
  19.        //请提前开通智能语音服务(https://data.aliyun.com/product/nls)
  20.        String ak_id = args[0];
  21.        String ak_secret = args[1];
  22.        //使用对应的ASR模型 详情见文档部分2
  23.        String model = "chat";
  24.        url = url+"model="+model;
  25.        //读取本地的语音文件
  26.        Path path = FileSystems.getDefault().getPath("src/main/resources/demo.wav");
  27.        byte[] data = Files.readAllBytes(path);
  28.        HttpResponse response = HttpUtil.sendAsrPost(data,"pcm",16000,url,ak_id,ak_secret);
  29.        logger.info(JSON.toJSONString(response));
  30.     }
  31. }


5.2 请求服务的HttpUtil类

<pre style='background: rgb(246, 246, 246); font: 12px/1.6 "YaHei Consolas Hybrid", Consolas, "Meiryo UI", "Malgun Gothic", "Segoe UI", "Trebuchet MS", Helvetica, monospace, monospace; margin: 0px 0px 16px; padding: 10px; outline: 0px; border-radius: 3px; border: 1px solid rgb(221, 221, 221); overflow: auto; white-space: pre-wrap; word-wrap: break-word; box-sizing: border-box; font-size-adjust: none; font-stretch: normal;' prettyprinted?="" linenums="">
  1. package com.alibaba.idst.nls.utils;
  2. /**
  3. * Created by songsong.sss on 16/5/23.
  4. */
  5. import java.io.*;
  6. import java.net.HttpURLConnection;
  7. import java.net.URL;
  8. import java.security.MessageDigest;
  9. import java.text.SimpleDateFormat;
  10. import java.util.*;
  11. import javax.crypto.spec.SecretKeySpec;
  12. import com.alibaba.idst.nls.response.HttpResponse;
  13. import javax.crypto.Mac;
  14. import  org.slf4j.Logger;
  15. import  org.slf4j.LoggerFactory;
  16. import sun.misc.BASE64Encoder;
  17. @SuppressWarnings("restriction")
  18. public class HttpUtil {
  19.     static Logger logger = LoggerFactory.getLogger(HttpUtil.class);
  20.     /*
  21.      * 计算MD5+BASE64
  22.      */
  23.     public static String MD5Base64(byte[] s) throws UnsupportedEncodingException {
  24.         if (s == null){
  25.             return null;
  26.         }
  27.         String encodeStr = "";
  28.         //string 编码必须为utf-8
  29.         MessageDigest mdTemp;
  30.         try {
  31.             mdTemp = MessageDigest.getInstance("MD5");
  32.             mdTemp.update(s);
  33.             byte[] md5Bytes = mdTemp.digest();
  34.             BASE64Encoder b64Encoder = new BASE64Encoder();
  35.             encodeStr = b64Encoder.encode(md5Bytes);
  36.             /* java 1.8以上版本支持
  37.             Encoder encoder = Base64.getEncoder();
  38.             encodeStr = encoder.encodeToString(md5Bytes);
  39.             */
  40.         } catch (Exception e) {
  41.             throw new Error("Failed to generate MD5 : " + e.getMessage());
  42.         }
  43.         return encodeStr;
  44.     }
  45.     /*
  46.      * 计算 HMAC-SHA1
  47.      */
  48.     public static String HMACSha1(String data, String key) {
  49.         String result;
  50.         try {
  51.             SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), "HmacSHA1");
  52.             Mac mac = Mac.getInstance("HmacSHA1");
  53.             mac.init(signingKey);
  54.             byte[] rawHmac = mac.doFinal(data.getBytes());
  55.             result = (new BASE64Encoder()).encode(rawHmac);
  56.             /*java 1.8以上版本支持
  57.             Encoder encoder = Base64.getEncoder();
  58.             result = encoder.encodeToString(rawHmac);
  59.             */
  60.         } catch (Exception e) {
  61.             throw new Error("Failed to generate HMAC : " + e.getMessage());
  62.         }
  63.         return result;
  64.     }
  65.     /*
  66.      * 等同于javaScript中的 new Date().toUTCString();
  67.      */
  68.     public static String toGMTString(Date date) {
  69.         SimpleDateFormat df = new SimpleDateFormat("E, dd MMM yyyy HH:mm:ss z", Locale.UK);
  70.         df.setTimeZone(new java.util.SimpleTimeZone(0, "GMT"));
  71.         return df.format(date);
  72.     }
  73.     /*
  74.      * 发送POST请求
  75.      */
  76.     public static HttpResponse sendAsrPost(byte[] audioData, String audioFormat, int sampleRate, String url,String ak_id, String ak_secret) {
  77.         PrintWriter out = null;
  78.         BufferedReader in = null;
  79.         String result = "";
  80.         HttpResponse response = new HttpResponse();
  81.         try {
  82.             URL realUrl = new URL(url);
  83.             /*
  84.              * http header 参数
  85.              */
  86.             String method = "POST";
  87.             String accept = "application/json";
  88.             String content_type = "audio/"+audioFormat+";samplerate="+sampleRate;
  89.             int length = audioData.length;
  90.             String date = toGMTString(new Date());
  91.             // 1.对body做MD5+BASE64加密
  92.             String bodyMd5 = MD5Base64(audioData);
  93.             String md52 = MD5Base64(bodyMd5.getBytes());
  94.             String stringToSign = method + "\n" + accept + "\n" + md52 + "\n" + content_type + "\n" + date ;
  95.             // 2.计算 HMAC-SHA1
  96.             String signature = HMACSha1(stringToSign, ak_secret);
  97.             // 3.得到 authorization header
  98.             String authHeader = "Dataplus " + ak_id + ":" + signature;
  99.             // 打开和URL之间的连接
  100.             HttpURLConnection conn = (HttpURLConnection) realUrl.openConnection();
  101.             // 设置通用的请求属性
  102.             conn.setRequestProperty("accept", accept);
  103.             conn.setRequestProperty("content-type", content_type);
  104.             conn.setRequestProperty("date", date);
  105.             conn.setRequestProperty("Authorization", authHeader);
  106.             conn.setRequestProperty("Content-Length", String.valueOf(length));
  107.             // 发送POST请求必须设置如下两行
  108.             conn.setDoOutput(true);
  109.             conn.setDoInput(true);
  110.             // 获取URLConnection对象对应的输出流
  111.             OutputStream stream = conn.getOutputStream();
  112.             // 发送请求参数
  113.             stream.write(audioData);
  114.             // flush输出流的缓冲
  115.             stream.flush();
  116.             stream.close();
  117.             response.setStatus(conn.getResponseCode());
  118.             // 定义BufferedReader输入流来读取URL的响应
  119.             if (response.getStatus() ==200){
  120.                 in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
  121.             }else {
  122.                 in = new BufferedReader(new InputStreamReader(conn.getErrorStream()));
  123.             }
  124.             String line;
  125.             while ((line = in.readLine()) != null) {
  126.                 result += line;
  127.             }
  128.             if (response.getStatus() == 200){
  129.                 response.setResult(result);
  130.                 response.setMassage("OK");
  131.             }else {
  132.                 response.setMassage(result);
  133.             }
  134.             System.out.println("post response status code: ["+response.getStatus()+"], response massage : ["+response.getMassage()+"] ,result :["+response.getResult()+"]");
  135.         } catch (Exception e) {
  136.             System.out.println("发送 POST 请求出现异常!" + e);
  137.             e.printStackTrace();
  138.         }
  139.         // 使用finally块来关闭输出流、输入流
  140.         finally {
  141.             try {
  142.                 if (out != null) {
  143.                     out.close();
  144.                 }
  145.                 if (in != null) {
  146.                     in.close();
  147.                 }
  148.             } catch (IOException ex) {
  149.                 ex.printStackTrace();
  150.             }
  151.         }
  152.         return response;
  153.     }
  154.     /*
  155.      * 发送POST请求
  156.      */
  157.     public static HttpResponse sendTtsPost(String textData,String audioType, String audioName,String url,String ak_id, String ak_secret) {
  158.         PrintWriter out = null;
  159.         BufferedReader in = null;
  160.         String result = "";
  161.         HttpResponse response = new HttpResponse();
  162.         try {
  163.             URL realUrl = new URL(url);
  164.             /*
  165.              * http header 参数
  166.              */
  167.             String method = "POST";
  168.             String content_type = "text/plain";
  169.             String accept = "audio/"+audioType+",application/json";
  170.             int length = textData.length();
  171.             String date = toGMTString(new Date());
  172.             // 1.对body做MD5+BASE64加密
  173.             String bodyMd5 = MD5Base64(textData.getBytes());
  174. //            String md52 = MD5Base64(bodyMd5.getBytes());
  175.             String stringToSign = method + "\n" + accept + "\n" + bodyMd5 + "\n" + content_type + "\n" + date ;
  176.             // 2.计算 HMAC-SHA1
  177.             String signature = HMACSha1(stringToSign, ak_secret);
  178.             // 3.得到 authorization header
  179.             String authHeader = "Dataplus " + ak_id + ":" + signature;
  180.             // 打开和URL之间的连接
  181.             HttpURLConnection conn = (HttpURLConnection) realUrl.openConnection();
  182.             // 设置通用的请求属性
  183.             conn.setRequestProperty("accept", accept);
  184.             conn.setRequestProperty("content-type", content_type);
  185.             conn.setRequestProperty("date", date);
  186.             conn.setRequestProperty("Authorization", authHeader);
  187.             conn.setRequestProperty("Content-Length", String.valueOf(length));
  188.             // 发送POST请求必须设置如下两行
  189.             conn.setDoOutput(true);
  190.             conn.setDoInput(true);
  191.             // 获取URLConnection对象对应的输出流
  192.             OutputStream stream = conn.getOutputStream();
  193.             // 发送请求参数
  194.             stream.write(textData.getBytes());
  195.             // flush输出流的缓冲
  196.             stream.flush();
  197.             stream.close();
  198.             response.setStatus(conn.getResponseCode());
  199.             // 定义BufferedReader输入流来读取URL的响应
  200.             InputStream is = null;
  201.             String line = null;
  202.             if (response.getStatus() ==200){
  203.                 is=conn.getInputStream();
  204.             }else {
  205.                 in = new BufferedReader(new InputStreamReader(conn.getErrorStream()));
  206.                 while ((line = in.readLine()) != null) {
  207.                     result += line;
  208.                 }
  209.             }
  210.             FileOutputStream fileOutputStream = null;
  211.             File ttsFile = new File(audioName+"."+audioType);
  212.             fileOutputStream = new FileOutputStream(ttsFile);
  213.             byte[] b=new byte[1024];
  214.             int len=0;
  215.             while(is!=null&&(len=is.read(b))!=-1){  //先读到内存
  216.                 fileOutputStream.write(b, 0, len);
  217.             }
  218.             if (response.getStatus() == 200){
  219.                 response.setResult(result);
  220.                 response.setMassage("OK");
  221.                 System.out.println("post response status code: ["+response.getStatus()+"], generate tts audio file :" + audioName+"."+audioType);
  222.             }else {
  223.                 response.setMassage(result);
  224.                 System.out.println("post response status code: ["+response.getStatus()+"], response massage : ["+response.getMassage()+"]");
  225.             }
  226.         } catch (Exception e) {
  227.             System.out.println("发送 POST 请求出现异常!" + e);
  228.             e.printStackTrace();
  229.         }
  230.         // 使用finally块来关闭输出流、输入流
  231.         finally {
  232.             try {
  233.                 if (out != null) {
  234.                     out.close();
  235.                 }
  236.                 if (in != null) {
  237.                     in.close();
  238.                 }
  239.             } catch (IOException ex) {
  240.                 ex.printStackTrace();
  241.             }
  242.         }
  243.         return response;
  244.     }
  245. }


5.3 请求结果类

<pre style='background: rgb(246, 246, 246); font: 12px/1.6 "YaHei Consolas Hybrid", Consolas, "Meiryo UI", "Malgun Gothic", "Segoe UI", "Trebuchet MS", Helvetica, monospace, monospace; padding: 10px; outline: 0px; border-radius: 3px; border: 1px solid rgb(221, 221, 221); overflow: auto; margin-top: 0px; margin-right: 0px; margin-bottom: 0px !important; margin-left: 0px; white-space: pre-wrap; word-wrap: break-word; box-sizing: border-box; font-size-adjust: none; font-stretch: normal;' prettyprinted?="" linenums="">
  1. package com.alibaba.idst.nls.response;
  2. public class HttpResponse {
  3.     private int status;
  4.     private String result;
  5.     private String massage;
  6.     public int getStatus() {
  7.         return status;
  8.     }
  9.     public void setStatus(int status) {
  10.         this.status = status;
  11.     }
  12.     public String getResult() {
  13.         return result;
  14.     }
  15.     public void setResult(String result) {
  16.         this.result = result;
  17.     }

展开
收起
nicenelly 2017-10-25 15:10:20 2058 0
0 条回答
写回答
取消 提交回答
问答排行榜
最热
最新

相关电子书

更多
API 网关实践 立即下载
API PLAYBOOK 立即下载
API平台的安全实践 立即下载