业务背景

最近在做SAP项目的时候,OA创建流程,SAP一直调用的是OA的标准接口/api/workflow/paService/doCreateRequest,但标准接口的调用需要在SAP存很多OA自己的数据,比如OAID,明细表明,workflow ID等等,这些数据都是OA本身的配置数据,领导说存在SAP不合理,也有风险,想要把OA接口封装一下,调用的时候就传简单的报文数据,其他数据在OA做自建表自己取。

实现思路

1:封装后报文格式
请求头:token(获取token参照OA标准的接口)
USERID(默认值即可,真正的流程创建人在报文中)
JSON:

{
    "UUID": "UUID",
    "USERID": "工号",
    "SYSTEM": "系统标识",
    "requestName": "业务请求描述",
    "mainData": [
        {
            "fieldName": "field1",
            "fieldValue": "vlaue1"
        }
    ],
    "detailData": [
        {
            "workflowRequestTableRecords": [
                {
                    "recordOrder": 0,
                    "workflowRequestTableFields": [
                        {
                            "fieldName": "field1",
                            "fieldValue": "vlaue1"
                        },
                        {
                            "fieldName": "field2",
                            "fieldValue": "vlaue1"
                        }
                    ]
                }
            ]
        }
    ]
}

2:在OA建配置表
在这里插入图片描述
根据报文中的业务请求描述,在封装的接口中取出workflow id和明细表名
3:根据工号在表hrmresource中取到OAID并加密
4:调用前告知OA相关运维人员,进行接口注册,OA会提供相关数据(APPID、SPK、公钥、密钥等数据)
PS:获取token的接口需要用公钥加密密钥(第三方系统实现)
根据USERID获取workcode后也需要进行加密处理(本接口中实现)

封装后代码

package com.api.encapsulation;

import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.api.customization.hxwn.web.ForwardController;
import com.customization.hxwn.util.HttpKit;
import com.customization.hxwn.util.RequestUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.zxing.oned.OneDimensionalCodeWriter;
import org.apache.bcel.generic.NEW;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import weaver.conn.RecordSet;
import weaver.general.BaseBean;
import weaver.interfaces.util.GenUtil;
import zipkin2.Call;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

/**
 * @Package: com.api.encapsulation
 * @Author: cwt
 * @DateTime: 2025/8/27
 * @Description: SAP2OA通用接口封装
 */
@Path("/encapsulation/***/***")
public class SAP2OARequestDealCmd {
    Logger logger = LoggerFactory.getLogger(SAP2OARequestDealCmd.class);//初始化日志监控器
    HttpKit httpkit = new HttpKit();//实例化HTTP工具类
    RequestUtil requestUtil = new RequestUtil();//实例化请求工具类

