ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

EBS采购订单审批API集成实战:工作流、中间件与存储过程设计

EBS采购订单审批API集成实战:工作流、中间件与存储过程设计

做EBS集成的同学对采购订单审批肯定不陌生。标准功能里,审批人每天登录EBS工作台,从一堆待办通知里逐个点开、审批、写意见,流程虽然完整,但一旦公司上了OA、企业微信、钉钉或者供应商协同平台,业务方就会立刻找你:能不能让我们在OA里直接审批采购订单?能不能把待办自动推过来?审批完能不能把结果回传给ERP?这个时候,你需要开发和部署一套Oracle EBS采购订单审批和转发API,也就是常说的审批API。

这篇文章基于我最近完整落地的一个EBS采购订单移动审批集成项目,把审批API的需求拆解、技术选型、核心存储过程实现、中间件封装、测试排错思路完整梳理一遍。内容偏实战,适合正在被同样需求折磨的EBS开发、ERP集成顾问、项目经理参考,也适合想了解企业里“审批API到底是怎么通”的初学者。

1. 项目背景与需求拆解

1.1 为什么需要审批API

EBS采购订单审批,本质上不是一张表加一个状态字段那么简单。它背后是一整套工作流引擎在驱动:采购员创建订单,系统根据审批规则生成待办通知,审批人在通知上点击“批准”后,Workflow引擎自动判断是否还需要下一级审批,直到全部审批完成,订单状态才变成“已批准”。

原生模式下,所有审批动作都只能在EBS的审批工作台里操作。这意味着审批人必须能访问局域网,必须有EBS账号,必须记得每天去待办列表里翻一翻。很多企业里实际业务人员的习惯是“OA弹个窗我才知道有事情要做”,你让他在两个系统之间来回切换,审批时效一定起不来。

所以,企业真正需要的不是一个“审批按钮”,而是把审批动作能力开放成接口:外部业务系统可以查询当前用户的审批待办,可以执行同意、拒绝、退回、转发等操作,然后把结果同步回EBS。这就是审批API的存在意义。

1.2 核心需求清单

我在项目初期和业务方、IT方来回对了好几轮需求,最后确定下来的核心功能可以归纳成五块:

  • 待办查询:根据工号查询当前登录用户在EBS中的采购订单待办列表,包括订单编号、供应商、金额、提交人、当前节点、发起时间。
  • 审批处理:对待办执行同意(APPROVE)或拒绝(REJECT),必须能附带审批意见。
  • 审批转发:原审批人因出差、离职、业务调整等原因,把待办转发给其他有权限的用户,并保留转发记录。
  • 结果回写:审批动作完成后,将EBS侧的最新审批状态回传给调用方,方便OA刷新界面。
  • 审计留痕:每一次API调用都记录操作人、目标订单、动作、结果、耗时,方便内控和追溯。

注意,这里还有一个很容易被忽略的需求:幂等性。外部系统因为网络超时可能会自动重发请求,同一笔审批如果被重复执行,要么报错,要么返回“已处理”结果,绝不能把一笔订单从“已批准”状态再往下推进一遍。

1.3 典型业务场景还原

某个制造企业(这里就叫A公司吧)的场景比较典型。A公司上了OA审批中心,希望采购订单审批在OA里完成。一笔采购订单提交后,走“项目负责人 → 部门经理 → 财务 → 总经理”四级审批。总经理出差,需要把待办转发给副总代审。同时财务在OA里看到审批关联的合同信息不完整,直接点了“拒绝”并填写意见,要求采购员修改后重新提交。

没有API的时候,这四步审批里只要有一级人不在电脑前,整个链条就卡着。有了转发API后,总经理在手机OA上看到待办,一键转发给副总,副总实时收到消息,直接审批通过。整个链路在半天内走完。这就是审批API最直接的价值。

2. 技术方案选型与架构设计

2.1 三条技术路线的对比

接EBS审批,业内常见做法大致分三类,我在这里用表格做个横向对比,方便不同体量项目的同学对号入座。

