引言

最近在项目上有做一些restful接口的需求,其中涉及到多个外围系统,就想着如何通过只发布一个服务,让个外围系统传入不同的报文,来决定动态的调用不同的业务处理程序,于是就有了这篇博客.

实现过程

跟外围系统定义报文格式的时候,确定了报文的总体格式,要求不管什么接口,总体格式不变。结构如下:

{
    "HEAD": {
        "TARGET_SYS": "SAP",
        "INTF_ID": "INT_FI001",
        "SOURCE_SYS": "OA"
    },
    "DATA":""
}

其中定义了两个固定格式,head与data,head中包含了接口的目标系统、源系统以及接口编号,data部分用于传输业务数据,结构与业务字段需根据实际确定。

注意,由于在json转abap对象时没有指定具体数据类型,所以由前端系统将所有数据转成字符串类型传输,避免在sap将小数转换成浮点数。

原始数据
小数
转换后数据
浮点数

确定好报文格式后,剩下来的只需要在SAP内处理了。代码如下:

  METHOD if_rest_resource~post.
    DATA lo_object TYPE REF TO object.
    DATA lo_infter TYPE REF TO zif_interface_http.
    DATA lv_clname TYPE c LENGTH 30.
    DATA ls_head   TYPE zsifheadinfo.
    DATA ls_return TYPE bapiret2.
    DATA:BEGIN OF ls_data,
           head TYPE zsifheadinfo,
           data TYPE REF TO data,
         END OF ls_data.
    " 获取报文。
    DATA(lv_json) = mo_request->get_entity( )->get_string_data( ).
    /ui2/cl_json=>deserialize( EXPORTING json = lv_json
                               CHANGING  data = ls_data ).

    ASSIGN COMPONENT 'HEAD' OF STRUCTURE ls_data TO FIELD-SYMBOL(<fs_head>).
    IF sy-subrc <> 0 OR <fs_head> IS NOT ASSIGNED OR <fs_head> IS INITIAL.
      ls_return-message = '数据解析异常:缺少HEAD数据'.
      return_error( ls_return ).
      RETURN.
    ELSE.
      MOVE-CORRESPONDING <fs_head> TO ls_head.
    ENDIF.

    ASSIGN COMPONENT 'DATA' OF STRUCTURE ls_data TO FIELD-SYMBOL(<fs_data>).
    IF sy-subrc <> 0 OR <fs_data> IS NOT ASSIGNED OR <fs_data> IS INITIAL.
      ls_return-message = '数据解析异常:缺少DATA数据'.
      return_error( ls_return ).
      RETURN.
    ENDIF.

    " 根据接口id确定业务实现类
    SELECT SINGLE object_name 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_uuid_error INTO DATA(lo_uuid_error).
        ls_return-message = lo_uuid_error->get_text( ).
        return_error( ls_return ).
        RETURN.
      CATCH cx_sy_create_object_error INTO DATA(lo_object_error).
        ls_return-message = lo_object_error->get_text( ).
        return_error( ls_return ).
        RETURN.
      CATCH cx_sy_dyn_call_illegal_method INTO DATA(lo_illegal_method).
        ls_return-message = lo_illegal_method->get_text( ).
        return_error( ls_return ).
        RETURN.
      CATCH cx_sy_dyn_call_illegal_type INTO DATA(lo_illegal_type).
        ls_return-message = lo_illegal_type->get_text( ).
        return_error( ls_return ).
        RETURN.
      CATCH cx_sy_dyn_call_param_not_found INTO DATA(lo_param_not_found).
        ls_return-message = lo_param_not_found->get_text( ).
        return_error( ls_return ).
        RETURN.
      CATCH cx_root INTO DATA(lo_root).
        ls_return-message = lo_root->get_text( ).
        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)'.
      return_error( ls_return ).
      RETURN.
    ENDIF.

    DATA re_data TYPE string.
    DATA iv_data TYPE string.

    iv_data = /ui2/cl_json=>serialize( <fs_data> ).

    lo_infter->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.
      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的转换,用于更方便传递内容。
接口 zif_interface_http 的方法如下:

INTERFACE zif_interface_http
  PUBLIC.

  METHODS 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 check_data
    CHANGING VALUE(cs_return) TYPE bapiret2.

  METHODS save_data
    CHANGING VALUE(cs_return) TYPE bapiret2.

  METHODS call_bapi
    CHANGING VALUE(cs_return) TYPE bapiret2.

  METHODS abap2json
    IMPORTING iv_return      TYPE bapiret2
    RETURNING VALUE(ev_data) TYPE string.

  METHODS json2data
    IMPORTING iv_data        TYPE string OPTIONAL
    CHANGING  VALUE(re_data) TYPE data.

  METHODS lock_order
    CHANGING VALUE(cs_return) TYPE bapiret2.
ENDINTERFACE.

配置表中的业务处理类只有一个实例构造方法,其他方法引用接口。代码如下

    DATA lo_error TYPE REF TO cx_sy_dyn_call_illegal_method.

    TRY.
        zif_interface_http~json2data( EXPORTING iv_data = iv_data
                                               CHANGING  re_data = mt_data ).

        zif_interface_http~check_data( CHANGING cs_return = re_return ).

        zif_interface_http~lock_order( CHANGING cs_return = re_return ).

        zif_interface_http~call_bapi( CHANGING cs_return = re_return ).

        zif_interface_http~save_data( CHANGING cs_return = re_return  ).

        re_data = zif_interface_http~abap2json( iv_return = re_return ).

      CATCH cx_sy_dyn_call_illegal_method INTO lo_error.
        re_return-message = lo_error->if_message~get_text( ).
    ENDTRY.

处理之后的响应报文由各不同的类传递出json对象,直接返回给发送方。

剩下的就是一个返回错误的方法,没什么好说的,返回一个400的报错.
PS:本博文的服务发布过程参考我的另一篇文章SAP 发布HTTP接口之完整的Restful 含( 含Token 验证)

更多推荐