SAP RFC接口集成指引

一:SAP代理服务器的设计思路

  • 实现动态加载指定的jar包,支持客户上传jar包,保证相关的安全性,很好的兼容不同的SAP版本的jar包。
  • 支持用户自定义groovy脚本,在集成平台提供的基础上,做二次开发,实现逻辑闭环。实现SAP的RFC协议转成http-json返回。

二:SAP代理服务器协议

集成平台提供的sap-rfc代理暴露/proxy/sapProxyexecuteRfcMethod 支持传递相关的参数,具体参数SapFunctionParamsArg以及返回值看下面说明:
fieldNamefieldTypefieldDescremark
functionNameStringsap-rfc函数名必填
importParamsValuesMap<String,Object>sap-rfc的函数入参根据sap_rfc函数定义,区分是标量参数/结构参数(标量参数就是标准的key-value,{"params1":"test"},简单类型,结构参数Structure是封装后的复杂类型)
tableParamsMap<String, List<Object>>表的参数 没有可不传,RFC-函数的import定义有时候是以table作为入参。
outTablesNamesList<String>返回的表名没有可不传
returnParamsFieldList<String>返回的参数没有可不传
batchQueryArgBatchQueryArg批量查询的参数用于批量查询,具体定义看BatchQueryArg参数定义
BatchQueryArg参数定义
fieldNamefieldTypefieldDescremark
masterApiNameString主对象的apiname必填
detailApiNamesList<String>从对象的apiname
limitInteger批量查询,一次返回最大条数默认100
offsetInteger批量查询,从第几条开始查询默认从0
masterFieldNameString主对象的字段field_name必填
masterDetailFieldNamesMap<String,String>从对象的主从字段field_name,从对象的apiname-主从字段有主从对象必填

三:代理服务器安装步骤

windows安装步骤

  1. 从SAP官网下载jar包 , 访问SAP官网:SAP NetWeaver Remote Function Call (RFC) Software Development Kit (SDK) 到页面最底部进行下载 sapjco3.jar
  2. 下载对应系统的libsapjco3.so文件(linux系统)sapjco3.dll(windows系统),以及下载集成平台提供的SAP代理jar包,以及提供的groovy脚本
  3. window系统,下载winsw.zip到服务器进行解压
  4. sapjco3.jar,libsapjco3.so,sapjco3.dll由于合规问题,需要客户方IT人员在SAP官方渠道下载,其他文件可以从文章底部下载
  5. 在对应的服务器上创建一个sapConnect文件夹,winsw文件解压放在sapConnect文件,sapjar文件夹存放相关的sapjar,dll,so文件,config文件夹存放application.properties
6.编辑winsw.xml文件
sapConnect 是集成平台提供的jar包的名称
-Djava.library.path 需要改成具体存放 SAP的dll/so文件的文件夹路径。
--spring.config.location 指定jar包需要读取哪个配置文件application.properties
<service> <id>sapConnect</id> <name>sapConnect</name> <description>fxiaoke erpdss proxy-server.</description> <executable>java</executable> <arguments>-jar "%BASE%\sapConnect.jar" "-Djava.library.path=D:/sapConnect/sapjar" --spring.config.location=file:%BASE%/config/application.properties </arguments> <log mode="roll" /> <logpath>%BASE%\app\logs</logpath> <onfailure action="restart" /> </service>
7.配置文件支持的属性:
#服务器端口 server.port=8080 #服务器 JCO_ASHOST= #系统编号 JCO_SYSNR= #SAP集团 JCO_CLIENT= #SAP用户名 JCO_USER= #SAP用户密码 JCO_PASSWD= #登录用语 ZH EN JCO_LANG=ZH #最大连接数 JCO_POOL_CAPACITY=4 #最大线程 JCO_PEAK_LIMIT=10 #sap——groovy脚本的地址 groovy.path=file:/D:/xxxxxxxxx/xxxxxxx/SapGroovyServiceImpl.groovy #SAP的jar targetUrl=D:/xxxxxxx/xxxxxxxxx/
sapConnect.jar包同级目录下,创建/config/application.properties
JCO开头的相关属性,都是需要填写SAP属性
groovy.path是集成平台提供的SapGroovyServiceImpl的脚本存放位置。具体替换D:/的文件路径,不要删除file:/
targetUrl是sapjar包的存放路径
8.上面的配置完成后,参考winsw的启动:在配置winsw的文件夹下,cmd该文件夹回车
在sapConnect文件夹看app/log,看下具体的启动日志
win+r 打开执行框输入:services.msc,找到对应的sapConnec服务,点击属性看到正常启动,支持开机自启动代理服务
在浏览器输入http://ip:port/proxy/sapProxy/connectFunction (ip 需要输入机器正确的ip地址,port端口没有改动,则默认是8080)
9.winsw常用的命令:
winsw.exe install 安装,重启后即可启动
winsw.exe start 手动启动
winsw.exe stop 手动停止
winsw.exe uninstall 手动卸载

