首页 > 其他分享 >钉钉OA自定义审批流的创建和使用

钉钉OA自定义审批流的创建和使用

时间:2023-10-25 19:33:07浏览次数:39  
标签:自定义 err models 创建 OA aliyun 审批 new com

前言

大家好!我是sum墨,一个一线的底层码农,平时喜欢研究和思考一些技术相关的问题并整理成文,限于本人水平,如果文章和代码有表述不当之处,还请不吝赐教。

钉钉作为一款办公软件,审批功能是它的核心功能之一,最常见的审批场景就是请假和报销了。虽然钉钉也内置了一些审批流,但是审批场景层出不穷,光靠钉钉内置的那些是不够用的。尤其一些公司自己也有技术团队,则更希望可以二次开发一下,做一套更适合自己公司的审批流。那么本文我们就钉钉的审批能力来讲一下:钉钉OA自定义审批流的创建和使用。

tips:钉钉OA审批在哪里

这个还是要说下,否则很多人都找不到!

1. 扫码登录钉钉OA

登录链接如下:https://oa.dingtalk.com/

2. 工作台-应用管理-OA审批-进入

进去之后是这样的,我们也可以在这里创建新表单,不过这里创建的表单是不支持代码调用的。

3. 如果实在找不到,去搜索框中搜索审批

那么,接下来正文开始!

一、创建小程序

如果你的组织的类型是认证服务商,那么可以选择创建第三方企业应用,否则就创建企业内部应用。

这两种应用的主要区别就是获取AccessToken的方式不同,如何不同可以看我的这篇文章:钉钉小程序生态1—区分企业内部应用、第三方企业应用、第三方个人应用

那么如何判断自己是不是服务商组织呢?登录开放平台—>首页—>有认证服务商标签的就是啦

这里我为了方便文章撰写,我就创建一个企业内部应用来说明接下来的流程。如果大家使用的是第三方企业应用,那么还需要配置一下钉钉事件回调,详细可见我这篇文章:
钉钉小程序生态4—钉钉应用事件与回调

1. 应用开发-企业内部应用-创建应用-H5微应用

这里H5微应用、小程序两种类型都可以,我们主要是为了获取创建钉钉OA自定义审批流的权限。

2. 基础信息—权限管理—搜索审批

权限一共5个全都点申请,将对应权限权限申请好之后,我们就可以调用接口创建OA审批模板和发起审批实例了。

3. 应用功能—事件与回调—事件订阅—开启审批事件回调

如何接入可以看钉钉的官方文档:配置Stream推送,非常的简单,这里我就不贴代码了。
配置回调的作用是为了后续审批状态发生变化的时候可以及时通知到我们。

到目前为止,创建和配置相关的工作我们已经完成了,接下来就是开发了。

二、创建或更新审批表单模板

模板的创建是一次性的,也就是说只需要调用一下创建接口就行,这里复杂的东西是它的控件很多,比如:文本框、数字框、日期选择器等等,如下图:

用可视化界面创建固然是容易,但是要用代码来创建就有点麻烦了,我开始也错了好几次,从简单的控件开始尝试就好了,多试几次就行。
官方链接如下:创建或更新审批表单模板
这里我自己创建的代码如下:

package com.example.dingtalkoa.demo;

