大家好,我是杨成功。我公司使用钉钉签到,人员要求比较严格。两次未签到,按缺席1天计算。但是我们的团队工作比较沉迷,上下班的时候总是忘记签到,每个月的工资都被扣到肉里。一开始,我们都设置了签到闹钟,下班后准时提醒,但有时我们加班,加班到家忘记签到了。有时候我很自信签到了,但是第二天我看考勤记录发现我没有签到。为了彻底解决这个问题,保住我们的钱袋子,我开发了一个签到提醒工具,让全队连续三个月全勤!下面介绍一下这个小工具是如何实现的。小工具的实现思路首先想一想:闹钟提醒为什么不能100%有用?机械提醒闹钟提醒很机械,每天固定在一个时间点提醒,时间久了人就会免疫。就好像闹钟用了很久,渐渐的声音对你不起作用了,这时候就得换铃声了。无法重复提醒闹钟只会在固定时间提醒一次,没有办法判断你是否签到过,更不会智能发现你没有签到,再次提醒。既然报警做不到,那我们就用一个程序来实现吧。根据以上两点原因,我们要实现的提醒工具必须包含两个功能:检测用户是否登录,未登录提醒,已登录不提醒。循环检测用户没有签到,重复提醒,直到签到。如果能实现这两个功能,那么忘记签到的问题估计就解决了。签到数据需要从钉钉获取,钉钉有推送功能。因此,我们的解决方案是:使用Node.js+DingTalkAPI实现签到状态检测和精准提醒推送。认识钉钉API钉钉是一款企业版的即时通讯软件。与微信最大的不同在于它提供了开放的能力,可以通过API来实现创建群组、发送消息等功能,这意味着使用钉钉可以实现高度定制化的通信能力。我们这里使用的钉钉API主要有以下几种:获取凭证、获取用户ID、查看登录状态、群@someonepush推送消息在使用钉钉API之前,您首先要确认您拥有公司级钉钉账号(使用钉钉登录功能一般都有一个公司账号),后面的步骤都是在这个账号下实现的。申请开放平台APP钉钉开发第一步是去钉钉开放平台申请一个APP,获取appKey和appSecret。钉钉开放平台地址:https://open.dingtalk.com/dev...进入平台后,点击“开发者后台”,如下图:开发者后台是管理开发者开发的钉钉应用的地方你自己。进入后选择“应用开发->企业内部开发”,如下图:进入该页面可能会提示暂时没有权限。这是因为开发企业钉钉应用需要开发者权限,而这个权限需要管理员在后台添加。如何为开发者添加管理员权限:进入OA管理后台,选择设置-权限管理-管理群组-开发者权限下添加相应权限。进入后选择【创建应用->H5微应用】,按照提示创建应用。创建完成后,可以在【应用信息】中看到两个关键字段:AppKeyAppSecret这两个字段非常重要,需要在获取接口调用凭证时作为参数传入。AppKey是企业内部应用的唯一标识,AppSecret是对应的调用密钥。构建服务端应用,需要在服务端调用钉钉API。也就是说,我们需要搭建一个服务端应用来请求钉钉API。切记不要在客户端直接调用钉钉API,因为AppKey和AppSecret是保密的,不能直接暴露给客户端。我们使用Node.js的Express框架搭建一个简单的服务端应用,并在这个应用上与钉钉API进行交互。构建的Express目录结构如下:|--app.js//入口文件|--catch//缓存目录|--router//路由目录||--ding.js//钉钉路由|--utils//工具目录||--token.js//Token相关的app.js是应用的入口文件和核心逻辑。代码简单写成如下:constexpress=require('express');constapp=express();constbodyParser=require('body-parser');constcors=require('cors');app.use(bodyParser.json());app.use(cors());//路由配置app.use('/ding',require('./router/ding'));//捕获404app.use((req,res,next)=>{res.status(404).send('NotFound');});//捕获异常app.use((err,req,res,next)=>{console.error(err);res.status(err.status||500).send(err.inner||err.stack);});app.listen(8080,()=>{console.log(`监听http//localhost:8080`);});另一个router/ding.js文件是Express标准路由文件,这里写钉钉API的相关逻辑,代码基础如下://router/ding.jsvarexpress=require('express');varrouter=express.Router();router.get('/',(req,res,next)=>{res.send('钉钉API');});module.exports=路由器;现在运行应用程序:$nodeapp.js并访问http://localhost:8080/ding,浏览器页面显示“钉钉API”字样,表示钉钉应用连接成功。一个简单的服务端应用搭建完成后,就可以访问钉钉API了。接入步骤参考开发文档,文档地址在这里。1、获取API调用凭证钉钉API调用需要验证权限。验证权限的方式是根据上一步得到的AppKey和AppSecret得到一个access_token。这个access_token就是钉钉接口的调用凭证。后面调用其他API时,只要携带access_token就可以验证权限。钉钉API分为新旧版本两个版本。我们使用旧版本的兼容性。旧版本API的URL根路径为https://oapi.dingtalk.com,将替换为下面的变量baseURL。根据文档,获取access_token的接口是${baseURL}/gettoken。在utils/ding.js文件中定义一个token获取方法,使用GET请求获取access_token,代码如下:constfetchToken=async()=>{try{letparams={appkey:'xxx',appsecret:'xxx',};让url=`${baseURL}/gettoken`;让result=awaitaxios.get(url,{params});if(result.data.errcode!=0){抛出结果.data;}else{返回结果.data;}}catch(错误){console.log(错误);}};上面的代码写完之后,就可以调用fetchToken函数来获取access_token了。获取到access_token后,需要持久化保存,以备后续使用。在浏览器端,我们可以保存在localStorage中,而在Node.js端,最简单的方式就是直接保存在文件中。写一个类,将access_token保存为文件,可以读取。代码如下:varfs=require('fs');varpath=require('path');varcatch_dir=path.resolve(__dirname,'../','catch');classDingToken{get(){letres=fs.readFileSync(`${catch_dir}/ding_token.json`);返回res.toString()||无效的;}set(token){fs.writeFileSync(`${catch_dir}/ding_token.json`,token);}}写完之后,现在我们获取access_token并存储起来:通过newDingToken().get()获取。2.找到群成员ID有了access_token后,第一个调用的钉钉API就是获取员工的userid。userid是员工在钉钉的唯一标识。通过userid,我们可以获取群组成员对应的登录状态。最简单的方法是通过手机号获取员工的userid,直接在钉钉上查到。在此根据手机号码查询用户文档。接口调用代码如下:letaccess_token=newDingToken().get();让params={access_token,};axios.post(`${baseURL}/topapi/v2/user/getbymobile`,{mobile:'xxx',//用户手机号码},{params},).then((res)=>{console.日志(资源);});通过上面的请求方法,一一获取所有团队成员的userid并保存,我们一步一步做下面的使用。3.获取签到状态获取团队成员的userid列表,我们可以得到所有团队成员的签到状态。钉钉需要在H5应用中申请权限才能获取登录状态。打开之前创建的应用,点击【权限管理->考勤】,批量添加所有权限:然后进入【开发管理】,配置服务器出口IP。这个IP是指我们调用钉钉API的服务器IP地址。开发时可填写为127.0.0.1,部署后替换为真实IP地址。做好这些准备工作后,我们就可以获取签到状态了。获取签到状态的API如下:API地址:${baseURL}/attendance/list请求方式:POST该API的请求体是一个对象,该对象必须包含的属性如下:workDateFrom:查询考勤签到记录的起始工作日。workDateTo:查询考勤记录的结束工作日。userIdList:查询某个用户的用户ID列表。offset:数据起点,用于分页,传0即可。limit:获取考勤记录条数,最大50条。这里的字段都有解释。workDateFrom和workDateTo表示查询考勤的时间范围,因为我们只需要查询当天的数据,所以事件范围是当天的0:00到24:00。userIdList就是我们上一步得到的所有组成员的userid列表。将签到状态写成单独的方法,代码如下:constdayjs=require('dayjs');constaccess_token=newDingToken().get();//获取签到状态constgetAttendStatus=(userIdList)=>{letparams={access_token,};让body={workDateFrom:dayjs().startOf('day').format('YYYY-MM-DDHH:mm:ss'),workDateTo:dayjs().endOf('day').format('YYYY-MM-DDHH:mm:ss'),userIdList,//用户id列表offset:0,limit:40,};returnaxios.post(`${baseURL}/attendance/list`,body,{params});};查询考勤状态的返回结果是一个列表,列表项的关键字段如下:userId:签到人的用户ID。userCheckTime:用户实际签到时间。timeResult:用户登录结果。Normal:正常,NotSigned:未签到。checkType:考勤类型。OnDuty:上班,OffDuty:下班。其他字段的含义,请参考文档上方的4个字段,轻松判断哪些人应该签到,签到是否正常,以便我们过滤掉没有签到的用户,准确提醒这些用户未签到。筛选签到状态分为上班签到、下班签到、上班签到、下班签到、上班签到两种情况过滤不同的返回数据。假设获取到的打卡数据存储在变量attendList中,获取方式如下://获取打卡记录constgetOnUids=()=>attendList.filter((row)=>row.checkType=='OnDuty').map((row)=>row.userId);//获取下班签到记录constgetOffUids=()=>attendList.filter((row)=>row.checkType=='OffDut').map((row)=>row.userId);获取已登录的用户,再找到未登录的用户,即可发送通知提醒。4.发送提醒在钉钉中最常见的消息推送方式是:在群聊中添加机器人,向机器人的webhook地址发送消息,即可实现自定义推送。或者进入之前创建的H5应用,在菜单中找到【应用功能->消息推送->机器人】,根据提示配置机器人。机器人创建完成后,打开团队成员所在的钉钉群(现有群或新群均可)。点击【群组设置->智能群组助手->添加机器人】,选择刚刚创建的机器人,然后将机器人绑定到群组中。绑定机器人后,点击机器人设置,会看到一个Webhook地址,请求该地址向群聊发送消息。对应的API如下:API地址:${baseURL}/robot/send?access_token=xxx请求方式:POST现在发送一个“我是签到机器人”,实现代码如下:constsendNotify=(msg,atuids=[])=>{letaccess_token='xxx';//Webhook地址上的access_token//消息模板配置letinfos={msgtype:'text',text:{content:msg,},at:{atUserIds:atuids,},};//API发送消息axios.post(`${baseURL}/robot/send`,infos,{params:{access_token},});};sendNotify('我是签到机器人');说明:代码中atUserIds的atUserIds属性表示要@的用户,它的值是一个userids数组,可以@组内的某些成员,这样消息推送会更准确。发送后,消息会在钉钉群中收到,效果如下:综合代码实现了前面创建钉钉应用、获取签到状态、使用机器人发送群通知的步骤。现在结合这些功能写一个接口,查询考勤状态,给未签到的用户发送提醒。在路由文件router/ding.js中创建一个路由方法来实现这个功能:vardayjs=require('dayjs');router.post('/attend-send',async(req,res,next)=>{try{//需要检测签到的userid数组letalluids=["xxx","xxxx"];//获取签到状态letattendList=awaitgetAttendStatus(alluids);//是否在之前9点(上班时间)letisOnDuty=dayjs().isBefore(dayjs().hour(9).minute(0));//是否是18:00之后(下班时间)letisOffDuty=dayjs().isAfter(dayjs().hour(18).minute(0));if(isOnDuty){//登录用户letuids=getOnUids(attendList);if(alluids.length>uids.length){//unsigneduserslettxuids=alluids.filter((r)=>!uids.includes(r));sendNotify("上班没签到,小心扣钱!",txuids);}}elseif(isOffDuty){//登录用户letuids=getOffUids(attendList);if(alluids.length>uids.length){//未登录用户lettxuids=alluids.filter((r)=>!uids.includes(r));sendNotify("下班未签到,小心扣钱!",txuids);}}else{returnres.send("不在签到时间");}res.send("没有未签到的同学");}catch(error){res.status(error.status||500).send(error);}});上面的接口写完之后,我们只需要调用这个接口就可以自动检测上班或者下班的签到状态了。@未签名的团队成员。#调用接口$curl-XPOSThttp://localhost:8080/ding/attend-send已经实现了查看签到状态和提醒的功能,现在还有“循环提醒”的功能。循环提醒的思路是在一定时间内每隔几分钟调用一次接口。如果检测到未检查状态,将发出循环提醒。假设上下班时间分别为上午9:00和下午18:00,则检测时间段可分为:工作:8:30-9:00之间,每5分钟检测一次;下班时间:18:00-19:00之间,每10分钟检测一次;上班签到比较紧急,所以检测时间短,频率高。下班签到相对宽松,下班时间不固定,因此检测时间较长,频率较低。确定检测规则后,我们使用Linux的crontab来实现上述功能。首先,将上面编写的Node.js代码部署到Linux服务器上。部署完成后,即可在Linux内部调用该接口。crontab配置分析简单说说如何配置crontab定时任务。它的配置方式是每行一个任务,每行的配置字段如下://分别代表:分,时,日,月,周,要执行的命令minutehourdaymonthweekdaycmd每个字段表示按特定数字,如果要匹配所有,请使用*。工作中签到检测配置如下:29-59/58**1-5curl-XPOSThttp://localhost:8080/ding/attend-send上面29-59/58的意思8点29分到8点59分之间,每5分钟执行一次;1-5表示周一到周五,这样配置就完成了。同理,下班签到检测配置如下:*/1018-19**1-5curl-XPOSThttp://localhost:8080/ding/attend-send执行crontab-e在Linux下打开编辑页面,写入并保存以上两个配置,然后查看是否生效:$crontab-l29-59/58**1-5curl-XPOSThttp://localhost:8080/ding/attend-send*/1018-19**1-5curl-XPOSThttp://localhost:8080/ding/attend-send看到如上输出说明定时任务创建成功.现在每天上班前、下班后,小工具会自动检测团队成员的签到状态,并循环提醒。最终效果如下:综上所述,这个小工具是基于钉钉API+Node.js实现的,思路很有意思,解决了实际问题。而且这个小项目非常适合学习Node.js,代码简洁干净,易于理解和阅读。小项目已经开源,开源地址为:https://github.com/ruidoc/att...欢迎大家入手,谢谢。
