SAP 对接钉钉 Webhook 实现方案及证书问题排查
前言
近期公司全面启用钉钉,业务需求是将 SAP 系统的消息推送到钉钉,支持推送给特定人员或群聊。本文分享具体的实现路径:利用钉钉 AI 表格的自动化流程作为接收端,通过 SAP ABAP 接口发送数据。

一、钉钉配置
本次方案采用钉钉 AI 表格的自动化功能中的 Webhook 接口。相比传统的机器人 API,这种方式在参数传递和后续处理上更为灵活。
1. AI 表格创建
若未看到 AI 表格选项,请确认钉钉版本是否已更新。创建一个空白模板表格,设置好字段结构。该表格将作为 SAP 数据的存储后端,类似于日志表的功能。

2. 自动化流程设置
在表格编辑界面找到'自动化'入口,配置触发条件为'收到 Webhook 请求'。

配置完成后,系统会生成一个唯一的 Webhook 地址。SAP 调用该地址即可触发后续操作,如发送消息、添加待办等。
3. Webhook 接口参数
Webhook 本质是一个 API 接口,需定义传输的数据结构。建议将文本字段封装为结构体,便于解析。

参考官方文档进行详细配置:钉钉开发者文档
二、SAP 开发
接入逻辑与其他 RESTful 接口类似,建议在 SAP 中封装标准函数模块,便于复用。
1. 接口开发
核心在于 HTTP 客户端的构建与 JSON 序列化。以下是完整的 ABAP 代码示例,包含请求头设置、异常处理及响应解析。
FUNCTION ZTEST_API.
"*----------------------------------------------------------------------*
"*"本地接口:
"* IMPORTING
"* REFERENCE(I_BUDAT) TYPE BUDAT
"* REFERENCE(I_TYPE) TYPE BAPI_MTYPE
"* EXPORTING
"* VALUE(E_MSEG) TYPE BAPI_MSG
"* VALUE(E_TYPE) TYPE BAPI_MTYPE
"* TABLES
"* T_TAB STRUCTURE ZTXX_WEBHOOK
"*-------------------------------------------------------------------*
DATA: LV_ECS_JSON_REQ TYPE STRING,
LV_ECS_JSON_RES TYPE STRING,
LV_URL TYPE STRING,
LV_RESPONSE TYPE STRING.
DATA: LO_HTTP_CLIENT TYPE REF TO IF_HTTP_CLIENT.
DATA: LO_EXCEPTION TYPE REF TO CX_ROOT.
DATA: LV_ERROR_TEXT TYPE STRING.
TYPES: TY_DETAILS TYPE STANDARD TABLE OF ZTXX_WEBHOOK WITH DEFAULT KEY.
TYPES: BEGIN OF TY_REQUEST,
BUDAT TYPE BUDAT,
TYPE1 TYPE BAPI_MTYPE,
TAB TYPE TY_DETAILS,
END OF TY_REQUEST.
TYPES: BEGIN OF TY_RESPONSE,
DATA TYPE ABAP_BOOL,
SUCCESS TYPE ABAP_BOOL,
END OF TY_RESPONSE.
DATA: LS_REQUEST TYPE TY_REQUEST,
LS_RESPONSE TYPE TY_RESPONSE.
* 1. 准备请求数据
LS_REQUEST = VALUE #( BUDAT = I_BUDAT TYPE1 = I_TYPE ).
APPEND LINES OF T_TAB[] TO LS_REQUEST-TAB.
* 2. 序列化请求数据
TRY.
LV_ECS_JSON_REQ = /UI2/CL_JSON=>SERIALIZE( DATA = LS_REQUEST PRETTY_NAME = /UI2/CL_JSON=>PRETTY_MODE-LOW_CASE ).
CATCH CX_ROOT INTO LO_EXCEPTION.
E_MSEG = 'JSON 序列化失败:' && LO_EXCEPTION->GET_TEXT( ).
E_TYPE = 'E'.
RETURN.
ENDTRY.
* 3. 设置 URL(固定地址,可配置成后台表去读取)
LV_URL = 'webhook 地址'. "需替换为实际地址
* 4. 创建 HTTP 客户端
TRY.
CALL METHOD CL_HTTP_CLIENT=>CREATE_BY_URL
EXPORTING URL = LV_URL
IMPORTING CLIENT = LO_HTTP_CLIENT
EXCEPTIONS ARGUMENT_NOT_FOUND = 1
PLUGIN_NOT_ACTIVE = 2
INTERNAL_ERROR = 3
OTHERS = 4.
IF SY-SUBRC <> 0.
E_MSEG = '创建 HTTP 客户端失败'.
E_TYPE = 'E'.
RETURN.
ENDIF.
CATCH CX_ROOT INTO LO_EXCEPTION.
E_MSEG = '创建 HTTP 客户端异常:' && LO_EXCEPTION->GET_TEXT( ).
E_TYPE = 'E'.
RETURN.
ENDTRY.
* 5. 配置请求头
LO_HTTP_CLIENT->REQUEST->SET_METHOD( 'POST' ).
LO_HTTP_CLIENT->REQUEST->SET_CONTENT_TYPE( 'application/json' ).
LO_HTTP_CLIENT->REQUEST->SET_HEADER_FIELD( NAME = 'OperationCode' VALUE = 'xxxx.webhook' ). "需替换
LO_HTTP_CLIENT->REQUEST->SET_HEADER_FIELD( NAME = 'ClientId' VALUE = 'XXX' ). "需替换
* 设置请求数据
LO_HTTP_CLIENT->REQUEST->SET_CDATA( LV_ECS_JSON_REQ ).
* 6. 发送请求
TRY.
LO_HTTP_CLIENT->SEND( EXCEPTIONS HTTP_COMMUNICATION_FAILURE = 1
HTTP_INVALID_STATE = 2
OTHERS = 3 ).
IF SY-SUBRC <> 0.
LV_ERROR_TEXT = '发送请求失败'.
IF SY-SUBRC = 1. LV_ERROR_TEXT = 'HTTP 通信失败'.
ELSEIF SY-SUBRC = 2. LV_ERROR_TEXT = 'HTTP 状态无效'.
ENDIF.
E_MSEG = LV_ERROR_TEXT.
E_TYPE = 'E'.
LO_HTTP_CLIENT->CLOSE( ).
RETURN.
ENDIF.
CATCH CX_ROOT INTO LO_EXCEPTION.
E_MSEG = '发送请求异常:' && LO_EXCEPTION->GET_TEXT( ).
E_TYPE = 'E'.
LO_HTTP_CLIENT->CLOSE( ).
RETURN.
ENDTRY.
* 7. 接收响应
TRY.
LO_HTTP_CLIENT->RECEIVE( EXCEPTIONS HTTP_COMMUNICATION_FAILURE = 1
HTTP_INVALID_STATE = 2
OTHERS = 3 ).
IF SY-SUBRC <> 0.
LV_ERROR_TEXT = '接收响应失败'.
IF SY-SUBRC = 1. LV_ERROR_TEXT = 'HTTP 通信失败'.
ELSEIF SY-SUBRC = 2. LV_ERROR_TEXT = 'HTTP 状态无效'.
ENDIF.
E_MSEG = LV_ERROR_TEXT.
E_TYPE = 'E'.
LO_HTTP_CLIENT->CLOSE( ).
RETURN.
ENDIF.
CATCH CX_ROOT INTO LO_EXCEPTION.
E_MSEG = '接收响应异常:' && LO_EXCEPTION->GET_TEXT( ).
E_TYPE = 'E'.
LO_HTTP_CLIENT->CLOSE( ).
RETURN.
ENDTRY.
* 8. 获取响应数据
LV_RESPONSE = LO_HTTP_CLIENT->RESPONSE->GET_CDATA( ).
* 9. 关闭连接
LO_HTTP_CLIENT->CLOSE( ).
* 10. 解析响应数据
TRY.
/UI2/CL_JSON=>DESERIALIZE( EXPORTING JSON = LV_RESPONSE
PRETTY_NAME = /UI2/CL_JSON=>PRETTY_MODE-LOW_CASE
CHANGING DATA = LS_RESPONSE ).
CATCH CX_ROOT INTO LO_EXCEPTION.
E_MSEG = 'JSON 反序列化失败:' && LO_EXCEPTION->GET_TEXT( ).
E_TYPE = 'E'.
RETURN.
ENDTRY.
* 11. 处理响应结果
IF LS_RESPONSE-SUCCESS = ABAP_TRUE.
E_TYPE = 'S'.
E_MSEG = '接口调用成功'.
ELSE.
E_TYPE = 'E'.
E_MSEG = '接口调用失败'.
ENDIF.
ENDFUNCTION.