import com.aliyun.dingtalkworkflow_1_0.models.FormComponent;
import com.aliyun.dingtalkworkflow_1_0.models.FormComponentProps;
import com.aliyun.dingtalkworkflow_1_0.models.FormCreateHeaders;
import com.aliyun.dingtalkworkflow_1_0.models.FormCreateRequest;
import com.aliyun.dingtalkworkflow_1_0.models.FormCreateResponse;
import com.aliyun.tea.TeaException;
import com.aliyun.teaopenapi.models.Config;
import com.aliyun.teautil.Common;
import com.aliyun.teautil.models.RuntimeOptions;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Sample3 {

    /**
     * 获取AccessToken
     *
     * @return
     */
    public static String getAccessToken() throws Exception {
        com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config();
        config.protocol = "https";
        config.regionId = "central";
        com.aliyun.dingtalkoauth2_1_0.Client client = new com.aliyun.dingtalkoauth2_1_0.Client(config);
        com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenRequest getAccessTokenRequest
            = new com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenRequest()
            .setAppKey("xxx")
            .setAppSecret("xxxx");
        try {
            return client.getAccessToken(getAccessTokenRequest).getBody().getAccessToken();
        } catch (TeaException err) {
            if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
                // err 中含有 code 和 message 属性,可帮助开发定位问题
            }

        } catch (Exception _err) {
            TeaException err = new TeaException(_err.getMessage(), _err);
            if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
                // err 中含有 code 和 message 属性,可帮助开发定位问题
            }

        }
        return null;
    }

    public static void main(String[] args) throws Exception {
        Config config = new Config();
        config.protocol = "https";
        config.regionId = "central";
        com.aliyun.dingtalkworkflow_1_0.Client client = new com.aliyun.dingtalkworkflow_1_0.Client(config);
        FormCreateHeaders formCreateHeaders = new FormCreateHeaders();
        formCreateHeaders.xAcsDingtalkAccessToken = getAccessToken();
        // 1. 单行输入控件
        FormComponentProps formComponentProps1 = new FormComponentProps()
            .setComponentId("TextField-title")
            .setPlaceholder("文章标题")
            .setLabel("文章标题")
            .setRequired(true);
        FormComponent formComponent1 = new FormComponent()
            .setComponentType("TextField")
            .setProps(formComponentProps1);
        FormComponentProps formComponentProps2 = new FormComponentProps()
            .setComponentId("TextField-url")
            .setPlaceholder("文章内容链接")
            .setLabel("文章内容链接")
            .setRequired(true);
        FormComponent formComponent2 = new FormComponent()
            .setComponentType("TextField")
            .setProps(formComponentProps2);

        FormCreateRequest formCreateRequest = new FormCreateRequest()
            .setName("文章发布申请")
            .setDescription("文章发布申请")
            .setFormComponents(java.util.Arrays.asList(formComponent1, formComponent2));
        try {
            FormCreateResponse formCreateResponse = client.formCreateWithOptions(formCreateRequest, formCreateHeaders,
                new RuntimeOptions());
            System.out.println("创建的processCode:" + formCreateResponse.getBody().getResult().getProcessCode());
        } catch (TeaException err) {
            log.error("--->", err);
            if (!Common.empty(err.code) && !Common.empty(err.message)) {
                // err 中含有 code 和 message 属性,可帮助开发定位问题
            }

        } catch (Exception _err) {
            log.error("--->", _err);
            TeaException err = new TeaException(_err.getMessage(), _err);
            if (!Common.empty(err.code) && !Common.empty(err.message)) {
                // err 中含有 code 和 message 属性,可帮助开发定位问题
            }

        }
    }

}

maven依赖代码如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.17</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>DingTalkOA</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>DingTalkOA</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>com.dingtalk.open</groupId>
            <artifactId>app-stream-client</artifactId>
            <version>1.1.0</version>
        </dependency>

        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>dingtalk</artifactId>
            <version>2.0.14</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.26</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

创建好之后可以在OA里面找到刚才创建的审批流模板

审批表单模板创建结束后,钉钉会返回一个processCode给我们,这个processCode很重要需要保存下来。整体来说,审批表单模板的创建不难理解,毕竟在这里不需要设置各个环节的审批人,真正复杂的是发起审批实例这个接口,下面我们来讲一下如何发起审批实例。

三、发起审批实例

