大家好!我是sum墨,一個一線的底層碼農,平時喜歡研究和思考一些技術相關的問題並整理成文,限於本人水平,如果文章和程式碼有表述不當之處,還請不吝賜教。
釘釘作為一款辦公軟體,審批功能是它的核心功能之一,最常見的審批場景就是請假和報銷了。雖然釘釘也內建了一些審批流,但是審批場景層出不窮,光靠釘釘內建的那些是不夠用的。尤其一些公司自己也有技術團隊,則更希望可以二次開發一下,做一套更適合自己公司的審批流。那麼本文我們就釘釘的審批能力來講一下:釘釘OA自定義審批流的建立和使用。
這個還是要說下,否則很多人都找不到!
登入連結如下:https://oa.dingtalk.com/
進去之後是這樣的,我們也可以在這裡建立新表單,不過這裡建立的表單是不支援程式碼呼叫的。
那麼,接下來正文開始!
如果你的組織的型別是認證服務商
,那麼可以選擇建立第三方企業應用,否則就建立企業內部應用。
這兩種應用的主要區別就是獲取AccessToken的方式不同,如何不同可以看我的這篇文章:釘釘小程式生態1—區分企業內部應用、第三方企業應用、第三方個人應用
那麼如何判斷自己是不是服務商組織
呢?登入開放平臺—>首頁—>有認證服務商
標籤的就是啦
這裡我為了方便文章撰寫,我就建立一個企業內部應用來說明接下來的流程。如果大家使用的是第三方企業應用,那麼還需要設定一下釘釘事件回撥,詳細可見我這篇文章:
釘釘小程式生態4—釘釘應用事件與回撥
這裡H5微應用、小程式兩種型別都可以,我們主要是為了獲取建立釘釘OA自定義審批流的許可權。
許可權一共5個全都點申請,將對應許可權許可權申請好之後,我們就可以呼叫介面建立OA審批模板和發起審批範例了。
如何接入可以看釘釘的官方檔案:設定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很重要需要儲存下來。整體來說,審批表單模板的建立不難理解,畢竟在這裡不需要設定各個環節的審批人,真正複雜的是發起審批範例這個介面,下面我們來講一下如何發起審批範例。
官方檔案:發起審批範例
這裡我自己發起範例的程式碼如下:
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審批啦。
所謂審批範例狀態監控,就是當前審批流程是被同意啦還是被拒絕了。這裡有兩種方案:
而作為一個成年人,這兩個肯定是全都要啦,一個用來實時更新,一個用來做兜底。
這裡查詢的審批範例的介面檔案連結如下:獲取單個審批範例詳情。
如果前面建立審批模板、發起審批範例都能跑通,那麼這個介面也肯定不在話下,所以這裡我就不貼程式碼了。
最後我把事件訂閱推播的資料格式貼一下:
[
{
"result": "refuse",
"processInstanceId": "xxx",
"eventId": "xxx",
"finishTime": 1698231807000,
"createTime": 1698227806000,
"processCode": "PROC-xxx",
"businessId": "xxx",
"title": "xxx提交的文章釋出申請",
"type": "finish",
"staffId": "xxx",
"taskId": "xxx"
}
]
寫在最後:其實這些東西大部分都是釘釘官方檔案上面的,除了那個agentId... 但是釘釘檔案的東西實在是太多,作為一個開發者,我們不可能去從頭到尾看一遍的,一般都是用到了就去找。但是這樣一來又會很混亂,所以我這篇文章主要是從開發者角度來梳理一下這個流程,不僅利己也能幫助其他人。
本文來自部落格園,作者:sum墨,轉載請註明原文連結:https://www.cnblogs.com/wlovet/p/17785671.html