1 开放平台API (作者:姜鹏)
1.1 API对接
1.1.1 开发者API对接流程
开发者对接开放平台API流程,请参考下图:
1.1.2 API 签名
由于SHA-1和RSA-1024已过时且安全性较低,因此SHA-256和RSA 2048是当前的标准。SHA-256是一种非常好的安全散列算法,非常适合在证书上使用,而2048位RSA是一种很好的签名算法(注意签名与加密不同)。使用带有SHA-256的2048位RSA是证书的安全签名方案。本签名方案采用SHA-256和RSA2048.
step1 生成签名串:
appId\n
appSecret\n
method\n
url\n
noncestr\n
timestamp\n
body\n
说明:按照以上格式将7个参数按顺序排成7行。
签名参数 | 类型 | 是否必要 | 说明 |
---|---|---|---|
appId | String | Y | 开放平台应用Id |
appSecret | String | Y | 开放平台应用密钥 |
method | String | Y | Http的请求类型 , POST,GET,DELETE 等,一律大写 |
url | String | Y | Http的请求URL,含参数,以/开头,不含服务器域名和端口, 有参数请按参数的ASCII码升序拼接。如:请求URL为:https://api-sit.remacsmart.com/open/account/get?account_type=remac&id=1029&year=2021,实际url取值为:/open/account/get?account_type=remac&id=1029&year=2021,参数要按ASCII保证顺序 |
noncestr | String | Y | 32位随机字符串,在5分钟内有重复的随机字符串将视为重复请求。生成方法参照:UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32); |
timestamp | String | Y | 系统时间戳,秒为单位,长度10位,如:1649715582, 当开发者应用传参时间戳与平台接收到请求的时间戳相差10秒将被视为过期请求。生成方法参考:String.format("%d",System.currentTimeMillis()/1000) |
body | String | Y | http 请求体,非表单参数。无值用\n表示独占一行。JSON参数顺序按ASCII码升序排序,如有嵌套JSON,嵌套JSON中的参数也遵循按ASCII码升序排序。 |
Step2 开发者利用平台私钥对签名串进行签名
引入睿智云开放平台签名SDK
<dependency>
<groupId>com.remac.opencloud</groupId>
<artifactId>remac-open-signature</artifactId>
<version>1.0</version>
</dependency>
调用签名SDK完成签名。
String privateKey = "-----BEGIN RSA PRIVATE KEY-----\n" +
"MIICXAIBAAKBgQC7NmfnT4MaT/NtadggelMRpNIY6Fb7vyAGBSZFbhgNSQaSJqGH\n" +
"UWsyHpyr+DOt15erardcncrxvIrWuzxWJa123jnDwHjrTFEZCTbuwoIwaEnNqStR\n" +
"uWY2RqTLqzFcUH73lLaOiIyuEKylPL8+ypHv7JBzzn9q5vIdu3P4kKlItwIDAQAB\n" +
"AoGANmx+kDQBPOj5L1mRxv9Intx3Z15DyzOor5dXpN94hoQwMNSiKKB2tT9I9IVX\n" +
"ILaIFQEVBQuqL9RL2FjeFX0mi+QIRoVmquEwRC0lb5dp2KyYQF7/NHoZkQacZMYe\n" +
"0+XHjn8vf9sKz0/E0WQmt4CBvrDzbUgP3WyvpZaNbTf88KECQQDvorD4MU1wQ7nk\n" +
"furTFiDp8jcTwIrpmPllO5y1wZXy4xHxaYkvyfPngoHrvCHh+qZ29t2wMeaNxnjG\n" +
"Mt+WNu79AkEAx/9Bd21lv70bq9GW3q/xTuyeEI8CdD31Vwud97MKL+CHb2hWavH+\n" +
"VPeZfpa1ZqqKFOEl9DP3hai9AeXx1L2WwwJAHCp8MH/jAToEpHXCdhMYxUah8KFl\n" +
"8nT3g02Ras+ZJ1ZHKp/j7wkGsQRm7uVK+juyyzLS9b23wpw8X9dk7kwApQJBAJI+\n" +
"um2NRpENxNVAfrU6LReMeQ0ctiuwPt429X1yo6lc40x5HHA5osQZBloF9IIthKk9\n" +
"9lX7Ri2rtQJVViA9vqcCQBRx4QsL/dZ6zZpW+BPNl8wFhvgfguXDfNzL7A3QA45k\n" +
"4OPqj5hw1e8PYB45ZG0K5WkknpLAtzDpxiMQWoj+u9w=\n" +
"-----END RSA PRIVATE KEY-----\n";
SignUtil util = new SignUtil(privateKey);
String appId = "remac16800901";
String appSecret = "9lX7Ri2rtQJVViA9vqcCQBRx4QsL/dZ6zZpW+BPNl8wFhvgfguXDfNzL7A3QA45k";
String url = "/account/get?id=1108";
String method = "GET";
String noncestr = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
String timestamp = String.format("%d",System.currentTimeMillis()/1000);
String body = null;
String message = util.buildMessage(appId,appSecret,method, url,noncestr, timestamp, body);
System.out.println("--->签名串:"+message);
long start = System.currentTimeMillis();
String signature = util.sign(privateKey,message );
System.out.println("--->签名:signature="+signature);
long end = System.currentTimeMillis();
System.out.println("--签名耗时:"+(end-start)+" 豪秒");
执行结果:
--->签名串:remac16800901
9lX7Ri2rtQJVViA9vqcCQBRx4QsL/dZ6zZpW+BPNl8wFhvgfguXDfNzL7A3QA45k
GET
/account/get?id=1108
64c3a85b5b8d4216966652a42078c3c9
1649657739
null
--->签名:
signature=E1ucuUvX6VqxtFewl9LS2nl66fyZ2LaJ/DE/vaqM2rvaKOu0wFoGHJ1l3fELqvYJT6LMYpF7vxbzo3u8EZIg2Z0xCfY9mBYnuN+XXnmxbjJt2cDMkvJ+tXS8qOXACQNMghco+Ek90OrfrYA6CkOUA4VRV0/Y2N/wISN50US5XS8=
--签名耗时:946 豪秒
step3 构造一个包含签名的Http头部信息
将生成的签名与其他信息一起按照格式放入请求头signToken中,请求头格式为:
signToken: 认证类型 固定参数(含签名)
其中, 认证类型固定为:REMAC-SHA256-RSA2048
固定参数(含签名): 将必要的appId, appSecret, nocestr, timestamp,signature 以健值对key=value加逗号隔开,无顺序要求。
示例:
signToken: REMAC-SHA256-RSA2048 appId=remac16800901,appSecret=9lX7Ri2rtQJVViA9vqcCQBRx4QsL/dZ6zZpW+BPNl8wFhvgfguXDfNzL7A3QA45k,noncestr=462e70c75cee42fcad7b8bd54b1ee4e0,timestamp=1649717011,signature=PxJgOwB1q4SnNbBAlb3hhuL+DnPcEmpVoZzgGIgUWISQVJxg66y+VIBRSwr0wyv7Qyw+osWJERr5IeEaHdjmOOq6KV0Iu9zDBTdH23dAoYyvK6uxcQolI3Wx5/pz+KcwsgzZ5JqKgQFcjnZWGOfJPDKwS4cKQcuEf9LuVCG4py8=
step4 调用睿智云开放平台接口,将签名值携带在http 请求头部。
参考源码:
// 开放平台API测试环境域名
// String domain = "https://api-sit.remacsmart.com";
//// 开放平台API生产环境域名
String domain = "https://api.remacsmart.com";
String privateKey = "-----BEGIN RSA PRIVATE KEY-----\n" +
"MIICXAIBAAKBgQC7NmfnT4MaT/NtadggelMRpNIY6Fb7vyAGBSZFbhgNSQaSJqGH\n" +
"UWsyHpyr+DOt15erardcncrxvIrWuzxWJa123jnDwHjrTFEZCTbuwoIwaEnNqStR\n" +
"uWY2RqTLqzFcUH73lLaOiIyuEKylPL8+ypHv7JBzzn9q5vIdu3P4kKlItwIDAQAB\n" +
"AoGANmx+kDQBPOj5L1mRxv9Intx3Z15DyzOor5dXpN94hoQwMNSiKKB2tT9I9IVX\n" +
"ILaIFQEVBQuqL9RL2FjeFX0mi+QIRoVmquEwRC0lb5dp2KyYQF7/NHoZkQacZMYe\n" +
"0+XHjn8vf9sKz0/E0WQmt4CBvrDzbUgP3WyvpZaNbTf88KECQQDvorD4MU1wQ7nk\n" +
"furTFiDp8jcTwIrpmPllO5y1wZXy4xHxaYkvyfPngoHrvCHh+qZ29t2wMeaNxnjG\n" +
"Mt+WNu79AkEAx/9Bd21lv70bq9GW3q/xTuyeEI8CdD31Vwud97MKL+CHb2hWavH+\n" +
"VPeZfpa1ZqqKFOEl9DP3hai9AeXx1L2WwwJAHCp8MH/jAToEpHXCdhMYxUah8KFl\n" +
"8nT3g02Ras+ZJ1ZHKp/j7wkGsQRm7uVK+juyyzLS9b23wpw8X9dk7kwApQJBAJI+\n" +
"um2NRpENxNVAfrU6LReMeQ0ctiuwPt429X1yo6lc40x5HHA5osQZBloF9IIthKk9\n" +
"9lX7Ri2rtQJVViA9vqcCQBRx4QsL/dZ6zZpW+BPNl8wFhvgfguXDfNzL7A3QA45k\n" +
"4OPqj5hw1e8PYB45ZG0K5WkknpLAtzDpxiMQWoj+u9w=\n" +
"-----END RSA PRIVATE KEY-----\n";
SignUtil util = new SignUtil(privateKey);
String appId = "remac16800901";
String appSecret = "9lX7Ri2rtQJVViA9vqcCQBRx4QsL/dZ6zZpW+BPNl8wFhvgfguXDfNzL7A3QA45k";
String url = "/account/query?id=1108";
String method = "POST";
String noncestr = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
System.out.println("--->noncestr="+noncestr);
String timestamp = String.format("%d",System.currentTimeMillis()/1000);
System.out.println("--->timestamp ="+timestamp );
JSONObject reqBody = new JSONObject();
reqBody .put("name", "张三");
reqBody .put("account_type", "remac");
String body = reqBody.toJSONString();
//step1 构造签名串
String message = util.buildMessage(appId,appSecret,method, url,noncestr, timestamp, body);
System.out.println("--->签名串:"+message);
long start = System.currentTimeMillis();
//step2 生成签名
String signature = util.sign(privateKey,message );
System.out.println("--->签名:signature="+signature);
long end = System.currentTimeMillis();
System.out.println("--签名耗时:"+(end-start)+" 豪秒");
//step3 http调用睿智云中台接口
int connectTimeOut = 10;
int readTimeOut = 5;
int writeTimeOut = 5;
int maxIdleConnections = 100;
long keepAliveDuration = 300L;
OkHttpUtil httpUtil = new OkHttpUtil(connectTimeOut, readTimeOut, writeTimeOut, maxIdleConnections, keepAliveDuration);
// 头部携带签名
Map<String, String> headers = new HashMap<>();
String header = util.buildHeader( appId,appSecret,noncestr,timestamp, signature);
System.out.println("--->header value="+header);
headers.put("signToken", header);
String api = domain+url;
Response response = httpUtil.post(null, api, body, headers);
String res = response.body().string();
System.out.println("--->收到开放平台的返回结果JSON:"+res);
1.1.2.1 开放平台调试API
(1) 调用GET 请求
https://api-sit.remacsmart.com/v1/open/fake/account?id=XX
其中id 为任意字符串
正确响应:
{
"code": "200",
"message":"success",
"data": {
"id":"XXX",
"user_name":"fake01",
"avatar":"https://iot-main-oss.oss-cn-hangzhou.aliyuncs.com/icons/monkey.png"
}
}
(2) 调用 POST 请求
https://api-sit.remacsmart.com/v1/open//fake/submit
reqBody: HTTP请求体, 用户自定义一个JSON ,在返回结果的data 原样输出
正确响应:
{
"code": "200",
"message":"success",
"data": {
开发者传HTTP请求体原样输出
}
}
1.1.4 API 安全规范
任何调用开放平台的请求,须按《1.1.2 API 签名》规定生成签名,在http头部携带签名标识,约定如下:
http 头部名称: signToken , 值: 开发者按《1.1.2 API 签名》规定生成的签名。
1.1.5 开放平台接口响应约定
1.1.5.1 响应格式
正确响应:
{
"code": "200",
"message":"success",
"data": {
"result":[{...},{..}]
}
}
错误响应:
{
"code": "非200",
"message":"错误提示"
}
1.1.5.2 错误码
code | message | 说明 |
---|---|---|
10001 | 开发者应用密钥错误 | |
10002 | 开发者应用ID不存在 | |
10003 | 签名格式不正确 | |
10004 | 缺少头部signToken | |
10005 | 签名中必要参数缺失 | |
10006 | 开发者应用IP不在白名单中 | |
10007 | 请求重复 | |
10008 | 请求已过期 | |
10009 | 开发者应用未关联开发者帐号 | |
10010 | 平台未找到开发者公钥 | |
10011 | 随机字符串noncestr非法 | |
10012 | 请求时间戳非法 | |
10013 | 验签失败 | |
9999 | 通用错误码 |
1.2 文档
1.2.1 文档范围
所有需要访问睿智云开放平台标准API的开发者。
1.2.2 文档列表
1.2.3 全局字典
字典key | 类型 | 字典Value | 字典value意义 |
---|---|---|---|
account_type | Int | 1 | 个人开发者 |
2 | 企业开发者 | ||
1.3 开放平台API
使用对象: 开发者。
1.3.1 开发者帐号
1.3.1.1 开发者帐号注册
URL: /v1/open/account/register
method : POST
body 约定:
字段 | 类型 | 说明 | 是否必填 |
---|---|---|---|
account_type | int | 开发者帐号类型,见《1.2.3 全局字典》 | y |
user_name | String | 用户名,可以为手机号 6-12 字符长度。 | y |
nick_name | String | 用户昵称, 1-32 字符长度 | n |
avatar | String | 用户头像,<=200个字符长度 | n |
pwd | String | 用户密码, 明文6-12 字符长度。规范见《http://arch.smartmideazy.com/docs/account/account-doc.html【5.2 密码明文加密】》 | y |
enabled | Int | 1: 激活 2:禁用 3: 临时锁定 ; 默认1. | n |
materials | JSONObject | 开发者资料 | n |
开发者资料 materials
个人开发者materials对象各字段约定如下:
字段 | 类型 | 说明 | 是否必填 |
---|---|---|---|
id_no | int | 身份证编码 | y |
id_photo_face | byte[] | 身份证人像面 | y |
id_photo_nation | byte[] | 身份证国徽面 | y |
valid_time_span | Array | 身份证有效期 ["2009-09-01", "长期"] | y |
企业开发者materials对象各字段约定如下:
字段 | 类型 | 说明 | 是否必填 |
---|---|---|---|
id_no | int | 法人(代运营者)身份证编码 | y |
id_photo_face | byte[] | 法人(代运营者)身份证人像面 | y |
id_photo_nation | byte[] | 法人(代运营者)身份证国徽面 | y |
valid_time_span | Array | 法人(代运营者)身份证有效期 ["2009-09-01", "长期"] | n |
legal_type | Int | 1: 法人 2: 代运营者 | y |
certificate_proof | byte[] | 代运营者加盖企业公章的证明函。模板从开放平台下载。 | N; 代运营者须上传加盖企业公章的证明函。 |
business_certificate | byte[] | 公司营业执照 | Y |
business_no | String | 统一社会信用号 | Y |
tel | String | 联系电话 | Y |
返回字段:
字段 | 类型 | |
---|---|---|
id | Biting | 帐号主键 |
account_type | int | 开发者帐号类型,见《1.2.3 全局字典》 |
user_name | String | 用户名,可以为手机号 6-12 字符长度。 |
nick_name | String | 用户昵称, 1-32 字符长度 |
avatar | String | 用户头像,<=200个字符长度 |
lock_time | String | yyyy-MM-dd HH:mm:ss 格式。帐号锁定时间。同一天最大密码重试次数达5次将被锁定,登录功能限制。次日方可重试。 |
enabled | Int | 1: 激活 2:禁用 3: 临时锁定 |
last_login_time | String | yyyy-MM-dd HH:mm:ss 格式。帐号最近一次成功登录时间 |
register_time | String | yyyy-MM-dd HH:mm:ss 格式。注册时间 |
1.3.1.2 开发者帐号安全-绑定手机号
URL: /open/account/bind/mobile
method : POST
body 约定:
字段 | 类型 | 说明 | 是否必填 |
---|---|---|---|
id | bigint | 开发者帐号主键ID | y |
mobile | String | 手机号码 | y |
verify_code | String | 6位短信随机码 | y |
1.3.1.3 开发者帐号安全-绑定邮箱
URL: /open/account/bind/mobile
method : POST
body 约定:
字段 | 类型 | 说明 | 是否必填 |
---|---|---|---|
id | Biting | 开发者帐号主键ID | y |
String | 电子邮件地址 | y | |
verify_code | String | 平台向开发者邮箱发送6位确认码。 | y |
1.3.1.4 开发者帐号找回-手机号找帐号
URL: /open/account/fetch/mobile
method : POST
body 约定:
字段 | 类型 | 是否必填 | |
---|---|---|---|
mobile | String | 开发者手机号 | y |
verify_code | String | 平台向开发者发送的6位手机验证码 | y |
1.3.1.5 向开发者邮箱发送确认码及链接
URL: /open/account/fetch/email
method : POST
body 约定:
body 约定:
字段 | 类型 | 说明 | 是否必填 |
---|---|---|---|
String | 电子邮件地址 | y |
成功执行后返回结果:
字段 | 类型 | 说明 | 是否必返回 |
---|---|---|---|
verify_code | String | 平台发送的邮箱确认码 | N |
expired_in | int | 过期时间。将在expired_in分钟后过期 | N |
makesure_url | String | 开发者邮箱中找回帐号的链接 | N |
1.3.1.6 开发者帐号找回-邮箱找帐号
URL: /open/account/fetch/email
method : POST
body 约定:
字段 | 类型 | 说明 | 是否必填 |
---|---|---|---|
String | 电子邮件地址 | Y | |
verify_code | String | 6位手机确认码 | Y |
nonce_str | String | 平台生成的随机码 | Y |
返回:
{
"code":"200",
"message":"success",
"user_name":"betty2022"
}
返回字段说明
字段 | 类型 | 说明 | 是否必返回 |
---|---|---|---|
user_name | String | 开发者帐号 | N; 仅帐号找回成功才返回此字段 |
1.3.1.7 开发者重置密码
URL: /open/account/pwd/reset
method : POST
body 约定:
字段 | 类型 | 说明 | 是否必填 |
---|---|---|---|
user_name | String | 开发者帐号 | Y |
pwd | String | 开发者新密码 | Y |
random_code | String | 平台生成6位随机验证码 | Y |
返回:
{
"code":"200",
"message":"success",
}
1.3.2 开发者应用
1.3.2.1 应用创建
描述: 开发者可以创建多个应用。
URL: /open/app/create
method : POST
body 约定:
字段 | 类型 | 说明 | 是否必填 |
---|---|---|---|
app_name | String | 应用名称[4-32]字符长度 | Y |
app_logo | Byte[] | 应用LOGO, 长 120像素 宽 120像素 清晰。 | Y |
app_discription | String | 应用介绍。200字符以下。 | Y |
app_type | String | 行业类型编码 (待补充) | Y |
返回:
{
"code":"200",
"message":"success",
"data": {
"id":1120,
"app_key":"109667788",
"app_sectet":"VrG8kPpCGFWs25NpOQelaYtrDd1Cg0YdBIKqJIgknojatWo6DcV5P",
}
}
成功返回:
字段 | 类型 | 说明 | 是否必填 |
---|---|---|---|
id | bigint | 应用主键ID | Y |
app_key | String | 开发者应用帐号 | Y |
app_secret | String | 开发者应用密钥 | Y |
account_id | Biting | 开发者帐号主键ID | Y |
1.3.2.2 开发者公私钥设置
描述:
URL: /open/api/key/set
method : POST
body 约定:
字段 | 类型 | 说明 | 是否必填 |
---|---|---|---|
account_id | Bigint | 开发者帐号主键ID | |
private_key | String | 开发者私钥。2048 长度RSA公私钥,开发者个人保存。 | Y |
public_key | String | 开发者公钥。平台需要保存。 | Y |
status | Int | 1:启用2:禁用。默认为启用。 | Yid |
id | Bigint | 开发者API配置表主键ID。N; 此值为空代表新增。非空代表修改 |
1.3.2.3 开发者AES加密密钥设置
描述: 用于开发者应用与平台通讯协议需要加解密的场合。
URL: /open/api/aes/set
method : POST
body 约定:
字段 | 类型 | 说明 | 是否必填 |
---|---|---|---|
account_id | Biting | 开发者帐号主键ID | |
aes_key | String | 开发者AES密钥。长度32位。 | Y |
id | Bigint | 开发者API配置表主键ID | N; 此值为空代表新增。非空代表修改 |
1.3.2.4 开发者IP白名单
描述: 开发者将自己的应用部署环境的IP作为白名单配置在平台,进一步提升开发者应用与开放平台之间的安全性。
URL: /open/api/ip/set
method : POST
body 约定:
字段 | 类型 | 说明 | 是否必填 |
---|---|---|---|
account_id | Biting | 开发者帐号主键ID | |
aes_key | String | 开发者AES密钥。长度32位。 | Y |
id | Bigint | 开发者API配置表主键ID | N; 此值为空代表新增。非空代表修改 |
1.4 开放平台后台API
对象: 开放平台管理员。
1.4.1 开发者帐号审核
URL: /open/admin/account/audit
method : POST
body 约定:
字段 | 类型 | 说明 | 是否必填 |
---|---|---|---|
user_name | String | 开发者帐号 | Y |
status | Int | 1: 通过 2:拒绝 | Y |
返回:
{
"code":"200",
"message":"success",
}
1.4.2 开发者帐号列表
URL: /open/admin/accounts
method : POST
body 约定:
字段 | 类型 | 说明 | 是否必填 |
---|---|---|---|
pageNo | String | 开发者帐号 | Y |
pageSize | Int | 1: 通过 2:拒绝 | Y |
key_word | String | 根据帐号、手机号、邮箱模糊搜索 | N |
返回:
{
"code":"200",
"message":"success",
"data": {
"pageNo":2,
"pageSize": 20,
"total": 112000,
"result": [
{...},... {...}
]
}
}
2 开放平台与现有的平台架构关系
3 测试数据
3.1 平台管理员新增开发者平台登录帐号
POST https://gw-sit.remacsmart.com/v1/open/account/register
请求:
{
"acount_type": 1,
"user_name": "remac",
"pwd":"a123456",
"nick_name":"Love flower"
}
返回:
{
"code": "200",
"data": {
"id": 1,
"user_name": "remac",
"nick_name": "Love flower",
"avatar": null,
"pwd": "$2a$10$T24vHSxpDDuaYrWq.XE80eWyCfo109Koi9b37qQBpxmvPXqf0tcvO",
"materials": null,
"update_time": "2022-04-24 18:12:39",
"enabled": null
},
"message": "success"
}
3.1 平台管理员新增开发者应用
POST https://gw-sit.remacsmart.com/v1/open/app/register
{
"account_id": 1,
"app_name", "睿智云智慧社区",
"aes_key": "goodblessme20220",
"aes_encrypt" : 1
}