    @Path("/***")
    @POST
    @Produces(MediaType.APPLICATION_JSON)
    public JSONObject apiProcess(@Context HttpServletRequest req, @Context HttpServletResponse resp) throws IOException, InterruptedException {
        HashMap<String, Object> logMap = new HashMap<>();
        logger.info("doCreateRequest接口封装日志记录开始!!!");
        RecordSet rs = new RecordSet();
        String result = null;
        int logId ;
        //定义接口返回消息
        JSONObject returnRes = new JSONObject();
        JSONObject data = new JSONObject();
        JSONObject auto_data = new JSONObject();//接口注册请求报文
        JSONObject requestData = new JSONObject();//处理后的请求报文
        JSONArray newDetailData = new JSONArray();//处理后的请求报文

        try {
            logger.info("请求头参数处理!!!");
            Map<String, String> header = new HashMap<>();
            header.put("token", req.getHeader("token"));
            header.put("appid", "ecology");

            String reqJSON = RequestUtil.getText(req);
            JSONObject reqJSONObj = JSONObject.parseObject(reqJSON);

            //字段校验
            if (reqJSONObj == null) {
                throw new Exception("请求参数不能为空");
            }
            if (!reqJSONObj.containsKey("USERID")) {
                throw new Exception("USERID 参数缺失");
            }
            if (!reqJSONObj.containsKey("requestName")) {
                throw new Exception("requestName 参数缺失");
            }
            if (!reqJSONObj.containsKey("SYSTEM")) {
                throw new Exception("SYSTEM 参数缺失");
            }
            if (!reqJSONObj.containsKey("UUID")) {
                throw new Exception("UUID 参数缺失");
            }

            logMap.put("uuid", reqJSONObj.get("UUID"));
            logMap.put("systemName", reqJSONObj.get("SYSTEM"));
            logMap.put("requestName", reqJSONObj.get("requestName"));
            logMap.put("requestmessage", reqJSON);

            //根据工号获取OAID
            logger.info("根据工号处理OAID!!!");
            String workCode = reqJSONObj.getString("USERID");
            String sql = "select id from hrmResource where workCode = ?";
            String id = "";
            if ("1".equals(workCode)) {
                id = "1";
            } else {
                rs.executeQuery(sql, workCode);
                if (rs.next()) {
                    id = rs.getString("id");
                } else {
                    throw new Exception("请联系管理员,根据用户工号未查询到用户ID!");
                }
            }
            //给ID加密
            sql = "SELECT * FROM ECOLOGY_BIZ_EC WHERE APPID = ?";
            rs.executeQuery(sql, "ecology");
            String rsaUserId = "";
            if (rs.next()) {
                rsaUserId = rs.getString("SERVER_PUBLIC_KEY");
            } else {
                throw new Exception("请联系管理员,用户账号加密失败!");
            }
            RSA rsa = new RSA(null, rsaUserId);
            String encryptSecret = rsa.encryptBase64(id, StandardCharsets.UTF_8, KeyType.PublicKey);

            header.put("userId", encryptSecret);
            logger.info("流程userId加密参数:{}", encryptSecret);

            //根据业务请求描述获取流程id和明细表名
            requestData.put("requestName", reqJSONObj.getString("requestName"));

            logger.info("业务请求描述:{}", reqJSONObj.getString("requestName"));
            logger.info("WORKFLOW_ID、DETAIL_DATA字段处理!!!");
            String REQUEST_NAME = reqJSONObj.getString("requestName");
            sql = "select * from uf_sap2oapam where REQUEST_NAME = ?";
            rs.executeQuery(sql, REQUEST_NAME);
            String workflowId;
            String detailData;
            logger.info("获取流程ID以及明细表名!!!");
            if (rs.next()) {
                workflowId = rs.getString("WORKFLOW_ID");
                logger.info("workflowId:{}", workflowId);
                detailData = rs.getString("DETAIL_DATA");
                logger.info("detailData:{}", detailData);
            } else {
                throw new Exception("请联系管理员,根据业务请求描述未找到流程ID!");
            }
            //WORKFLOW_ID(业务流程ID嵌套至json串顶部)
            logger.info("添加流程id和主表数据至报文中");
            requestData.put("workflowId", workflowId);
            requestData.put("mainData", reqJSONObj.getJSONArray("mainData"));

            //明细表detailData数据处理
//          String[] detailDataName = detailData.split(",");
            JSONArray detailDataArr = reqJSONObj.getJSONArray("detailData");
            if (detailDataArr != null) {
                logger.info("报文中存在明细表数据!");
                for (int i = 0; i < detailDataArr.size(); i++) {
                    JSONObject detailObj = new JSONObject();
                    if (i > 0) {
                        if (rs.next()) {
                            detailData = rs.getString("DETAIL_DATA");
                        } else {
                            throw new Exception("OA配置明细行数据与传入明细报文不一致,请联系管理员检查!");
                        }
                    }
                    detailObj.put("tableDBName", detailData);
                    detailObj.put("workflowRequestTableRecords", reqJSONObj.getJSONArray("detailData").getJSONObject(i).getJSONArray("workflowRequestTableRecords"));
                    newDetailData.add(detailObj);
                }
                requestData.put("detailData", newDetailData);
                logger.info("添加明细表数据至报文中值为:{}", detailData);
            }
            logger.info("数据填充完毕");
            returnRes.put("code", "PARAM_ERROR");
            returnRes.put("data", data);
            returnRes.put("msg", "");
            returnRes.put("errMsg", "");
            returnRes.put("reqFailMsg", "");
            String oaHost = req.getScheme() + "://" + req.getServerName() + ":" + req.getServerPort();
            logger.info("weaver-流程创建参数:Header:{},Param:{}", JSONObject.toJSONString(header), requestData.toJSONString());
            result = httpkit.doPost(oaHost + "/api/workflow/paService/doCreateRequest", header, requestData.toJSONString());
            logger.info("weaver-流程创建结果:{}", result);
            JSONObject res = JSONObject.parseObject(result);
            returnRes.put("data", res.getJSONObject("data"));
            returnRes.put("msg", res.getJSONObject("msg"));
            returnRes.put("code", res.getString("code"));
            returnRes.put("errMsg", res.getString("errMsg"));
            returnRes.put("reqFailMsg", res.getString("reqFailMsg"));

        } catch (IOException e) {
            returnRes.put("code", "PARAM_ERROR");
            returnRes.put("msg", "接口异常!" + e.getMessage());
            logger.error("weaver-流程创建失败:", e);
        } catch (Exception e) {
            returnRes.put("code", "PARAM_ERROR");
            returnRes.put("msg", "数据处理异常!" + e.getMessage());
            logger.error("数据处理异常:", e);
        }
         if ("success".equals(returnRes.getString("code"))){
             String respJson = returnRes.toString();
             logMap.put("type", "S");
             logMap.put("message", "流程创建成功!");
             logMap.put("responsemessage", respJson);
         }else {
             String respJson = returnRes.toString();
             logMap.put("type", "E");
             logMap.put("message", "流程创建失败!");
             logMap.put("responsemessage", respJson);
         }
         String getLogSql = "select * from uf_sap2oapam where REQUEST_NAME = ?" ;
         String modeId = "" ;
         rs.executeQuery(getLogSql,"流程创建接口日志表") ;
         if (rs.next()){
             modeId = rs.getString("WORKFLOW_ID") ;
             logId = GenUtil.createModeData(Integer.parseInt(modeId),1,logMap) ;
             (new BaseBean()).writeLog("流程创建接口日志表ID为:" + logId) ;
             logger.info("日志存储成功!");
         }else {
             logger.info("日志表存储失败,请检查配置表:uf_sap2oapam !");
         }
        return returnRes;
    }
}

