kintone和钉钉、阿里云的整合

cybozu发表于:2019年07月15日 14:23:22更新于:2022年11月01日 23:44:04

Index

引言

钉钉在企业移动办公领域有着很高的占有率,但是可能大家都会觉得,他在企业定制化,数据分析等领域有着很大的短板。

而我们的kintone作为paas平台,可以补足这个短板。很多开发者想知道我们的kintone和钉钉还有阿里云这些服务,如何来做一个整合开发呢?

那下面我们就结合钉钉和kintone两者的API,来完成他们之间数据的整合吧。

课题

这次我们的课题是获取钉钉的打卡结果、签到、审批数据,并且同步到kintone。

钉钉篇

创建一个小程序

在钉钉开放平台,在“企业内部开发”下建立一个小程序:

 0015d2c0db44baac51d6104a20a7ef8

记下他的应用信息:

0015d2c0dc43ba85ac53b331d2b0d81

打开应用权限

在“接口权限”下根据自己的需求打开权限:

0015d2d60e3d8ec0926689fd813e624

0015d2c0fc69c87df9866d781b107fb


钉钉代码片段

API简介

钉钉开放了丰富的服务端接口,借助这些接口我们可以很轻松的抓取数据到kintone端来进行处理,分析和记录。
调用钉钉接口时,需使用HTTPS协议、JSON数据格式、UTF8编码,访问域名为https://oapi.dingtalk.com。POST请求请在HTTP Header中设置 Content-Type:application/json。

调用钉钉的API前需要先获取access_token令牌,通过access_token才能调用钉钉的其他业务的API。

获取access_token

请求方式:GET

请求地址:https://oapi.dingtalk.com/gettoken?appkey=key&appsecret=secret

0015d2c1c7cb24d37aacd21c0d77bcd

例:利用access_token获取打卡详情

0015d2d6b91183f35a603df744d783f

以下是示例代码:
package d2;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import com.dingtalk.api.DefaultDingTalkClient;
import com.dingtalk.api.DingTalkClient;
import com.dingtalk.api.request.OapiAttendanceListRequest;
import com.dingtalk.api.response.OapiAttendanceListResponse;
import com.dingtalk.api.response.OapiUserListbypageResponse.Userlist;
import com.dingtalk.api.response.OapiAttendanceListResponse.Recordresult;
import cn.cybozu.api.KintoneRecord;
public class D2TimeCardResultConnector extends D2Connector
{
    private static final int LIMIT_USER_SIZE = 7;
    private static final long LIMIT_TIMECARD_SIZE = 50;
    private Map<String, Userlist> users;
    public D2TimeCardResultConnector(String appKey, String appSecret, String domain, long appId, long spaceId, String apiToken)
    {
        super(appKey, appSecret, domain, appId, spaceId, apiToken);
    }
    public void execute(Date startDate, Date endDate)
        throws Exception
    {
        users = getAllUsers();
        int count = users.size();
        deleteTimeCardResult(startDate, endDate);
        List<String> userIds = new ArrayList<>();
        for(String id: users.keySet())
        {
            userIds.add(id);
            if(userIds.size() == count)
            {
                getTimeCardResult(userIds, startDate, endDate);
                userIds.clear();
            }
        }
        if(userIds.size() != 0)
        {
            getTimeCardResult(userIds, startDate, endDate);
        }
    }
    /**
    * 结果的删除
    * @throws Exception
    */
    private void deleteTimeCardResult(Date startDate, Date endDate)
        throws Exception
    {
        String query = "workDate >= \"" + formatDate(startDate) + "\" and workDate <= \"" + formatDate(endDate) + "\"";
        List<KintoneRecord> recs = getRecordsFromKintone(query, new String[] {"$id"});
        if(recs.size() == 0)
        {
            return;
        }
        deleteRecordsToKintone(recs);
    }
    private void getTimeCardResult(List<String> userIds, Date startDate, Date endDate)
        throws Exception
    {
        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/attendance/list");
        OapiAttendanceListRequest request = new OapiAttendanceListRequest();
        request.setUserIdList(userIds);
        request.setLimit(LIMIT_TIMECARD_SIZE);
        Date tmpDate = (Date)startDate.clone();
        while(tmpDate.compareTo(endDate) <= 0)
        {
            request.setWorkDateFrom(formatDate(tmpDate) + " 00:00:00");
            request.setWorkDateTo(formatDate(tmpDate) + " 00:00:00");
            long offset = 0;
            for(;;)
            {
                request.setOffset(offset);
                OapiAttendanceListResponse response = client.execute(request, getAccessToken());
                if(response.getErrcode() != 0)
                {
                    throw new Exception(response.getErrmsg());
                }
                List<Recordresult> list = response.getRecordresult();
                if(list.size() == 0)
                {
                    break;
                }
                offset += LIMIT_TIMECARD_SIZE;
                copyRecords(list);
                if(!response.getHasMore())
                {
                    break;
                }
            }
            tmpDate = new Date(tmpDate.getTime() +  TimeUnit.DAYS.toMillis(1));
        }
    }
    private void copyRecords(List<Recordresult> results)
        throws Exception
    {
        List<KintoneRecord> postRecs = new ArrayList<>();
        for(Recordresult result : results)
        {
            KintoneRecord rec = new KintoneRecord();
            String id = "" + result.getId();
            rec.setString("id", id);
            rec.setString("groupId", result.getGroupId() == null ? null : "" + result.getGroupId());
            rec.setString("planId", result.getPlanId() == null ? null : "" + result.getPlanId());
            rec.setString("recordId", result.getRecordId() == null ? null : "" + result.getRecordId());
            rec.setString("workDate", formatDate(result.getWorkDate()));
            rec.setString("userId", result.getUserId());
            rec.setString("checkType", result.getCheckType());
            rec.setString("timeResult", result.getTimeResult());
            rec.setString("locationResult", result.getLocationResult());
            rec.setString("sourceType", result.getSourceType());
            rec.setString("approveId", result.getApproveId() == null ? null : "" + result.getApproveId());
            rec.setString("procInstId", result.getProcInstId() == null ? null : "" + result.getProcInstId());
            rec.setString("baseCheckTime", formatDateTime(result.getBaseCheckTime()));
            rec.setString("userCheckTime", formatDateTime(result.getUserCheckTime()));
            Userlist user = users.get(result.getUserId());
            if(user != null)
            {
                rec.setString("userName", user.getName());
                rec.setString("userNo", user.getJobnumber());
            }
            else
            {
                rec.setString("userName", null);
                rec.setString("userNo", null);
            }
            postRecs.add(rec);
        }
        postRecordsToKintone(postRecs);
    }
    /**
    * @param args
    * @throws Exception
    */
    public static void main(String[] args)
        throws Exception
    {
        String appKey = "xxxxxxxxxxxxxxxxx";
        String appSecret = "xxxxxxxxxxxxxxxxx";
        String domain = "xxxxxx.cybozu.cn";
        long appId = 116;
        long spaceId = 0;
        String apiToken = "xxxxxxxxxxxxxxxxx";
        //-------------------------------------------------------------------------------------------
        if(args.length < 2)
        {
            System.err.println("args[0]: start date(yyyy-mm-dd). args[1]: end date(yyyy-mm-dd).");
            System.exit(-1);
        }
        //modify
        D2TimeCardResultConnector qyd2 = new D2TimeCardResultConnector(appKey, appSecret, domain, appId, spaceId, apiToken);
        qyd2.execute(D2Connector.parseDate(args[0]), D2Connector.parseDate(args[1]));
        System.out.println("success");
    }
}

