sap restful json接口动态调用RFC,生成动态结构

Posted 想发财的小夏

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了sap restful json接口动态调用RFC,生成动态结构相关的知识,希望对你有一定的参考价值。

需求:项目上有一个restful接口需求,想着能不能做成动态的,只发布一个sap的restful接口出去,然后外围系统根据不同的json来动态调用sap的rfc。

结果:结合同事以前的开发demo和abapgit上面大佬的abap_fm_json demo,写了一个简易的demo,如下代码:

  METHOD http_broker.

    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,
          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.

    "检查函数是否存在
    SELECT funcname, paramtype, pposition, parameter, structure
      FROM fupararef
     WHERE funcname = @iv_function_name
      INTO TABLE @DATA(parameters_tab).

    IF sy-subrc <> 0.
      response->set_status( code = 400 reason = 'target_function no exist,Please check the url address' ).
      EXIT.
    ENDIF.

    LOOP AT parameters_tab ASSIGNING FIELD-SYMBOL(<ptab>).
      IF strlen( <ptab>-structure ) > 30.
        CONTINUE.
      ENDIF.
      lt_dd04l = VALUE #( BASE lt_dd04l ( domname = <ptab>-structure ) ).
    ENDLOOP.

    IF lt_dd04l IS NOT INITIAL.
      SELECT domname FROM dd04l
        FOR ALL ENTRIES IN @lt_dd04l
        WHERE domname = @lt_dd04l-domname
        INTO TABLE @DATA(data_elements).
    ENDIF.

    " MAPPING 字段映射
    me->json_mapping( EXPORTING iv_function_name = iv_function_name
                      IMPORTING name_mappings_i  = DATA(lt_mapping_i)
                                name_mappings_o  = DATA(lt_mapping_o)
                                name_mappings_x  = DATA(lt_mapping_x) ).

    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(json) = request->get_cdata( ).
          DATA(json_data) = zcl_json=>generate( json = json name_mappings = lt_mapping_i ).
          ASSIGN json_data->* TO FIELD-SYMBOL(<json_data>).

          READ TABLE lt_mapping_x INTO DATA(ls_x) WITH KEY abap = <parameter>-parameter.
          IF sy-subrc EQ 0.
            DATA(lv_parameter) = ls_x-json.
          ELSE.
            lv_parameter = <parameter>-parameter.
          ENDIF.
          "根据函数的入参匹配接口传入参数
          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.

          CREATE DATA data_ref TYPE (<parameter>-structure).
          FIELD-SYMBOLS: <temp> TYPE any.
          ASSIGN <parameter_val>->* TO <temp>.

          IF data_ref IS BOUND.
            ASSIGN data_ref->* TO FIELD-SYMBOL(<data_ref>).
          ENDIF.

          "将传入参数按照特定格式转换
          IF line_exists( data_elements[ domname = <parameter>-structure ] ).
            <data_ref> = <temp>.
          ELSE.
            DATA(json_temp) = /ui2/cl_json=>serialize( data = <parameter_val> ).
            /ui2/cl_json=>deserialize( EXPORTING json = json_temp name_mappings = lt_mapping_i CHANGING data = <data_ref> ).
          ENDIF.

          GET REFERENCE OF <data_ref> INTO ptab_line-value.

          INSERT ptab_line INTO TABLE ptab.

        ENDLOOP.

      CATCH cx_sy_create_data_error INTO DATA(lo_data_error).
        lv_error_msg = lo_data_error->get_text( ).
        me->set_message( EXPORTING request          = request
                                   iv_function_name = iv_function_name
                                   iv_code          = 400
                                   iv_mtype         = 'E'
                                   iv_reason        = 'target_function Handling Exceptions'
                                   iv_message       = lv_error_msg
                         IMPORTING response         = response ).
        EXIT.
    ENDTRY.

    etab_line-name = 'OTHERS'.
    etab_line-value = 10.
    INSERT etab_line INTO TABLE etab.

    TRY.
        "动态调用函数,业务逻辑部分
        CALL FUNCTION iv_function_name
          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_exporting.

          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_o ).

          READ TABLE lt_mapping_x INTO ls_x WITH KEY abap = ptab_line-name.
          IF sy-subrc EQ 0.
            lv_parameter = ls_x-json.
          ELSE.
            lv_parameter = ptab_line-name.
          ENDIF.

          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 && ''.

      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.
    IF lv_error_msg IS NOT INITIAL.
      me->set_message( EXPORTING request          = request
                                 iv_function_name = iv_function_name
                                 iv_code          = 400
                                 iv_mtype         = 'E'
                                 iv_reason        = 'target_function Handling Exceptions'
                                 iv_message       = lv_error_msg
                       IMPORTING response         = response ).
      EXIT.
    ENDIF.

    response->set_status( code = 200 reason = '' ).
    response->set_header_field( name = 'Content-Type' value = 'application/json' ).
    response->set_cdata( lv_response_json  ).

  ENDMETHOD.