PS:非专业java工作者,第一次写Java可能有很多问题,用的话建议根据实际业务再优化

SAP调用类封装

1:PO或其他接口中间件配置(就不说了)
2:SAP调用封装在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  METHOD data_process_send2oa.
    DATA : lv_uuid     TYPE guid,
           lcl_po      TYPE REF TO zclco_si_s4_oa060_out_syn,
           ls_response TYPE zclmt_oa060_resp.

    MOVE-CORRESPONDING in_t_data TO it_data_table .

    CALL METHOD me->get_token.  "获取token
    CHECK es_return IS INITIAL.

    TRY.
        lv_uuid = cl_uuid_factory=>create_system_uuid( )->create_uuid_x16( ).
      CATCH cx_uuid_error INTO DATA(exception).
        es_return-msg = exception->if_message~get_text( ) .
    ENDTRY.
    CHECK es_return IS INITIAL.

    ls_request-mt_oa060_req-token = token .
    ls_request-mt_oa060_req-sysid = 'SAP' .
    ls_request-mt_oa060_req-request_name = in_v_requestname .
    ls_request-mt_oa060_req-userid = sy-uname .
    ls_request-mt_oa060_req-uuid = lv_uuid .

    CALL METHOD me->data_mapping.

    "调用接口
    TRY.
        CREATE OBJECT lcl_po.
        CALL METHOD lcl_po->si_s4_oa060_out_syn
          EXPORTING
            output = ls_request
          IMPORTING
            input  = ls_response.
        IF ls_response-mt_oa060_resp-code <>  'SUCCESS'.
          es_return-type = 'E'.
          es_return-msg = ls_response-mt_oa060_resp-msg.
        ELSE. "流程提交成功
          es_return-type = 'S'.
          es_return-msg = '流程创建成功!'.
        ENDIF.
      CATCH cx_ai_system_fault INTO DATA(lcx_excep).
        es_return-type = 'E'.
        es_return-msg = lcx_excep->get_text( ).
    ENDTRY.

  ENDMETHOD.