linux安装步骤

1.集成平台提供的jar包,脚本文件以及SAP的jar包,dll,so文件还是参考windows的下载方式。
文件夹的存放位置参考
2.补充config/application.properties对应的属性
 在sapConnect.jar包同级目录下,创建/config/application.properties
 JCO开头的相关属性,都是需要填写SAP属性
 groovy.path是集成平台提供的SapGroovyServiceImpl的脚本存放位置。具体替换D:/的文件路径,不要删除file:/
 targetUrl是sapjar包的存放路径
#服务器端口 server.port=8080 #服务器 JCO_ASHOST= #系统编号 JCO_SYSNR= #SAP集团 JCO_CLIENT= #SAP用户名 JCO_USER= #SAP用户密码 JCO_PASSWD= #登录用语 ZH EN JCO_LANG=ZH #最大连接数 JCO_POOL_CAPACITY=4 #最大线程 JCO_PEAK_LIMIT=10 #sap——groovy脚本的地址 groovy.path=file:/D:/xxxxxxxxx/xxxxxxx/SapGroovyServiceImpl.groovy #SAP的jar targetUrl=D:/xxxxxxx/xxxxxxxxx/
3. 在jar包同级目录下,新建run.sh文件,并输入脚本内容。替换开头第四行的serviceName参数为jar包名称;同样的,更改jarPath参数  
#!/bin/bash basePath=$(pwd) serviceName="sapConnect" jarPath="$basePath/sapConnect.jar" libraryPath="-Djava.library.path=$basePath/sapjar" config="--spring.config.location=file:$basePath/config/application.properties" # 检查Java是否安装 function check_java_installed { if ! command -v java &> /dev/null then echo "未找到Java环境,请确保Java已安装并配置在PATH中。" exit 1 fi } echo "检查Java环境..." check_java_installed servicePath="/etc/systemd/system/${serviceName}.service" # 检查服务是否已存在 if [ -f "$servicePath" ]; then echo "服务已存在,配置文件路径:$servicePath" read -p "是否重新生成配置文件并重启服务?(y/n): " response if [ "$response" != "y" ]; then echo "已退出。" exit 0 fi fi if [ ! -f "$jarPath" ]; then echo "目录下不存在jar包:db-proxy-server.jar" exit 1 fi # 创建服务文件 cat <<EOF >"$servicePath" [Unit] Description=Java Service - $serviceName After=network.target network-online.target Wants=network-online.target [Service] Type=simple ExecStart=/usr/bin/java -jar $jarPath $libraryPath $config WorkingDirectory=$basePath User=root Restart=on-failure RestartSec=3 [Install] WantedBy=multi-user.target EOF echo "配置文件已生成并写入:$servicePath" # 重新加载systemd守护进程,启动和启用服务 systemctl daemon-reload systemctl start $serviceName systemctl enable $serviceName # 等待进度条 echo -n "启动服务中" for i in {1..10}; do echo -n "." sleep 1 done echo "完成" echo "服务${serviceName}已启动。" echo "在app/logs下查看服务日志" echo "执行以下命令查看服务状态: systemctl status $serviceName" # 显示服务状态 systemctl status $serviceName
4.
运行以下命令以使脚本可执行: chmod +x run.sh
执行脚本,创建或更新服务。需要root权限,如有需要在前面增加sudo执行:./run.sh​

四:配置集成平台