PS:里面有一些表和mapping方法可以去掉,对代码本身没有影响。
我们通过
CALL FUNCTION iv_function_name
PARAMETER-TABLE
ptab
EXCEPTION-TABLE
etab.
可以动态的对函数进行调用。

不过sap标准的json转换方法存在一定的bug,/ui2/cl_json=>generate转换json的时候,如果是金额类型的字段,并且不加””,会导致json序列化转换的时候将字段转换成f类型,f类型会导致金额在超过千万的时候会自动转换成科学计数法,导致数据异常。我们在处理的时候将/ui2/cl_json这个类copy了出来并在GENERATE_INT方法里做了修改,将下图为之的so_type_f改成了so_type_s,数据转换成string类型就不会产生科学计数法问题了。(这个增强位置可能不同版本的系统不一样,需要找一下具体的位置)

同时在程序里还支持了动态传入的JSON字符和SAP系统字段不一样时的mapping,通过调用json_mapping对字段进行处理即可。

METHOD json_mapping.

    SELECT fnam,           " 函数名
           direction,      " 传输方向
           field_sap,      " sap字段名
           field_other     " 其他字段名
      FROM zepict009
      INTO TABLE @DATA(lt_mapping)
     WHERE fnam = @iv_function_name.
    CHECK lt_mapping IS NOT INITIAL.

    SORT lt_mapping BY field_sap.

    LOOP AT lt_mapping INTO DATA(ls_mapping).
      CASE ls_mapping-direction.  " 判断接口传入方向
        WHEN 'I'.
          INSERT VALUE #( abap = ls_mapping-field_sap
                          json = ls_mapping-field_other ) INTO TABLE name_mappings_i.
        WHEN 'O'.
          INSERT VALUE #( abap = ls_mapping-field_sap
                          json = ls_mapping-field_other ) INTO TABLE name_mappings_o.
        WHEN 'X'.
          INSERT VALUE #( abap = ls_mapping-field_sap
                          json = ls_mapping-field_other ) INTO TABLE name_mappings_x.
        WHEN OTHERS.
          INSERT VALUE #( abap = ls_mapping-field_sap
                          json = ls_mapping-field_other ) INTO TABLE name_mappings_i.
          INSERT VALUE #( abap = ls_mapping-field_sap
                          json = ls_mapping-field_other ) INTO TABLE name_mappings_o.
      ENDCASE.
    ENDLOOP.

  ENDMETHOD.

以上是关于sap restful json接口动态调用RFC,生成动态结构的主要内容,如果未能解决你的问题,请参考以下文章

SAP的RFC接口的发布与JAVA调用

php调用sap和.net的webservices接口

java 调用 sap rfc函数 rfc函数是自定义的还是sap系统组件自带的

PO配置调用SAP RFC

SAP是不是有java可以调用的接口?这些接口可以做啥?

Java 调用SAP PO 的Rest接口