钉钉二次开发-企业内部系统集成官方OA审批流程(二)

书接上回,本文主要分享 企业内部系统集成钉钉官方OA审批流程的步骤 的第二部分。

后端代码中集成钉钉官方OA审批API:

表结构设计、钉钉API访问工具类、发起流程实例接口、查询流程审核日志接口、流程审核同意回调接口、流程审核拒绝回调接口、钉钉免登录接口。

1. 表结构设计

在已经业务数据表结构的基础上添加钉钉审核流程相关字段:

|-------------------|-------------------------------------------------------------------------------------|
| 字段名称 | 功能描述 |
| dingtalk_union_id | 钉钉unionId |
| dingtalk_user_id | 钉钉userId |
| dingtalk_dept_id | 钉钉用户所属部门id 多个逗号分隔 |
| process_id | 钉钉流程实例id |
| auditing_result | 审核结果 同意agree 拒绝refuse |
| auditing_status | 审核状态:NEW("新创建"), RUNNING("审批中"), TERMINATED("被终止"), COMPLETED("完成"), CANCELED("取消") |

2. 钉钉API访问工具类

获取 钉钉API访问 access_token

java 复制代码
    /**
     * 获取 钉钉API访问 access_token
     * @return
     * @throws ApiException
     */
    public static String getAccessToken() throws ApiException {
        DefaultDingTalkClient client = new DefaultDingTalkClient(DingConstantsUtil.GET_ACCESS_TOKEN_URL);
        OapiGettokenRequest request = new OapiGettokenRequest();

        request.setAppkey(DingConstantsUtil.APP_KEY);
        request.setAppsecret(DingConstantsUtil.APP_SECRET);
        request.setHttpMethod("GET");
        OapiGettokenResponse response = client.execute(request);

        return response.getAccessToken();
    }

获取钉钉API访问客户端

java 复制代码
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);
    }

3. 发起流程实例接口

