SAP->钉钉审批集成流程图
钉钉开放平台下载证书
进入钉钉开放平台,找到钉钉被调用api的域名
钉钉获取应用token官方文档
请求方法处可以找到域名,直接通过域名访问api地址下载证书
域名:api.dingtalk.com
访问域名获取证书信息
有三层证书,每一层都需要单独下载导出到本地
SAP STRUST导入证书
进入sap系统输入事务码STRUST
按如下步骤以此导入之前下载的三个证书
SAP SM59新建sap与外部系统的http连接
事务码sm59
选中到外部服务器的http连接,再选中创建
参考如下信息创建
登录&安全性处,需要将安全协议选择【活动】,ssl证书选择刚刚钉钉证书导入的【标准】
如果证书无误,此时点击上方连接测试,即可收到2xx响应成功的反馈
此处如果提示ssl证书错误,重复前两部重新导入证书
SE37 封装通用请求函数
使用该函数避免相同调用接口代码重复使用,故封装成统一请求函数
通过SM59创建HTTP实例的目的也是为了将域名和请求地址解耦
FUNCTION ZDD_SEND_TO_DING .
*"----------------------------------------------------------------------
*"*"本地接口:
*" IMPORTING
*" REFERENCE(IV_PATH) TYPE STRING OPTIONAL 接口后缀
*" REFERENCE(IV_KEY) TYPE STRING OPTIONAL
*" REFERENCE(IV_BODY) TYPE STRING OPTIONAL
*" REFERENCE(IV_METHOD) TYPE STRING OPTIONAL
*" EXPORTING
*" REFERENCE(EV_DATA)
*" REFERENCE(EV_STATUS)
*" TABLES
*" IT_HEADER STRUCTURE ZDDS001 OPTIONAL
*" IT_PARAM STRUCTURE ZDDS001 OPTIONAL
*"----------------------------------------------------------------------
*&---Global data declarations
DATA: lr_http_client TYPE REF TO if_http_client,
lv_url TYPE string,
lv_xstr TYPE xstring,
lv_len TYPE i,
lv_result TYPE string.
DATA: lv_key TYPE string,
lv_value TYPE string,
lv_entcode TYPE string,
lv_json TYPE string,
lr_json_serializer TYPE REF TO cl_trex_json_serializer,
lr_json_deserializer TYPE REF TO cl_trex_json_deserializer,
lv_sign TYPE string,
lv_msg TYPE string,
lv_name TYPE string,
lv_path TYPE string.
*&--- 根据SM59 创建 HTTP 对象实例
cl_http_client=>create_by_destination(
EXPORTING
destination = 'DINGTALK'
IMPORTING
client = lr_http_client
EXCEPTIONS
argument_not_found = 1
destination_no_authority = 2
destination_not_found = 3
internal_error = 4
plugin_not_active = 5
OTHERS = 6
).
*&---- 设置PARAM
lv_path = iv_path.
LOOP AT it_param INTO DATA(ls_param).
AT FIRST.
lv_path = iv_path && '?'.
ENDAT.
lv_path = lv_path && ls_param-key && '=' && ls_param-value && '&'.
ENDLOOP.
lv_path = shift_right( val = iv_path sub = |&| ). " 去除末尾多余&
*&--- 补充后缀
cl_http_utility=>set_request_uri(
EXPORTING
request = lr_http_client->request
uri = lv_path
).
*&---- 设置调用方法
CALL METHOD lr_http_client->request->set_header_field
EXPORTING
name = '~request_method'
value = iv_method.
*&---- 设置调用方法
CALL METHOD lr_http_client->request->set_header_field
EXPORTING
name = 'Content-Type' ##NO_TEXT
value = 'application/json'.
*&---- 设置HEADER
LOOP AT it_header INTO DATA(ls_header).
lv_key = ls_header-key.
lv_value = ls_header-value.
CALL METHOD lr_http_client->request->set_header_field
EXPORTING
name = lv_key ##NO_TEXT
value = lv_value.
ENDLOOP.
lv_json = iv_body.
CONDENSE lv_json.
CALL FUNCTION 'SCMS_STRING_TO_XSTRING'
EXPORTING
text = lv_json
encoding = '4110'
IMPORTING
buffer = lv_xstr
EXCEPTIONS
failed = 1
OTHERS = 2.
lr_http_client->request->set_data( data = lv_xstr ).
*&--- 数据发送
CALL METHOD lr_http_client->send
EXCEPTIONS
http_communication_failure = 1
http_invalid_state = 2
http_processing_failed = 3
http_invalid_timeout = 4.
IF sy-subrc <> 0.
lr_http_client->get_last_error( IMPORTING message = ev_data ).
ev_status = 'E'.
RETURN.
ENDIF.
*&--- 返回结果判断
CALL METHOD lr_http_client->receive
EXCEPTIONS
http_communication_failure = 1
http_invalid_state = 2
http_processing_failed = 3.
IF sy-subrc <> 0 . " 连接异常
lr_http_client->get_last_error( IMPORTING message = ev_data ).
ev_status = 'E'.
RETURN.
ELSE.
ev_data = lr_http_client->response->get_cdata( ).
CALL METHOD lr_http_client->close.
ev_status = 'S'.
ENDIF.
ENDFUNCTION.
SE37 封装获取钉钉 token函数
获取token的前提是需要有相关权限,参考官方文档配置权限
钉钉获取应用token官方文档
获取token函数几乎在每个接口前均需调用,因为钉钉的鉴权机制要求调用其他接口前需传入token
新建一个钉钉应用接口的配置表,用于存储不同应用的token,因为频繁调用获取token接口将会被拦截,使用中间表可以降低访问频次同时也节省接口调用次数
FUNCTION zdd_get_token.
*"----------------------------------------------------------------------
*"*"本地接口:
*" IMPORTING
*" REFERENCE(IV_APPNAME) TYPE ZEL_APPNAME
*" EXPORTING
*" REFERENCE(EV_TOKEN) TYPE ZEL_TOKEN
*"----------------------------------------------------------------------
*----------------------------------------------------------------------*
* 结构声明类型/Structure type declaration
*----------------------------------------------------------------------*
TYPES: BEGIN OF ty_request,
appkey TYPE string,
appsecret TYPE string,
END OF ty_request.
TYPES: BEGIN OF ty_result,
expirein TYPE i,
accesstoken TYPE string,
END OF ty_result.
*----------------------------------------------------------------------*
* 变量定义/Global variable definition
*----------------------------------------------------------------------*
DATA: ls_request TYPE ty_request,
ls_result TYPE ty_result,
ls_zddt001 TYPE zddt001,
lv_body TYPE string,
lv_path TYPE string,
lv_method TYPE string,
lv_result TYPE string,
lv_status TYPE c.
lv_path = '/v1.0/oauth2/accessToken'. " 获取token
lv_method = 'POST'.
" 根据应用获取token信息
SELECT SINGLE
appname , " 应用名称
appkey , " appKey
appsecret , " appSecret
token , " token
overdate , " token过期日
overtime " token过期时间
FROM zddt001
WHERE
appname = @iv_appname
INTO CORRESPONDING FIELDS OF @ls_zddt001.
" 校验过期日
" 钉钉有限额,且频繁访问token会被拦截
IF sy-datum <= ls_zddt001-overdate AND sy-uzeit < ls_zddt001-overtime.
" 使用已保存的token
ev_token = ls_zddt001-token.
ELSE.
" token已过期则重新获取
" 结构转换json
MOVE-CORRESPONDING ls_zddt001 TO ls_request.
lv_body = /ui2/cl_json=>serialize(
data = ls_request
compress = abap_true
pretty_name = /ui2/cl_json=>pretty_mode-camel_case ).
" 替换请求字符
REPLACE ALL OCCURRENCES OF 'appkey' IN lv_body WITH 'appKey'.
REPLACE ALL OCCURRENCES OF 'appsecret' IN lv_body WITH 'appSecret'.
CALL FUNCTION 'ZDD_SEND_TO_DING'
EXPORTING
iv_path = lv_path
iv_body = lv_body
iv_method = lv_method
IMPORTING
ev_data = lv_result
ev_status = lv_status.
IF lv_status = 'E'.
" 异常处理
ELSE.
" 返回成功
" 解析json
/ui2/cl_json=>deserialize(
EXPORTING
json = lv_result
CHANGING data = ls_result ).
IF ls_result IS INITIAL.
" 异常处理
ELSE.
" 更新过期日
IF sy-uzeit > '220000'. " 跨日
ls_zddt001-overtime = '000000'.
ls_zddt001-overdate = sy-datum + 1.
ELSE.
ls_zddt001-overtime = sy-uzeit + ( 60 * 90 ). " 设置逾期时间90分钟后(官网是2小时,稍微提前一点)
ls_zddt001-overdate = sy-datum.
ENDIF.
ls_zddt001-token = ls_result-accesstoken.
MODIFY zddt001 FROM ls_zddt001.
ENDIF.
ENDIF.
ev_token = ls_result-accesstoken.
ENDIF.
ENDFUNCTION.
创建钉钉待办任务示例
到上一步为止基本的框架就搭建好了,后面的步骤就是封装其他功能接口做对接,以创建钉钉待办为例子
创建钉钉待办任务
FUNCTION zdd_create_task.
*"----------------------------------------------------------------------
*"*"本地接口:
*" EXPORTING
*" REFERENCE(EV_ID) TYPE STRING
*"----------------------------------------------------------------------
*----------------------------------------------------------------------*
* 结构声明类型/Structure type declaration
*----------------------------------------------------------------------*
" 请求body
TYPES: BEGIN OF ty_request,
subject TYPE string, " 主题
description TYPE string, " 描述
duetime TYPE p, " 逾期时间
END OF ty_request.
" 响应
TYPES: BEGIN OF ty_result,
id TYPE string, " 待办id
END OF ty_result.
*----------------------------------------------------------------------*
* 变量定义/Global variable definition
*----------------------------------------------------------------------*
DATA: lt_header TYPE TABLE OF zdds001,
ls_header TYPE zdds001,
ls_request TYPE ty_request,
ls_result TYPE ty_result,
lv_token TYPE zel_token,
lv_body TYPE string,
lv_path TYPE string,
lv_method TYPE string,
lv_result TYPE string,
lv_status TYPE c.
" 调用的路径可以再设计自建表存储,暂时没想好用哪些字段
lv_path = '/v1.0/todo/users/PUoiinWIpa2yH2ymhiiGiP6g/tasks?operatorId=PUoiinWIpxxx'.
lv_method = 'POST'.
" 请求body
ls_request-subject = '待办事项测试'.
ls_request-description = '你有一个待办事项'.
ls_request-duetime = 1684287389233.
" 请求token
CALL FUNCTION 'ZDD_GET_TOKEN'
EXPORTING
iv_appname = 'DEMO'
IMPORTING
ev_token = lv_token.
" token填入header
ls_header-key = 'x-acs-dingtalk-access-token'.
ls_header-value = lv_token.
APPEND ls_header TO lt_header.
" 请求body转json
lv_body = /ui2/cl_json=>serialize(
data = ls_request
compress = abap_true
pretty_name = /ui2/cl_json=>pretty_mode-camel_case ).
REPLACE ALL OCCURRENCES OF 'duetime' IN lv_body WITH 'dueTime'.
" 发送钉钉请求待办
CALL FUNCTION 'ZDD_SEND_TO_DING'
EXPORTING
iv_path = lv_path
iv_body = lv_body
iv_method = lv_method
IMPORTING
ev_data = lv_result
ev_status = lv_status
TABLES
it_header = lt_header.
IF lv_status = 'E'.
" SM59连接异常
ELSE.
" 返回成功
" 解析json
/ui2/cl_json=>deserialize(
EXPORTING
json = lv_result
CHANGING data = ls_result ).
IF ls_result IS INITIAL.
" 未正常解析,异常
ELSE.
ev_id = ls_result-id.
ENDIF.
ENDIF.
ENDFUNCTION.
调用结果
PS:钉钉待办事项有新版和旧版,下图为旧版待办事项,如果为新版待办事项可能显示不出来,在其他栏位藏着