引言
最近在项目上有做一些restful接口的需求,其中涉及到多个外围系统,就想着如何通过只发布一个服务,让个外围系统传入不同的报文,来决定动态的调用不同的业务处理程序,于是就有了这篇博客.
实现过程
跟外围系统定义报文格式的时候,确定了报文的总体格式,要求不管什么接口,总体格式不变。结构如下:
{
"HEAD": {
"TARGET_SYS": "SAP",
"INTF_ID": "INT_FI001",
"SOURCE_SYS": "OA"
},
"DATA":""
}
其中定义了两个固定格式,head与data,head中包含了接口的目标系统、源系统以及接口编号,data部分用于传输业务数据,结构与业务字段需根据实际确定。
确定好报文格式后,剩下来的只需要在SAP内处理了。代码如下:
METHOD if_rest_resource~post.
DATA: lo_json TYPE REF TO zcl_json_to_data,
lo_object TYPE REF TO object,
lo_infter TYPE REF TO zif_interface_http,
lv_clname TYPE c LENGTH 30,
ls_head TYPE zsifheadinfo,
ls_return TYPE bapiret2.
DATA: lv_data TYPE REF TO data.
"获取报文。
DATA(lv_json) = mo_request->get_entity( )->get_string_data( ).
"通过Json动态生成内部结构
CREATE OBJECT lo_json.
lo_json->deserialize_json_declare_main(
EXPORTING
iv_json = lv_json
IMPORTING
data = lv_data ).
IF lv_data IS BOUND.
ASSIGN lv_data->* TO FIELD-SYMBOL(<fs_str>).
ELSE.
ls_return-message = '结构生成异常'.
me->return_error( ls_return ).
RETURN.
ENDIF.
IF <fs_str> IS ASSIGNED..
/ui2/cl_json=>deserialize(
EXPORTING
json = lv_json
CHANGING
data = <fs_str> ).
ENDIF.
IF <fs_str> IS INITIAL.
ls_return-message = '数据解析异常'.
me->return_error( ls_return ).
RETURN.
ENDIF.
ASSIGN COMPONENT 'HEAD' OF STRUCTURE <fs_str> TO FIELD-SYMBOL(<fs_head>).
IF sy-subrc NE 0 OR <fs_head> IS NOT ASSIGNED.
ls_return-message = '数据解析异常:缺少HEAD数据'.
me->return_error( ls_return ).
RETURN.
ELSE.
MOVE-CORRESPONDING <fs_head> TO ls_head.
ENDIF.
ASSIGN COMPONENT 'DATA' OF STRUCTURE <fs_str> TO FIELD-SYMBOL(<fs_data>).
IF sy-subrc NE 0 OR <fs_data> IS NOT ASSIGNED.
ls_return-message = '数据解析异常:缺少DATA数据'.
me->return_error( ls_return ).
RETURN.
ENDIF.
"根据接口id确定业务实现类.
SELECT SINGLE zfname INTO lv_clname
FROM ztiftable
WHERE intf_id = ls_head-intf_id
AND zactive = 'X'
AND zinout = 'I'.
TRY.
CREATE OBJECT lo_object TYPE (lv_clname)
EXPORTING
iv_head = ls_head
iv_uuid = cl_system_uuid=>create_uuid_c36_static( ).
CATCH cx_sy_create_object_error INTO DATA(cx_sy_create_object_error).
ls_return-message = cx_sy_create_object_error->get_text( ).
me->return_error( ls_return ).
RETURN.
CATCH cx_sy_dyn_call_illegal_method INTO DATA(cx_sy_dyn_call_illegal_method).
ls_return-message = cx_sy_dyn_call_illegal_method->get_text( ).
me->return_error( ls_return ).
RETURN.
CATCH cx_sy_dyn_call_illegal_type INTO DATA(cx_sy_dyn_call_illegal_type).
ls_return-message = cx_sy_dyn_call_illegal_type->get_text( ).
me->return_error( ls_return ).
RETURN.
CATCH cx_sy_dyn_call_param_not_found INTO DATA(cx_sy_dyn_call_param_not_found).
ls_return-message = cx_sy_dyn_call_param_not_found->get_text( ).
me->return_error( ls_return ).
RETURN.
ENDTRY.
IF lo_object IS INSTANCE OF zif_interface_http.
lo_infter ?= lo_object.
ENDIF.
IF lo_infter IS INITIAL.
ls_return-message = '对象未引用接口类(zif_interface_http)'.
me->return_error( ls_return ).
RETURN.
ENDIF.
DATA: re_data TYPE string,
iv_data TYPE string.
iv_data = /ui2/cl_json=>serialize( <fs_data> ).
lo_infter->me_procrss_data(
EXPORTING
iv_data = iv_data
CHANGING
re_data = re_data
re_return = ls_return ).
IF re_data IS NOT INITIAL.
mo_response->set_reason( 'ok' ).
mo_response->set_status( 200 ).
mo_response->create_entity( )->set_string_data( re_data ).
mo_response->create_entity( )->set_content_type( if_rest_media_type=>gc_appl_json ).
ELSE.
me->return_error( ls_return ).
ENDIF.
ENDMETHOD.
以上代码的大致内容就是,通过接口传入的报文,生成对应的abap内部类型,进一步将报文转换为内表或结构体,从而根据HEAD与DATA字段来将其拆分处理,HEAD为接口相关数据,DATA为业务数据。之后通过接口编号找到对应的类名,实例化出一个对应的对象,后面如果lo_object是zif_interface_http接口的实例,则将其引用赋值给lo_infter变量,这样就可以利用lo_infter来调用zif_interface_http接口中定义的方法来进行业务处理,在处理之前将data数据执行了abap2json的转换,用于更方便传递内容。
以上内容涉及到一个类与一个接口,zcl_json_to_data类参考另一位大佬的项目,根据json生成abap对象,实际使用时有一些调整。接zif_interface_http的方法如下:
interface ZIF_INTERFACE_HTTP
public .
methods ME_PROCRSS_DATA
importing
!IV_DATA type STRING optional
!IV_UUID type STRING optional
changing
value(RE_DATA) type STRING optional
!RE_RETURN type BAPIRET2 optional .
methods ME_CHECK_DATA
changing
value(RE_RETURN) type BAPIRET2 .
methods ME_SAVE_DATA
changing
value(RE_RETURN) type BAPIRET2 .
methods ME_CALL_BAPI
changing
value(RE_RETURN) type BAPIRET2 .
methods ME_ABAP2JSON
importing
!IV_RETURN type BAPIRET2
returning
value(EV_DATA) type STRING .
methods ME_JSON2DATA
importing
!IV_DATA type STRING optional
changing
value(RE_DATA) type DATA .
endinterface.
配置表中的业务处理类只有一个实例构造方法,其他方法引用接口。代码如下
METHOD zif_interface_http~me_procrss_data.
DATA lo_error TYPE REF TO cx_sy_dyn_call_illegal_method.
TRY.
me->zif_interface_http~me_json2data( EXPORTING iv_data = iv_data CHANGING re_data = me->mt_data ).
me->zif_interface_http~me_check_data( CHANGING re_return = re_return ).
me->zif_interface_http~me_call_bapi( CHANGING re_return = re_return ).
me->zif_interface_http~me_save_data( CHANGING re_return = re_return ).
re_data = me->zif_interface_http~me_abap2json( EXPORTING iv_return = re_return ).
CATCH cx_sy_dyn_call_illegal_method INTO lo_error.
re_return-message = lo_error->if_message~get_text( ).
ENDTRY.
ENDMETHOD.
处理之后的响应报文由各不同的类传递出json对象,直接返回给发送方。
剩下的就是一个返回错误的方法,没什么好说的,返回一个400的报错.
PS:本博文的服务发布过程参考我的另一篇文章SAP 发布HTTP接口之完整的Restful 含( 含Token 验证)