java 复制代码
/**
     * 发起 钉钉审核流程
     * @param dingtalkUserId
     * @param dingtalkDeptId
     * @param processCode
     * @param performance
     * @return
     * @throws Exception
     */
    private Map<String, Object> startProjectProfitFinalizationProcess(
            String dingtalkUserId, String dingtalkDeptId, String processCode, Performance performance,
            List<Attachment> attachments) throws Exception {
        // 重新获取 钉钉的 accessToken
        String accessToken = DingtalkUtil.getAccessToken();
        com.aliyun.dingtalkworkflow_1_0.Client client = DingtalkUtil.createClient();
        StartProcessInstanceHeaders startProcessInstanceHeaders = new StartProcessInstanceHeaders();
        startProcessInstanceHeaders.xAcsDingtalkAccessToken = accessToken;
        List<StartProcessInstanceRequest.StartProcessInstanceRequestFormComponentValues> formComponentValues = new ArrayList<>();
        StartProcessInstanceRequest.StartProcessInstanceRequestFormComponentValues formComponentValues00 = new StartProcessInstanceRequest.StartProcessInstanceRequestFormComponentValues()
                .setName("隐藏字段-更新流程进度使用")
                .setValue(performance.getId().toString());
        formComponentValues.add(formComponentValues00);
        StartProcessInstanceRequest.StartProcessInstanceRequestFormComponentValues formComponentValues18 = new StartProcessInstanceRequest.StartProcessInstanceRequestFormComponentValues()
                .setName("标题")
                .setValue(performance.getTitle());
        formComponentValues.add(formComponentValues18);
        StartProcessInstanceRequest.StartProcessInstanceRequestFormComponentValues formComponentValues0 = new StartProcessInstanceRequest.StartProcessInstanceRequestFormComponentValues()
                .setName("被考核人")
                .setValue(performance.getAppraisee());
        formComponentValues.add(formComponentValues0);
        StartProcessInstanceRequest.StartProcessInstanceRequestFormComponentValues formComponentValues9 = new StartProcessInstanceRequest.StartProcessInstanceRequestFormComponentValues()
                .setName("部门名称")
                .setValue(performance.getDeptName());
        formComponentValues.add(formComponentValues9);
        StartProcessInstanceRequest.StartProcessInstanceRequestFormComponentValues formComponentValues1 = new StartProcessInstanceRequest.StartProcessInstanceRequestFormComponentValues()
                .setName("岗位名称")
                .setValue(performance.getPosName());
        formComponentValues.add(formComponentValues1);
		// 这里需要将 绩效指标 明细数据 转换成json字符数组的形式,以满足钉钉的方法入参格式要求
        StartProcessInstanceRequest.StartProcessInstanceRequestFormComponentValues formComponentValues17 = new StartProcessInstanceRequest.StartProcessInstanceRequestFormComponentValues()
                .setName("绩效指标")
                .setValue(performance.getAppraiseeIndex());
        formComponentValues.add(formComponentValues17);
        StartProcessInstanceRequest.StartProcessInstanceRequestFormComponentValues formComponentValues16 = new StartProcessInstanceRequest.StartProcessInstanceRequestFormComponentValues()
                .setName("绩效等级")
                .setValue(performance.getGrade());
        formComponentValues.add(formComponentValues16);
        StartProcessInstanceRequest.StartProcessInstanceRequestFormComponentValues formComponentValues15 = new StartProcessInstanceRequest.StartProcessInstanceRequestFormComponentValues()
                .setName("评语")
                .setValue(performance.getComment());
        formComponentValues.add(formComponentValues15);
        // 解析出钉钉用户所属部门
        List<String> deptIds = new ArrayList<>(Arrays.asList(dingtalkDeptId.split(",")));
        long deptId = 0l;
        if (!CollectionUtils.isEmpty(deptIds)) {
            deptId = Long.valueOf(deptIds.get(0));
        }
        // 发起流程
        String processInstanceId = "";
        String errMsg = "";
        Map<String, Object> returnMap = new HashMap<>();
        StartProcessInstanceRequest startProcessInstanceRequest = new StartProcessInstanceRequest()
                .setOriginatorUserId(dingtalkUserId)
                .setDeptId(deptId)
                .setProcessCode(processCode)
                .setFormComponentValues(formComponentValues);
        Gson gson = new Gson();
        logger.info("发起流程实例的请求对象:" + gson.toJson(formComponentValues)+"-"+dingtalkUserId+"-"+deptId+"-"+processCode);
        try {
            StartProcessInstanceResponse response = client.startProcessInstanceWithOptions(startProcessInstanceRequest,
                    startProcessInstanceHeaders, new RuntimeOptions());
            if (!ObjectUtils.isEmpty(response)) {
                processInstanceId = response.getBody().getInstanceId();
            }
        } catch (TeaException err) {
            logger.info("发起流程调用钉钉接口异常信息对象:" + gson.toJson(err));
            if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
                logger.info("发起流程调用钉钉接口返回错误信息:" + ExceptionUtils.getStackTrace(err));
                returnMap.put("errMsg", err.message);
                return returnMap;
            }
        } catch (Exception _err) {
            logger.info("发起流程调用钉钉接口异常信息对象:" + gson.toJson(_err));
            TeaException err = new TeaException(_err.getMessage(), _err);
            if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
                returnMap.put("errMsg", err.message);
                return returnMap;
            }
        }
        // 如果启动流程实例成功,返回流程实例id
        if (StringUtils.isNotBlank(processInstanceId)) {
            returnMap.put("processInstanceId", processInstanceId);
        }
        return returnMap;
    }

4. 查询流程审核日志

预测审批流程节点信息 (需要用到前文提到的钉钉流程模板 code)

java 复制代码
/**
     * 审批流程预测
     * @param processCode
     * @param userId
     * @param deptId
     * @param accessToken
     * @return
     * @throws Exception
     */
    public static Map<String, Object> forecastProcesses(String processCode, String userId, Integer deptId, String accessToken, List<ProcessForecastRequest.ProcessForecastRequestFormComponentValues> formComponentValues) throws Exception {
        Map<String, Object> returnMap = new HashMap<>();
        com.aliyun.dingtalkworkflow_1_0.Client client = createClient();
        com.aliyun.dingtalkworkflow_1_0.models.ProcessForecastHeaders processForecastHeaders
                = new com.aliyun.dingtalkworkflow_1_0.models.ProcessForecastHeaders();
        processForecastHeaders.xAcsDingtalkAccessToken = accessToken;
        com.aliyun.dingtalkworkflow_1_0.models.ProcessForecastRequest processForecastRequest
                = new com.aliyun.dingtalkworkflow_1_0.models.ProcessForecastRequest()
                .setDeptId(deptId)
                .setUserId(userId)
                .setProcessCode(processCode)
                .setFormComponentValues(formComponentValues);
        try {
            ProcessForecastResponse response = client.processForecastWithOptions(processForecastRequest, processForecastHeaders, new com.aliyun.teautil.models.RuntimeOptions());
            if (!ObjectUtils.isEmpty(response) && response.getBody().getResult().isForecastSuccess) {
                // 审批节点
                List<ProcessForecastResponseBody.ProcessForecastResponseBodyResultWorkflowActivityRules> activityRules = response.getBody().getResult().getWorkflowActivityRules();
                if (!CollectionUtils.isEmpty(activityRules)) {
                    activityRules.forEach(rule -> {
                        returnMap.put(rule.getActivityId(), rule.getActivityName());
                    });
                }
            }
        } catch (TeaException err) {
            if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
                // err 中含有 code 和 message 属性,可帮助开发定位问题
                logger.info("调用钉钉流程审批预测接口-错误信息:" + err.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 属性,可帮助开发定位问题
                logger.info("调用钉钉流程审批预测接口-错误信息:" + err.message);
            }
        }
        return returnMap;
    }

查询审核流程的各个节点的审核结果

java 复制代码
/**
     * 查询单个钉钉流程实例的 审核状态 审核结果
     * @param instanceId
     * @param accessToken
     * @return
     * @throws Exception
     */
    public static Map<String, Object> processInstanceStatusAndResult(String instanceId, String accessToken) throws Exception {
        Map<String, Object> returnMap = new HashMap<>();
        com.aliyun.dingtalkworkflow_1_0.Client client = createClient();
        com.aliyun.dingtalkworkflow_1_0.models.GetProcessInstanceHeaders getProcessInstanceHeaders
                = new com.aliyun.dingtalkworkflow_1_0.models.GetProcessInstanceHeaders();
        getProcessInstanceHeaders.xAcsDingtalkAccessToken = accessToken;
        com.aliyun.dingtalkworkflow_1_0.models.GetProcessInstanceRequest getProcessInstanceRequest
                = new com.aliyun.dingtalkworkflow_1_0.models.GetProcessInstanceRequest()
                .setProcessInstanceId(instanceId);
        try {
            GetProcessInstanceResponse response = client.getProcessInstanceWithOptions(getProcessInstanceRequest, getProcessInstanceHeaders, new com.aliyun.teautil.models.RuntimeOptions());
            if (!ObjectUtils.isEmpty(response) && "true".equals(response.getBody().getSuccess())) {
                // 审批状态
                String status = response.getBody().getResult().getStatus();
                // 审批结果
                String approvalResult = response.getBody().getResult().getResult();
                if (StringUtils.isNotBlank(approvalResult)) {
                    returnMap.put("approvalResult", approvalResult);
                }
                if (StringUtils.isNotBlank(status)) {
                    returnMap.put("status", status);
                }
                // 查询历史审核节点和当前的审核节点
                List<GetProcessInstanceResponseBody.GetProcessInstanceResponseBodyResultTasks> tasks = response.getBody().getResult().getTasks();
                if (!CollectionUtils.isEmpty(tasks)) {
                    returnMap.put("tasks", tasks);
                }
                // 审核日志 评论
                List<GetProcessInstanceResponseBody.GetProcessInstanceResponseBodyResultOperationRecords> records = response.getBody().getResult().getOperationRecords();
                if (!CollectionUtils.isEmpty(records)) {
                    returnMap.put("operationRecords", records);
                }
            }
        } catch (TeaException err) {
            if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
                // err 中含有 code 和 message 属性,可帮助开发定位问题
                logger.info("调用钉钉查询单个流程的审批实例详情接口-错误信息:" + err.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 属性,可帮助开发定位问题
                logger.info("调用钉钉查询单个流程的审批实例详情接口-错误信息:" + err.message);
            }
        }
        return returnMap;
    }

