应用调用 API 接口

最近更新时间:2023-11-24 14:52:28

我的收藏

获取密钥生成签名

应用在调用孪生中台 API 之前,首先需要获取接口鉴权的签名。您需要使用应用所分配的 appId (应用 ID)和 AppKey,参考下方示例代码,生成 API 接口鉴权签名。
请求路径:/proxy/gateway/application/GenerateToken
请求方法:POST
请求参数:
nonce:随机字符串,不可重复,建议使用 uuid
timestamp:当前时间戳(毫秒),前后五分钟有效
注意:
tenantId:应用被下发到其他租户后,会生成新的 appId(应用 ID) 和 appSecret,所以请求时需要使用新生成的 appId(应用 ID)和 appSecret,或者使用原始 appId(应用 ID)+ 原始 appSecret + 对应租户的 tenantId,来为不同的租户生成 token。


应用签名生成规则

拼接字符串:
appId=10001&appSecret=xxxxxxxxxxxxxxxxxxxxxxxx&nonce=VlghmWSvnod7MvcC&timestamp=1640783576118
对该字符串进行 md5 加密生成32位签名(小写)
sign = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Java
Go
import org.apache.commons.lang3.RandomStringUtils;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Objects;

public class SignUtil {

/**
* 生成签名
*
* @param appId 应用ID
* @param appSecret 应用密钥
* @return 生成的签名
*/
public static String generateSign(Long appId, String appSecret) throws NoSuchAlgorithmException {
Objects.requireNonNull(appId, "应用ID不能为空");
Objects.requireNonNull(appSecret, "应用密钥不能为空");

long timestamp = System.currentTimeMillis();
String nonce = RandomStringUtils.randomAlphanumeric(32);
String strToMD5 = "appId=" + appId + "&appSecret=" + appSecret + "&nonce=" + nonce + "&timestamp=" + timestamp;
return calculateMD5(strToMD5);
}

/**
* 计算字符串的MD5哈希值
*
* @param plainText 待计算的字符串
* @return 字符串的MD5哈希值
*/
public static String calculateMD5(String plainText) throws NoSuchAlgorithmException {
Objects.requireNonNull(plainText, "输入字符串不能为空");

MessageDigest md = MessageDigest.getInstance("MD5");
byte[] hashBytes = md.digest(plainText.getBytes(StandardCharsets.UTF_8));

StringBuilder md5code = new StringBuilder();
for (byte b : hashBytes) {
String hex = Integer.toHexString(0xFF & b);
if (hex.length() == 1) {
md5code.append("0");
}
md5code.append(hex);
}

return md5code.toString();
}
}
message genTokenReq {
string appId = 1;
int64 timestamp = 2;
string nonce = 3;
string sign = 4;
int32 tenantId = 5;
}

secret := "xxxx"
req := &apigateway.GenTokenReq{
AppId: "xxx",
Nonce: uuid.New().String(),
TenantId: xxxx,
Timestamp: time.Now().UnixMilli(),
}
req.Sign = GenSign(secret, req.AppId, req.Nonce, req.Timestamp)

func GenSign(secret, appid, nonce string, timestamp int64) string {
// 创建一个map
params := map[string]string{
"appId": appid,
"appSecret": secret,
"nonce": nonce,
"timestamp": strconv.FormatInt(timestamp, 10),
}

// 对map的键进行字典序排序
keys := make([]string, 0, len(params))
for k := range params {
keys = append(keys, k)
}
sort.Strings(keys)

// 拼接map的键值对
var str string
for i, k := range keys {
str += k + "=" + params[k]
if i != len(keys)-1 {
str += "&"
}
}

// 使用md5算法对字符串进行签名
hash := md5.Sum([]byte(str))
signature := hex.EncodeToString(hash[:])

return signature
}

HTTP/HTTPS

