ToB企服应用市场:ToB评测及商务社交产业平台

标题: 钉钉二次开辟-企业内部系统集成官方OA审批流程(二) [打印本页]

作者: 曂沅仴駦    时间: 2024-6-11 12:43
标题: 钉钉二次开辟-企业内部系统集成官方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
  1.     /**
  2.      * 获取 钉钉API访问 access_token
  3.      * @return
  4.      * @throws ApiException
  5.      */
  6.     public static String getAccessToken() throws ApiException {
  7.         DefaultDingTalkClient client = new DefaultDingTalkClient(DingConstantsUtil.GET_ACCESS_TOKEN_URL);
  8.         OapiGettokenRequest request = new OapiGettokenRequest();
  9.         request.setAppkey(DingConstantsUtil.APP_KEY);
  10.         request.setAppsecret(DingConstantsUtil.APP_SECRET);
  11.         request.setHttpMethod("GET");
  12.         OapiGettokenResponse response = client.execute(request);
  13.         return response.getAccessToken();
  14.     }
复制代码
获取钉钉API访问客户端 
  1. public static com.aliyun.dingtalkworkflow_1_0.Client createClient() throws Exception {
  2.         Config config = new Config();
  3.         config.protocol = "https";
  4.         config.regionId = "central";
  5.         return new com.aliyun.dingtalkworkflow_1_0.Client(config);
  6.     }