其中appKey,appSecret请按照实际注册的钉钉小程序里的内容来填写。

domain,appId,spaceId,apiToken   则是你kintone端的实际信息。

所有API文档请参见钉钉官网:https://open-doc.dingtalk.com/microapp/serverapi2

demo代码里提供了获取打卡结果,签到,审批这3个功能的sample。具体更多的功能,大家可以参照钉钉和kintone的开发文档来自己完成。

文件结构

0015d2d2fa78d64f72dbe4929aa14ee

以下是cybozush和d2-kintone-sample所加载的外部jars包

0015d2d60b13391e49290659a4f2ed8

0015d2d60b14a19b914964187be5623

导出成jar包

0015d2d859a989980e6fbb5fc885f24

0015d2d859c596117170bd0757969ed

kintone篇

导入应用

通过导入模板文件创建 => 选择D2 sample.zip。

0015d2c52f202e5c371876093245014

0015d2c52f26815c62816de2d12a37c

0015d2c52f3430093b670ac0cbb5cbc

导入之后,会自动创建三个应用。

分别是D2审批,D2打卡结果,D2签到。

应用设置

在导入后的每个应用的后台设置里添加API令牌。

b916.png

环境部署篇

云环境部署

这里我们将他部署在阿里的云服务器 ECS实例上,当然我们也可以部署在亚马逊云,腾讯云等等。

我们只需要配置好java的环境:jre: 1.8即可。

后面,我还会写几篇serverless的部署方式,大家有兴趣也可以看看。

运行方式

例:java -cp  test.jar d2.D2TimeCardResultConnector 2019-07-01 2019-07-30

触发器

我们可以配置每日执行来定期同步钉钉的数据到kintone

例:

crontab 0 0 1 * * java -cp  test.jar d2.D2TimeCardResultConnector 2019-07-01 2019-07-30


验证

好了,我们已经完成了所有设置,在钉钉里添加一条打卡记录,签到记录,还有审批记录,看看是不是同步到了kintone呢?

0015d2d64ad2eec26fa1e55a7e8dca3

源码文件下载地址

下载地址:

https://gitee.com/cybozudeveloper/dingtalk2kintone


1. 使用的libraries

  • cybozush
    -- kintone REST API

  • taobao-sdk-java-auto_1479188381469-20190425.jar
    -- 钉钉SDK

  • commons-logging-1.2.jar
    -- 钉钉SDK使用的外部jar

2. 整合程序的class说明

  • D2Connector
    -- 连接kintone和钉钉的基类

  • D2CheckinConnector
    -- 签到sample program

  • D2TimeCardResultConnector
    -- 打卡结果sample program

  • D2WfConnector
    -- 审批sample program

 注意事项

  • 本示例代码不保证其运行。

  • 我们不为本示例代码提供技术支持。


相关链接