1. 参数说明

  • processCode
    审批模板code,好理解。
  • originatorUserId
    审批发起人的userId,也理解。
  • deptId或approvers
    选择审批的部门或者审批的人,这是二选一,传一个就行了,理解上还行。
  • ccList
    抄送人 userId,就是只会通知到他,而不用他点审批的人,好理解。
  • ccPosition
    抄送时间点,取值:START:开始时抄送;FINISH:结束时抄送;START_FINISH:开始和结束时都抄送,理解上还行。
  • formComponentValues
    表单数据内容,控件列表,也就是我们创建的那些控件的具体的值,理解上还行。
  • microappAgentId
    不理解的来了,这个东西如果你是企业内部应用,你可以很快的在应用信息中找到,如下图

    但是!!!谁能告诉我第三方企业应用的agentId在哪???下面是第三方企业应用的应用信息,根本就没有!!!

    这是钉钉官方教我们查看官方应用和第三方应用的AgentId的方法

    但是!!!钉钉OA升级了了,不是,你们特么升级版本不考虑一下这个的吗???新版本没了,找不到了,打开是这个东西

    而且开放平台里面也根本没有可以直接获取AgentId的接口,最后找来找去,终于给找到一个接口:获取企业授权信息,这个接口的返回值里面有一个auth_info,里面有授权应用的agentId,唉,,,

2. 调用示例

官方文档:发起审批实例
这里我自己发起实例的代码如下:

package com.example.dingtalkoa.demo;

import java.util.ArrayList;
import java.util.List;

import com.alibaba.fastjson.JSONObject;

import com.aliyun.dingtalkworkflow_1_0.models.StartProcessInstanceRequest.StartProcessInstanceRequestApprovers;
import com.aliyun.dingtalkworkflow_1_0.models.StartProcessInstanceResponse;
import com.aliyun.tea.TeaException;
import com.aliyun.teaopenapi.models.Config;
import com.aliyun.teautil.models.RuntimeOptions;

public class Sample4 {
    /**
     * 获取AccessToken
     *
     * @return
     */
    public static String getAccessToken() throws Exception {
        com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config();
        config.protocol = "https";
        config.regionId = "central";
        com.aliyun.dingtalkoauth2_1_0.Client client = new com.aliyun.dingtalkoauth2_1_0.Client(config);
        com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenRequest getAccessTokenRequest
            = new com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenRequest()
            .setAppKey("xxx")
            .setAppSecret("xxx");
        try {
            return client.getAccessToken(getAccessTokenRequest).getBody().getAccessToken();
        } catch (TeaException err) {
            if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
                // err 中含有 code 和 message 属性,可帮助开发定位问题
            }

        } catch (Exception _err) {
            TeaException err = new TeaException(_err.getMessage(), _err);
            if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
                // err 中含有 code 和 message 属性,可帮助开发定位问题
            }

        }
        return null;
    }

    /**
     * 使用 Token 初始化账号Client
     *
     * @return Client
     * @throws Exception
     */
    public static com.aliyun.dingtalkworkflow_1_0.Client createClient() throws Exception {
        Config config = new Config();
        config.protocol = "https";
        config.regionId = "central";
        return new com.aliyun.dingtalkworkflow_1_0.Client(config);
    }

    public static void main(String[] args_) throws Exception {
        //调用钉钉审核发起接口
        com.aliyun.dingtalkworkflow_1_0.models.StartProcessInstanceRequest.StartProcessInstanceRequestFormComponentValues
            formComponentValues0
            =
            new com.aliyun.dingtalkworkflow_1_0.models.StartProcessInstanceRequest.StartProcessInstanceRequestFormComponentValues()
                .setName("TextField-title")
                .setValue("测试文章标题");

        com.aliyun.dingtalkworkflow_1_0.models.StartProcessInstanceRequest.StartProcessInstanceRequestFormComponentValues
            formComponentValues1
            =
            new com.aliyun.dingtalkworkflow_1_0.models.StartProcessInstanceRequest.StartProcessInstanceRequestFormComponentValues()
                .setName("TextField-url")
                .setValue("https://baidu.com");
        //获取审批人

        List<StartProcessInstanceRequestApprovers> approvers = new ArrayList<>();

        approvers.add(
            new com.aliyun.dingtalkworkflow_1_0.models.StartProcessInstanceRequest.StartProcessInstanceRequestApprovers()
                .setActionType("NONE")
                .setUserIds(java.util.Arrays.asList(
                    "xxx"
                )));

        com.aliyun.dingtalkworkflow_1_0.models.StartProcessInstanceRequest startProcessInstanceRequest
            = new com.aliyun.dingtalkworkflow_1_0.models.StartProcessInstanceRequest()
            //.setDeptId(1L)
            .setApprovers(approvers)
            .setMicroappAgentId(xxx)
            .setOriginatorUserId("xxx")
            .setProcessCode("xxx")
            .setFormComponentValues(java.util.Arrays.asList(
                formComponentValues0,
                formComponentValues1
            ));

        com.aliyun.dingtalkworkflow_1_0.Client client = createClient();
        com.aliyun.dingtalkworkflow_1_0.models.StartProcessInstanceHeaders startProcessInstanceHeaders
            = new com.aliyun.dingtalkworkflow_1_0.models.StartProcessInstanceHeaders();
        startProcessInstanceHeaders.xAcsDingtalkAccessToken = getAccessToken();
        JSONObject.toJSONString(startProcessInstanceRequest);
        StartProcessInstanceResponse startProcessInstanceResponse = client.startProcessInstanceWithOptions(
            startProcessInstanceRequest, startProcessInstanceHeaders,
            new RuntimeOptions());

    }
}

