乐趣区

关于javascript:组员老是忘记打卡我开发了一款小工具让全组三个月全勤

大家好,我是杨胜利。

我司应用钉钉考勤打卡,人事要求的比拟严格,两次未打卡记缺勤一天。但咱们组醉心于工作,老是上下班遗记打卡,每月的工资被扣到肉疼。

开始的时候咱们都设置了一个打卡闹铃,上班后准时揭示,但有的时候加班,加完班回家又遗记打卡了。还有的时候迷之自信的认为本人打卡了,第二天看考勤记录发现没打卡。

为了彻底解决这个问题,守住咱们的钱袋子,我开发了一款打卡揭示工具,让全组间断三个月全勤!

上面介绍一下,这个小工具是如何实现的。

小工具实现思路

首先思考一下:闹铃揭示为什么不能百分之百有用?

  1. 机械的揭示

闹铃揭示很机械,每天一个点固定揭示,工夫久了人就会免疫。就像起床闹铃用久了,缓缓的那个声音对你不起作用了,此时不得不换个铃声才行。

  1. 不能反复揭示

闹铃只会在固定工夫揭示一次,没有方法判断是否打卡,更不会智能地发现你没有打卡,再揭示一次。

既然闹铃做不到,那咱们就用程序来实现吧。依照上述两个起因,咱们要实现的揭示工具必须蕴含两个性能:

  1. 检测用户是否打卡,未打卡则揭示,已打卡不揭示。
  2. 对未打卡用户循环检测,反复揭示,直到打卡为止。

如果能实现这两个性能,那么遗记打卡的问题多半也就解决了。

打卡数据须要从钉钉获取,并且钉钉有推送性能。因而咱们的计划是:利用 Node.js + 钉钉 API 来实现打卡状态检测和精准的揭示推送。

意识钉钉 API

钉钉是企业版的即时通讯软件。与微信最大的区别是,它提供了凋谢能力,能够用 API 来实现创立群组,发送音讯等性能,这象征使着用钉钉能够实现高度定制的通信能力。

咱们这里用到的钉钉 API 次要有以下几个:

  • 获取凭证
  • 获取用户 ID
  • 查看打卡状态
  • 群内音讯推送
  • @某人推送

在应用钉钉 API 之前,首先要确认有公司级别的钉钉账号(应用过钉钉打卡性能个别就有公司账号),前面的步骤都是在这个账号下实现。

申请开放平台利用

钉钉开发第一步,先去钉钉开放平台申请一个利用,拿到 appKey 和 appSecret。

钉钉开放平台地址:https://open.dingtalk.com/dev…

进入平台后,点击“开发者后盾”,如下图:

开发者后盾就是治理本人开发的钉钉利用的中央,进入后抉择“利用开发 -> 企业外部开发”,如下图:

进入这个页面可能提醒暂无权限,这是因为开发企业钉钉利用须要开发者权限,这个权限须要管理员在后盾增加。

管理员加开发者权限形式:
进入 OA 治理后盾,抉择设置 - 权限治理 - 治理组 - 增加开发者权限下的对应权限。

进入之后,抉择【创立利用 -> H5 微利用】,依据提醒创立利用。创立之后在【利用信息】中能够看到两个关键字段:

  • AppKey
  • AppSecret

这两个字段十分重要,获取接口调用凭证时须要将它们作为参数传递。AppKey 是企业外部利用的惟一身份标识,AppSecret 是对应的调用密钥。

搭建服务端利用

钉钉 API 须要在服务端调用。也就是说,咱们须要搭建一个服务端利用来申请钉钉 API。

切记不能够在客户端间接调用钉钉 API,因为 AppKey 和 AppSecret 都是窃密的,绝不能够间接裸露在客户端。

咱们应用 Node.js 的 Express 框架来搭建一个简略的服务端利用,在这个利用上与钉钉 API 交互。搭建好的 Express 目录构造如下:

|-- app.js // 入口文件
|-- catch // 缓存目录
|-- router // 路由目录
|   |-- ding.js // 钉钉路由
|-- utils // 工具目录
|   |-- token.js // token 相干 

app.js 是入口文件,也是利用外围逻辑,代码简略书写如下:

const express = require('express');
const app = express();
const bodyParser = require('body-parser');
const cors = require('cors');

app.use(bodyParser.json());
app.use(cors());

// 路由配置
app.use('/ding', require('./router/ding'));

// 捕捉 404
app.use((req, res, next) => {res.status(404).send('Not Found');
});

// 捕捉异样
app.use((err, req, res, next) => {console.error(err);
  res.status(err.status || 500).send(err.inner || err.stack);
});

app.listen(8080, () => {console.log(`listen to http://localhost:8080`);
});

另一个 router/ding.js 文件是 Express 规范的路由文件,在这里编写钉钉 API 的相干逻辑,代码根底构造如下:

// router/ding.js
var express = require('express');
var router = express.Router();

router.get('/', (req, res, next) => {res.send('钉钉 API');
});

module.exports = router;

当初将利用运行起来:

$ node app.js

