saas 服务一般提供了一个文档来描述提供的 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);
}
其他
- Model 和 Object 和 map[string]any
它们三个是等价的,可以直接转换,不用转换,其他的都需要调用工具类转换,这点很重要
- 接口参数很少,不想定义 Model?
直接使用 map[string]any。
_requestRAny(request, {
// 和前面 _header 函数一样构建 map 一样
thirdPartyUserId = thirdPartyUserId
})
- 接口参数包含数组,签名怎么计算?
调用 OpenApiUtil.arrayToStringWithSpecifiedStyle() 方法转为为字符串
// 风格自己选,和服务端保持一直就可以
var arrayString: string = OpenApiUtil.arrayToStringWithSpecifiedStyle(null, null, null);
- 返回值类型怎么转换?
_requestRAny 函数返回的是任意类型。
- 基本类型使用 Util.assertAsXXX() 转换
- 数组类型需要定义一个 model,在 model 里定义一个数组,然后用 map 设置数组再转换为 model
model Response {
items: [ string ](description = "items")
}
// 返回 Response 类型,函数其余部分不展示了
return {
items = _requestRAny(request, modelRequest)
};
推荐依赖
-
类似 JDK,几乎必装的依赖,包含类型转换、JSON 转换 等等:Util | Darabonba Repo (aliyun.com)
-
byte 数组转 string,获取时间戳:OpenApiUtil | Darabonba Repo (aliyun.com)