把参数都准备好之后,实现起来还是比较简单的,调用代码创建的审批实例,钉钉会返回一个实例ID:instanceId,这个instanceId和processCode一样也需要保存下来,发送成功后钉钉APP上就会自动出现一条OA审批啦。

四、审批实例状态监控

所谓审批实例状态监控,就是当前审批流程是被同意啦还是被拒绝了。这里有两种方案:

  1. 定时去调用获取单个审批实例详情接口,同步审批实例状态,优点是状态肯定可以同步到,缺点是实时性差;
  2. 通过事件订阅的方式获取审批实例的状态,优点是实时性高,审批状态变化服务端就可以指定,缺点是只会推送一次;

而作为一个成年人,这两个肯定是全都要啦,一个用来实时更新,一个用来做兜底。
这里查询的审批实例的接口文档链接如下:获取单个审批实例详情
如果前面创建审批模板、发起审批实例都能跑通,那么这个接口也肯定不在话下,所以这里我就不贴代码了。

最后我把事件订阅推送的数据格式贴一下:

[
    {
        "result": "refuse",
        "processInstanceId": "xxx",
        "eventId": "xxx",
        "finishTime": 1698231807000,
        "createTime": 1698227806000,
        "processCode": "PROC-xxx",
        "businessId": "xxx",
        "title": "xxx提交的文章发布申请",
        "type": "finish",
        "staffId": "xxx",
        "taskId": "xxx"
    }
]

写在最后:其实这些东西大部分都是钉钉官方文档上面的,除了那个agentId... 但是钉钉文档的东西实在是太多,作为一个开发者,我们不可能去从头到尾看一遍的,一般都是用到了就去找。但是这样一来又会很混乱,所以我这篇文章主要是从开发者角度来梳理一下这个流程,不仅利己也能帮助其他人。

标签:自定义,err,models,创建,OA,aliyun,审批,new,com
From: https://www.cnblogs.com/wlovet/p/17785671.html