方案实现方式开发成本安全性灵活性维护难度
方案A开放数据库包,外部系统直连数据库调用低低,账号密码直接落到外部系统,审计困难低,外部系统需要直连数据库网络中
方案B基于EBS自带的REST服务扩展高,需要熟悉EBS的ADF/BC4J框架和定制发布流程较高,走EBS应用服务器认证中,受原生框架限制较多高
方案C独立中间件 + 数据库存储过程包中,需要开发一个轻量级Java服务高,认证和审计可以统一管控高,接口字段和协议完全自定义中

我在这个项目里选的是方案C。理由很简单:EBS在绝大多数企业里属于核心交易系统,数据库连接不能直接暴露给外部应用;而EBS原生REST服务定制审批流程需要改CUST扩展点,开发和版本升级成本都偏高,团队里如果没有熟悉Oracle ADF的人,后续维护会很痛苦。

2.2 为什么采用“中间件 + 存储过程包”组合

先理解一下EBS审批的数据结构。审批人看到的一条待办,实际对应的是工作流引擎里的一个通知(Notification),存在于WF_NOTIFICATION表。审批动作的本质,是调用标准工作流API更新这个通知的状态,并触发引擎向下一个节点推进。

所以这里有一条红线:绝不能绕过工作流引擎直接改表。有些人图省事,直接UPDATE WF_NOTIFICATION把状态改成“已批准”,看起来待办消失了,但工作流活动实例的状态没变、审计信息缺失,后续整个审批链路的计数全乱,最后订单状态和工作流状态对不上,排查起来会非常痛苦。

方案C里,Java中间件只负责通信协议转换和业务编排,核心审批动作放在数据库包中完成。数据库包内部调用的是EBS标准工作流API,由工作流引擎自己去推进节点。这样的层级关系是:

外部系统(OA/钉钉/移动App) → 中间件API服务 → 数据库存储过程包 → EBS工作流引擎 → 采购订单审批数据

这样做的好处是:中间件可以统一处理认证、参数校验、接口文档、日志;数据库包可以保证审批逻辑不依赖中间件,即使以后中间件重构了,审批核心逻辑依然跑在数据库层,不会丢。

2.3 架构中的身份映射设计

EBS的审批人和外部系统用户默认不是一套账号体系。API接收到的是OA工号(比如10086),数据库里查的却是FND_USER里的USER_NAME(比如ZHANGSAN)。所以中间件必须维护一张映射表,或者调用方直接传EBS账号。

我当时做的是双模式:优先按工号查映射表,映射不到就直接按USER_NAME匹配。这种设计灵活性高,但要注意,映射表本身也要有维护界面,否则上线后人员变动全靠写SQL,迟早出问题。

3. 核心API的实现与实操步骤

3.1 接口契约设计

接口设计上不要贪多,我最终精简成三个核心接口。

接口方法路径核心参数
待办查询GET/api/po/notificationsapprover(审批人工号), pageSize, pageNum
审批处理POST/api/po/approvenotificationId, action, comment, operator
审批转发POST/api/po/forwardnotificationId, fromUser, toUser, comment, operator

统一响应结构我习惯这样做:

{ "code": "200", "message": "success", "data": { "notificationId": 123456, "poHeaderId": 7890, "poNum": "PO-2024-00123", "status": "APPROVED" } }

状态码不能只用HTTP状态码。我自定义了业务码:200正常,400参数错误,404待办不存在,409订单已被处理(幂等冲突),500系统异常。这样排查问题时,只看返回码就能定位大部分问题。

3.2 数据库包主体结构设计

我在EBS数据库层创建一个独立的Package,命名上加上公司标识防止和标准对象冲突,比如XXAPP_PO_APRV_PKG。包内拆分四个过程:

  • get_todo_list:查待办列表
  • submit_approval:审批通过/拒绝
  • forward_notification:转发待办
  • post_process_audit:写审计日志

这里有一个很重要的设计原则:数据库包只接收解析后的业务参数,不负责外部协议解析和鉴权。鉴权在中间件完成,数据库包内部只做业务合法性校验。

3.3 审批主逻辑实现

