一:SAP代理服务器的设计思路
- 实现动态加载指定的jar包,支持客户上传jar包,保证相关的安全性,很好的兼容不同的SAP版本的jar包。
- 支持用户自定义groovy脚本,在集成平台提供的基础上,做二次开发,实现逻辑闭环。实现SAP的RFC协议转成http-json返回。
二:SAP代理服务器协议
集成平台提供的sap-rfc代理暴露/proxy/sapProxyexecuteRfcMethod 支持传递相关的参数,具体参数SapFunctionParamsArg以及返回值看下面说明:
fieldName | fieldType | fieldDesc | remark |
---|---|---|---|
functionName | String | sap-rfc函数名 | 必填 |
importParamsValues | Map<String,Object> | sap-rfc的函数入参 | 根据sap_rfc函数定义,区分是标量参数/结构参数(标量参数就是标准的key-value,{"params1":"test"},简单类型,结构参数Structure是封装后的复杂类型) |
tableParams | Map<String, List<Object>> | 表的参数 | 没有可不传,RFC-函数的import定义有时候是以table作为入参。 |
outTablesNames | List<String> | 返回的表名 | 没有可不传 |
returnParamsField | List<String> | 返回的参数 | 没有可不传 |
batchQueryArg | BatchQueryArg | 批量查询的参数 | 用于批量查询,具体定义看BatchQueryArg参数定义 |
BatchQueryArg参数定义
fieldName | fieldType | fieldDesc | remark |
---|---|---|---|
masterApiName | String | 主对象的apiname | 必填 |
detailApiNames | List<String> | 从对象的apiname | |
limit | Integer | 批量查询,一次返回最大条数 | 默认100 |
offset | Integer | 批量查询,从第几条开始查询 | 默认从0 |
masterFieldName | String | 主对象的字段field_name | 必填 |
masterDetailFieldNames | Map<String,String> | 从对象的主从字段field_name,从对象的apiname-主从字段 | 有主从对象必填 |
三:代理服务器安装步骤
windows安装步骤
- 从SAP官网下载jar包 , 访问SAP官网:SAP NetWeaver Remote Function Call (RFC) Software Development Kit (SDK) 到页面最底部进行下载 sapjco3.jar
- 下载对应系统的libsapjco3.so文件(linux系统)sapjco3.dll(windows系统),以及下载集成平台提供的SAP代理jar包,以及提供的groovy脚本
- window系统,下载winsw.zip到服务器进行解压
- sapjco3.jar,libsapjco3.so,sapjco3.dll由于合规问题,需要客户方IT人员在SAP官方渠道下载,其他文件可以从文章底部下载
- 在对应的服务器上创建一个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该文件夹回车
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实施的时候,有需要欢迎找研发协助