相关文章

  • 在Houdini中创建布料,并导入到Unity中
    在Houdini中创建一个具有物理效果和贴图的布料,导入到Unity中,实现一个效果良好的、可以与模型互动、有贴图的静态布料模型。参考视频:Houdini+Unity2021制作布料全流程!_哔哩哔哩_bilibili1、创建节点首先创建一个obj文件:随后右键这个节点,创建一个DigitalAssret。进入Typ......
  • MapReduce自定义GroupingComparator
    需求:有如下订单明细数据0000001 01 222.80000002 06 722.40000001 05 25.80000003 01 222.80000003 01 33.80000002 03 522.80000002 04 122.4第一列是订单编号,第二列是商品id,第三列是商品金额,列与列之间用制表符分隔。现在需要求出每一个订单中最贵的商品。思路:将订单id和商......
  • Spring Boot整合OAuth2实现GitHub第三方登录
    GithubOAuth第三方登录示例1、第三方登录原理第三方登录的原理是借助OAuth授权来实现,首先用户先向客户端提供第三方网站的数据证明自己的身份获取授权码,然后客户端拿着授权码与授权服务器建立连接获得一个AccessToken,之后客户端就可以通过AccessToken来与资源服务器进行交互......
  • SOA与大数据实战:企业私有云平台规划和建设pdf电子版 何明璐 / 邹海锋
    SOA与大数据实战:企业私有云平台规划和建设pdf电子版下载作者:何明璐/邹海锋出版年: 2020-7ISBN: 9787302530695连接提取码:5f14介绍了传统企业推进私有云平台建设的一整套指导方法和实践步骤。通过先架构再实战,学习企业IT架构转型与架构设计方面能有一个循序渐进的过程和方法指......
  • C语言接口与实现: 创建可重用软件的技术 pdf电子版
    C语言接口与实现:创建可重用软件的技术pdf电子版作者: DavidR.Hanson原作名: CInterfacesandImplementations:TechniquesforCreatingReusableSoftware出版年: 2011-9ISBN: 9787115260314连接提取码:cnkx一度有点觉得是一本说数据结构的书,但其实不是(不是所有的例子......
  • 基于RuoYi-Flowable-Plus的若依ruoyi-nbcio支持自定义业务表单流程的集成方法与步骤(二
    更多ruoyi-nbcio功能请看演示系统gitee源代码地址演示地址:RuoYi-Nbcio后台管理系统前面讲了集成的后端部分内容,下面简单介绍一下前端的内容 1、前端生成的页面需要进行修改,增加流程状态启动等相关信息,如demo的index修改如下<template><divclass="app-container"><el-form......
  • 基于RuoYi-Flowable-Plus的若依ruoyi-nbcio支持自定义业务表单流程的集成方法与步骤(一
    更多ruoyi-nbcio功能请看演示系统gitee源代码地址演示地址:RuoYi-Nbcio后台管理系统由于大家最自定义业务表单的整个集成方法还不熟悉,下面大概介绍一下这个流程与方法。1、首先需要建立数据库表,根据自己业务进行数据表的建立,目前系统需要在另外sql进行数据库表的建立,以后可以考虑系......
  • 基于RuoYi-Flowable-Plus的若依ruoyi-nbcio支持自定义业务表单流程(五)
    更多ruoyi-nbcio功能请看演示系统gitee源代码地址演示地址:RuoYi-Nbcio后台管理系统今天讲一下wf_demo表单的一些修改1、demo的实现类修改如下:主要是增加一个服务名称,后面要用到,同时继承于WfCallBackServiceI,以便进行调用。@Service("wfDemoService")publicclassWfDemoServiceImp......
  • 基于RuoYi-Flowable-Plus的若依ruoyi-nbcio支持自定义业务表单流程(四)
    更多ruoyi-nbcio功能请看演示系统gitee源代码地址演示地址:RuoYi-Nbcio后台管理系统自定义业务表单里的流程历史需要单独设计,所以下面就这部分进行介绍。1、后端部分,这部分增加单独的接口,只需要单独的dataID就可以了,如下:/***流程详情信息**@paramdataId业务数......
  • 基于RuoYi-Flowable-Plus的若依ruoyi-nbcio支持自定义业务表单流程(三)
    更多ruoyi-nbcio功能请看演示系统gitee源代码地址演示地址:RuoYi-Nbcio后台管理系统相应的后端也要做一些调整1、启动流程修改如下:/***启动流程实例*/privateRstartProcess(ProcessDefinitionprocDef,Map<String,Object>variables){if(ObjectUti......