在这里插入图片描述

  METHOD field_process.
    IF lv_type_kind = 'D' AND lv_value IS NOT INITIAL. "日期类型需要做内外码转换
      lv_value = lv_value+0(4) && '-' && lv_value+4(2) && '-' && lv_value+6(2).
    ELSEIF lv_type_kind = 'D' AND lv_value IS INITIAL.
      CLEAR lv_value .
    ELSEIF lv_type_kind = 'P'.                           "去空格
      CONDENSE lv_value NO-GAPS.
    ELSEIF lv_type_kind = 'T'.                           "时间类型处理
      IF lv_value IS INITIAL.
        CLEAR lv_value.
      ELSE.
        lv_value = lv_value+0(2) && ':' && lv_value+2(2).
      ENDIF.
    ENDIF.
  ENDMETHOD.
  METHOD data_mapping.
    DATA: lr_head  TYPE REF TO cl_abap_structdescr,
          lr_item  TYPE REF TO cl_abap_structdescr,
          lr_descr TYPE REF TO cl_abap_tabledescr,
          ls_comp  TYPE abap_compdescr.
    FIELD-SYMBOLS : <lt_data> TYPE STANDARD TABLE .

    "将主表数据填充至REQLOOP AT it_data_table ASSIGNING FIELD-SYMBOL(<ft_item>) WHERE type = '1' .
      ASSIGN <ft_item>-table->* TO <lt_data> .
      IF <lt_data> IS ASSIGNED .
        lr_descr ?= cl_abap_typedescr=>describe_by_data( <lt_data> ).
        lr_head ?= lr_descr->get_table_line_type( ).
        READ TABLE <lt_data> ASSIGNING FIELD-SYMBOL(<fs_item>) INDEX 1 .
        LOOP AT lr_head->components INTO ls_comp.
          APPEND INITIAL LINE TO ls_request-mt_oa060_req-main_data ASSIGNING FIELD-SYMBOL(<fs_main_data>) .
          <fs_main_data>-field_name = ls_comp-name .
          ASSIGN COMPONENT ls_comp-name OF STRUCTURE <fs_item> TO FIELD-SYMBOL(<fs_value>) .
          IF <fs_value> IS ASSIGNED .
            me->field_process( EXPORTING lv_type_kind = ls_comp-type_kind CHANGING lv_value = <fs_value> ).
            <fs_main_data>-field_value = <fs_value> .
          ENDIF .
        ENDLOOP .
      ENDIF .
    ENDLOOP .

    "将明细表数据填充至REQLOOP AT it_data_table ASSIGNING <ft_item> WHERE type = '2' .
      ASSIGN <ft_item>-table->* TO <lt_data> .
      IF <lt_data> IS ASSIGNED .
        lr_descr ?= cl_abap_typedescr=>describe_by_data( <lt_data> ).
        lr_item ?= lr_descr->get_table_line_type( ).
        APPEND INITIAL LINE TO ls_request-mt_oa060_req-detail_data ASSIGNING FIELD-SYMBOL(<fs_detail_data>) .
        LOOP AT <lt_data> ASSIGNING <fs_item> .
          APPEND INITIAL LINE TO <fs_detail_data>-workflow_request_table_records ASSIGNING FIELD-SYMBOL(<fs_records>) .
          <fs_records>-record_order = '0' .
          LOOP AT lr_item->components INTO ls_comp.
            APPEND INITIAL LINE TO <fs_records>-workflow_request_table_fields ASSIGNING FIELD-SYMBOL(<fs_fields>) .
            <fs_fields>-field_name = ls_comp-name .
            ASSIGN COMPONENT ls_comp-name OF STRUCTURE <fs_item> TO <fs_value> .
            IF <fs_value> IS ASSIGNED .
              me->field_process( EXPORTING lv_type_kind = ls_comp-type_kind CHANGING lv_value = <fs_value> ).
              <fs_fields>-field_value = <fs_value> .
            ENDIF .
          ENDLOOP.
        ENDLOOP .
      ENDIF .
    ENDLOOP .
  ENDMETHOD.