1.给需要的客户下单SAP集成连接器
2.进入集成平台-连接器,选择对应的SAP连接器进行填写
3.SAP-RFC创建函数示例:
/** * @author 管理员 * @codeName create_sapaccount * @description create_sapaccount * @createTime 2024-09-09 * @函数需求编号 */ //选用sap系统的保存产品的函数。BAPI_MATERIAL_SAVEDATA,参数传递参考文档定义SapFunctionParamsArg log.info("入参前:集成平台的函数入参" + Fx.json.toJson(syncArg)) String objectApiName=syncArg["sourceObjectApiName"] as String; log.info("入参前:objectApiName" + objectApiName) Map objectData=syncArg["objectData"] as Map; if(objectApiName=="ProductObj"){ Map sapFunctionParamsArgs = [:]; //输入函数apiname sapFunctionParamsArgs.put("functionName", "BAPI_MATERIAL_SAVEDATA"); Map masterFieldValue = objectData["masterFieldVal"] //根据sap系统函数,选择输入对应的入参 Map objectImports=[:]; Map objectImport=[:]; objectImport.put("MATERIAL",masterFieldValue["MATERIAL"]); objectImport.put("IND_SECTOR","M"); objectImport.put("MATL_TYPE","FERT"); objectImport.put("BASIC_VIEW","X"); objectImports.put("HEADDATA",objectImport); Map<String,Object> objectImportsClient= new HashMap<>(); objectImportsClient.put("BASE_UOM","EA"); objectImportsClient.put("BASE_UOM_ISO","EA"); objectImports.put("CLIENTDATA",objectImportsClient); //CLIENTDATAX Map objectImportsClientX=[:]; objectImportsClientX.put("BASE_UOM","X"); objectImportsClientX.put("BASE_UOM_ISO","X"); objectImports.put("CLIENTDATAX",objectImportsClientX); Map<String, List<Object>> objectImportsTable= new HashMap<>(); Map<String,Object> objectImportDesc= new HashMap<>(); objectImportDesc.put("MATL_DESC",masterFieldValue["MATL_DESC"]); objectImportDesc.put("LANGU","ZH"); objectImportDesc.put("LANGU_ISO","ZH"); objectImportsTable.put("MATERIALDESCRIPTION",Lists.newArrayList(objectImportDesc)); //传递函数的入参 sapFunctionParamsArgs.put("importParamsValues", objectImports); //传递函数表参数列表 sapFunctionParamsArgs.put("tableParams", objectImportsTable); List<String> returnParamsField = Lists.newArrayList(); returnParamsField.add("RETURN"); //输入函数的返回字段 sapFunctionParamsArgs.put("returnParamsField", returnParamsField); log.info("data:"+sapFunctionParamsArgs); // String urlProxy="http://10.22.0.5:8080/proxy/sapProxy/executeRfcMethod"; // def data= Fx.http.post( urlProxy, [:], sapFunctionParamsArgs, 20, false, 0, true); StringBody body = StringBody.builder().content(sapFunctionParamsArgs).build() //函数固定执行地址http://ip:port/proxy/sapProxy/executeRfcMethod //根据sap系统的用户名-密码生成对应的token,按照这个规则生成token def sappassword="sappassword具体sap系统的用户名"; def sapusername="sapusername具体sap系统的密码"; def sapToken=Fx.crypto.MD5.encode(sappassword+"&"+sapusername) Request request = Request.builder() .method("POST") .url('http://ip:port/proxy/sapProxy/executeRfcMethod') .timeout(7000) .retryCount(0) .header("Content-Type", "application/json") .header("X-fs-sap-token",sapToken) .body(body) .build() def(Boolean error, HttpResult result, String message) = Fx.http.execute(request) Map erpDataMap=[:] if (error || result.statusCode != 200) { log.info("error :" + message) erpDataMap.put("errCode",result.statusCode); erpDataMap.put("errMsg",message); return erpDataMap; } else { log.info("result"+result) Map resultMap = result.content as Map; //判断成功 if(resultMap.errCode=='s106240000'){ Map dataMap=resultMap.data as Map; Map exportDataMap= dataMap.exportParamsData as Map; Map returnDataMap=exportDataMap.RETURN as Map; String masterId=returnDataMap.MESSAGE_V1 as String; Map erpDetailMap=[:] erpDetailMap.put("masterDataId", masterId) erpDataMap.put("data", erpDetailMap) log.info(masterId) } erpDataMap.put("errCode", resultMap.errCode); erpDataMap.put("errMsg", resultMap.errMsg); return erpDataMap; } }
4.SAP-RFC批量查询函数示例:
/** * @author 管理员 * @codeName queryproduct * @description queryproduct * @createTime 2024-09-11 * @函数需求编号 */ log.info("入参前:集成平台的函数入参test" + Fx.json.toJson(syncArg)) Map sapFunctionParamsArg = [:]; // 函数名称 sapFunctionParamsArg.put("functionName","BAPI_MATERIAL_GETLIST"); Map<String,Object> itemTables=Maps.newHashMap(); itemTables.put("SIGN", "I"); itemTables.put("OPTION", "BT"); itemTables.put("MATNR_LOW", "100-807"); itemTables.put("MATNR_HIGH", "100-809"); Map<String, List<Object>> importTables=Maps.newHashMap(); importTables.put("MATNRSELECTION",Lists.newArrayList(itemTables)); Map<String,Object> objectImport= new HashMap<>(); objectImport.put("MAX_ROWS","100"); sapFunctionParamsArg.put("importParamsValues",objectImport); List<String> tableParams= new ArrayList<>(); tableParams.add("MATNRLIST"); // 函数传递的表格参数 sapFunctionParamsArg.put("tableParams",importTables); //返回执行后的表格名称 sapFunctionParamsArg.put("outTablesNames",tableParams); //执行批量查询需要传递BatchQueryArg,具体参数参考文档 def batchQueryArg=Maps.newHashMap(); sapFunctionParamsArg.put("batchQueryArg",batchQueryArg); StringBody body = StringBody.builder().content(sapFunctionParamsArg).build() //根据sap系统的用户名-密码生成对应的token,按照这个规则生成token def sappassword="sappassword具体sap系统的用户名"; def sapusername="sapusername具体sap系统的密码"; def sapToken=Fx.crypto.MD5.encode(sappassword+"&"+sapusername) Request request = Request.builder() .method("POST") .url('http://ip:port/proxy/sapProxy/executeRfcMethod') .timeout(7000) .retryCount(0) .header("Content-Type", "application/json") .header("X-fs-sap-token",sapToken) .body(body) .build() def(Boolean error, HttpResult result, String message) = Fx.http.execute(request) log.info("error :" + result) //根据具体的返回值做处理逻辑 def content= result.content as Map; Map contentData=content.data as Map; Map tableData=contentData["tableData"] as Map; Map dataItem=tableData["MATNRLIST"] as Map; log.info("dataItem:"+dataItem) Map resultMap=[:] if(dataItem!=null){ List dataList=dataItem["item"] as List; List dataList1=[]; dataList.each { item -> Map masterFieldValMapValue=item as Map; def MATERIAL= masterFieldValMapValue["MATERIAL"] as String; def MATL_DESCRIPTION= masterFieldValMapValue["MATL_DESCRIPTION"] as String; def MATL_GROUP= "MATL_GROUP"; Map masterValueMap=Maps.newHashMap(); masterValueMap.put("MATERIAL", MATERIAL); masterValueMap.put("MATL_DESC", MATL_DESCRIPTION); masterValueMap.put("MATL_GROUP", MATL_GROUP); masterValueMap.put("IND_SECTOR", "IND_SECTOR"); Map masterFieldVal=[:] masterFieldVal.put("masterFieldVal", masterValueMap); masterFieldVal.put("objAPIName", "MATERIAL"); dataList1.add(masterFieldVal); } Map dataMap=[:] dataMap.put("totalNum", dataList1.size()); dataMap.put("dataList", dataList1); resultMap.put("data", dataMap); } resultMap.put("errCode", content.errCode); resultMap.put("errMsg", content.errMsg); // log.info("error :" + resultMap) return resultMap;

总结:

 1.需要从官网下载SAP的jar包
2.从文章链接下载集成平台提供的相关文件
3.参考上面的文件路径,进行创建文件夹放置资源
4.根据SAP真实的系统信息,补充对应的属性
5.启动代理服务器后,在浏览器输入http://ip:port/proxy/sapProxy/connectFunction (ip 需要输入机器正确的ip地址,port端口没有改动,则默认是8080)
6.根据SAP提供真实的函数,进行对应的参数入参传递,批量查询的时候需要定义BatchQueryArg
7.函数请求的时候,为了安全权限,记得系统SAP账号密码生成对应的saptoken.不然会被代理服务器拦截。
8.函数请求批量查询的时候,受限于研发测试环境的SAP系统,上面提供范例还比较粗糙,真正实施的时候可以反馈研发,进行协助。
9.SAP实施的时候,有需要欢迎找研发协助
SapGroovyServiceImpl.groovy
18.6 KB
winsw.zip
6.5 MB
sapConnect.jar
50.8 MB
2024-10-16
1 0