复制代码
3. 发起流程实例接口

  1. /**
  2.      * 发起 钉钉审核流程
  3.      * @param dingtalkUserId
  4.      * @param dingtalkDeptId
  5.      * @param processCode
  6.      * @param performance
  7.      * @return
  8.      * @throws Exception
  9.      */
  10.     private Map<String, Object> startProjectProfitFinalizationProcess(
  11.             String dingtalkUserId, String dingtalkDeptId, String processCode, Performance performance,
  12.             List<Attachment> attachments) throws Exception {
  13.         // 重新获取 钉钉的 accessToken
  14.         String accessToken = DingtalkUtil.getAccessToken();
  15.         com.aliyun.dingtalkworkflow_1_0.Client client = DingtalkUtil.createClient();
  16.         StartProcessInstanceHeaders startProcessInstanceHeaders = new StartProcessInstanceHeaders();
  17.         startProcessInstanceHeaders.xAcsDingtalkAccessToken = accessToken;
  18.         List<StartProcessInstanceRequest.StartProcessInstanceRequestFormComponentValues> formComponentValues = new ArrayList<>();
  19.         StartProcessInstanceRequest.StartProcessInstanceRequestFormComponentValues formComponentValues00 = new StartProcessInstanceRequest.StartProcessInstanceRequestFormComponentValues()
  20.                 .setName("隐藏字段-更新流程进度使用")
  21.                 .setValue(performance.getId().toString());
  22.         formComponentValues.add(formComponentValues00);
  23.         StartProcessInstanceRequest.StartProcessInstanceRequestFormComponentValues formComponentValues18 = new StartProcessInstanceRequest.StartProcessInstanceRequestFormComponentValues()
  24.                 .setName("标题")
  25.                 .setValue(performance.getTitle());
  26.         formComponentValues.add(formComponentValues18);
  27.         StartProcessInstanceRequest.StartProcessInstanceRequestFormComponentValues formComponentValues0 = new StartProcessInstanceRequest.StartProcessInstanceRequestFormComponentValues()
  28.                 .setName("被考核人")
  29.                 .setValue(performance.getAppraisee());
  30.         formComponentValues.add(formComponentValues0);
  31.         StartProcessInstanceRequest.StartProcessInstanceRequestFormComponentValues formComponentValues9 = new StartProcessInstanceRequest.StartProcessInstanceRequestFormComponentValues()
  32.                 .setName("部门名称")
  33.                 .setValue(performance.getDeptName());
  34.         formComponentValues.add(formComponentValues9);
  35.         StartProcessInstanceRequest.StartProcessInstanceRequestFormComponentValues formComponentValues1 = new StartProcessInstanceRequest.StartProcessInstanceRequestFormComponentValues()
  36.                 .setName("岗位名称")
  37.                 .setValue(performance.getPosName());
  38.         formComponentValues.add(formComponentValues1);
  39.                 // 这里需要将 绩效指标 明细数据 转换成json字符数组的形式,以满足钉钉的方法入参格式要求
  40.         StartProcessInstanceRequest.StartProcessInstanceRequestFormComponentValues formComponentValues17 = new StartProcessInstanceRequest.StartProcessInstanceRequestFormComponentValues()
  41.                 .setName("绩效指标")
  42.                 .setValue(performance.getAppraiseeIndex());
  43.         formComponentValues.add(formComponentValues17);
  44.         StartProcessInstanceRequest.StartProcessInstanceRequestFormComponentValues formComponentValues16 = new StartProcessInstanceRequest.StartProcessInstanceRequestFormComponentValues()
  45.                 .setName("绩效等级")
  46.                 .setValue(performance.getGrade());
  47.         formComponentValues.add(formComponentValues16);
  48.         StartProcessInstanceRequest.StartProcessInstanceRequestFormComponentValues formComponentValues15 = new StartProcessInstanceRequest.StartProcessInstanceRequestFormComponentValues()
  49.                 .setName("评语")
  50.                 .setValue(performance.getComment());
  51.         formComponentValues.add(formComponentValues15);
  52.         // 解析出钉钉用户所属部门
  53.         List<String> deptIds = new ArrayList<>(Arrays.asList(dingtalkDeptId.split(",")));
  54.         long deptId = 0l;
  55.         if (!CollectionUtils.isEmpty(deptIds)) {
  56.             deptId = Long.valueOf(deptIds.get(0));
  57.         }
  58.         // 发起流程
  59.         String processInstanceId = "";
  60.         String errMsg = "";
  61.         Map<String, Object> returnMap = new HashMap<>();
  62.         StartProcessInstanceRequest startProcessInstanceRequest = new StartProcessInstanceRequest()
  63.                 .setOriginatorUserId(dingtalkUserId)
  64.                 .setDeptId(deptId)
  65.                 .setProcessCode(processCode)
  66.                 .setFormComponentValues(formComponentValues);
  67.         Gson gson = new Gson();
  68.         logger.info("发起流程实例的请求对象:" + gson.toJson(formComponentValues)+"-"+dingtalkUserId+"-"+deptId+"-"+processCode);
  69.         try {
  70.             StartProcessInstanceResponse response = client.startProcessInstanceWithOptions(startProcessInstanceRequest,
  71.                     startProcessInstanceHeaders, new RuntimeOptions());
  72.             if (!ObjectUtils.isEmpty(response)) {
  73.                 processInstanceId = response.getBody().getInstanceId();
  74.             }
  75.         } catch (TeaException err) {
  76.             logger.info("发起流程调用钉钉接口异常信息对象:" + gson.toJson(err));
  77.             if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
  78.                 logger.info("发起流程调用钉钉接口返回错误信息:" + ExceptionUtils.getStackTrace(err));
  79.                 returnMap.put("errMsg", err.message);
  80.                 return returnMap;
  81.             }
  82.         } catch (Exception _err) {
  83.             logger.info("发起流程调用钉钉接口异常信息对象:" + gson.toJson(_err));
  84.             TeaException err = new TeaException(_err.getMessage(), _err);
  85.             if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
  86.                 returnMap.put("errMsg", err.message);
  87.                 return returnMap;
  88.             }
  89.         }
  90.         // 如果启动流程实例成功,返回流程实例id
  91.         if (StringUtils.isNotBlank(processInstanceId)) {
  92.             returnMap.put("processInstanceId", processInstanceId);
  93.         }
  94.         return returnMap;
  95.     }
