书接上回,本文主要分享 企业内部系统集成钉钉官方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
- /**
- * 获取 钉钉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访问客户端
- 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. 发起流程实例接口
- /**
- * 发起 钉钉审核流程
- * @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)
- /**
- * 审批流程预测
- * @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;
- }
复制代码 查询审核流程的各个节点的审核结果
- /**
- * 查询单个钉钉流程实例的 审核状态 审核结果
- * @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. 流程审核同意回调接口
- /**
- * 流程 审批完成后 回调更新 审核进度、审核结果
- * 正常审核完成 同意
- * @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. 流程审核拒绝回调接口
- /**
- * 流程 审批完成后 回调更新记录的审核进度、审核结果
- * 流程被拒绝
- * * @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
返回参数示例
- {
- "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,如果不合法,引导用户重新登录。
接口返回参数示例
- {
- "code": 200,
- "userno": "123456",
- "name": "mary",
- "id": "456123",
- "tenant": "abc",
- "email": "abc@qq.com",
- "token": "7ee22693d72a0f4aa0d1ad041c0dccac"
- }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |