Skip to content

企业微信踩坑记录

Published: at 01:05 PM

第一次接触,一步一个坑,一个都没落的踩完了,记录一波~~

项目背景:企业微信接入h5网页应用,获取群ID跟userID。

步骤:1.先看文档,如何接入JS-SDK

参考文档:https://open.work.weixin.qq.com/api/doc/90000/90136/90512

2.跟着步骤走,一来就有一行红字,自己不看,踩坑别怪我

所有的JS接口只能在企业微信应用的可信域名下调用(包括子域名),且可信域名必须有ICP备案且在管理端验证域名归属

看了,跟着步骤乖乖走,坑还是来了:设置可信域名 在这里插入图片描述 点进去 炸一看,鬼知道要填啥域名。

先把检验文件下载下来, 放在项目的根目录下面(/public/文件),需要保证浏览器能访问到,于是我就自己搞了个代理,全局安装一下http-sever 命令: npm install -g http-sever 然后给项目打个包到dist文件,启动服务:http-server ./dist -p 10088
(-p是设置端口号,自己随便设置) 启起来之后长这个样子 在这里插入图片描述 然后要外部能访问到这个文件,搞个内网穿透: 我用的是花生壳,添加映射: 在这里插入图片描述 这里的ip就是你http-server启动的Ip,外网域名用默认的就行。 完了之后有个可访问的地址,访问一下能访问到认证文件就行。访问不到 看看是不是你Ip配错了 在这里插入图片描述 完事之后回到企业微信后台把可信域名设置成刚映射出来的地址点确定(两个填成一样的就行) 在这里插入图片描述 可信域名配置完之后 ,到企业微信去构造网页授权: 在这里插入图片描述 把redirect-url设置成刚刚可访问的可信域名+应用页面的路由:

在这里插入图片描述 完事之后,微信打开改路径,能访问就成功了

  1. 配置应用入口,配置到聊天工具栏就行。(自定义注意路径)在这里插入图片描述

  2. 这些都完之后,就是接入SDK,获取code,拿到userId。跟着文档走就是了。这里只记录踩的坑,不赘述。 ( https://open.work.weixin.qq.com/api/doc/90000/90136/90514)

  3. 拿到群ID,必须要配置agentConfig,在wx.ready中配置,配置成功config之后才能配置agentConfig

建议把token存入缓存里去取,过期了再重新调用接口。

const ACCESSKEY = "access_token";
const storageToken = Taro.getStorageSync(ACCESSKEY);
let assess_token = "";
let jsapiTicket = "";
let agentJsapiTicket = "";
// 从缓存获取token
if (storageToken) {
  const { accessToken, accessTokenExpireTime, accessTimeStamp } = storageToken;
  const NOWTIME = new Date().getTime();
  const isExpier = NOWTIME - accessTimeStamp > accessTokenExpireTime;
  assess_token = isExpier ? "" : accessToken;
  // assess_token存在且没有过期
  if (!isExpier) {
    const STORAGEOBJ = Taro.getStorageSync("storageObj");
    jsapiTicket = STORAGEOBJ.jsapiTicket;
    agentJsapiTicket = STORAGEOBJ.agentJsapiTicket;
  }
}

if (!assess_token) {
  let storageObj = {};
  const corpId = "ww687ede74265ce79f"; // 测试
  const corpSecrect = "B64Gavrk2G-l-zF5nssHkhJsvJi5r_OlUae0GWP1TjI"; // 测试
  // 获取access_token
  await fetch(
    `/wx-api/cgi-bin/gettoken?corpid=${corpId}&corpsecret=${corpSecrect}`
  )
    .then(res => res.json())
    .then(datas => {
      assess_token = datas.access_token;
      storageObj[ACCESSKEY] = datas.access_token;
    });
  // 获取企业的jsapi_ticket
  await fetch(`/wx-api/cgi-bin/get_jsapi_ticket?access_token=${assess_token}`)
    .then(res => res.json())
    .then(datas => {
      jsapiTicket = datas.ticket;
      storageObj[jsapiTicket] = datas.ticket;
    });
  // 获取应用的jsapi_ticket
  await fetch(
    `/wx-api/cgi-bin/ticket/get?access_token=${assess_token}&type=agent_config`
  )
    .then(res => res.json())
    .then(datas => {
      agentJsapiTicket = datas.ticket;
      storageObj[agentJsapiTicket] = datas.ticket;
    });
  Taro.setStorageSync("storageObj", storageObj);
}

生成签名算法,跟着文档要求来,没踩坑,贴一下代码:

// 生成签名算法
const nonceStr = uuidv4().replace(/-/g, "");
const timestamp = new Date().getTime();
// 忠于文档!!! 顺序一定不要乱
const signature = sha1(
  `jsapi_ticket=${jsapiTicket}&noncestr=${nonceStr}&timestamp=${timestamp}&url=${
    window.location.href.split("#")[0]
  }`
);
const jsApiList = [
  "agentConfig", // 使用到的接口api一定要在这里配置,没使用的别乱加
  "getCurExternalContact",
  "getCurExternalChat",
];
const agentSignature = sha1(
  `jsapi_ticket=${agentJsapiTicket}&noncestr=${nonceStr}&timestamp=${timestamp}&url=${
    window.location.href.split("#")[0]
  }`
);

window.wx.config({
  beta: true,
  debug: false,
  appId: "设置应用那里去找自己的appId",
  timestamp,
  nonceStr,
  signature,
  jsApiList,
});

要在ready函数中配置agentconfig,注意,agentconfig票据的获取跟config的不一样,忠于文档~

window.wx.ready(() => {
  // 判断当前客户端版本是否支持指定JS接口
  window.wx.checkJsApi({
    jsApiList: ["agentConfig", "getCurExternalContact", "getCurExternalChat"],
    success: () => {
      // 通过agentConfig注入应用的权限
      window.wx.agentConfig({
        corpid: "", // 必填,企业微信的corpid,必须与当前登录的企业一致
        agentid: "", // 必填,企业微信的应用id (e.g. 1000247)
        timestamp,
        nonceStr,
        signature: agentSignature,
        jsApiList: [
          "getContext",
          "getCurExternalContact",
          "getCurExternalChat",
        ], //必填
        success: () => {
          // 获取入口环境
          window.wx.invoke("getContext", {}, function (res) {
            // 如果是群入口,则获取群id
            if (
              res.err_msg == "getContext:ok" &&
              res.entry == "group_chat_tools"
            ) {
              window.wx.invoke("getCurExternalChat", {}, function (chartData) {
                if (chartData.err_msg == "getCurExternalChat:ok") {
                  Taro.setStorageSync("chatId", chartData.chatId);
                } else {
                  //错误处理
                  Taro.showToast({
                    title: "群ID获取失败",
                    icon: "none",
                    duration: 2000,
                  });
                }
              });
            }
            // 如果是单入口,则获取客户id
            if (
              res.err_msg == "getContext:ok" &&
              res.entry == "single_chat_tools"
            ) {
              window.wx.invoke(
                "getCurExternalContact",
                {},
                function (singleData) {
                  if (singleData.err_msg == "getCurExternalContact:ok") {
                    Taro.setStorageSync("userId", singleData.userId);
                  } else {
                    //错误处理
                    Taro.showToast({
                      title: "用户ID获取失败",
                      icon: "none",
                      duration: 2000,
                    });
                  }
                }
              );
            }
          });
        },
        fail: res => {
          if (res.errMsg.indexOf("function not exist") > -1) {
            Taro.showToast({
              title: "版本过低请升级",
              icon: "none",
              duration: 3000,
            });
          }
        },
      });
    },
  });
});

到这里好像就完事了…做完了回头看,感觉还行,没那么复杂。做的时候真的是头皮发麻,一定一定要忠于文档。

最后show一下获取code的方法:(MDN有api可以直接用:URLSearchParams)

export const urlResolve = (): any => {
  let url = window.location.search; //获取url中"?"符后的字符串
  let theRequest = new Object(); //声明一个对象
  if (url.indexOf('?') != -1) {
    let str = url.substr(1);
    let strs = str.split('&');
    for (let i = 0; i < strs.length; i++) {
      theRequest[strs[i].split('=')[0]] = strs[i].split('=')[1];
    }
  }
  return theRequest;
};

// 使用
urlResolve().code;

补充:

1.手机上调试企业微信不太好调试,推荐vconsole,使用方法很简单,打开即用; 2.vconsole看不到发起请求,所以再推荐一个抓包的应用 HttpCanary, 手机应用下载即可。

再补充个后续bug: 在添加群应用之后,从群入口点击进去,第一次确实能拿到code;当切换到B应用再切换回去的时候,这时获取到的还是相同的code。由于企业微信后台的规则,code只能使用一次,于是乎 恭喜我 喜提bug。

解决办法:通过第一次获取到的code去获取当前用户的userId,拿到之后并存在sessionStorage里面。在群应用不停切换应用,当前用户肯定不会发生改变,即获取到的也是当前用户的信息;当退出当前群应用窗口或者切换群的时候缓存失效,再次打开 重新获取新的code,新的当前userId.

简单来说:在入口获取到code,就通过code去拿到唯一的userId,只要还是这个用户在操作(不关闭应用窗口,随便你切换)就使用userId,只有退出且重新进入才会获取新的code,此时的code最新且有效。