曂沅仴駦 发表于 2024-6-11 12:43:07

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

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

后端代码中集成钉钉官方OA审批API:
表布局设计、钉钉API访问工具类、发起流程实例接口、查询流程审核日记接口、流程审核同意回调接口、流程审核拒绝回调接口、钉钉免登录接口。

1. 表布局设计

在已经业务数据表布局的底子上添加钉钉审核流程相关字段:
字段名称            功能描述dingtalk_union_id钉钉unionIddingtalk_user_id钉钉userIddingtalk_dept_id钉钉用户所属部门id 多个逗号分隔process_id钉钉流程实例idauditing_result审核结果 同意agree  拒绝refuseauditing_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企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 钉钉二次开辟-企业内部系统集成官方OA审批流程(二)