审批是核心中的核心。网上很多例子里直接UPDATE状态,那是错误的。我的做法是先锁定通知行,再调用EBS标准API。

PROCEDURE submit_approval( p_notification_id IN NUMBER, p_action IN VARCHAR2, -- APPROVE / REJECT p_comment IN VARCHAR2, p_operator IN VARCHAR2, x_return_status OUT VARCHAR2 ) IS l_nid NUMBER; l_status VARCHAR2(30); BEGIN x_return_status := 'S'; -- 第一步:锁定当前通知,防止并发重复审批 SELECT notification_id INTO l_nid FROM wf_notifications WHERE notification_id = p_notification_id AND status = 'OPEN' FOR UPDATE NOWAIT; -- 第二步:调用EBS标准工作流API完成审批 fnd_wf_notification.set_approval_status( p_notification_id => l_nid, p_status => p_action, p_comment => p_comment, p_responder => p_operator ); COMMIT; EXCEPTION WHEN OTHERS THEN ROLLBACK; x_return_status := 'E'; -- 记录错误日志到自定义日志表 END submit_approval;

这套逻辑里的关键点,第一个是FOR UPDATE NOWAIT。两套系统同时调用同一个接口时,这个锁能保证只有一笔审批能成功,另一笔要么等锁,要么直接报“数据被锁定”。第二个是调用标准API而不是直接改表,把动作交给EBS工作流引擎去处理,后续任务自动推进。第三个是事务处理,审批成功才提交,失败必须回滚,绝不能让外部系统拿到一个假成功。

实际操作里,参数p_responder在部分EBS版本中叫法略有不同,甚至某些功能顾问会建议同步更新接口人字段。你在自己环境联调时以标准API签名为准,核心思路是一致的。

3.4 转发逻辑实现

转发API的业务逻辑比审批稍微绕一点,因为它涉及审批责任转移。我实现时分成三步。

第一步,校验被转发人是否存在且在该PO的审批规则范围内。否则会出现把单子转给一个根本没有审批权限的人,待办发出去了却没人能处理的情况。

第二步,调用标准工作流API执行转发。

PROCEDURE forward_notification( p_notification_id IN NUMBER, p_from_user IN VARCHAR2, p_to_user IN VARCHAR2, p_comment IN VARCHAR2, x_return_status OUT VARCHAR2 ) IS BEGIN x_return_status := 'S'; -- 校验目标用户存在且有效 SELECT user_id INTO l_user_id FROM fnd_user WHERE user_name = p_to_user AND end_date IS NULL; -- 调用EBS标准转发API fnd_wf_notification.set_forwarder( p_notification_id => p_notification_id, p_forwarder => p_from_user, p_forwarded_to => p_to_user, p_forwarded_note => p_comment ); COMMIT; END forward_notification;

第三步,写转发历史。标准API本身会记录部分日志,但为了业务追溯,我在自定义表里记录了转发前后处理人、转发时间、转发原因,方便后来审计。

这里有一个我在实施中踩过的坑:转发不是改字段那么简单。有些开发图省事,直接通过UPDATE把WF_NOTIFICATION里的收件人改了,原审批人列表里确实看不到待办了,但工作流内部的“通知用户”节点没有重新分配,被转发人也收不到新的待办,结果审批单就凭空消失了。所以转发必须走标准API。

3.5 中间件封装细节

数据库包实现后,我用Spring Boot做了一个轻量级中间件,核心工作只有四件事:认证、参数校验、调用数据库包、返回结果。

认证用的是JWT。OA系统审批时携带token,中间件校验token后从上下文里取出用户身份,再和请求体里的operator做比对,防止一名普通员工冒充总经理审批。这一步不能省。

调用数据库包的代码长这样:

