SAP ABAP 使用SICF发布Restful HTTP API接口
最近由于需要需要在一台物理隔离的SAP应用服务器上测试接口,所以不方便使用PO中间件,故采用发布restful接口的方式。
1、SE24建立类YCL_REST_TEST
2、继承接口IF_HTTP_EXTENSION(很重要)

输入回车后,保存激活。
3、在方法页签添加GET或者POST方法

为IF_HTTP_EXTENSION~HANDLE_REQUEST添加下参数

双击IF_HTTP_EXTENSION~HANDLE_REQUEST方法,写入代码
METHOD IF_HTTP_EXTENSION~HANDLE_REQUEST. DATA(LV_METHOD) = SERVER->REQUEST->GET_METHOD( )."查看调用方法GET OR POST CASE LV_METHOD. WHEN 'GET'. ME->GET( SERVER ). WHEN 'POST'. ME->POST( SERVER ). WHEN OTHERS. "Sets current HTTP status code SERVER->RESPONSE->SET_STATUS( CODE = 405 REASON = '请求方法不支持,请联系管理员!' ). ENDCASE. ENDMETHOD.
这里以POST方法为例
METHOD POST. DATA: LT_FIELDS TYPE TIHTTPNVP. FIELD-SYMBOLS: <FS_FIELD> LIKE LINE OF LT_FIELDS. DATA: LV_JSON TYPE STRING, "返回参数 LV_JSON1 TYPE STRING, "获取传回值数据 LV_JSON2 TYPE STRING. "获取传回值数据 DATA:LS_ZTMM023 TYPE ZSMM023, LT_ZTMM023 TYPE TABLE OF ZSMM023. DATA:T_PURINFO TYPE STANDARD TABLE OF ZSMM023. DATA:T_PURINFO_MAL TYPE STANDARD TABLE OF ZSMM023. DATA:E_SUCC TYPE CHAR1, E_MESSAGE TYPE MSGTX. DATA:GV_AVG_PRICE TYPE BWERT. " 平均价格 TYPES: BEGIN OF TY_AVG_PRICE, MATNR1 TYPE MATNR, AVGPRICE TYPE P LENGTH 13 DECIMALS 3, " 平均价格 COUNT TYPE I, " 行数 END OF TY_AVG_PRICE. DATA LT_AVG_PRICE TYPE STANDARD TABLE OF TY_AVG_PRICE. DATA:EBELN TYPE EBELN, BSART TYPE BSART, EKORG TYPE EKORG, EKOTX TYPE EKOTX, BUKRS TYPE BUKRS, BUTXT TYPE BUTXT, LIFNR TYPE LIFNR, FDATE TYPE BEDAT, EDATE TYPE BEDAT, NAME1 TYPE NAME1, MATNR TYPE MATNR, MAKTX TYPE MAKTX. DATA: LR_EBELN TYPE RANGE OF EBELN. " 范围内表:存储单值对应的范围 DATA: LR_BSART TYPE RANGE OF BSART. " 范围内表:存储单值对应的范围 DATA: LR_EKORG TYPE RANGE OF EKORG. " 范围内表:存储单值对应的范围 DATA: LR_BUKRS TYPE RANGE OF BUKRS. " 范围内表:存储单值对应的范围 DATA: LR_LIFNR TYPE RANGE OF LIFNR. " 范围内表:存储单值对应的范围 DATA: LR_MATNR TYPE RANGE OF MATNR. " 范围内表:存储单值对应的范围 DATA: LR_DATE TYPE RANGE OF BEDAT. " 开始日期范围内表:存储单值对应的范围 *----------------------------------------------------------------------* * 1. 传入类型定义:与 JSON 报文完全同构 *----------------------------------------------------------------------* TYPES: BEGIN OF TY_EBELN, EBELN TYPE EBELN, END OF TY_EBELN, TY_EBELN_T TYPE STANDARD TABLE OF TY_EBELN WITH DEFAULT KEY. TYPES: BEGIN OF TY_BSART, BSART TYPE BSART, END OF TY_BSART, TY_BSART_T TYPE STANDARD TABLE OF TY_BSART WITH DEFAULT KEY. TYPES: BEGIN OF TY_EKORG, EKORG TYPE EKORG, END OF TY_EKORG, TY_EKORG_T TYPE STANDARD TABLE OF TY_EKORG WITH DEFAULT KEY. TYPES: BEGIN OF TY_BUKRS, BUKRS TYPE BUKRS, END OF TY_BUKRS, TY_BUKRS_T TYPE STANDARD TABLE OF TY_BUKRS WITH DEFAULT KEY. TYPES: BEGIN OF TY_LIFNR, LIFNR TYPE LIFNR, END OF TY_LIFNR, TY_LIFNR_T TYPE STANDARD TABLE OF TY_LIFNR WITH DEFAULT KEY. TYPES: BEGIN OF TY_MATNR, MATNR TYPE MATNR, END OF TY_MATNR, TY_MATNR_T TYPE STANDARD TABLE OF TY_MATNR WITH DEFAULT KEY. * 顶层结构 TYPES: BEGIN OF TY_REQUEST, EBELN_REQ TYPE TY_EBELN_T, BSART_REQ TYPE TY_BSART_T, EKORG_REQ TYPE TY_EKORG_T, BUKRS_REQ TYPE TY_BUKRS_T, LIFNR_REQ TYPE TY_LIFNR_T, MATNR_REQ TYPE TY_MATNR_T, FDATE TYPE DATUM, EDATE TYPE DATUM, END OF TY_REQUEST. * 传入内表类型 TYPES TY_REQUEST_T TYPE STANDARD TABLE OF TY_REQUEST WITH DEFAULT KEY. *传出内表定义 *1主体结构 TYPES: TT_ITEM TYPE STANDARD TABLE OF ZSMM023 WITH EMPTY KEY. *----------------------------------------------------------------------* * 2. 平均价结构(avgdata 节点) *----------------------------------------------------------------------* TYPES: BEGIN OF TY_AVG, MATNR1 TYPE MATNR, AVGPRICE TYPE BSTWR, END OF TY_AVG. TYPES: TT_AVG TYPE STANDARD TABLE OF TY_AVG WITH EMPTY KEY. *----------------------------------------------------------------------* * 3. 完整 JSON 对应的根结构 *----------------------------------------------------------------------* TYPES: BEGIN OF TY_ROOT, DATA_RES TYPE TT_ITEM, E_SUCC(1) , E_MESSAGE TYPE STRING, TOTALCOUNT TYPE I, AVGDATA TYPE TT_AVG, END OF TY_ROOT. *----------------------------------------------------------------------* * 2. 演示:JSON → 内表 *----------------------------------------------------------------------* *DATA: LV_JSON TYPE STRING, DATA: GS_REQ TYPE TY_REQUEST. DATA: LT_TABLE TYPE TY_ROOT. * 获取JSON抬头数据 SERVER->REQUEST->GET_HEADER_FIELDS( CHANGING FIELDS = LT_FIELDS ). CALL METHOD SERVER->RESPONSE->IF_HTTP_ENTITY~SET_CONTENT_TYPE "设置返回JSON格式 EXPORTING CONTENT_TYPE = 'application/json'. * 获取JSON行数据 LV_JSON1 = SERVER->REQUEST->IF_HTTP_ENTITY~GET_CDATA( ). * 解析json至内表方法1 * 反序列化 /UI2/CL_JSON=>DESERIALIZE( EXPORTING JSON = LV_JSON1 PRETTY_NAME = /UI2/CL_JSON=>PRETTY_MODE-CAMEL_CASE CHANGING DATA = GS_REQ ). * /UI2/CL_JSON=>DESERIALIZE( EXPORTING JSON = LV_JSON1 * CHANGING DATA = LS_REQ ). IF NOT LV_JSON1 IS INITIAL. * 获取数据 IF GS_REQ-EBELN_REQ IS NOT INITIAL. LOOP AT GS_REQ-EBELN_REQ INTO DATA(LS_EBELN). LR_EBELN = VALUE #( BASE LR_EBELN ( SIGN = 'I' OPTION = 'EQ' LOW = LS_EBELN-EBELN HIGH = '' ) ). ENDLOOP. SORT LR_EBELN BY LOW. " 可选:排序去重以优化性能 DELETE ADJACENT DUPLICATES FROM LR_EBELN COMPARING LOW. DELETE LR_EBELN WHERE LOW = ''. ELSE. CLEAR LR_EBELN[]."这个很关键,如果没有值,要清空,才能算没有条件 ENDIF. IF GS_REQ-BSART_REQ IS NOT INITIAL. LOOP AT GS_REQ-BSART_REQ INTO DATA(LS_BSART). LR_BSART = VALUE #( BASE LR_BSART ( SIGN = 'I' OPTION = 'EQ' LOW = LS_BSART-BSART HIGH = '' ) ). ENDLOOP. SORT LR_BSART BY LOW. " 可选:排序去重以优化性能 DELETE ADJACENT DUPLICATES FROM LR_BSART COMPARING LOW. DELETE LR_BSART WHERE LOW = ''. ELSE. CLEAR LR_BSART[]. ENDIF. IF GS_REQ-EKORG_REQ IS NOT INITIAL. LOOP AT GS_REQ-EKORG_REQ INTO DATA(LS_EKORG). LR_EKORG = VALUE #( BASE LR_EKORG ( SIGN = 'I' OPTION = 'EQ' LOW = LS_EKORG-EKORG HIGH = '' ) ). ENDLOOP. SORT LR_EKORG BY LOW. " 可选:排序去重以优化性能 DELETE ADJACENT DUPLICATES FROM LR_EKORG COMPARING LOW. DELETE LR_EKORG WHERE LOW = ''. ELSE. CLEAR LR_EKORG[]. ENDIF. IF GS_REQ-BUKRS_REQ IS NOT INITIAL. LOOP AT GS_REQ-BUKRS_REQ INTO DATA(LS_BUKRS). LR_BUKRS = VALUE #( BASE LR_BUKRS ( SIGN = 'I' OPTION = 'EQ' LOW = LS_BUKRS-BUKRS HIGH = '' ) ). ENDLOOP. SORT LR_BUKRS BY LOW. " 可选:排序去重以优化性能 DELETE ADJACENT DUPLICATES FROM LR_BUKRS COMPARING LOW. DELETE LR_BUKRS WHERE LOW = ''. ELSE. CLEAR LR_BUKRS[]. ENDIF. IF GS_REQ-LIFNR_REQ IS NOT INITIAL. LOOP AT GS_REQ-LIFNR_REQ INTO DATA(LS_LIFNR). LIFNR = LS_LIFNR-LIFNR . LIFNR = |{ LIFNR ALPHA = IN }|. LR_LIFNR = VALUE #( BASE LR_LIFNR ( SIGN = 'I' OPTION = 'EQ' LOW = LIFNR HIGH = '' ) ). ENDLOOP. SORT LR_LIFNR BY LOW. " 可选:排序去重以优化性能 DELETE ADJACENT DUPLICATES FROM LR_LIFNR COMPARING LOW. DELETE LR_LIFNR WHERE LOW = ''. ELSE. CLEAR LR_LIFNR[]. ENDIF. IF GS_REQ-MATNR_REQ IS NOT INITIAL. LOOP AT GS_REQ-MATNR_REQ INTO DATA(LS_MATNR). MATNR = LS_MATNR-MATNR. MATNR = |{ MATNR ALPHA = IN WIDTH = 18 }|. LR_MATNR = VALUE #( BASE LR_MATNR ( SIGN = 'I' OPTION = 'EQ' LOW = MATNR HIGH = '' ) ). ENDLOOP. SORT LR_MATNR BY LOW. " 可选:排序去重以优化性能 DELETE ADJACENT DUPLICATES FROM LR_MATNR COMPARING LOW. DELETE LR_MATNR WHERE LOW = ''. ELSE. CLEAR LR_MATNR[]. ENDIF. "起始日期为2000年,终止日期为9999年 IF GS_REQ-FDATE IS INITIAL AND GS_REQ-EDATE IS NOT INITIAL. LR_DATE = VALUE #( BASE LR_DATE ( SIGN = 'I' OPTION = 'BT' LOW = '20000000' HIGH = GS_REQ-EDATE ) ). ELSEIF GS_REQ-FDATE IS NOT INITIAL AND GS_REQ-EDATE IS INITIAL. LR_DATE = VALUE #( BASE LR_DATE ( SIGN = 'I' OPTION = 'BT' LOW = GS_REQ-FDATE HIGH = '99999999' ) ). ELSEIF GS_REQ-FDATE IS INITIAL AND GS_REQ-EDATE IS INITIAL. LR_DATE = VALUE #( BASE LR_DATE ( SIGN = 'I' OPTION = 'BT' LOW = '20000000' HIGH = '99999999' ) ). ELSEIF GS_REQ-FDATE IS NOT INITIAL AND GS_REQ-EDATE IS NOT INITIAL. LR_DATE = VALUE #( BASE LR_DATE ( SIGN = 'I' OPTION = 'BT' LOW = GS_REQ-FDATE HIGH = GS_REQ-EDATE ) ). ENDIF. SELECT EKPO~EBELN, EKPO~EBELP, EKKO~BUKRS, T001~BUTXT, EKKO~EKORG, T024E~EKOTX, EKKO~BSART, EKKO~LIFNR, LFA1~NAME1, EKKO~BEDAT, EKPO~MATNR, MAKT~MAKTX, EKPO~MENGE, EKPO~MEINS, EKPO~PEINH, EKPO~NETWR AS SUMPRICE_NO_TAX,"未税总值 EKPO~BRTWR AS SUMPRICE_TAX, "含税总值 EKPO~MWSKZ, * EKPO~EBELP AS ZTAX, EKKO~WAERS, CASE WHEN EKPO~PEINH = 0 THEN 0 ELSE DIVISION( EKPO~NETPR ,EKPO~PEINH,3 ) END AS UNPRICE_NO_TAX, "未税单价 CASE WHEN EKPO~MENGE = 0 THEN 0 ELSE DIVISION( EKPO~BRTWR ,EKPO~MENGE,3 ) END AS UNPRICE_TAX,"总金额除以数量=含税单价 EKKO~LANDS FROM EKPO INNER JOIN EKKO ON EKKO~EBELN = EKPO~EBELN LEFT JOIN MAKT ON MAKT~MATNR = EKPO~MATNR LEFT JOIN T024E ON T024E~EKORG = EKKO~EKORG LEFT JOIN LFA1 ON LFA1~LIFNR = EKKO~LIFNR LEFT JOIN T001 ON T001~BUKRS = EKKO~BUKRS WHERE EKKO~EBELN IN @LR_EBELN AND EKKO~BSART IN @LR_BSART AND EKKO~EKORG IN @LR_EKORG AND EKKO~BUKRS IN @LR_BUKRS AND EKKO~LIFNR IN @LR_LIFNR AND EKPO~MATNR IN @LR_MATNR AND EKKO~BEDAT IN @LR_DATE AND EKPO~LOEKZ <> 'X' " 排除已删除项目 AND EKKO~BSTYP = 'F' "只取采购订单的 INTO CORRESPONDING FIELDS OF TABLE @T_PURINFO. IF T_PURINFO IS NOT INITIAL. "1. 全局最早/最晚日期(不区分物料) DATA(LV_MIN_DATE) = REDUCE D( INIT MIN = CONV D( '99991231' ) FOR WA IN T_PURINFO NEXT MIN = NMIN( VAL1 = MIN VAL2 = WA-BEDAT ) ). DATA(LV_MAX_DATE) = REDUCE D( INIT MAX = CONV D( '00000000' ) FOR WA IN T_PURINFO NEXT MAX = NMAX( VAL1 = MAX VAL2 = WA-BEDAT ) ). LOOP AT T_PURINFO INTO DATA(LS_PURINFO). "标记最早最晚行 LS_PURINFO-EARLIESTTXN = COND #( WHEN LS_PURINFO-BEDAT = LV_MIN_DATE THEN '1' ELSE '' ). LS_PURINFO-LATESTTXN = COND #( WHEN LS_PURINFO-BEDAT = LV_MAX_DATE THEN '9' ELSE '' ). "取税率及含税价 SELECT SINGLE KNUMH INTO @DATA(LS_KNUMH) FROM A003 WHERE MWSKZ = @LS_PURINFO-MWSKZ AND ALAND = @LS_PURINFO-LANDS. SELECT SINGLE KBETR INTO @DATA(LS_KBETR) FROM KONP WHERE KNUMH = @LS_KNUMH. LS_PURINFO-ZTAX = LS_KBETR / 1000. IF LS_PURINFO-EKORG = '1010' OR LS_PURINFO-EKORG = '1011' OR LS_PURINFO-EKORG = '1020' OR LS_PURINFO-EKORG = '1030'. "含税单价 LS_PURINFO-UNPRICE_TAX = LS_PURINFO-UNPRICE_NO_TAX * ( 1 + LS_KBETR / 1000 ). "含税总金额 LS_PURINFO-SUMPRICE_TAX = LS_PURINFO-SUMPRICE_NO_TAX * ( 1 + LS_KBETR / 1000 ). ENDIF. MODIFY T_PURINFO FROM LS_PURINFO. CLEAR:LS_PURINFO,LS_KNUMH,LS_KBETR. ENDLOOP. "取行数 * DESCRIBE TABLE T_PURINFO LINES DATA(LV_LINE). "去订单行数(去重) SORT T_PURINFO BY EBELN. DATA(LV_LINE) = REDUCE I( INIT CNT = 0 FOR GROUPS GRP_EBELN OF WA IN T_PURINFO GROUP BY WA-EBELN ASCENDING NEXT CNT = CNT + 1 ). "取平均价表 T_PURINFO_MAL = CORRESPONDING #( T_PURINFO ). DELETE T_PURINFO_MAL WHERE MATNR IS INITIAL. SORT T_PURINFO_MAL BY MATNR. " 使用 FOR GROUPS 按物料分组,累加单价并计数 LT_AVG_PRICE = VALUE #( FOR GROUPS GRP_MATNR OF <WA> IN T_PURINFO_MAL " 分组标识改为 grp_matnr(避免冲突) GROUP BY <WA>-MATNR " 按内表字段 matnr 分组(明确引用字段) ( MATNR1 = GRP_MATNR " 用分组标识赋值(对应当前组的 matnr 值) AVGPRICE = REDUCE BWERT( INIT P = 0 FOR <LINE> IN GROUP GRP_MATNR " 引用修改后的分组标识 NEXT P = P + <LINE>-UNPRICE_TAX ) " 累加单价 COUNT = REDUCE I( INIT C = 0 FOR <LINE> IN GROUP GRP_MATNR NEXT C = C + 1 ) " 统计行数 ) ). " 计算平均值 LOOP AT LT_AVG_PRICE ASSIGNING FIELD-SYMBOL(<AVG>). IF <AVG>-COUNT > 0. * <AVG>-AVG_PRICE = <AVG>-AVG_PRICE / <AVG>-COUNT. APPEND VALUE #( MATNR1 = <AVG>-MATNR1 AVGPRICE = <AVG>-AVGPRICE / <AVG>-COUNT ) TO LT_TABLE-AVGDATA. ELSE. APPEND VALUE #( MATNR1 = <AVG>-MATNR1 AVGPRICE = 0 ) TO LT_TABLE-AVGDATA. ENDIF. ENDLOOP. LT_TABLE-E_SUCC = 'S'. LT_TABLE-TOTALCOUNT = LV_LINE. LT_TABLE-E_MESSAGE = '成功查到数据!'. LT_TABLE-DATA_RES = CORRESPONDING #( T_PURINFO ). * 内表转换json CALL METHOD /UI2/CL_JSON=>SERIALIZE EXPORTING DATA = LT_TABLE RECEIVING R_JSON = LV_JSON. * 将行数据JSON返回给调用端 SERVER->RESPONSE->SET_CDATA( EXPORTING DATA = LV_JSON ). ELSE. LT_TABLE-E_SUCC = 'E'. LT_TABLE-E_MESSAGE = '没有收到数据!'. ENDIF. ELSE. LT_TABLE-E_SUCC = 'E'. LT_TABLE-E_MESSAGE = '没有收到数据!'. ENDIF. ENDMETHOD.

