1

云服务多语言 SDK - hligy

 1 year ago
source link: https://www.cnblogs.com/hligy/p/16714954.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

原文地址

云服务一般提供了一个文档来描述提供的 OpenAPI,然会每个用户根据文档自己实现签名、调用等功能的封装,这部分会出现各种各样的问题,因此用户更希望平台直接提供一个 SDK。

darabonba

darabonba 是阿里云开源的用于 OpenAPI 的 DSL 语言,编写 darabonba 就可以生成多语言的 SDK 等等。本文不介绍入门(README 比较全)。

编写 SDK

首先是 Config 类,一般包括 appKey 和 secretKey,并定义模块变量,定义如下:

// 引入的依赖见最后的推荐依赖
import Util;
import SignatureUtil;
import OpenApiUtil;

type @host = string
type @appKey = string
type @secretKey = string

model Config {
  appKey: string(description = "appKey"),
  secretKey: string(description = "secretKey")
}

接着是 Client 类的初始化方法里初始化:

init(config: Config) {
  @host = "www.aliyun.com";
  @appKey = config.appKey;
  @secretKey = config.secretKey;
}

接着定义请求方法:

// 请求的通用参数封装为一个 model
model Request {
  // GET/POST
  method: string,
  // 请求路径
  pathname: string,
  // 拼接参数字符串,作为签名的一部分
  param: string,
  // 参数是否是 Json 格式
  isJson?: boolean
}

// http 请求,返回一个任意类型的结果
api _requestRAny(request: Request, body: any): any {
  __request.pathname = request.pathname;
  __request.method = request.method;
  // 获取时间戳
  var date: string = OpenApiUtil.getTimestamp();
  // 计算签名
  __request.headers = _header(@appId, date, _sign(request.param, date));
  if (request.isJson) {
    __request.headers.accept = 'application/json';
    __request.body = Util.toJSONString(body);
  } else {
    __request.query = Util.stringifyMapValue(Util.assertAsMap(body));
  }
} returns {
  // 处理 http response
  return _handle(__response);
} runtime {
  // 超时时间 10 s,你也可以配成模块参数
  timeout = 10000
}

// 调用 api 的函数必须用 async 关键字修饰
async function _requestRObj(request: Request, body: any): object {
  // object 等价 map[string]any 等价 $Model,它们之间可以直接转换
  return Util.assertAsMap(_requestRAny(request, body));
}

// 处理 http response,返回任意类型的结果
function _handle(response: $Response): any {
  // 读取响应的数据,通常是一个包含 code、status 和 data 的 Json 串
  var result = Util.assertAsMap(Util.readAsJSON(response.body));
  if (!Util.equalNumber(response.statusCode, 200) || !Util.assertAsBoolean(result.ok)) {
      // 抛异常,通过 throw 关键字仅能抛出一种异常
      throw {
          message = `httpCode: ${response.statusCode}, serverCode: ${result.code}, reason: ${result.message}`,
          code = `${result.code}`
      };
  }
  return result.data;
}

// 构建请求头
function _header(appKey: string, signedAt: string, sign: string): map[string]string {
  var headers: map[string]string = {
    host = @endpoint_host,
    app_key = appKey,
    signed_at = signedAt,
    sign = sign
  };
  return headers;
}

// 签名
function _sign(param: string, date: string): string {
  // 模式字符串拼接参数,计算 md5 摘要做签名
  var sign: string = OpenApiUtil.hexEncode(SignatureUtil.MD5Sign(`app_key${@appKey}secret_key${@secretKey}signed_at${date}${param}));
  return sign;
}

比如一个接入用户接口

model UserRequest {
  userId: string(description = "userId", name = "userId", required = true),
  nickName?: string(description = "nickName", name = "nickName")
}

async function createUser(userRequest: UserRequest): void {
  // 校验参数,UserRequest 的 nickName 使用了 ?:,? 表示不会进行校验,如果不调用这个方法那就没有区别
  Util.validateModel(userRequest);
  // 构建通用参数
  var request = new Request {
    method = @get,
    pathname = "/user/createUser",
    // 模式字符串拼接参数做签名
    param = `nickName${userRequest.nickName}userId${userRequest.userId}`
  };
  // 发起 http 请求,如果返回值是基本类型比如数字,那么可以这样:
  _requestRAny(request, accessThirdPartyUserRequest);
}
  1. Model 和 Object 和 map[string]any

它们三个是等价的,可以直接转换,不用转换,其他的都需要调用工具类转换,这点很重要

  1. 接口参数很少,不想定义 Model?

直接使用 map[string]any。

_requestRAny(request, {
  // 和前面 _header 函数一样构建 map 一样
  thirdPartyUserId = thirdPartyUserId
})
  1. 接口参数包含数组,签名怎么计算?

调用 OpenApiUtil.arrayToStringWithSpecifiedStyle() 方法转为为字符串

// 风格自己选,和服务端保持一直就可以
var arrayString: string = OpenApiUtil.arrayToStringWithSpecifiedStyle(null, null, null);
  1. 返回值类型怎么转换?

_requestRAny 函数返回的是任意类型。

  • 基本类型使用 Util.assertAsXXX() 转换
  • 数组类型需要定义一个 model,在 model 里定义一个数组,然后用 map 设置数组再转换为 model
model Response {
  items: [ string ](description = "items")
}

// 返回 Response 类型,函数其余部分不展示了
return {
  items = _requestRAny(request, modelRequest)
};

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK