共计 3332 个字符,预计需要花费 9 分钟才能阅读完成。
大家好,我是杨胜利。
后面写了一篇文章,介绍了如何用 Node.js + 钉钉 API 实现考勤打卡间断揭示的小工具。有的同学留言说为什么不间接调用钉钉 API 主动打卡(这个我也想过)。惋惜我翻遍了钉钉的文档都没有找到这个 API。
再说了,怎么可能有这个 API 呢?想啥呢?
还有的同学严格的指出了问题:“我销假了你还始终揭示?token 用几个小时就过期!”。针对这两个问题,咱们在上次实现代码的根底上进行优化,增加两个逻辑:
- 获取未打卡的人员时,过滤已销假人员
- 当 token 过期时,主动刷新 token
如果没有看过上篇文章,请先看打卡小工具第一篇,源代码在 GitHub。
接下来咱们一起实现新增的需要,优化打卡性能。
过滤已销假人员
应用钉钉 API 能够获取一些人员的打卡状态。目前咱们的做法是,将须要检测打卡状态的人员(咱们全组人员)的 userid 保护在一个列表中,而后获取到这些人的打卡数据,从而筛选出未打卡的人员。
非凡状况是,假如咱们组的一个组员明天销假了,他会被当做未打卡人员进行一直揭示。其实咱们须要将已销假的人员排除在外,第一步是要获取今日已销假的人员。
获取销假状态的 API 如下:
API 地址:${baseURL}/topapi/attendance/getleavestatus
申请办法:POST
文档地址:这里
这个 API 的申请体是一个对象,对象的属性如下:
- userid_list:查问销假状态的 userid 列表
- start_time:查问开始工夫(当天上班时间)
- end_time:查问完结工夫(当天下班时间)
- size:返回条数,最大 20
- offset:分页,从 0 开始
将获取销假状态写为一个独自的办法,代码如下:
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)); | |
console.log(alluids); // 过滤后的 userid 数组 |
这样就不会对已销假的用户收回揭示了。
钉钉 token 主动刷新
在获取钉钉 API 时,首先要获取接口调用凭证(也就是 access_token),每个 API 调用时都要携带这个凭证。但这个凭证是有期限的,有效期一过 API 就会被禁止调用。
因而,这里十分重要的一个优化点,就是主动刷新 access_token。
怎么做呢?其实和在前端我的项目中实现一样,在 axios
的拦截器中判断 access_token 是否过期,如果过期则从新获取,而后继续执行申请。
首先,将获取凭证写成一个独自的办法,如下:
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 和无效工夫。这里咱们要设置一个过期工夫,就是 以后工夫 + 无效工夫
,生成一个过期工夫的工夫戳:
Date.now() + result.data.expires_in * 1000,
这里还有一个 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 过期之后才会从新申请,因而也不会触发调用频率限度。
总结
本篇介绍了钉钉打卡小工具两个方面的优化,还有配置局部的代码我也做了精简,能够更快的接入本人的钉钉利用。
本次代码曾经开源,地址在 GitHub,欢送大家尝试和 star。