很多同事问过我同一个问题:SAP ECC 这个已经服务了十几二十年的老系统,供应商管理、订单处理都还跑在上面,现在外面的新系统越来越多,怎么最快让它们拿到 ECC 的数据?RFC 和 IDoc 是经典方案,但对方如果是 Java 或 .NET 的第三方,往往他们更熟悉 Web Service。把 ECC 的能力通过 SOAP 接口开放出来,是 SAP 集成里绕不开的一步。
这篇文章是我最近连续做过两个 ECC 接口发布项目之后的完整复盘。从 SE37 里准备一个函数模块,到 SOAMANAGER 里一步步配置,再到拿出 WSDL 让三方系统调用,中间踩过的坑、需要提前避开的问题,我会一次讲清楚。适合刚接手 SAP 集成任务的人 ABAP 开发、Basis 顾问,也适合正准备对接 SAP 但不太熟悉这一套流程的外部系统开发人员。
1. 做这个接口之前,先想清楚三个问题
1.1 Web Service 在 ECC 集成里是什么定位
SAP ECC 对外集成的老方案有很多,RFC 几乎是伴随 SAP 一起出现的。BAPI 调用、远程函数模块,只要两边都是 SAP,或者对方愿意装 SAP 客户端,这套东西很顺。但问题在于,现在的第三方系统常常是.NET、Java、Python 甚至直接跑在云上,让它们引入 SAP JCo 或者 RFC SDK,成本和边界都不小。
IDoc 是另一种典型方式,它适合异步批量业务,比如订单下发,但很多需要实时返回结果、查询主数据的场景并不合适。Web Service 出现之后,等于把 RFC Function Module 包装成了标准的 SOAP 服务,对方只需要拿到 WSDL,就能用自己熟悉的语言生成客户端,不用再关心 SAP 内部那些连接方式。这就是它在集成架构里的定位:一个连接 SAP 业务能力与异构系统的轻量桥。
还有一点很关键,ECC 的 Web Service 是基于 NetWeaver 栈实现的,底层连通了 ABAP 程序与 HTTP 层。也就是说发布服务并不需要额外采购中间件,系统本身已经具备能力,只是很多人只熟悉 ERP 业务,对这些网络通信和配置入口不熟。这一点理解之后,下面的事就容易了。
1.2 两条发布路径怎么选
ECC 上发布 Web Service,大体有两条路径。
第一条是把函数模块直接绑定成服务。具体做法是进入 SOAMANAGER,创建一个 Service Provider,后端绑定一个已经启用远程调用的函数模块。这种方式快,配置少,适合做查询类接口、数据查询、主数据校验等实时交互。我这次的项目就是使用这种方案。
第二条是基于 SAP Enterprise Service 的模式。先通过 ESI/ESR 建立服务接口,再生成 Proxy 并发布。优点是架构严谨、符合 SOA 规范,能支持复杂的消息头和异常处理,但配置量明显大,需要额外的开发时间,适合对接口管控要求高的企业。只是普通三方查询用不到这么重的东西。
我的建议是优先看业务真实需求。如果只是外部系统传几个参数,ECC 回传结果,直接走函数模块绑定就够了。需要审计、权限分级复杂、消息格式统一管理的,再考虑走完整的企业服务方案。尽量不在一开始就把复杂度拉满。
1.3 别人给你反码,你怎么判断是不是这个接口的问题
排查时要清楚 Web Service 发布的链路:外面 HTTP 请求进来,先到 ICM,再转到 SOAP Runtime,然后找到对应的 ABAP 类或函数模块,执行完之后再原路返回。如果三方说调用失败,你至少能判断是哪一段的问题。是连不上主机端口,还是 WSDL 能访问但调用报认证错误,又或者 SOAP 请求已经进去但函数模块报错。这几个不同阶段的日志位置完全不一样,你在动手发布前最好先有个整体概念。后文会专门拎出来说排查。
2. 发布前必须把一个函数模块准备到可用状态
2.1 什么样的函数模块才能被发布
发布 Web Service 不是随手拿个 SE37 里的函数就绑定,至少要满足几个条件,否则后面配置的时候会卡住。
函数模块必须被标记为远程启用模块。在 SE37 中点击功能模块,进入属性页签,将“远程启用的模块”勾上。这一步决定 SAP 是否允许外部通过 RFC 或其他远程协议来调用它,Web Service 底层同样受这个标志位影响。很多人忘了勾,在 SOAMANAGER 里选择函数模块时就搜不到它。
函数模块的参数定义要清晰。建议输入参数统一用单一的结构,输出参数用表或者结构,避免大而全的多个导出参数。这样后续生成 WSDL 时,接口字段层级清楚,第三方生成客户端时不容易产生歧义。比如一个查询物料信息的接口,输入结构里放物料号、工厂,输出用一张返回表,字段名规范,命名和业务含义一致。
函数模块代码里不要直接 COMMIT WORK。外部调用进来时,这个 RFC 运行在自己的事务 LUW 里,提交操作要交给调用方的控制逻辑。如果需要写数据,一般建议在函数内只做读取,或者由调用方决定是否提交。这是 ABAP 开发的老规矩,但在 Web Service 场景下更容易被忽视,一旦放了 COMMIT,并发和回滚控制就乱套了。
2.2 一个可以直接套用的查询接口实例
我这次做的是物料主数据查询,给外部仓储系统读 ECC 的物料描述、基本计量单位、物料组。函数模块大概长这样:
FUNCTION z_get_material_info. *"---------------------------------------------------------------------- *"*"本地接口: *" IMPORTING *" VALUE(IS_INPUT) TYPE ZST_MAT_INPUT *" EXPORTING *" VALUE(ET_DATA) TYPE ZTT_MAT_OUTPUT *" EXCEPTIONS *" NO_DATA_FOUND *"---------------------------------------------------------------------- DATA: ls_output TYPE zst_mat_output. SELECT makt~maktx, mara~meins, mara~matkl INTO CORRESPONDING FIELDS OF ls_output FROM mara INNER JOIN makt ON makt~matnr = mara~matnr WHERE mara~matnr = is_input-matnr AND makt~spras = sy-langu. IF sy-subrc NE 0. RAISE no_data_found. ENDIF. APPEND ls_output TO et_data. ENDFUNCTION.这里面我用了一个输入结构 ZST_MAT_INPUT,一个输出表 ZTT_MAT_OUTPUT。输出表比单个导出结构的好处是将来如果一次要查多个物料,接口不用改结构,只要函数内加个 SELECT 循环就能扩展。
还要注意一个细节:函数模块的短文本描述建议写上业务含义,比如“物料主数据查询接口”。发布服务时,SOAMANAGER 会把这个描述带到服务信息里,后续做文档和排查时一眼能认出来。很多人随意命名,几个月后自己都忘了是干什么的。
2.3 发布前的权限和授权检查
Web Service 发布出来后,调用方通常用 Basic Auth 方式访问,也就是提交用户名密码。那么这个 SAP 用户需要有相应权限,才能通过 HTTP 请求进入函数模块。
在 SAP 用户授权层面,最基本的是 S_ICF 授权,子字段里允许调用 HTTP Service 节点。此外,函数模块并不像 RFC 那样在 SU53 里有独立授权对象,因为最终执行还是落在函数模块所在的包上。建议把需要调用接口的用户设置成服务用户,只授予调用相关服务的权限,不要放开整个 SAPGUI 桌面访问权限。
我在项目里单独建了一个 Z_SERVICE_USER,分配了 S_ICF 和相关角色。三方系统拿到这个账号以后,只用于接口调用,不会误操作业务前台。这一点不要省略,很多上线后的安全扫描问题就出在接口用户权限过大上。
3. 在 SOAMANAGER 里把 Web Service 发布出去
3.1 一步步创建服务提供者
一切准备就绪后,进入核心环节。事务代码输入 SOAMANAGER,回车后会在浏览器里打开一个配置界面。这个界面初看有点乱,但只需要关注“Web服务配置”这一项。
点击进入后,左侧会显示服务定义和服务提供者列表。我们要创建的是服务提供者,也就是后端具体由哪个函数模块提供实现。点击创建,选择“Service Provider”。配置逻辑可以选择“后端”,类型选择“函数模块”。
接下来就是绑定函数模块的时候。系统会弹出一个窗口,让你输入已经远程启用的函数模块名称。这里要注意,这个窗口里的搜索是基于“远程启用的模块”过滤的,如果前面忘了勾,你在这里根本看不到它。输入 z_get_material_info 后,系统会自动带出函数的输入输出参数,并生成对应的接口结构。
保存之后,系统会给这个服务配置分配一个内部名,比如覆以 CO_ 之类的前缀。此时服务还没有绑定具体操作,也就是说第三方即使拿到地址,也不知道具体调用的方法名。下面就需要进入“操作”页签。
3.2 绑定操作与方法名
在服务配置界面找到“操作”相关区域,选择“绑定”,系统会列出当前服务提供者可用的函数模块列表。选中我们那一个,点击下一步。
系统会让你确认操作方法名。这个操作名最终会在 SOAP Body 中出现,相当于定义了像 Z_GET_MATERIAL_INFO 这样的方法节点。建议操作方法名保持与函数模块名一致,风格统一,不要自动生成一串带数字的匿名方法。名称太乱,后面第三方开发人员会很难维护。
绑定时系统会要求确认参数的映射规则。一般保持默认映射,SAP 会把 IMPORTING 参数放到请求结构里,把 EXPORTING 和表参数放到响应结构里。如果你定义的输入输出结构字段多一些,WSDL 里会生成一整套嵌套的复杂类型。在这里能顺便做最后一次字段检查,避免发布完成后才发现结构不对。
绑定完成后,系统会要求激活服务。激活本质上是把服务定义注册到 SOAP Runtime 中,并生成对应的运行时对象。激活成功后,服务状态会显示为“已发布”,这时地址才真正可用。
3.3 获取 WSDL 地址,验证节点是否可访问
发布成功后,在服务配置界面选中该服务,界面会显示服务访问的详细信息。最常用的是 WSDL 字段,复制出来,大概会得到这种形式:
http://<host>:8000/sap/bc/srt/wsdl/flv_10002A1B30/srvc_ws_xxx/wsdl11/allinone/ws_policy看起来很长,这是正常的,SAP 在末尾用策略标注版本。这个地址可以直接交给第三方开发人员,用于生成客户端代码。如果要正式环境走 HTTPS,就把 HTTP 改成 HTTPS,端口换成 ICM 的 SSL 端口,通常是 443。
拿到地址后,别急着发出去,先用浏览器访问一次。如果能看到一长串 XML 文档,说明服务已经正常暴露。如果出现 403 或 404,多半是 SICF 里对应的服务节点没有激活。进入事务代码 SICF,找到默认主机的/sap/bc/srt节点,确保节点状态是激活。通常 SOAMANAGER 发布服务时会自动激活,但有些权限受限的环境下,这一步需要 Basis 手动处理。
检查 WSDL 时还要留意主机名和端口。如果服务地址里出现的是内网 IP,而三方系统在另一个网络区域,就需要通过 SMICM 配置或反向代理把外部域名映射到 ECC 内部的 HTTP 端口。很多项目联调时出现“WSDL 打不开”,其实不是接口问题,是网络地址没有打通。
3.4 用 SoapUI 做一次冒烟测试
服务发布完,先用 SoapUI 拉起来做一次端到端测试,不要直接让三方系统开始联调,省得从自己的坑变成对方的坑。
打开 SoapUI,新建 SOAP Project,WSDL 地址填入刚才复制的那一串。SoapUI 会自动生成一个请求模板。在请求模板中,填入物料号,并设置 Basic Auth,用户名和密码填我们专门建的服务用户。
发送请求后,正常情况下响应体里会带回物料描述等信息。如果出现 HTTP 401,先检查用户名密码是否正确,再检查用户是否有调用服务的权限。如果出现 SOAP Fault,一般问题出在函数模块内部,比如物料不存在导致 no_data_found 异常被抛出,这是业务逻辑问题,返回给外部会是一段标准 Fault 报文。
到这里,接口发布这半边已经完成。但真正的挑战往往出现在三方系统调用时。
4. 三方系统的调用方式和代码示例
4.1 Basic Auth 认证和端口对接
SAP 的 Web Service 最常用认证方式是 Basic Auth。每个 SOAP 请求的 HTTP Header 中带上 Authorization 字段,值由 Base64 编码的“用户名:密码”组成。SAP 端识别后,会话里就有了用户身份,后续逻辑基于这个用户执行权限检查。
三方系统要注意,很多 HTTP 客户端默认不启用 Preemptive Authentication,也就是说服务器没有要求时不会主动发送 Authorization Header。对 SAP 来说,它要求认证,会返回 401,然后客户端带着凭据重发,这也能成功,但会增加一次请求往返。如果业务对性能有要求,可以手动设置预认证。
端口方面,ECC 的 ABAP HTTP 服务默认监听在 8000 或 50000,不同系统可能不同。生产环境一般是通过 WAF 或负载均衡把 8443/443 转发到内部端口。发布服务后,记得在防火墙里放通对应端口。我碰到过一次三方报“连接超时”,结果排查半天发现防火墙上只开了 443,但 ECC 内部监听在 8000,没有做端口映射。
4.2 Java 调用代码片段
Java 调用 SOAP 接口有各种方式,用 JAX-WS 的技术栈相对正统。但有些第三方开发只想要一个最小示例,那么用原生 HttpURLConnection 构造 SOAP 请求反而更直观。这里给一个可运行的示例,重点是演示 HTTP 请求结构,而不是完整工程代码。
import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.Base64; public class SapWsClient { public static void main(String[] args) throws Exception { String endpoint = "http://10.1.1.10:8000/sap/bc/srt/soap/ ... "; String user = "svc_ws_user"; String password = "xxxxxx"; String auth = Base64.getEncoder().encodeToString( (user + ":" + password).getBytes(StandardCharsets.UTF_8)); URL url = new URL(endpoint); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("POST"); conn.setRequestProperty("Content-Type", "text/xml; charset=utf-8"); conn.setRequestProperty("Authorization", "Basic " + auth); conn.setDoOutput(true); String soapXml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\"" + " xmlns:urn=\"urn:sap-com:document:sap:rfc:functions\">" + "<soapenv:Body>" + "<urn:z_get_material_info>" + "<IS_INPUT><MATNR>1000001</MATNR></IS_INPUT>" + "</urn:z_get_material_info>" + "</soapenv:Body></soapenv:Envelope>"; try (OutputStream os = conn.getOutputStream()) { os.write(soapXml.getBytes(StandardCharsets.UTF_8)); } int code = conn.getResponseCode(); System.out.println("HTTP Response: " + code); // 读取输入流或错误流,打印响应 XML } }Java 代码里有个关键点:SOAP Body 的命名空间和元素名必须和 WSDL 里定义的完全一致。实际项目里不要在文档上猜,直接用 SoapUI 生成的请求模板来改造,比手写可靠得多。如果请求里少了命名空间声明,SAP 会报无法解析 SOAP Body 的错误。
4.3 .NET 调用方式
.NET 端调用 SAP Web Service 最简单的方式是在 Visual Studio 中添加服务引用,把 WSDL 地址贴进去。VS 会生成代理类,后面直接调用代理方法即可。
BasicHttpBinding binding = new BasicHttpBinding(); binding.Security.Mode = BasicHttpSecurityMode.TransportCredentialOnly; binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic; EndpointAddress address = new EndpointAddress("http://10.1.1.10:8000/sap/bc/srt/soap/..."); Z_GET_MATERIAL_INFOClient client = new Z_GET_MATERIAL_INFOClient(binding, address); client.ClientCredentials.UserName.UserName = "svc_ws_user"; client.ClientCredentials.UserName.Password = "xxxxxx"; var input = new ZST_MAT_INPUT { MATNR = "1000001" }; var output = client.Z_GET_MATERIAL_INFO(input, out var etData);这里特别提醒.NET 的绑定配置。SAP ABAP 发布的 Web Service 默认使用 HTTP 传输,不是 WS-Security 标准,所以绑定安全模式一般选 TransportCredentialOnly。有些开发误配置成 Message 或者 WSHttpBinding,会报安全协商失败,实际上 SAP 侧根本没有参与那些握手逻辑。
4.4 请求报文的常见调试入口
如果三方反馈接口调不通,第一步就是让对方把请求日志发过来。你不需要看全部报文,只要看几点:HTTP 状态码、SOAPAction 是否为空、请求 XML 是否完整。很多 ECC 发布的 Web Service 对 SOAPAction 并不会严格校验,但有些服务配置会需要。更常见的是请求 XML 里命名空间或字段名大小写不对,SAP 对大小写敏感,WSDL 里叫什么就必须叫什么。
想查 SAP 端到底有没有收到请求,可以进事务代码 SICF,找到对应的服务节点,激活跟踪功能。一旦开启跟踪,HTTP 请求和响应的概要会写进日志,用 SMICM 也能看到连接状态。这些数据能帮你判断请求是没到 ECC,还是到了但执行报错。
5. 常见问题与故障排查实录
5.1 WSDL 打不开,提示 403 Forbidden
这是最容易出现的问题,根源通常是用户权限或节点激活。先分两步检查。
第一,进入 SICF,确认/sap/bc/srt这个虚拟主机下确实有对应服务节点,并且节点状态是绿色激活。第二,检查当前访问 WSDL 的用户是否有 S_ICF 授权。浏览器访问 WSDL 时如果把用户密码弹出框取消,就会看到 403。推荐先让有权限的用户访问试试。
我在一个项目里遇到的情况是,服务节点状态正常,但三方通过负载均衡访问时用了域名,而 ECC 返回的 WSDL 里写的是内网主机名。外部系统拿到 WSDL 后下一步去解析主机名,解析不了直接报错。这个问题不在 ECC 配置,而在负载均衡改写或 DNS 解析,需要在基础设施层解决。
5.2 调用时报 HTTP 500,里面有 SOAP Fault
HTTP 500 说明请求已经进了 ECC,但函数模块执行时抛出了异常。展开响应体里的 SOAP Fault 就能看到具体信息,比如 NO_DATA_FOUND 错误。这其实是业务逻辑问题,不是服务发布问题。
处理思路是让函数模块尽量做完整的数据校验,把错误信息写入导出参数而不是抛出异常。比如物料不存在时,先向输出表里返回一条空记录或自定义的错误码,让三方系统能友好感知。否则三方只会看到一行晦涩的 XML Fault,联调效率很低。
5.3 中文乱码和编码问题
ECC 系统如果是早期非 Unicode 系统,SOAP 报文默认编码可能和外部系统 UTF-8 不一致,中文就会乱码。在新的 ECC 版本里基本没有这个困扰,但也需要考虑两端 HTTP 头中 Content-Type 的 charset 声明是否统一。
建议三方请求统一使用 UTF-8 编码,SAP 侧函数模块里接收字符串时不要因为系统代码页问题产生转换字符。如果遇到同一份物料描述通过 SoapUI 正常、通过 Java 客户端乱码的情况,重点查对方 HttpClient 是否把 Content-Type 设置成了text/xml; charset=ISO-8859-1,改回 UTF-8 通常就好了。
5.4 大批量数据返回慢
Web Service 适合交互型查询,不适合一次性拉几万行主数据。函数模块如果直接 SELECT 全表返回给外部,SOAP 序列化和传输都会成为瓶颈,三方客户端还可能因为解析大报文直接内存溢出。
这种场景建议做分页查询接口,入参里带记录起点和行数,或者增加时间范围限制。还有一种做法是配合 IDoc 做异步批处理,但这就不属于本文 Web Service 的讨论范围了。发布接口前先和业务方对齐数据量,宁可多定义一个翻页参数,也不要等上线后被打爆。
5.5 顺带提一句:LSMW 和 Web Service 的关系
聊到 SAP 数据交换时,LSMW 也是高频词。LSMW 是一种批量导入工具,用来把 Excel、顺序文件等数据转换成 SAP 数据,常用于主数据初始导入或历史数据迁移。它非常强,但本质上是一个 SAPGUI 里的人工工具,不是系统间实时调用的服务。
有些业务方会把两者搞混,比如问“能不能让第三方系统直接往 ECC 写主数据,用 LSMW 实时接收”。严格说 LSMW 不支持外部系统直接触发,它需要有人登录 SAPGUI 执行。实时接口还是得走 Web Service 或 IDoc。我习惯在和业务确认需求时先讲清楚这层区别,避免后面花大量精力搭了一个“面向人的自动导入”方案,实际上根本满足不了实时性要求。
6. 实操过程中的经验沉淀
6.1 发布前先把命名规范定下来
接口名、结构名、消息类型名,如果项目里只有两三个接口可能感觉不明显,一旦做到十几个,命名混乱会让你怀疑人生。我自己会按这个模式:远程函数模块用Z_<业务动词>_<对象>,输入结构用ZST_<接口名>_IN,输出表用ZTT_<接口名>_OUT,操作名保持和函数模块一致。这样不管是看 SE37 还是看 WSDL,都能直接对应。
另外 WSDL 里的命名空间,SAP 默认生成一大段带数字前缀的 URL。虽然能用,但不太好读。创建服务提供者时可以在配置界面指定命名空间,建议格式用urn:z:material这种短命名空间。三方系统生成代理类之后,代码里不会到处出现乱码式的前缀,平时维护起来舒服得多。
6.2 让三方系统提前介入,联调成本可以少一半
很多项目把 WSDL 发给第三方后就放手了,等到联调时才发现各种对不上。我现在的做法是先和对方开发开一个 20 分钟的技术对齐会,讲清楚方法名、字段说明、认证方式、请求示例。然后把 SoapUI 导出的 XML 请求模板直接发给对方参考,让他们照着改,而不是从空白的 WSDL 自己发挥。
这个动作很有效,因为 SOAP 接口看起来简单,实际对命名空间和字段大小写的要求非常严格,减少一点猜测成本,联调阶段能省下不少来回。我在最近一次项目里,由于提前做了这一步,三方开发第一次请求就通了。
6.3 别忘了为接口留日志和监控
Web Service 上线后,出现问题时如果只能问“你调了没?返回什么?”会很被动。建议在函数模块内部,用 SPLOG 或者应用日志对象把关键入参、返回条数、异常信息记录一下。这样对方报问题时,你可以马上拉出日志对照。对于性能敏感接口,还可以在 SMICM 里观察连接数和响应时长,判断是否需要扩容。
日志记得不要记录密码等敏感信息。调用认证通过 HTTP Header 传输,函数模块本身拿不到密码,所以不会误记录。但入参里的客户号、物料号是业务数据,日志权限也要控制好。
6.4 后续扩展方向
如果接口数量多起来,流程开始有路由、转换、消息可靠性的需求,单纯靠 ECC 里的 SOAMANAGER 发布就会管理不过来。这时可以考虑引入 SAP PI/PO 或云集成套件作为中间层,把 ECC 服务封装成标准 API 供上层调用。那是一个更大的架构话题,但基础仍然是本文这套函数模块发布机制,只是多了个外层平台。
就我个人经验来说,把 ECC 的 Web Service 做通并不难,难的是在发布前想清楚边界、命名、权限和日志,然后把细节告诉每一端的人。踩过几次坑之后,你会发现这套流程已经像喝水一样自然了。最后再分享一个小技巧:发布前把 SOAMANAGER 里的服务导出成一个配置文件,放到项目文档里,下次环境重建或迁移时可以直接导入,能省下重新点击配置的时间。