由于钉钉和宜搭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)}&timestamp=${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 更新
经过测试,标注部分计算结果是一致的
22
.digest('base64')Buffer.from(digest).toString('base64')效果一致