4、配置服务
使用代码SICF

进去后右击default_host





保存后激活服务。

点击测试服务获取URL地址

点击允许

可能跳出的网页需要用户名和密码输入,输入正常的通讯账号或者个人账号都可以,上图里有地址:http://s4appprd:8000/z_han_http

此时这个地址是映射地址,如果需要调试的话需要配置host文件,加上X.X.X.XXX s4appprd
或者直接将s4appprd替换为IP地址。
5、调试
使用第三方调试工具发送报文

具体body
{ "EBELN_REQ": [ { "EBELN": "" }, { "EBELN": "" } ], "BSART_REQ": [ { "BSART": "" }, { "BSART": "" } ], "EKORG_REQ": [ { "EKORG": "1011" }, { "EKORG": "" } ], "BUKRS_REQ": [ { "BUKRS": "" }, { "BUKRS": "" } ], "LIFNR_REQ": [ { "LIFNR": "" }, { "LIFNR": "" } ], "MATNR_REQ": [ { "MATNR": "" }, { "MATNR": "" } ], "FDATE": "20250901", "EDATE": "20250901" }
返回

如果要打断点的话,需要在SE24类的方法里设置下用户名

这里的账号可以建一个服务账号即可

这里的账号密码也可以用于第三方工具访问的账号密码。PS:上面配置服务时候的登录数据页签,有的博主说输了就调用时候不要的,但是自己操作没效果,也没去研究了。

这个时候在POST方法界面打外部断点,可以进入调试界面

如果进不了断点,将SAP和调试工具都完全退出后再进行发送即可。

浙公网安备 33010602011771号