复制代码
4. 查询流程审核日记

预测审批流程节点信息 (需要用到前文提到的钉钉流程模板 code)
  1. /**
  2.      * 审批流程预测
  3.      * @param processCode
  4.      * @param userId
  5.      * @param deptId
  6.      * @param accessToken
  7.      * @return
  8.      * @throws Exception
  9.      */
  10.     public static Map<String, Object> forecastProcesses(String processCode, String userId, Integer deptId, String accessToken, List<ProcessForecastRequest.ProcessForecastRequestFormComponentValues> formComponentValues) throws Exception {
  11.         Map<String, Object> returnMap = new HashMap<>();
  12.         com.aliyun.dingtalkworkflow_1_0.Client client = createClient();
  13.         com.aliyun.dingtalkworkflow_1_0.models.ProcessForecastHeaders processForecastHeaders
  14.                 = new com.aliyun.dingtalkworkflow_1_0.models.ProcessForecastHeaders();
  15.         processForecastHeaders.xAcsDingtalkAccessToken = accessToken;
  16.         com.aliyun.dingtalkworkflow_1_0.models.ProcessForecastRequest processForecastRequest
  17.                 = new com.aliyun.dingtalkworkflow_1_0.models.ProcessForecastRequest()
  18.                 .setDeptId(deptId)
  19.                 .setUserId(userId)
  20.                 .setProcessCode(processCode)
  21.                 .setFormComponentValues(formComponentValues);
  22.         try {
  23.             ProcessForecastResponse response = client.processForecastWithOptions(processForecastRequest, processForecastHeaders, new com.aliyun.teautil.models.RuntimeOptions());
  24.             if (!ObjectUtils.isEmpty(response) && response.getBody().getResult().isForecastSuccess) {
  25.                 // 审批节点
  26.                 List<ProcessForecastResponseBody.ProcessForecastResponseBodyResultWorkflowActivityRules> activityRules = response.getBody().getResult().getWorkflowActivityRules();
  27.                 if (!CollectionUtils.isEmpty(activityRules)) {
  28.                     activityRules.forEach(rule -> {
  29.                         returnMap.put(rule.getActivityId(), rule.getActivityName());
  30.                     });
  31.                 }
  32.             }
  33.         } catch (TeaException err) {
  34.             if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
  35.                 // err 中含有 code 和 message 属性,可帮助开发定位问题
  36.                 logger.info("调用钉钉流程审批预测接口-错误信息:" + err.message);
  37.             }
  38.         } catch (Exception _err) {
  39.             TeaException err = new TeaException(_err.getMessage(), _err);
  40.             if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
  41.                 // err 中含有 code 和 message 属性,可帮助开发定位问题
  42.                 logger.info("调用钉钉流程审批预测接口-错误信息:" + err.message);
  43.             }
  44.         }
  45.         return returnMap;
  46.     }
