大家好,让打我是工具杨成功。 前面写了一篇文章,智能自动介绍了如何用 Node.js + 钉钉 API 实现考勤打卡连续提醒的点添小工具。 有的加请假过同学留言说为什么不直接调用钉钉 API 自动打卡(这个我也想过)。可惜我翻遍了钉钉的刷新文档都没有找到这个 API。 再说了,让打怎么可能有这个 API 呢?工具想啥呢? 还有的同学严厉的指出了问题:“我请假了你还一直提醒?token 用几个小时就过期!”。智能自动 针对这两个问题,点添我们在上次实现代码的加请假过基础上进行优化,添加两个逻辑: 接下来我们一起实现新增的需求,优化打卡功能。 使用钉钉 API 可以获取一些人员的打卡状态。 目前我们的做法是,将需要检测打卡状态的人员(我们全组人员)的 userid 维护在一个列表中,源码库然后获取到这些人的打卡数据,从而筛选出未打卡的人员。 特殊情况是,假设我们组的一个组员今天请假了,他会被当作未打卡人员不断地被提醒,这是不合理的。 其实我们应该将已请假的人员排除在外。要实现这个,第一步是要获取今日已请假的人员。 获取请假状态的 API 如下: API 地址:${ baseURL}/topapi/attendance/getleavestatus。 请求方法:POST。 这个 API 的请求体是一个对象,对象的属性如下: 将获取请假状态写为一个单独的方法,代码如下: const dayjs = require(dayjs); const access_token = new DingToken().get(); // 获取请假状态 const getLeaveStatus = async (userid_list) => { let params = { access_token, }; let body = { start_time: dayjs().startOf(day).valueOf(), end_time: dayjs().endOf(day).valueOf(), userid_list: userid_list.join(), // userid 列表 offset: 0, size: 20, }; let res = await axios.post(`${ baseURL}/topapi/attendance/getleavestatus`, body, { params }); if (res.errcode != 0) { return res; } else { return res.result.leave_status.map((row) => row.userid); } 执行以上方法后,就可以获取到当天已请假的用户。接着在所有需要检测打卡状态的用户列表中,过滤掉已请假的用户: // 需要检测打卡的 userid 数组 let alluids = [xxx, xxxx]; // 获取请假状态 let leaveRes = await getLeaveStatus(alluids); if (leaveRes.errcode) { return leaveRes; } alluids = alluids.filter((uid) => !leaveRes.includes(uid)); 这样就不会对已请假的用户发出提醒了。 在获取钉钉 API 时,首先要获取接口调用凭证(也就是 access_token),每个 API 调用时都要携带这个凭证。但这个凭证是有期限的,有效期一过 API 就会被禁止调用。 因此,这里非常重要的一个优化点,就是自动刷新 access_token。 怎么做呢?其实和在前端项目中实现一样,在 axios 的拦截器中判断 access_token 是否过期,如果过期则重新获取,然后继续执行请求。 首先,将获取凭证写成一个单独的云南idc服务商方法,如下: const fetchToken = async () => { try { let params = { appkey: xxx, appsecret: xxx, }; let url = https://oapi.dingtalk.com/gettoken; let result = await axios.get(url, { params }); if (result.data.errcode != 0) { throw result.data; } else { let token_str = JSON.stringify({ token: result.data.access_token, expire: Date.now() + result.data.expires_in * 1000, }); new DingToken().set(token_str); return token_str; } } catch (error) { console.log(error); } 这个方法主要是调用获取凭证的 API,调用成功后会返回 access_token 和有效时间。这里我们要设置一个过期时间,就是当前时间+有效时间,生成一个过期时间的时间戳: 这里还有一个 DingToken 类是用于获取和存储 access_token 的,代码如下: var fs = require(fs); var catch_dir = path.resolve(__dirname, ../, catch); class DingToken { get() { let res = fs.readFileSync(`${ catch_dir}/ding_token.json`); return res.toString() || null; } set(token) { fs.writeFileSync(`${ catch_dir}/ding_token.json`, token); } 将 access_token 和过期时间组成一个 JSON 字符串存储到文件中,接下来就可以在 axios 的请求拦截器中获取到这个 JSON 数据,然后判断当前时间是否大于过期时间。 如果是,则重新调用 fetchToken() 方法生成新 token,并继续执行请求。拦截器代码如下: const axios = require(axios); const instance = axios.create({ baseURL: https://oapi.dingtalk.com, timeout: 5000, }); const dingToken = new DingToken(); // 请求拦截器 instance.interceptors.request.use(async (config) => { if (!config.params.access_token) { let catoken = { }; if (dingToken.get()) { catoken = JSON.parse(dingToken.get()); // 判断是否过期 if (Date.now() - catoken.expire >= 0) { console.log(钉钉 token 过期); await fetchToken(); catoken = JSON.parse(dingToken.get()); } } else { // 第一次获取token await fetchToken(); catoken = JSON.parse(dingToken.get()); } // 将 token 携带至请求头 config.params.access_token = catoken.token; } return config; 通过上面在拦截器中编写的逻辑,我们就不需要关心 access_token 过期了。并且我们是在 token 过期之后才会重新请求,因此也不会触发调用频率限制。 本篇介绍了钉钉打卡小工具两个方面的优化,还有配置部分的代码我也做了精简,可以更快的接入自己的钉钉应用。过滤已请假人员
钉钉 token 自动刷新
总结