在这里插入图片描述

  METHOD get_token.
    DATA:lcl_po      TYPE REF TO zpoco_si_s4_oa006_out_syn,
         ls_request  TYPE zpomt_oa006_req,
         ls_response TYPE zpomt_oa006_resp.

    ls_request-mt_oa006_req-appid = 'ecology'.
    IF sy-mandt = '200' OR sy-mandt = '300' OR sy-mandt = '400' .
      ls_request-mt_oa006_req-secret = 'sxIXPsCg+PKlJDcIrD5RJpFzayz1CL6JmeVzHcu4PmX/bYAkOhnc4DauiEBh/U1xSuhB' &&
                                       '1gTMqhVTfa/sCwQPPdqDz9x8c+hwTmRWlpQEj1bn60qU0bIdaMv0NHiWV4FEAY74dqul' &&
                                       'WIwdDtXdCrJOx4vzTp/Cu/KV8QpjbOaj0tb/aeUgDD+5fWi6p57AkJ+7R9I+m5jcYaXK' &&
                                       'NTg3ZXjJKsDU5thVQM9IqZfrnw1YHNkuupmkj/ulJzzXgK1h25CogvouXgUh9TugHgvR' &&
                                       'c7S9Hirzc29XgIpUpiXChPhw6IODqa9+5d4IZzaYgz227rJ8y0VhESTHDuoXNM9Jg=='.
    ELSEIF sy-mandt = '500' OR sy-mandt = '600' .
    ELSEIF sy-mandt = '800' .
    ENDIF .
    TRY.
        CREATE OBJECT lcl_po.
        CALL METHOD lcl_po->si_s4_oa006_out_syn
          EXPORTING
            output = ls_request
          IMPORTING
            input  = ls_response.

        token = ls_response-mt_oa006_resp-token.

        IF token IS INITIAL.
          es_return-type = 'E'.
          es_return-msg = ls_response-mt_oa006_resp-msg.
        ENDIF.
      CATCH cx_ai_system_fault INTO DATA(lcx_excep).
        es_return-type = 'E'.
        es_return-msg = lcx_excep->get_text( ).
    ENDTRY.
  ENDMETHOD.

SAP调用案例:

*-------------------------------------Str--------------------------------------*
"可封装于include中
TYPES : BEGIN OF ty_ref ,
          type  TYPE char1,
          table TYPE REF TO data,
        END OF ty_ref .

DATA : zcl_test TYPE REF TO zcl_send2oa_apigeneral,
       it_tab   TYPE TABLE OF  ty_ref.
FIELD-SYMBOLS : <fs_outer>       TYPE ty_ref,
                <fs_inner_table> TYPE STANDARD TABLE.

DEFINE _data_process.
  APPEND INITIAL LINE TO it_tab ASSIGNING <fs_outer>.
  CREATE DATA <fs_outer>-table LIKE lt_data.
  ASSIGN <fs_outer>-table->* TO <fs_inner_table>.
  MOVE-CORRESPONDING &1 TO <fs_inner_table> .
  <fs_outer>-type = &2 .
END-OF-DEFINITION.
*-------------------------------------End--------------------------------------*
"主表测试数据
SELECT * FROM mara INTO TABLE @DATA(lt_data) WHERE MATNR = '123' .
_data_process lt_data '1'.
"明细表测试数据
SELECT * FROM ekko INTO TABLE @DATA(lt_ekko) .
_data_process lt_ekko '2'.

TRY.
    CREATE OBJECT zcl_test.
    CALL METHOD zcl_test->data_process_send2oa
      EXPORTING
        in_t_data        = it_tab
        in_v_requestname = '流程名称'.
  CATCH cx_ai_system_fault INTO DATA(lcx_excep).
ENDTRY.

在这里插入图片描述
在这里插入图片描述

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