复制代码
查询审核流程的各个节点的审核结果
  1. /**
  2.      * 查询单个钉钉流程实例的 审核状态 审核结果
  3.      * @param instanceId
  4.      * @param accessToken
  5.      * @return
  6.      * @throws Exception
  7.      */
  8.     public static Map<String, Object> processInstanceStatusAndResult(String instanceId, String accessToken) throws Exception {
  9.         Map<String, Object> returnMap = new HashMap<>();
  10.         com.aliyun.dingtalkworkflow_1_0.Client client = createClient();
  11.         com.aliyun.dingtalkworkflow_1_0.models.GetProcessInstanceHeaders getProcessInstanceHeaders
  12.                 = new com.aliyun.dingtalkworkflow_1_0.models.GetProcessInstanceHeaders();
  13.         getProcessInstanceHeaders.xAcsDingtalkAccessToken = accessToken;
  14.         com.aliyun.dingtalkworkflow_1_0.models.GetProcessInstanceRequest getProcessInstanceRequest
  15.                 = new com.aliyun.dingtalkworkflow_1_0.models.GetProcessInstanceRequest()
  16.                 .setProcessInstanceId(instanceId);
  17.         try {
  18.             GetProcessInstanceResponse response = client.getProcessInstanceWithOptions(getProcessInstanceRequest, getProcessInstanceHeaders, new com.aliyun.teautil.models.RuntimeOptions());
  19.             if (!ObjectUtils.isEmpty(response) && "true".equals(response.getBody().getSuccess())) {
  20.                 // 审批状态
  21.                 String status = response.getBody().getResult().getStatus();
  22.                 // 审批结果
  23.                 String approvalResult = response.getBody().getResult().getResult();
  24.                 if (StringUtils.isNotBlank(approvalResult)) {
  25.                     returnMap.put("approvalResult", approvalResult);
  26.                 }
  27.                 if (StringUtils.isNotBlank(status)) {
  28.                     returnMap.put("status", status);
  29.                 }
  30.                 // 查询历史审核节点和当前的审核节点
  31.                 List<GetProcessInstanceResponseBody.GetProcessInstanceResponseBodyResultTasks> tasks = response.getBody().getResult().getTasks();
  32.                 if (!CollectionUtils.isEmpty(tasks)) {
  33.                     returnMap.put("tasks", tasks);
  34.                 }
  35.                 // 审核日志 评论
  36.                 List<GetProcessInstanceResponseBody.GetProcessInstanceResponseBodyResultOperationRecords> records = response.getBody().getResult().getOperationRecords();
  37.                 if (!CollectionUtils.isEmpty(records)) {
  38.                     returnMap.put("operationRecords", records);
  39.                 }
  40.             }
  41.         } catch (TeaException err) {
  42.             if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
  43.                 // err 中含有 code 和 message 属性,可帮助开发定位问题
  44.                 logger.info("调用钉钉查询单个流程的审批实例详情接口-错误信息:" + err.message);
  45.             }
  46.         } catch (Exception _err) {
  47.             TeaException err = new TeaException(_err.getMessage(), _err);
  48.             if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {
  49.                 // err 中含有 code 和 message 属性,可帮助开发定位问题
  50.                 logger.info("调用钉钉查询单个流程的审批实例详情接口-错误信息:" + err.message);
  51.             }
  52.         }
  53.         return returnMap;
  54.     }
复制代码
5. 流程审核同意回调接口

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

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

https://sso.abc.com/dd/login?code=81168e62b8a53a1ab2d6c4dff0d2e26f
code 前端通过钉钉JSAPI获得的暂时访问码
后台结合业务系统的token校验机制,匹配用户的钉钉信息,如果匹配乐成,则视为合法用户,返回token
返回参数示例
  1. {
  2.         "code": 200,
  3.         "data": {
  4.                 "password_status": "N",
  5.                 "user_id": "12345",
  6.                 "user_info": "mary",
  7.                 "dingtalkUnionId": "lkgPd1UY5c6dN7EZ9CTdEgiEiE",
  8.                 "dingtalkUserId": "123456",
  9.                 "token": "1d9862bcdb7d345f55ed08673388bb84",
  10.                 "user_no": "456789",
  11.                 "dingtalkDeptIds": [
  12.                         1289839283
  13.                 ]
  14.         },
  15.         "message": "success"
  16. }
复制代码
8. 查验token合法性接口

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


 
token  当前用户存储在 localStorage 中的token
后台结合业务系统的token校验机制判定token是否合法,如果合法,正常访问系统API,如果不合法,引导用户重新登录。
接口返回参数示例
  1. {
  2.         "code": 200,
  3.         "userno": "123456",
  4.         "name": "mary",
  5.         "id": "456123",
  6.         "tenant": "abc",
  7.         "email": "abc@qq.com",
  8.         "token": "7ee22693d72a0f4aa0d1ad041c0dccac"
  9. }
复制代码


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4