由于钉钉和宜搭node版本的sdk相当难用,几乎没有文档,大部分是java示例,所以直接调用接口比较好。钉钉第三方企业应用调用接口都需要accessToken,而获取accessToken是需要签名算法的。宜搭也是需要签名算法的
钉钉签名算法文档地址
把timestamp+”\n”+suiteTicket当做签名字符串,suiteSecret/customSecret做为签名密钥,使用HmacSHA256算法计算签名,然后进行Base64 encode获取最后结果。然后把签名参数再进行urlEncode,加到请求url后面。
宜搭签名算法文档地址
宜搭签名算法其实本质跟钉钉的一样,只不过多了一步是将请求参数排列了下
钉钉签名算法
关于HmacSHA256算法,使用node自带的crypto模块
suiteTicket 是需要用钉钉推送数据库里面的suiteTicket,相关文档
'use strict';
const Service = require('egg').Service;
const { createHmac } = require('crypto');
class DingService extends Service {
get OpenSyncBizData() {
return this.ctx.model.OpenSyncBizData;
}
// 签名算法的相关参数
async getParams() {
// 读取数据库中biz_type为2的数据
const biz_type2 = await this.OpenSyncBizData.findOne({
order: [[ 'gmt_modified', 'DESC' ]], // asc升序 desc倒序 这是关键,需要取最新的
where: {
biz_type: 2,
},
raw: true,
});
const parseBizData2 = JSON.parse(biz_type2.biz_data);
const timestamp = new Date().getTime(); // 时间戳 毫秒
const suiteTicket = parseBizData2.suiteTicket;
const suiteSecret = 'xxx'; // 第三方企业应用 xxx应用的SuiteSecret
const accessKey = 'suitetvjnspt9jxlssyys'; //第三方企业应用 xx应用的 SuiteKey
const auth_corpid = 'ding152b8c2686eed6e635c2f4657eb6378f'; // 自己企业的corp_id
return {
timestamp,
suiteTicket,
suiteSecret,
accessKey,
auth_corpid,
};
}
// 签名算法获取签名
// 文档:https://developers.dingtalk.com/document/app/signature-calculation-method-for-third-party-access-interfaces-1?spm=ding_open_doc.document.0.0.5e9d993fLv97lG#topic-1949473
async getSignature({ suiteTicket, timestamp, suiteSecret }) {
const bytes_to_sign = timestamp + '\n' + suiteTicket; // 需加密的字符串
const digest = createHmac('sha256', suiteSecret).update(bytes_to_sign).digest();
return Buffer.from(digest).toString('base64');
}
// accessToken
async getToken() {
const { accessKey, timestamp, auth_corpid, suiteTicket, suiteSecret } = await this.getParams();
const signature = await this.getSignature({ suiteTicket, timestamp, suiteSecret });
const url = 'https://oapi.dingtalk.com/service/get_corp_token';
const dingUrl = url + `?signature=${encodeURIComponent(signature)}×tamp=${timestamp}&suiteTicket=${suiteTicket}&accessKey=${accessKey}&auth_corpid=${auth_corpid}`;
const result = await this.ctx.curl(dingUrl, {
dataType: 'json',
method: 'POST',
contentType: 'json',
data: {
auth_corpid,
},
});
return result.data && result.data.access_token;
}
// post请求url带access_token
async httpPost({ url, data }) {
const token = await this.getToken();
const apiUrl = `${url}?access_token=${token}`;
const result = await this.ctx.curl(apiUrl, {
dataType: 'json',
method: 'POST',
contentType: 'json',
data,
});
return result;
}
// 获取用户信息
async getUserInfo({ userid }) {
const url = 'https://oapi.dingtalk.com/topapi/v2/user/get';
const res = await this.httpPost(
{
url,
data: {
userid,
},
}
);
return res.data.result;
}
}
module.exports = DingService;
宜搭的签名算法
'use strict';
const Service = require('egg').Service;
const dayjs = require('dayjs');
const { Buffer } = require('buffer');
const { createHmac } = require('crypto');
// 签名算法
function get_signature(method, timestamp, nonce, url, url_params, API_SECRET_KEY) {
const bytes_to_sign = `${method}\n${timestamp}\n${nonce}\n${url}\n${url_params}`.trim();
const hmac = createHmac('sha256', API_SECRET_KEY);
hmac.update(bytes_to_sign);
const digest = hmac.digest();
return Buffer.from(digest).toString('base64');
}
// 参数排序
function orderParams(paramsObj) {
let str = '';
const orderKey = Object.keys(paramsObj).sort();
orderKey.forEach(item => {
const value = paramsObj[item];
str += `&${item}=${value}`;
});
return str.slice(1);
}
class YiDaService extends Service {
// 调用接口,根据appType获取密钥等信息
async getInfo(appType) {
const APIUrl = this.app.config.API;
const url = APIUrl + '/api/center/yida/info/details';
const r = await this.ctx.curl(`${url}`, {
data: {
appType,
},
dataType: 'json',
});
const data = r.res.data.data;
return data;
}
// 获取宜搭签名
getHeader({ url_info, url_params }) {
const {
API_SECRET_KEY,
API_KEY,
getIPAddress,
macAddress,
API_VERSION,
nonce,
} = this.ctx.helper;
const method = 'POST';
const dayjsNow = dayjs(new Date().getTime());
const timestamp = `${dayjsNow.format('YYYY-MM-DD')}T${dayjsNow.format('HH:mm:ss.SSS')}+08:00`;
const signature = get_signature(method, timestamp, nonce, url_info, url_params, API_SECRET_KEY);
return {
apiKey: API_KEY,
'X-Hmac-Auth-Signature': signature,
'X-Hmac-Auth-Timestamp': timestamp,
'X-Hmac-Auth-Nonce': nonce,
'X-Hmac-Auth-Version': API_VERSION,
'X-Hmac-Auth-IP': getIPAddress(),
'X-Hmac-Auth-MAC': macAddress(),
};
}
// 宜搭接口
async baseServe(bodyParams, yidaUrl) {
const { appType, systemToken, managerDingUserId: userId } = await this.getInfo(bodyParams.appType);
const { pre_url } = this.ctx.helper;
const serverUrl = `${pre_url}${yidaUrl}`;
const paramsObj = {
appType,
systemToken,
userId,
...bodyParams,
};
if (bodyParams.updateFormDataJson) {
paramsObj.updateFormDataJson = JSON.stringify(bodyParams.updateFormDataJson);
}
if (bodyParams.searchFieldJson) {
paramsObj.searchFieldJson = JSON.stringify(bodyParams.searchFieldJson);
}
const url_params = orderParams(paramsObj);
const headers = this.getHeader({ url_info: yidaUrl, url_params });
const res = await this.ctx.curl(`${serverUrl}`, {
method: 'POST',
headers,
data: paramsObj,
dataType: 'json',
});
return res;
}
async find(body) {
const res = await this.baseServe(body, '/yida_vpc/form/searchFormDatas.json');
return res;
}
// 更新表单
async updateForm(body) {
const res = await this.baseServe(body, '/yida_vpc/form/updateFormData.json');
return res;
}
}
module.exports = YiDaService;
2022-02-10 更新
经过测试,标注部分计算结果是一致的
即 .digest('base64')
与Buffer.from(digest).toString('base64')
效果一致
- 本文链接:https://harry-qi.github.io/2021/11/03/%E9%92%89%E9%92%89%E5%92%8C%E5%AE%9C%E6%90%AD%E7%9A%84%E7%AD%BE%E5%90%8D%E7%AE%97%E6%B3%95/
- 版权声明:本博客所有文章除特别声明外,均默认采用 许可协议。