一、前言
在 SAP 系统与外部系统对接场景中,RESTful 接口是数据交互的常用方案。传统接口往往需要针对性编码,新增业务对接需求时改动繁琐、复用性差。
本文介绍基于 SICF 实现的可配置 RESTful 接口方案。该框架搭建完成后,后续开发无需修改接口底层代码,只需编写对应业务 RFC 函数,并在自定义配置表中维护请求 ID 与目标函数的映射关系,就能快速实现数据对接与接口访问。整体方案上手简单、使用便捷,能极大提升接口开发与迭代效率。
二、方案介绍
2.1 实现思路
基于 SAP 原生 HTTP 服务 SICF + 标准接口 IF_HTTP_EXTENSION 实现 RESTful 风格接口。
通过自定义配置表 ZCAT004 做路由映射:
外部传入唯一请求标识 REQKEYID → 系统自动匹配对应 RFC 函数 → 动态调用函数并返回 JSON 结果。
2.2 整体调用流程
外部 POST 请求 → SICF 服务节点 → HANDLE_REQUEST 入口 → 解析 JSON 获取 REQKEYID → 查询 ZCAT004 匹配 RFC 函数 → 动态组装参数 → 动态调用函数 → 结果序列化 JSON 返回。
三、配置步骤
3.1 创建配置表 ZCAT004
主要用于存储请求ID — RFC函数名 映射关系,实现接口路由可配置化。其余字段大家也可以按照业务需求自行添加。
a. 进入 SE11,输入需要创建的表名 ZCAT004,点击创建
b. 在字段页签中添加如下字段,数据元素可以按照图中的数据类型自行定义。接口唯一ID和功能模块两个字段注意不要修改,后续 SICF 服务中会用到
| 字段 | 关键 字段 | 初始值 | 数据元素 | 数据 类型 | 长度 | 简短描述 |
|---|---|---|---|---|---|---|
| MANDT | √ | √ | MANDT | CLNT | 3 | 客户端 |
| REQKEYID | √ | √ | ZE_REQKEYID | CHAR | 40 | 接口唯一ID |
| FUNCNAME | RS38L_FNAM | CHAR | 30 | 功能模块的名称 | ||
| WORKFLOWID | ZE_WORKFLOWID | CHAR | 10 | OA流程id | ||
| TITLE | ZE_TITLE | CHAR | 40 | OA流程描述 | ||
| URL | ZE_URL | CHAR | 255 | 接口地址 | ||
| ZCRDATE7 | ERNAM | CHAR | 12 | 负责创建对象的人员姓名 |
3.2 创建 HTTP 处理类 ZCL_HTTP
实现标准接口:IF_HTTP_EXTENSION
a. 创建类名称
进入 SE24,输入 ZCL_HTTP,点击创建
b. 实现接口 IF_HTTP_EXTENSION
c. 添加系统函数调用方法 CALL_FUNCTION
这个方法的主要作用是接收前端传入的 JSON 请求 → 自动根据配置动态调用对应的 SAP 后台函数 → 把函数执行结果转回 JSON 返回给前端
为方法定义参数:
方法内容(直接复制可复用)
METHOD call_function. DATA: ptab TYPE abap_func_parmbind_tab, ptab_line TYPE abap_func_parmbind, etab TYPE abap_func_excpbind_tab, etab_line TYPE abap_func_excpbind, data_export TYPE REF TO data, data_ref TYPE REF TO data, * lt_mapping TYPE name_mappings, lv_response_json TYPE string. "接口返回参数 DATA: dyn_table TYPE REF TO data. DATA: lt_dd04l TYPE STANDARD TABLE OF dd04l. DATA:lv_error_msg TYPE bapi_msg. "JSON格式化 DATA(json_data) = /ui2/cl_json=>generate( json = json )."name_mappings = lt_mapping ). ASSIGN json_data->* TO FIELD-SYMBOL(<json_data>). IF <json_data> IS ASSIGNED. ASSIGN COMPONENT 'IS_REQ' OF STRUCTURE <json_data> TO FIELD-SYMBOL(<fs_req>). IF <fs_req> IS ASSIGNED. ASSIGN COMPONENT 'REQKEYID' OF STRUCTURE <fs_req>->* TO FIELD-SYMBOL(<fv_reqkeyid>). IF <fv_reqkeyid> IS ASSIGNED. DATA(lv_reqkeyid) = CONV /zyb/sappo_keyid( <fv_reqkeyid>->* ). "CHAR 40 ENDIF. ENDIF. ENDIF. SELECT fupararef~funcname, fupararef~paramtype, fupararef~pposition, fupararef~parameter, fupararef~structure FROM fupararef AS fupararef INNER JOIN zcat004 AS zcat004 ON zcat004~funcname = fupararef~funcname WHERE zcat004~reqkeyid = @lv_reqkeyid INTO TABLE @DATA(parameters_tab). IF sy-subrc <> 0. code = '400'. reason = 'target_function no exist'. EXIT. ENDIF. TRY. "函数入参动态拼接 LOOP AT parameters_tab ASSIGNING FIELD-SYMBOL(<parameter>). CLEAR ptab_line. ptab_line-name = <parameter>-parameter. ptab_line-kind = COND #( WHEN <parameter>-paramtype = 'E' THEN abap_func_importing WHEN <parameter>-paramtype = 'I' THEN abap_func_exporting WHEN <parameter>-paramtype = 'T' THEN abap_func_tables WHEN <parameter>-paramtype = 'C' THEN abap_func_changing ELSE '' ). DATA(json_field_name) = COND string( WHEN ptab_line-kind = abap_func_exporting THEN 'IMPORT' WHEN ptab_line-kind = abap_func_tables THEN 'TABLE' WHEN ptab_line-kind = abap_func_changing THEN 'CHANGE' WHEN ptab_line-kind = abap_func_importing THEN 'EXPORT' ELSE '' ). DATA(lv_parameter) = <parameter>-parameter. "根据函数的入参匹配接口传入参数 ASSIGN COMPONENT lv_parameter OF STRUCTURE <json_data> TO FIELD-SYMBOL(<parameter_val>). IF sy-subrc <> 0 OR json_field_name = 'EXPORT'. CASE json_field_name. WHEN 'TABLE'. "创建动态表结构 CREATE DATA dyn_table TYPE TABLE OF (<parameter>-structure). "创建动态内表 ASSIGN dyn_table->* TO FIELD-SYMBOL(<dyn_table>). GET REFERENCE OF <dyn_table> INTO ptab_line-value. INSERT ptab_line INTO TABLE ptab. CONTINUE. WHEN OTHERS. "动态定义承接返回参数的结构 CREATE DATA data_export TYPE (<parameter>-structure). ASSIGN data_export TO FIELD-SYMBOL(<data_export>). ptab_line-value = <data_export>. INSERT ptab_line INTO TABLE ptab. CONTINUE. ENDCASE. ENDIF. IF json_field_name EQ 'TABLE'. CREATE DATA data_ref TYPE TABLE OF (<parameter>-structure). ELSE. CREATE DATA data_ref TYPE (<parameter>-structure)."如果是表类型,参考的是结构,是不是有问题呢 ENDIF. FIELD-SYMBOLS: <temp> TYPE any. ASSIGN <parameter_val>->* TO <temp>. IF data_ref IS BOUND. ASSIGN data_ref->* TO FIELD-SYMBOL(<data_ref>). ENDIF. "将传入参数按照特定格式转换 DATA(json_temp) = /ui2/cl_json=>serialize( data = <parameter_val> ). /ui2/cl_json=>deserialize( EXPORTING json = json_temp CHANGING data = <data_ref> )."name_mappings = lt_mapping GET REFERENCE OF <data_ref> INTO ptab_line-value. INSERT ptab_line INTO TABLE ptab. ENDLOOP. CATCH cx_root. ENDTRY. etab_line-name = 'OTHERS'. etab_line-value = 10. INSERT etab_line INTO TABLE etab. TRY. READ TABLE parameters_tab INTO DATA(ls_parameters_tab) INDEX 1. "动态调用函数,业务逻辑部分 CALL FUNCTION ls_parameters_tab-funcname PARAMETER-TABLE ptab EXCEPTION-TABLE etab. SORT parameters_tab BY parameter. CLEAR:data_ref. UNASSIGN:<data_ref>. LOOP AT ptab INTO ptab_line WHERE kind = abap_func_importing."只返回导出的数据 ASSIGN ptab_line-value->* TO <data_ref>. "Serialize Data to Json DATA(lv_string) = /ui2/cl_json=>serialize( data = <data_ref> )."name_mappings = lt_mapping lv_parameter = ptab_line-name. IF lv_response_json IS INITIAL. lv_response_json = |"{ lv_parameter }":{ lv_string }|. ELSE. lv_response_json = |{ lv_response_json },"{ lv_parameter }":{ lv_string }|. ENDIF. ENDLOOP. lv_response_json = '{' && lv_response_json && '}'. ev_response_json = lv_response_json. code = '200'. CATCH cx_sy_dyn_call_param_not_found INTO DATA(lo_error_no_found). lv_error_msg = lo_error_no_found->get_text( ). CATCH cx_sy_dyn_call_param_missing INTO DATA(lo_error_missing). lv_error_msg = lo_error_missing->get_text( ). CATCH cx_sy_dyn_call_parameter_error INTO DATA(lo_error_parameter). lv_error_msg = lo_error_parameter->get_text( ). CATCH cx_sy_dyn_call_error INTO DATA(lo_error_call). lv_error_msg = lo_error_call->get_text( ). ENDTRY. ENDMETHOD.d. 编写接口的唯一入口
上面创建的 call_function 是核心业务逻辑,而这段handle_request 是整个 HTTP 接口的唯一入口,也是SAP标准Web接口的固定入口方法。所有前端/第三方发来的HTTP请求,都会先进入这个方法,再转发给动态函数调用逻辑。
双击 IF_HTTP_EXTENSION~HANDLE_REQUEST 方法
编写代码(直接复制可复用)
METHOD if_http_extension~handle_request. DATA: lv_string TYPE string, "传入和传出JSON格式数据所用到的变量 lv_json_req TYPE string, "传入和传出JSON格式数据所用到的变量 lv_json_res TYPE string, "传入和传出JSON格式数据所用到的变量 lv_function TYPE string. **------获取调用时候传入的参数 * CLEAR:lt_request. lv_string = server->request->get_cdata( ). "获取传入的数据 "JSON序列化 * DATA(json) = /ui2/cl_json=>serialize( data = lv_string * compress = 'X' * pretty_name = /ui2/cl_json=>pretty_mode-camel_case ). * lv_function = server->request->get_header_field( 'FUCTION_NAME' )."这里的值是约定的function名称(HEADER参数) * IF lv_function IS INITIAL. * lv_function = server->request->get_form_field( 'FUCTION_NAME' )."这里的值是约定的function名称(FPRM-DATA参数) * ENDIF. * lv_json_res = '{"code":"S","MESSAGE":"成功"}'. *———————动态调用rfc函数---------------* me->call_function( EXPORTING json = lv_string IMPORTING ev_response_json = lv_json_res code = DATA(code) reason = DATA(reason) ). *------设置返回数据格式为JSON CALL METHOD server->response->if_http_entity~set_content_type EXPORTING content_type = 'application/json'. *------设置返回数据 server->response->set_cdata( EXPORTING data = lv_json_res " Character data ). server->response->set_status( code = code reason = reason ). ENDMETHOD.3.3 发布服务
a. 创建子元素
输入事务代码 SICF,按回车随后点击执行,在 /default_host/sap/ 目录下右击 sap,选择新的子元素,这里我命名成 ZHTTP
b. 配置处理器清单 ZCL_HTTP
填写刚才创建的 ZCL_HTTP 类,点击保存
c. 激活服务
回到菜单栏,右击 ZHTTP 子元素,选择激活服务
四、测试服务
4.1 在 ZCAT004 表添加映射规则
维护 REQKEYID 和 FUNCNAME 的对应关系:
4.2 编写函数模块
进入 SE37 编写功能函数模块,这里我写了一个简单的库存查询函数 ZMMFM045 ,可以按照工厂、物料编码、批次、库存地点以及货位等维度查询库存并以内表形式返回。代码略。。。。
4.3 使用 postman 测试
这里 SAP 的鉴权方式使用 Basic Auth 的方式即可访问接口。