OPEN API Doc 声明定义包含接口名称、接口描述、接口定义、输入参数、请求示例、返回参数、错误码。
请求路径:/proxy/*
请求方法:*
请求头携带参数:
字段
数据类型
是否必须
释义
示例
Dtgw-Token
string(16)
登录后由应用中心返回,之后访问需要携带至请求头中。用于验证身份,并确认该请求所属应用
D6heVfeQT7RnINhM

WebSocket

下面以孪生底座公共租户下的设备通知 API 和巡检应用为例,讲述应用在某个 WS API 下建立 WebSocket 链接的过程。
设备通知 API,API ID 为:bff0bda8-95ed-4034-b36b-0c533bf4a077。



巡检应用,应用 appid 是:10097。
应用请求和 WS API 建立 WS 连接和请求普通 API 是一样的流程,第0步,均需要获取应用请求 API 的 token,详情可参见 获取密钥生成签名
1. 应用请求 WS API 建立 WS 连接:
示例如下:
wscat -c
'ws://ws.dtwin.tencent.com/proxy/bff0bda8-95ed-4034-b36b-0c533bf4a077/weiling-pubsub/test?Dtgw-Token=gi9UyVNnSK3tpbTekBBryqnbmJ3Ta5PT'
或:
wscat -c
'ws://ws.dtwin.tencent.com/proxy/bff0bda8-95ed-4034-b36b-0c533bf4a077/weiling-pubsub/test' -H "Dtgw-Token: gi9UyVNnSK3tpbTekBBryqnbmJ3Ta5PT"
其中:gi9UyVNnSK3tpbTekBBryqnbmJ3Ta5PT,是第0步获取的应用请求 API 的 token。token 可以放在请求头,也可以放在 url 的 query string 中。
说明:
(ws://ws.dtwin.tencent.com/proxy/bff0bda8-95ed-4034-b36b-0c533bf4a077/weiling-pubsub/test) WS API 要和资源中心看到的一样不能篡改。
2. WS 连接建立后,要进行具体的订阅操作:
WS 通道,消息传递格式是统一的 json 格式,格式定义如下:

参数说明:
1. ver:版本号,当前版本统一为1。
2. operation:操作,是一个枚举值,每个枚举值表达是一种类型的命令,不同的命令 body 是不同的 json 结构。
3. seq:请求响应序列,递增即可,请求和响应一一对应。
4. topic:只有在 operation 为9的时候有意义,代表 client 收到的消息是来自哪个 topic。有这个字段的原因是,订阅 topic 支持2级,通配订阅,详情请参见 订阅示例说明
5. body:不同 operation 命令下是不同的 json 结构,可参见如下示例。
6. 具体推送的消息唯一 id,通常是 traceid,下行消息才有。
具体 operation 命令枚举定义如下,需要用到的是1 ~9和100:


其中 topic 支持两级目录结构的形式: /first/second,并支持*表示通配某一级,例如:
订阅 topic: /device/*,那么/device/add,/device/del,/device/xx 等 topic 上的消息都能收到,并且在推送的消息结构里面会有具体是哪个 topic 的消息字段。
订阅 topic:/*/*,则关于某个 ws api 的所有 topic 消息都能收到。
以下是
各个操作的示例
订阅
请求:
{"ver":1,"operation":1,"seq_id":10001,"body":"{\\"sub_topic\\":[\\"/citybase/*\\",\\"/cim/add\\"]}"}
响应:
{"ver":1,"operation":2,"seq_id":10001,"body":"{\\"message\\":\\"ok\\"}"}
更新 topic
请求:
{"ver":1,"operation":3,"seq_id":10002,"body":"{\\"sub_topic\\":[\\"/cim/del\\",\\"/citybase/get\\"]}"}
响应:
{"ver":1,"operation":4,"seq_id":10001,"body":"{\\"message\\":\\"ok\\"}"}
取消订阅主题
请求:
{"ver":1,"operation":5,"seq_id":10003,"body":"{\\"sub_topic\\":[\\"/citybase/get\\"]}"}
响应:
{"ver":1,"operation":6,"seq_id":10003,"body":"{\\"message\\":\\"ok\\"}"}
心跳,维持 WS 连接,需要每隔45s发送一个心跳包,否则连接空闲,会关闭 WS 连接
请求:
{"ver":1,"operation":7,"seq_id":100011,"body":""}
响应:
{"ver":1,"operation":8,"seq_id":10001,"body":"{\\"gap\\":45}"}
接收业务推送的消息
{"ver":1,"operation":9,"topic":"/cim/add","body":"{\\"uid\\": 102002, \\"name\\": \\"dtwin test 4444\\"}"}
错误回复,如果发送错误,会发送一个错误的 WS 回复,错误回复的 operation 是100
{"ver":1,"operation":100,"seq_id":10001,"body":"{\\"code\\":110011,\\"message\\":\\"duplicate Subscribe operation\\"}"}
3. 示例:
以下是用 wscat 客户端,进行的一个完整流程的示例,白色是发送的请求,蓝色是收到的消息/响应。

说明:
WS 连接后的,第一个请求包一定要是订阅请求,operation 是1。
具体要订阅的 topic,应用根据具体的 WS API 的相关说明文档来进行订阅即可。

平台回调

考虑到适配的孪生应用支持多租户和多项目空间的产品形态,每个租户下的应用 ID 和 apiID 相对独立,孪生应用为了适配一次,达到万千租户开箱即用,注册应用时填写“消息通知服务回调地址”,并为应用授权“应用中心通知 API”这个消息通知服务接口,开通租户时,孪生底座通过调用应用所提供“消息通知服务回调地址”服务,通知孪生应用新租户下的应用 ID,达到不同租户下应用 ID 和 apiID 的应用权限系统相互隔离。
1. 孪生应用适配孪生底座的业务逻辑图:

2. 填写孪生应用“消息通知服务回调地址”。

3. 授权孪生应用消息通知服务接口“应用中心通知 API”

4. 应用创建回调消息:
business_type 为10001表示是来自应用中心的消息。
eventType 为1表示是应用创建的消息,不同 eventType 对应的 data 协议不同。
{
"business_type": 10001,
"data": "{\\"eventType\\":1,\\"data\\":\\"{\\\\\\"appId \\\\\\":26826,\\\\\\"appSecret\\\\\\":\\\\\\"***\\\\\\",\\\\\\"name\\\\\\":\\\\\\"智能资产管理\\\\\\",\\\\\\"originAppId\\\\\\":10041,\\\\\\"tenantId\\\\\\":100215}\\"}",
"event_id": "GBKHb4bbCFSSBlpicE4a37nban4bJeQt"
}
上图代码示例协议内容说明:
字段名
数据类型
说明
tenantId
integer
租户 id
appId
integer
新生成的 appId
appSecret
string
新生成的 appSecret
name
string
应用名称
originAppId
integer
原始应用 id
5. 应用下发到项目空间回调消息:
business_type 为10001表示是来自应用中心的消息。
eventType 为2表示是应用下发到项目空间的消息,不同 eventType 对应的 data 协议不同。
{
"business_type": 10001,
"data": "{\\"eventType\\":2,\\"data\\":\\"{\\\\\\"tenantName\\\\\\":\\\\\\"\\\\\\",\\\\\\"username\\\\\\":\\\\\\"dd\\\\\\",\\\\\\"tenantStatus\\\\\\":0,\\\\\\"tenantId\\\\\\":100101,\\\\\\"email\\\\\\":\\\\\\"22@2.cc\\\\\\",\\\\\\"workspaceId\\\\\\":1214,\\\\\\"workspaceName\\\\\\":\\\\\\"空间11\\\\\\",\\\\\\"workspaceDescription\\\\\\":\\\\\\"1\\\\\\",\\\\\\"workspaceStatus\\\\\\":0,\\\\\\"appId\\\\\\":20861,\\\\\\"appSecret\\\\\\":\\\\\\"43MGE0GN3Coc85KSotCn7BlkVHFs7Gf5\\\\\\",\\\\\\"name\\\\\\":\\\\\\"能源管理\\\\\\"}\\"}",
"event_id": "GBKHb4bbCFSSBlpicE4a37nban4bJeQt"
}
上图代码示例协议内容说明:
字段名
数据类型
说明
tenantName
string
租户名称
username
string
租户负责人名称
email
string
租户负责人 email
tenantStatus
integer
租户状态:
1:禁用
0:启用
-1:删除
tenantId
integer
租户 id
workspaceId
integer
空间 id
workspaceName
string
空间名称
workspaceDescription
string
空间描述
workspaceStatus
integer
工作空间状态:
0:启用
1:停用
-1:已删除
appId
integer
新生成的 appId
appSecret
string
新生成的 appSecret
name
string
应用名称
originAppId
integer
原始应用 id