5. 流程审核同意回调接口

java 复制代码
    /**
     * 流程 审批完成后 回调更新 审核进度、审核结果
     * 正常审核完成 同意
     * @return
     */
    @RequestMapping(value = "dingCallback", method = RequestMethod.POST, produces = {"application/json;charset=utf-8"})
    public Result<Object> dingCallback(@RequestBody DingCallbackUpdateReqVO updateReqVO) {
        // 校验业务主键对应的记录是否存在
        if (!ObjectUtils.isEmpty(updateReqVO.getId())) {
            // 更新业务单据的审核状态
            ......
        }
        return new Result<>(ResultStatusEnum.SUCCESS);
    }

6. 流程审核拒绝回调接口

java 复制代码
    /**
     * 流程 审批完成后 回调更新记录的审核进度、审核结果
     * 流程被拒绝
     * * @return
     */
    @RequestMapping(value = "dingRefuseCallback", method = RequestMethod.POST, produces = {"application/json;charset=utf-8"})
    public Result<Object> dingRefuseCallback(@RequestBody DingCallbackUpdateReqVO updateReqVO) {
        // 校验业务主键对应的记录是否存在
        if (!ObjectUtils.isEmpty(updateReqVO.getId())) {
            // 更新业务单据的审核状态
            ......
        }
        return new Result<>(ResultStatusEnum.SUCCESS);
    }

7. 钉钉免登录接口

https://sso.abc.com/dd/login?code=81168e62b8a53a1ab2d6c4dff0d2e26f

code 前端通过钉钉JSAPI获得的临时访问码

后台结合业务系统的token校验机制,匹配用户的钉钉信息,如果匹配成功,则视为合法用户,返回token

返回参数示例

javascript 复制代码
{
	"code": 200,
	"data": {
		"password_status": "N",
		"user_id": "12345",
		"user_info": "mary",
		"dingtalkUnionId": "lkgPd1UY5c6dN7EZ9CTdEgiEiE",
		"dingtalkUserId": "123456",
		"token": "1d9862bcdb7d345f55ed08673388bb84",
		"user_no": "456789",
		"dingtalkDeptIds": [
			1289839283
		]
	},
	"message": "success"
}

8. 检验token合法性接口

https://sso.abc.com/api/checkToken?token=d344a66aad475f473933ac7edcfce589

token 当前用户存储在 localStorage 中的token

后台结合业务系统的token校验机制判断token是否合法,如果合法,正常访问系统API,如果不合法,引导用户重新登录。

接口返回参数示例

javascript 复制代码
{
	"code": 200,
	"userno": "123456",
	"name": "mary",
	"id": "456123",
	"tenant": "abc",
	"email": "abc@qq.com",
	"token": "7ee22693d72a0f4aa0d1ad041c0dccac"
}
相关推荐
码农小野20 分钟前
基于Vue.js和SpringBoot的地方美食分享网站系统设计与实现
vue.js·spring boot·美食
ffyyhh99551128 分钟前
java进行音视频的拆分和拼接
java·音视频
乐之者v36 分钟前
Spring之 IoC、BeanFactory、ApplicationContext
java·后端·spring
sealaugh3237 分钟前
spring boot(学习笔记第十二课)
spring boot·笔记·学习
DS_Watson44 分钟前
字符串和正则表达式踩坑
java·开发语言
Wayfreem1 小时前
Java锁升级:无锁 → 偏向锁 → 轻量级锁 → 重量级锁
java·开发语言
我焦虑的编程日记1 小时前
【Java EE】验证码案例
java·java-ee
牧夏。1 小时前
vscode运行java中文乱码,引发的mac配置问题
java·vscode·macos
吃饱很舒服1 小时前
kotlin distinctBy 使用
android·java·开发语言·前端·kotlin
老马啸西风1 小时前
MySQL-18-mysql source 执行 sql 文件时中文乱码
java