@PostMapping("/api/po/approve") public ApiResponse approve(@RequestBody ApproveRequest request) { // 1. 鉴权 String operator = JwtUtil.parseToken(request.getToken()); if (!operator.equals(request.getOperator())) { return ApiResponse.error(403, "非法操作"); } // 2. 构造存储过程调用 String sql = "{call XXAPP_PO_APRV_PKG.submit_approval(?, ?, ?, ?, ?)}"; try (Connection conn = dataSource.getConnection(); CallableStatement cs = conn.prepareCall(sql)) { cs.setLong(1, request.getNotificationId()); cs.setString(2, request.getAction()); cs.setString(3, request.getComment()); cs.setString(4, request.getOperator()); cs.registerOutParameter(5, Types.VARCHAR); cs.execute(); String status = cs.getString(5); if ("E".equals(status)) { return ApiResponse.error(500, "EBS处理失败"); } return ApiResponse.ok(); } catch (SQLException e) { log.error("审批调用异常", e); return ApiResponse.error(500, "系统异常"); } }

有个细节值得提一下:数据库连接必须和EBS业务库的字符集保持一致。EBS常用ZHS16GBK或AL32UTF8,如果中间件连接串里的字符集和数据库不一致,审批意见里的中文会出现乱码,这种问题从参数上看永远没问题,但一写库就坏,很隐蔽。

3.6 审计日志与幂等控制

审计日志我建了一张独立表,字段包括:接口调用ID、调用的系统标识、操作人工号、PO头ID、通知ID、动作类型、审批意见、结果状态、异常信息、开始时间、结束时间、耗时毫秒数。每笔调用都记录,不仅是内控需要,出问题排查时也全靠这张表。

幂等控制我设计得比较轻量:在审批逻辑执行前,先查询该通知的当前状态,如果不是OPEN,直接返回409“已处理”。加上数据库层对通知行的锁定,基本能保证并发和重试场景下不会二次审批。

4. 部署、测试与常见问题排查

4.1 环境准备与部署步骤

整个系统部署涉及三层环境:EBS数据库层、EBS应用层、中间件。

数据库层需要做的操作:以系统管理账号登录数据库,创建自定义Package,授权给中间件使用的数据库账号,执行授权脚本让中间件账号可以调用包。中间件账号不建议直接用APPS账号,而是单独建一个只拥有包执行权限的最小权限账号。

中间件层配置比较简单:准备一台Linux/Windows服务器,安装JDK 8+,部署Spring Boot的jar包,修改application.yml里的数据源指向EBS数据库,配置好JWT密钥。如果是走HTTPS,还要在中间件前面加一层Nginx,配置SSL证书和反向代理,并把请求体大小限制调大一些,防止审批意见太长被Nginx拦截。

4.2 测试方案设计

测试环节,我建议不要只测“通不通”,而是按照业务矩阵来覆盖。

用例编号场景操作预期结果
T01正常审批合法审批人提交APPROVE待办消失,流程推进
T02拒绝审批合法审批人提交REJECT并填意见流程退回,单据状态更新
T03转发给有效用户原审批人转发给副手新审批人看到待办,原审批人看不到
T04转发给无效用户转发给已离职/锁定的用户返回400,待办不丢失
T05重复审批对已完成的待办再次提交APPROVE返回409,不产生二次推进
T06非本人审批A用户审批B用户的待办返回403,拒绝处理
T07并发审批两线程同时提交同一待办一笔成功,一笔报“数据被锁定”
T08中文审批意见输入包含生僻字的长文本数据库存储无乱码

这套矩阵跑下来,大部分常规问题都能暴露出来。

4.3 高频问题排查速查表

上线期间我整理了一个排查表,遇到问题直接对照,效率高很多。

症状可能原因解决思路
调用审批API报ORA-20001自定义校验抛错,常见于订单状态不允许当前动作查询订单当前审批状态,和调用方确认是否单据已变
提示待办不存在传入的notificationId不属于当前审批人,或通知已被处理联查WF_NOTIFICATION和审计日志确认当前状态
中文乱码中间件连接串字符集与EBS不一致在JDBC连接URL中显式指定字符集,并重启验证
审批成功但流程不往下走工作流后台进程未及时推进检查WF后台进程状态,必要时手动激活
调用方显示操作超时中间件到数据库的网络抖动或锁等待数据库包中尽量避免长事务,中间件设置合理超时和重试
字段值莫名被覆盖多来源更新同一单据禁止非标准API直接更新业务表,统一走包入口

4.4 一次线上排错实战记录

A公司上线第二周,OA反馈一个问题:审批人在OA里点了“同意”,界面提示成功,但采购员在EBS里看到订单还停在“审批中”,过了十几分钟才变成“已批准”。

我第一时间查了审计日志,发现API调用确实成功,数据库包返回正常。再查工作流后台进程,发现EBS的Workflow Background Process定时轮询间隔比较长,通知状态虽然改成完成态了,但引擎没有立刻被唤醒。

处理办法有两个。一是把工作流后台进程的轮询频率调短,这个需要EBS系统管理员配合评估对数据库性能的影响。二是更稳妥的做法——在审批包中调用标准API后,主动触发一次工作流推进动作,缩短等待窗口。我们最终在数据库包里补了一个唤醒动作,问题基本消失。

这个案例给我的教训是:API接口返回成功,不代表EBS端业务动作已经全部完成。工作流引擎有自己的异步机制,中间件侧的“成功”和业务侧的“完成”之间可能存在时间窗口。和OA联调时必须把这种异步特征讲清楚,否则OA那边会认为接口返回成功就该立刻看到结果。

5. 实操心得与扩展建议

5.1 最关键的三个坑

这套项目做完,我最想提醒后人的三件事:

第一,不要图省事绕过工作流引擎。审批状态和业务状态是两套体系,表面上改字段能解决眼前问题,后面流程计数和审计记录全对不上,排查成本远高于开发成本。所有审批动作必须通过标准工作流API。

第二,测试阶段一定要验证“离职用户”和“跨部门转发”这类边缘场景。EBS的FND_USER里很多账号是有有效期的,OA侧拿到的是一个已经失效的账号,转发时如果不做校验,接口会报错或者待办卡死。

第三,字符集和时区这种基础配置问题,越早确认越好。我见过太多项目联调几天后发现乱码和日期错位,最后定位到只是JDBC连接串少了一个参数。

5.2 性能与超时优化

采购订单审批有个特点:单笔调用非常轻量,但月底、年底会有集中审批的峰值。优化上我建议注意两点。

数据库包内尽量避免动态SQL,能一次查出来的不要拆成多次查询。审批待办查询接口要重点关注,如果使用方是OA首页那种频繁轮询的页面,建议在中间件层加缓存,5秒到10秒的缓存对业务影响基本为零。

中间件的HTTP连接池和数据库连接池参数也要匹配好。连接池太小,峰值时请求排队;连接池太大,数据库会话数爆掉。我当时调整到数据库单例空闲连接不超过20,事务提交后立即归还连接,这套配置在峰值时比较稳。

5.3 从审批API到流程自动化的扩展

审批API做出来后,很多延伸需求会自然浮出来。我这里简单提两个方向,后续项目可能会用到。

一个是审批超时自动提醒和升级。外部系统可以每天定时调用待办查询API,把超过24小时未审批的待办列表拉出来,推送给上级或启动催促流程。这个实现起来很简单,但业务价值很高。

另一个是审批意见的沉淀和分析。审批人拒绝订单时填写的意见,其实是很好的采购业务数据。通过API把意见存下来之后,可以按月统计拒绝原因分布,是价格问题、供应商问题还是需求描述不清,这对采购团队改进流程有实际帮助。

6. 写在最后的项目复盘

这套审批API上线运行到现在,稳定处理了几千笔采购订单审批,OA侧的审批人基本不再需要登录EBS工作台。我个人最深的体会是,这种集成类项目,技术实现本身不是最大的难点,最难的是把EBS的工作流机制讲清楚,让项目组里每一个人都理解“为什么不能直接改待办表”这件事。

如果你正在做类似的项目,我的建议是先花时间吃透EBS标准工作流的数据结构和处理逻辑,再动手写代码。接口契约、数据库包、中间件封装其实都是流水线工作,真正决定项目成败的,是那些藏在细节里的设计决策——锁、幂等、字符集、异步推进。把这些想清楚,这个项目就已经成功了一大半。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进