而后拜访 http://localhost:8080/ding,浏览器页面显示出“钉钉 API”几个字,示意运行胜利。

对接钉钉利用

一个简略的服务端利用搭建好之后,就能够筹备接入钉钉 API 了。

接入步骤参考开发文档,文档地址在这里。

1. 获取 API 调用凭证

钉钉 API 须要验证权限才能够调用。验证权限的形式是,依据上一步拿到的 AppKey 和 AppSecret 获取一个 access_token,这个 access_token 就是钉钉 API 的调用凭证。

后续在调用其余 API 时,只有携带 access_token 即可验证权限。

钉钉 API 分为新版和旧版两个版本,为了兼容性咱们应用旧版。旧版 API 的 URL 根门路是 https://oapi.dingtalk.com,下文用 baseURL 这个变量代替。

依据文档,获取 access_token 的接口是 ${baseURL}/gettoken。在 utils/ding.js 文件中定义一个获取 token 的办法,应用 GET 申请获取 access_token,代码如下:

const fetchToken = async () => {
  try {
    let params = {
      appkey: 'xxx',
      appsecret: 'xxx',
    };
    let url = `${baseURL}/gettoken`;
    let result = await axios.get(url, { params});
    if (result.data.errcode != 0) {throw result.data;} else {return result.data;}
  } catch (error) {console.log(error);
  }
};

上述代码写好之后,就能够调用 fetchToken 函数获取 access_token 了。

获取到 access_token 之后须要长久化的存储起来供后续应用。在浏览器端,咱们能够保留在 localStorage 中,而在 Node.js 端,最简略的办法是间接保留在文件中。

写一个将 access_token 保留为文件,并且可读取的类,代码如下:

var fs = require('fs');
var path = require('path');

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 并存储:

var res = await fetchToken();
if (res) {new DingToken().set(res.access_token);
}

在上面的接口调用时,就能够通过 new DingToken().get() 来获取到 access_token 了。

2. 查找组员 ID

有了 access_token 之后,第一个调用的钉钉 API 是获取员工的 userid。userid 是员工在钉钉中的惟一标识。

有了 userid 之后,咱们才能够获取组员对应的打卡状态。最简略的办法是通过手机号获取员工的 userid,手机号能够间接在钉钉上查到。

依据手机号查问用户文档在这里。

接口调用代码如下:

let access_token = new DingToken().get();
let params = {access_token,};
axios
  .post(`${baseURL}/topapi/v2/user/getbymobile`,
    {mobile: 'xxx', // 用户手机号},
    {params},
  )
  .then((res) => {console.log(res);
  });

通过下面申请办法,一一获取所有组员的 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 点到 24 点。

userIdList 就是咱们上一步取到的所有组员的 userid 列表。

将获取打卡状态写为一个独自的办法,代码如下:

const dayjs = require('dayjs');
const access_token = new DingToken().get();

// 获取打卡状态
const getAttendStatus = (userIdList) => {
  let params = {access_token,};
  let body = {workDateFrom: dayjs().startOf('day').format('YYYY-MM-DD HH:mm:ss'),
    workDateTo: dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss'),
    userIdList, // userid 列表
    offset: 0,
    limit: 40,
  };
  return axios.post(`${baseURL}/attendance/list`, body, {params});
};

查问考勤状态的返回后果是一个列表,列表项的关键字段如下:

  • userId:打卡人的用户 ID。
  • userCheckTime:用户理论打卡工夫。
  • timeResult:用户打卡后果。Normal:失常,NotSigned:未打卡。
  • checkType:考勤类型。OnDuty:下班,OffDuty:上班。

其余更多字段的含意请参考文档

下面的 4 个字段能够轻松判断出谁应该打卡,打卡是否失常,这样咱们就能筛选出没有打卡的用户,对这些未打卡的用户精准揭示。

筛选打卡状态分为两种状况:

  • 下班打卡
  • 上班打卡

上下班打卡要筛选不同的返回数据。假如获取的打卡数据存储在变量 attendList 中,获取形式如下:

// 获取下班打卡记录
const getOnUids = () =>
  attendList
    .filter((row) => row.checkType == 'OnDuty')
    .map((row) => row.userId);

// 获取上班打卡记录
const getOffUids = () =>
  attendList
    .filter((row) => row.checkType == 'OffDut')
    .map((row) => row.userId);

获取到已打卡的用户,接着找到未打卡用户,就能够发送告诉揭示了。

4. 发送揭示告诉

在钉钉中最罕用的音讯推送形式是:在群聊中增加一个机器人,向这个机器人的 webhook 地址发送音讯,即可实现自定义推送。

还是进入后面创立的 H5 利用,在菜单中找到【利用性能 -> 音讯推送 -> 机器人】,依据提醒配置好机器人。

创立好机器人后,关上组员所在的钉钉群(已有群或新建群都可)。点击【群设置 -> 智能群助手 -> 增加机器人】,抉择方才创立的机器人,就能够将机器人绑定在群里了。

绑定机器人后,点击机器人设置,会看到一个 Webhook 地址,申请这个地址即可向群聊发送音讯。对应的 API 如下:

API 地址:${baseURL}/robot/send?access_token=xxx
申请办法:POST

当初发送一条“我是打卡机器人”,实现代码如下:

const sendNotify = (msg, atuids = []) => {
  let access_token = 'xxx'; // Webhook 地址上的 access_token
  // 音讯模版配置
  let infos = {
    msgtype: 'text',
    text: {content: msg,},
    at: {atUserIds: atuids,},
  };
  // API 发送音讯
  axios.post(`${baseURL}/robot/send`, infos, {params: { access_token},
  });
};
sendNotify('我是打卡机器人');

解释一下:代码中的 atUserIds 属性示意要 @ 的用户,它的值是一个 userid 数组,能够 @ 群里的某几个成员,这样音讯推送就会更精准。

发送之后会在钉钉群收到音讯,成果如下:

综合代码实现

后面几步创立了钉钉利用,获取了打卡状态,并用机器人发送了群告诉。当初将这些性能联合起来,写一个查看考勤状态,并对未打卡用户发送揭示的接口。

在路由文件 router/ding.js 中创立一个路由办法实现这个性能:

var dayjs = require('dayjs');

router.post('/attend-send', async (req, res, next) => {
  try {
    // 须要检测打卡的 userid 数组
    let alluids = ["xxx", "xxxx"];
    // 获取打卡状态
    let attendList = await getAttendStatus(alluids);
    // 是否 9 点前(上班时间)let isOnDuty = dayjs().isBefore(dayjs().hour(9).minute(0));
    // 是否 18 点后(下班时间)let isOffDuty = dayjs().isAfter(dayjs().hour(18).minute(0));
    if (isOnDuty) {
      // 已打卡用户
      let uids = getOnUids(attendList);
      if (alluids.length > uids.length) {
        // 未打卡用户
        let txuids = alluids.filter((r) => !uids.includes(r));
        sendNotify("下班没打卡,小心扣钱!", txuids);
      }
    } else if (isOffDuty) {
      // 已打卡用户
      let uids = getOffUids(attendList);
      if (alluids.length > uids.length) {
        // 未打卡用户
        let txuids = alluids.filter((r) => !uids.includes(r));
        sendNotify("上班没打卡,小心扣钱!", txuids);
      }
    } else {return res.send("不在打卡工夫");
    }
    res.send("没有未打卡的同学");
  } catch (error) {res.status(error.status || 500).send(error);
  }
});

上述接口写好之后,咱们只须要调用一下这个接口,就能实现自动检测下班或上班的打卡状况。如果有未打卡的组员,那么机器人会在群里发告诉揭示,并且 @ 未打卡的组员。

# 调用接口
$ curl -X POST http://localhost:8080/ding/attend-send

查看打卡状态并揭示的性能实现了,当初还差一个”循环揭示“性能。

循环揭示的实现思路是,在某个时间段内,每隔几分钟调用一次接口。如果检测到未打卡的状态,就会循环揭示。

假如上下班工夫别离是上午 9 点和下午 18 点,那么检测的时间段能够划分为:

  • 下班:8:30-9:00 之间,每 5 分钟检测一次;
  • 上班:18:00-19:00 之间,每 10 分钟检测一次;

下班打卡绝对比拟紧急,所以工夫检测短,频率高。上班打卡绝对比拟宽松,下班时间也不固定,因而检测时间长,频率低一些。

确定好检测规定之后,咱们应用 Linux 的定时工作 crontab 来实现上述性能。

首先将下面写好的 Node.js 代码部署到 Linux 服务器,部署后可在 Linux 外部调用接口。

crontab 配置解析

简略说一下 crontab 定时工作如何配置。它的配置形式是一行一个工作,每行的配置字段如下:

// 别离示意:分钟、小时、天、月、周、要执行的命令
minute hour day month weekday cmd

每个字段用具体的数字示意,如果要全副匹配,则用 * 示意。下班打卡检测的配置如下:

29-59/5 8 * * 1-5 curl -X POST http://localhost:8080/ding/attend-send

下面的 29-59/5 8 示意在 8:29 到 8:59 之间,每 5 分钟执行一次;1-5 示意周一到周五,这样就配置好了。

同样的情理,上班打卡检测的配置如下:

*/10 18-19 * * 1-5 curl -X POST http://localhost:8080/ding/attend-send

在 Linux 中执行 crontab -e 关上编辑页面,写入下面的两个配置并保留,而后查看是否失效:

$ crontab -l
29-59/5 8 * * 1-5 curl -X POST http://localhost:8080/ding/attend-send
*/10 18-19 * * 1-5 curl -X POST http://localhost:8080/ding/attend-send

看到上述输入,示意定时工作创立胜利。

当初每天下班前和上班后,小工具会自动检测组员的打卡状态并循环揭示。最终成果如下:

总结

这个小工具是基于钉钉 API + Node.js 实现,思路比拟有意思,解决了理论问题。并且这个小我的项目非常适合学习 Node.js,代码精简洁净,易于了解和浏览。

小我的项目曾经开源,开源地址为:

https://github.com/ruidoc/att…

欢送大家 start,感激。

退出移动版