简介:这份PPT教学课件围绕BPM与SAP的集成展开,面向企业信息化建设人员、流程管理岗及SAP相关开发与实施人员,帮助厘清两套系统的定位差异与协同路径。课件先对比SAP以资源管理为核心、偏重物流与资金流,BPM以流程为导向、横向贯穿业务领域的定位,进而说明SAP流程管控能力不足、许可与二次开发成本高、跨部门协作易形成信息孤岛等现实问题。正文重点讲解集成的三类落地方式:中间件产品通过Web Service暴露通用接口、基于SAP .NET Connector调用BAPI或Web Service的纯开发方案,以及依托中间数据表或文件的导入导出方式。同时给出费用审批、客户线索、物料采购、BOM变更、资产全周期管理与紧急订单等典型场景,说明流程在BPM中审批执行后数据回写SAP的闭环。资源为单个pptx文件,压缩包约104KB,共7页,结构紧凑。已有53人学习,适合作为集成方案梳理与内部培训的参考。
1. BPM 与 SAP 集成,先把边界划清楚
采购申请在流程平台里走完三级会签,SAP 那边还得有人照着审批单一行行敲采购订单——这是做流程平台时最先撞上的一堵墙。BPM 的强项是流转、加签、超时提醒和留痕,SAP 的强项是主数据、单据和账务一致性,谁都不愿意把手伸到对方地盘,集成成了唯一解法。BPM 跟 SAP 的集成,说到底就是三件事:把审批结果翻译成 SAP 认得的入参,把单据号与凭证号回写进流程变量,把失败那部分兜住并留下能查的证据。适合写流程引擎服务任务的后端工程师、出接口和授权的 SAP 顾问,以及要把内部分享讲下去的人。后面按通道选型、契约设计、可跑通链路、排错四条线走。
2. BPM 对接 SAP 的四条通道与选型依据
判断走哪条通道,先问五个问题:审批通过后要不要立刻拿到单据号、单据量是每天几十张还是几万张、SAP 侧允许的改造范围有多大、SAP 停机时流程能不能先放行、出了错是重推还是人工补。这五个答案基本就把通道定死了。下面逐条拆开。
2.1 RFC/BAPI 同步调用:拿到单据号最快的一条路
BAPI 是 SAP 把业务逻辑包好之后暴露出来的标准函数,采购订单、发票、货物移动、成本中心都有对应的 BAPI,入参结构和 SE37 里看到的一致。同步 RFC 最大的好处是调用返回时就能拿到 EXPPURCHASEORDER 这类单据号,流程节点可以直接往下走,不需要额外的回调等待。代价是 BPM 的可用性被绑在 SAP 上,SAP 一停机,所有走这条路的流程节点都会挂起。
Python 侧用 pyrfc 打通链路最快,适合先做可行性验证:
from pyrfc import Connection # 连接参数与 SAP 侧 SM59 里的 RFC 目标保持一致 conn = Connection( ashost="10.0.0.21", # SAP 应用服务器地址 sysnr="00", # 系统编号 client="300", # 客户端 user="RFC_BPM", # 专用于集成的通信用户,别用个人账号 passwd="******", lang="ZH", ) # BAPI 入参名必须与 SE37 中完全一致,表参数用 list of dict 传递 result = conn.call( "BAPI_PO_CREATE1", POHEADER={ "COMP_CODE": "1000", "DOC_TYPE": "NB", "VENDOR": "0000100234", "PURCH_ORG": "1000", "PUR_GROUP": "001", "DOC_DATE": "20250401", }, POITEM=[{"PO_ITEM": "00010", "MATERIAL": "MAT-001", "PLANT": "1000", "QUANTITY": 10, "PO_UNIT": "PC"}], ) # RETURN 表里出现 E 或 A,整单都不算成功,不能只看有没有抛异常 errors = [r for r in result["RETURN"] if r["TYPE"] in ("E", "A")] print(result.get("EXPPURCHASEORDER"), errors)参数说明上,ashost、sysnr、client 三项对应 SM59 中的目标主机、系统编号与客户端;user 建议单独建通信用户,只给需要的 BAPI 授权;call 的第一个参数是 BAPI 名称,其后按 SE37 的参数名传值;QUANTITY 这类数值字段要传数字类型,传字符串会被 RFC 层直接拒掉。这里有个必踩的坑:BAPI 调用默认不提交,必须在检查返回表无错误后显式调用 BAPI_TRANSACTION_COMMIT,否则连接断开时数据回滚,日志里什么都看不到。
2.2 IDoc 与 ALE 异步通道:批量与解耦场景的常规选择
如果 BPM 侧一次要推几百条物料凭证,或者 SAP 侧本来就有 ALE 分发体系,IDoc 是更稳的选择。BPM 侧按 IDoc 的段结构拼出报文,通过文件或 RFC 投递到 SAP,SAP 侧用 WE20 配伙伴参数、WE21 配端口,落到 BD87 里做监控与重处理。它的好处是天然异步、有标准的重处理入口、报文格式固定;坏处是回执慢,业务上要接受"提交成功但过账还没完成"这个中间态,流程节点不能立刻断言 SAP 侧已经生成凭证。
2.3 OData 与 Web Service:语言无关的通用接口层
把 BAPI 通过 Gateway 发布成 OData 服务之后,BPM 侧只需要一个 HTTP 客户端,Java、Go、Node 都能调,测试用 Postman 就能跑。SAP BTP 上的集成套件也常被用来做协议转换和字段编排。这条路适合新系统对接新系统,缺点是服务发布和权限配置(S_SERVICE、角色)要单独走一遍,老系统上未必有现成服务可用。
2.4 消息中间件驱动:Flowable 等流程引擎的事件化做法
在 Spring Boot 集成 Flowable 的项目里,更常见的做法是把 SAP 调用从流程线程里剥出去:流程走到服务任务时往消息队列发一条事件,消费者调 SAP,成功后再用消息事件或信号把流程唤醒。这样 SAP 抖动不会把流程线程池打满,重试也有地方放。代价是流程状态变成了"等待中",前端集成要额外展示这个中间态,测试用例也要覆盖超时唤醒。
2.5 四条通道的对照表
| 通道 | 实时性 | SAP 侧改造量 | 适合的单据量 | 失败恢复方式 |
|---|---|---|---|---|
| RFC/BAPI 同步 | 毫秒到秒级 | 低,标准函数即可 | 每天几十到几千 | 调用方自行实现重试与幂等 |
| IDoc / ALE | 分钟级 | 中,需配 WE20/WE21 | 每天几千到几万 | BD87 重处理 |
| OData / Web Service | 秒级 | 中,需发布服务与授权 | 中低 | HTTP 重试加幂等键 |
| 消息中间件 | 秒到分钟级 | 中高,需订阅端 | 高,易削峰 | 队列重投加死信队列 |
注意:四条通道不是互斥的。常见组合是主数据校验走同步 RFC 求快,单据创建走 IDoc 求稳,两条路的单据号都回写同一组流程变量。
3. BPM 与 SAP 的接口契约设计
通道定了之后,真正花时间的活是契约。契约没定清楚,后面每改一个字段都要动两次代码、发两次版本。
3.1 先定单据粒度,再谈字段映射
一个流程实例对应一张 SAP 单据,还是一行?采购申请转采购订单时,一张申请可能按供应商或工厂拆成多张 PO,这时候流程变量里存的应该是行项目列表,而不是一串扁平字段。定粒度的判断标准是:SAP 侧单据号回写到流程变量时,能不能一一对应。做不到一一对应,就要在流程变量里加一层"子单"结构,每个子单有自己的单据号和状态,回写时按子单更新。
3.2 主数据校验:SAP BP 配置与编码存在性
字段映射里最容易出问题的不是金额和日期,是主数据编码。供应商和客户现在都是 BP 模型,一个伙伴号下可以有多个角色,BPM 侧表单里存的编码必须在 SAP 侧真实存在,而且已经分配了对应角色,否则 BAPI 会直接报"供应商不存在"。这类校验放到流程提交前置节点做,比等到 BAPI 报错再回退体验好得多。
" 在 ABAP Open SQL 中自查供应商编码与角色是否齐备 SELECT b~partner, b~name_org1, r~role FROM but000 AS b JOIN but100 AS r ON r~partner = b~partner WHERE b~partner = '0000100234' AND r~role = 'FLVN01' " 供应商角色 AND b~valid_to >= sy-datum INTO TABLE @DATA(lt_vendor).这段查的是 BP 主表 BUT000 与角色表 BUT100,partner 是十位定长的伙伴号,BPM 侧如果存的是去零后的数字,调用前要补足十位;valid_to 判断有效期而不是只判断存在,能挡掉一批"编码对但已失效"的脏数据。放到 BPM 侧做校验时,可以把这段逻辑包成一个 RFC 函数,让流程提交前调一次,返回可用性标记。
3.3 幂等键、单据号回写与状态对齐
流程引擎的重试是常态,SAP 侧必须能识别重复请求。惯常做法是用流程实例 ID 加单据类型拼成幂等键,写进 SAP 侧的自定义表或单据的参考字段,调用前先查一次,命中就直接返回已有单据号。回写要覆盖三个值:SAP 单据号、过账状态、错误消息。前两个给业务看,第三个给运维查。
| 流程变量 | SAP 侧字段 | 类型 | 说明 |
|---|---|---|---|
| instanceId | 自定义表 ZBPM_LOG-BIZKEY | CHAR(32) | 幂等键,流程实例 ID |
| companyCode | BAPIMEPOHEADER-COMP_CODE | CHAR(4) | 公司代码 |
| poType | BAPIMEPOHEADER-DOC_TYPE | CHAR(4) | 采购订单类型,如 NB |
| vendorCode | BAPIMEPOHEADER-VENDOR | CHAR(10) | 供应商,需补足十位 |
| docDate | BAPIMEPOHEADER-DOC_DATE | CHAR(8) | 格式 YYYYMMDD,不是日期类型 |
| sapPoNumber | BAPIMEPOHEADER-PO_NUMBER | CHAR(10) | 创建成功后回写 |
3.4 把流程变量映射成 BAPI 入参结构
映射层建议单独写一个类,不要让服务任务里散落 setValue。下面这段是 Java JCo 侧的写法,字段名必须与 SE37 中的参数名逐字一致:
// 流程变量 -> BAPIMEPOHEADER,避免在服务任务里散落字段赋值 JCoStructure header = repository.getStructure("BAPIMEPOHEADER"); header.setValue("COMP_CODE", (String) vars.get("companyCode")); header.setValue("DOC_TYPE", (String) vars.get("poType")); header.setValue("VENDOR", padLeft((String) vars.get("vendorCode"), 10, '0')); // BAPI 中的日期是 CHAR(8) 的 YYYYMMDD,传 java.util.Date 会直接报类型异常 header.setValue("DOC_DATE", sapDate((LocalDate) vars.get("docDate")));padLeft 处理编码补零,sapDate 处理日期格式转换,这两个函数看着琐碎,但少了它们,接口联调第一天就会卡住。字段映射集中在一个类里还有个好处:SAP 侧加了必填字段,改一个方法就能覆盖所有调用方。
4. 一条能跑通的 BPM 调 SAP 采购订单链路
这段给的是照着搭就能用的最小链路:Flowable 流程走到服务任务,调 BAPI_PO_CREATE1,成功后把单据号写回流程变量。
4.1 连接参数与目标系统配置
先把连接参数固化下来。JCo 侧的属性名与 pyrfc 不同,但含义一一对应,参数写错时的表现是连接直接失败,不会有中间态。
| JCo 属性 | 含义 | 建议值 |
|---|---|---|
| jco.client.ashost | 应用服务器地址 | 按实际填写 |
| jco.client.sysnr | 系统编号 | 00 |
| jco.client.client | 客户端 | 300 |
| jco.client.user | 通信用户 | RFC_BPM |
| jco.client.lang | 登录语言 | ZH |
| jco.client.pool_capacity | 连接池容量 | 5 到 10 |
| jco.client.peak_limit | 并发上限 | 不超过 SAP 侧允许的对话进程数 |
生产环境的密码放配置中心或密钥管理,不要写在 jcoDestination 文件里;连接池容量别贪大,SAP 侧的对话进程是共享资源,BPM 把池子开满,其他系统就会排队。
4.2 Java 侧调用与事务提交
public String createPo(Map<String, Object> vars) throws JCoException { JCoFunction fn = destination.getRepository().getFunction("BAPI_PO_CREATE1"); if (fn == null) { throw new IllegalStateException("BAPI_PO_CREATE1 在目标系统中不可用"); } // 入参结构赋值,字段名与 SE37 一致 fn.getImportParameterList().setValue("POHEADER", buildHeader(vars)); fn.getTableParameterList().getTable("POITEM").appendRows(buildItems(vars)); fn.execute(destination); // 先判返回表,再决定提交还是回滚 JCoTable ret = fn.getTableParameterList().getTable("RETURN"); List<String> errors = new ArrayList<>(); for (int i = 0; i < ret.getNumRows(); i++) { ret.setRow(i); String type = ret.getString("TYPE"); if ("E".equals(type) || "A".equals(type)) { errors.add(ret.getString("MESSAGE")); } } String poNumber = fn.getExportParameterList().getString("EXPPURCHASEORDER"); if (!errors.isEmpty()) { JCoFunction rollback = destination.getRepository().getFunction("BAPI_TRANSACTION_ROLLBACK"); rollback.execute(destination); // 有错误时不提交,直接回滚 throw new SapBusinessException(String.join("; ", errors)); } JCoFunction commit = destination.getRepository().getFunction("BAPI_TRANSACTION_COMMIT"); commit.getImportParameterList().setValue("WAIT", "X"); // 等待更新任务结束再返回 commit.execute(destination); return poNumber; }WAIT 参数设成 X 表示同步等待 SAP 侧的更新任务完成,返回给流程的就是最终状态,代价是调用耗时变长;批量场景可以设成空,把等待交给后续监控。返回表的判读顺序很关键,先收集 E 和 A,再取单据号,最后决定提交或回滚,任何一步顺序调换都可能造成"报了错但数据已经落库"。
4.3 在 Flowable 服务任务里挂载
public class SapPoCreateDelegate implements JavaDelegate { private final SapBapiClient client = SapBapiClient.getInstance(); @Override public void execute(DelegateExecution execution) { Map<String, Object> vars = execution.getVariables(); String poNumber = client.createPo(vars); // 失败会抛业务异常 execution.setVariable("sapPoNumber", poNumber); // 回写,后续节点直接引用 execution.setVariable("sapPostStatus", "DONE"); } }BPMN 里把服务任务配成 delegateExpression 指向这个 Bean,流程变量名与映射类里的 key 对齐即可。异常抛出后由流程引擎的边界事件兜住,走补偿分支,而不是让实例直接挂死。
4.4 错误处理、重试与补偿
错误要分三类对待:业务校验类(主数据不存在、金额超限)不该重试,直接推回人工;通信类(连接超时、连接池耗尽)可以重试,但要带退避;SAP 侧更新终止(事务提交后更新任务失败)最麻烦,需要运维在 SM13 里处理。重试次数建议不超过三次,超过就进人工队列,并且每次重试都带同一个幂等键。
| 错误类型 | 典型返回 | 处理方式 |
|---|---|---|
| 主数据错误 | RETURN 中 TYPE=E,消息含"供应商" | 不重试,转人工修正 |
| 连接超时 | JCoException,无返回表 | 退避重试,最多三次 |
| 更新终止 | 提交成功但 SM13 有记录 | 运维介入,流程挂等待 |
4.5 集成测试:用例要比正常流多
集成测试的用例设计里,正常流只占一条,剩下都要留给异常:主数据不存在、SAP 侧停机、重复提交同一实例、金额与数量为负、单据号回写失败。这些用例在联调环境跑一遍,比在生产上出事再补要划算得多。
5. 排错与进阶:把偶发问题钉在日志里
5.1 返回表、后台作业与日志三处对照
BAPI 报错时不要只看抛出的异常,返回表里通常有更具体的消息号和字段名。SAP 侧配合同步看三处:SM58 查 RFC 调用失败记录,SM13 查更新任务终止,BD87 查 IDoc 状态。BPM 侧则要把流程实例 ID、幂等键、SAP 单据号打在同一条日志里,出问题时用流程实例 ID 就能把两侧串起来。日志里别只打"调用失败",把返回表的 TYPE、ID、NUMBER、MESSAGE 四个字段一起落盘,排错时能少问一轮。
5.2 批量场景下的性能取舍
循环里单条调 BAPI 是最常见的性能杀手,一百行采购订单会产生一百次往返。正确做法是一次调用传多行 POITEM,字段量大的时候再拆批,每批两百行左右。另外两个容易被忽略的点:连接池容量要小于 SAP 侧允许的并发对话数,否则高峰期会在 SAP 侧排队;提交时机上,同一批数据用一次 COMMIT 提交,而不是每行提交一次。
5.3 一个可以直接抄的排查顺序
出问题时按这个顺序走一遍,多数情况三轮之内能定位:先看 BPM 侧日志里的幂等键,确认是不是重复请求;再看返回表的 TYPE 是不是 E 或 A,是的话把 MESSAGE 里的字段名拿到 SE37 里对参数;不是的话看 JCo 异常类型,区分连接问题和业务问题;最后去 SM58 或 SM13 确认 SAP 侧到底有没有收到请求。顺序颠倒过来查,往往会在无关的日志上浪费半天。
本文还有配套的精品资源,点击获取