泛微E9统一待办接口实战:从数据模型到多系统集成
2026/9/16 5:30:52 网站建设 项目流程

先说个实际场景。公司上了泛微E9之后,OA里的审批流程越建越多,同时金蝶、CRM、人事系统各自都有待办,业务人员每天要切换五六个系统去看"我有哪些事没处理"。领导最后拍板:所有待办必须汇总到一个入口,统一展示、统一跳转、统一处理。于是"泛微E9统一待办接口"就成了整个集成方案的核心。

这篇文章我不打算写成官方文档的复读机,而是把我实际对接E9统一待办时踩过的坑、理清的接口逻辑、以及最终沉淀下来的调用方式完整讲一遍。适合正在做E9二次开发、OA与ERP集成、或者准备搭建企业统一待办门户的实施工程师和开发同学参考。文章里涉及的接口路径和参数,不同小版本会有些差异,但整体思路和数据模型在E9里是通用的,你拿到自己环境的接口文档后按这个框架去套,基本不会跑偏。

1. 先搞清楚:统一待办的“待办”到底存在哪里

很多第一次做E9集成的同学上来就找接口,结果绕了一大圈发现最关键的其实是理解E9的数据模型。待办不是凭空生成的,它是工作流引擎在流程流转过程中写到数据库里的状态记录。理解这一点,后面不管是调接口还是自己写SQL,心里都有底。

1.1 E9工作流的核心表与状态流转

E9的工作流引擎里,三张表是绕不开的:

  • workflow_requestbase:流程请求主表,一条记录代表一个流程实例。核心字段包括requestid(流程实例ID)、workflowid(流程模板ID)、requestname(流程标题)、creater(发起人ID)、createdate(发起日期)、status(流程状态)、currentnodeid(当前节点ID)、isfinish(是否已完成)。
  • workflow_currentoperator:当前操作者表,这个是待办的直接来源。每条记录表示"某个节点上的某个人有一条待办",核心字段有userid(待办人ID)、requestidworkflowidnodeidisremark(是否待批)、isreject(是否驳回)、isover(是否已处理)、receivedate(接收时间)。
  • workflow_requestlog:流程操作日志表,记录每一步的审批意见、操作人、操作时间,主要用于追溯和状态判断。

待办的产生逻辑其实很朴素:当流程流转到某个节点时,E9会向workflow_currentoperator表写入对应处理人的记录,这条记录的isover字段为0,表示还没处理,这个人登录OA后在"待办事项"里就能看到。处理完之后,isover被置为1,同时流程引擎会判断是进入下一节点、驳回还是结束,并再次写新的workflow_currentoperator记录。

所以"统一待办接口"本质上做的事情,就是从这些表里把isover=0的记录捞出来,再关联上流程标题、发起人、紧急程度等业务信息,组装成前端能直接展示的待办列表。

1.2 为什么不建议直接查库做对接

我之前见过不少团队图省事,绕开接口直接对数据库跑SQL查询待办。短期内确实能出数据,但很快会碰到几个硬问题。

第一是权限。E9的权限模型很细,同一个流程节点,不同分部、不同部门的人看到的数据范围不一样。直接查库你很难把"这个人只能看到他自己相关的那部分待办"这个规则写清楚,一旦写错就是越权,在OA系统里这是大忌。

第二是缓存。E9对流程数据有缓存机制,你直接查库拿到的数据可能是旧的。我就遇到过明明流程已经走完了,直连数据库查待办还能查出这条记录,但通过官方接口查就正常,因为接口会走E9的缓存刷新和状态过滤逻辑。

第三是状态判断的复杂性。一个"待办"不只是简单的isover=0,还要排除流程已撤销、流程已归档、当前节点已跳转等异常状态。这些边界逻辑官方接口都封装好了,自己写SQL很容易漏掉一两个条件,导致待办列表出现"幽灵数据"。

所以我的建议很明确:能调接口就调接口,接口满足不了再考虑自建查询,而且自建时必须把权限和状态过滤做到位。这一点后面第5节会展开讲。

2. 对接前必须确认的三件事:版本、鉴权、返回结构

E9的接口体系说复杂也复杂,说简单也简单。复杂在于不同版本、不同部署方式下接口地址会有变化;简单在于整体思路是固定的——先鉴权拿身份,再带身份调业务接口。我每次做新项目的对接,第一件事永远是先确认这三件事。

2.1 先确认E9版本和接口形态

E9从早期版本到现在迭代了很多次,接口的开放程度和路径都有变化。一般登录OA后台,在"系统信息"里能看到具体版本号。我建议你在对接之前先确认三件事:

  • 版本号是9.0.几,因为部分接口在特定小版本才开放。
  • 部署容器是Resin还是Tomcat,这会影响接口对外路径的上下文。
  • 是否启用了独立的集成平台模块,E9的集成接口很多依赖/api/前缀,而这个前缀在没开启集成模块时可能不生效。

E9对外提供接口的形态主要有四种:RESTful API、WebService、数据库视图(只读)、Java集成SDK。做统一待办,绝大多数情况下用的是RESTful API,这也是我这篇文章重点讲的形态。

2.2 鉴权方式:Token怎么拿

E9的统一待办接口不是裸奔的,调用前要先获取访问凭证。常见的做法是走E9的集成认证接口,用系统账号换一个token,后续的待办接口请求都带上这个token

我以RESTful接口为例,典型的认证请求长这样:

POST /api/ec/dev/auth/applytoken Content-Type: application/json { "appid": "your_app_id", "appsecret": "your_app_secret" }

正常情况下,接口会返回类似下面的结果:

{ "code": "0", "message": "success", "data": { "token": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" } }

注意几个细节。第一,appidappsecret一般需要OA管理员在集成平台里提前申请,不是你随便编的。第二,token是有有效期的,常见的是2小时,过期后要重新申请,所以调用方要做好token的缓存和自动刷新,不要每个请求都去申请一次。第三,有些环境里还会要求把token放在请求头的token字段而不是Authorization里,这个以你们环境的接口文档为准。

2.3 待办接口的通用返回结构

E9接口的返回结构整体比较统一,我调过的几个待办相关接口基本都是这个套路:

{ "code": "0", "message": "success", "data": { "list": [], "total": 0, "pageindex": 1, "pagesize": 20 } }

code为"0"通常表示成功,其他值对应不同错误码;data.list才是待办数据的数组;total是符合条件的总条数,用于分页。分页参数一般叫pageindexpagesize,有的是从0开始,有的是从1开始,这个很坑,我建议你在写代码之前先手动调一次接口,确认下标从几开始,否则翻页会漏数据。

时间格式也要注意。E9接口返回的时间有的版本是yyyy-MM-dd HH:mm:ss,有的版本是带T的ISO格式。如果你要做前端展示排序,最好在接口层统一转成标准格式,不要在前端每个页面单独处理。

3. 待办列表接口的请求构造与返回解析

确认完版本和鉴权方式,就可以正式开始调统一待办接口了。这一步我建议分三个阶段走:先手工把接口调通,再封装成统一的服务层,最后再接到前端页面。很多人一上来就写代码,结果参数错了排查半天,不如先用Postman或Apifox把接口调通,看清楚返回的字段再动手。

3.1 请求URL和请求头怎么构造

待办列表接口的路径一般是/api/workflow/pa/todolist或者类似的名字,不同的集成方式会有差异。我在项目实施中常用的请求示例(Java HttpClient)如下:

HttpClient client = HttpClient.newHttpClient(); String url = "http://your-oa-server/api/workflow/pa/todolist?pageindex=1&pagesize=20"; HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(url)) .header("Content-Type", "application/json") .header("token", accessToken) .GET() .build(); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body());

这里有个重要参数:查询哪个人的待办。大部分统一待办场景下,当前登录用户就是待办人,接口可以通过token自动识别,也可以在请求参数里显式传loginiduserid。如果你的统一门户是代用户查询,那必须传对应的loginid,而且后端要校验这个用户有没有权限查别人的待办,不然就是越权。

3.2 返回字段里哪些是真正用得上的

接口返回的list数组里,字段通常会比较多,但真正做统一待办展示时,核心字段就下面这几个:

字段含义使用场景
requestid流程实例ID拼接详情地址、调处理接口
workflowid流程模板ID判断流程类型、拼接详情地址
nodeid当前节点ID判断当前审批到哪一步
requestname流程标题待办列表主标题展示
creater发起人姓名展示谁发起的
createdate发起时间列表排序、展示
urgentlevel紧急程度高亮显示、排序
isremark是否待批区分审批和知会
node name节点名称展示当前在哪个人/哪个节点

这里特别提醒一下isremark这个字段。在E9里,isremark=1表示这条待办是"知会"性质,不需要真正审批,只是通知你看一下;isremark=0才是需要你操作的审批待办。做统一待办时,一定要在列表里把这两个类型分开展示,不然用户把知会当成审批去点,流程操作会出问题。

3.3 分页与性能:数据量大了怎么办

待办列表接口默认分页大小一般是20条,这个对于日常使用没问题,但如果做数据同步或者批量处理,就要拉全量数据。这种情况下我建议用"游标式"循环拉取,不要一次性把页码调得很大。

所谓游标式,就是每次只取一页,记录当前最大的requestid或者时间戳,下一页用这个值做条件去查。这样即使待办数量很多,也不会因为深翻页导致接口响应越来越慢。另外,在做定时同步任务时,尽量选择业务低峰期执行,并且控制并发数,不然会拖慢OA服务器。

4. 流程ID的获取:从列表到详情的钥匙

"泛微获取流程id"这个词在搜索热度里一直很高,因为它确实是整个待办跳转逻辑里最关键的一环。我在这里把流程ID相关的概念一次说清楚。

4.1 requestid和workflowid是两码事

很多初学者会混淆这两个ID,其实它们的区别很简单:

  • requestid是流程实例ID,每次发起一条流程就会生成一个新的requestid,它是唯一的,代表"这一条具体的申请"。
  • workflowid是流程模板ID,同一个审批流程的所有实例共享同一个workflowid,代表"这是哪一种申请"。

举个例子,你公司有一个"请假申请"流程,模板ID是12345,员工A今天提交一条请假,生成的流程实例ID是100001;员工B明天也提交一条,流程实例ID是100002。这两条流程的workflowid都是12345,但requestid不同。

获取待办列表时,接口返回的数组里这两个字段都有,直接用就行。如果是要根据某个业务单据反查流程ID,那通常的做法是:在流程表单里加一个业务单号字段,然后通过这个字段关联查询workflow_requestbase表拿到requestid

4.2 通过流程ID拼接详情页地址

拿到requestid之后,最常用的操作是拼接流程详情页地址,让用户点击待办直接跳转。E9的流程详情页地址格式大致如下:

http://your-oa-server/wui/index.html#/workflow/RequestView?requestid=100001&workflowid=12345

注意这里的workflowid参数在部分版本里是可以省略的,但我建议还是带上,因为有些页面在缺少workflowid时无法正确渲染流程模板信息。跳转时如果是三方系统嵌入OA,建议用target=_blank方式打开新标签页,避免在iframe里出现登录态丢失的问题。

4.3 其他拿到流程ID的途径

除了待办接口,实际项目中还有两个场景经常需要获取流程ID:

一是从系统消息或邮件通知里带链接跳转。E9的工作流通知邮件里一般会带有requestid参数,从链接里解析出来就行。

二是数据库关联查询。比如外部系统只传了一个"单号",需要查出对应的requestid,常见SQL类似:

SELECT requestid, workflowid, requestname FROM workflow_requestbase WHERE requestname LIKE '%业务单号%' OR EXISTS (SELECT 1 FROM formtable_main_xx f WHERE f.requestid = workflow_requestbase.requestid AND f.billno = '业务单号')

formtable_main_xx是指你表单对应的业务表,每张自定义表单在数据库里都有一张独立的表,编号xx和表单ID对应。这种查询方式适合数据核对,不建议作为高频接口调用方式。

5. 自建待办接口:什么时候需要,代码骨架怎么写

前面说了优先调官方接口,但现实中"官方接口不够用"的情况太常见了。比如你们要做多系统待办聚合,需要同时返回OA待办、金蝶待办、CRM待办,并且按紧急程度混排;又比如需要过滤掉某些特定流程的待办,只展示业务部门关心的部分。这时候就需要在E9里自建一个自定义接口,自己控制查询逻辑和返回结构。

5.1 自建接口前先想清楚三个问题

动手写代码之前,先问自己三个问题:

  • 这个接口给谁用?只给统一门户用,还是也会被其他系统调用?这决定了要不要做独立的鉴权。
  • 数据范围怎么定?是按照登录人自动过滤,还是允许传loginid查别人?我强烈建议默认按登录人过滤,跨人查询必须加权限判断。
  • 返回字段怎么定义?统一待办前端需要哪些字段,一次性设计好,不要等前端开发到一半再改。

这三个问题想清楚,代码写起来就很快,否则就是反复改接口的节奏。

5.2 一个可直接参考的Java接口骨架

在E9里自建接口,通常是在E9的Java工程里新增一个RestController,代码骨架大致如下:

@RestController @RequestMapping("/api/custom/todo") public class CustomTodoController { @Autowired private CustomTodoService todoService; /** * 统一待办查询接口 * @param loginid 查询目标用户的登录ID * @param pageIndex 页码 * @param pageSize 每页大小 */ @GetMapping("/list") public Map<String, Object> list(@RequestParam(required = false) String loginid, @RequestParam(defaultValue = "1") int pageIndex, @RequestParam(defaultValue = "20") int pageSize) { String currentLoginId = getCurrentLoginId(); // 如果传了loginid且不是当前登录人,必须做权限校验 if (StringUtils.isNotBlank(loginid) && !currentLoginId.equals(loginid)) { if (!hasPermissionToQueryOtherTodo(currentLoginId)) { return Result.error("无权限查询其他用户的待办"); } currentLoginId = loginid; } List<TodoItemVO> todos = todoService.queryTodoList(currentLoginId, pageIndex, pageSize); int total = todoService.countTodoList(currentLoginId); Map<String, Object> data = new HashMap<>(); data.put("list", todos); data.put("total", total); data.put("pageindex", pageIndex); data.put("pagesize", pageSize); return Result.success(data); } private String getCurrentLoginId() { // 从当前请求的token/session中解析登录人 // E9中有对应的工具类,实际开发时按自己工程的写法来 return RequestUtil.getLoginIdFromRequest(); } private boolean hasPermissionToQueryOtherTodo(String loginid) { // 这里做管理员/指定角色判断 return true; } }

核心的查询逻辑在CustomTodoService.queryTodoList里。底层查询我推荐用workflow_currentoperator关联workflow_requestbase的方式,SQL的大致套路如下:

SELECT wr.requestid, wr.workflowid, wr.requestname, wr.creater, wr.createdate, wc.nodeid, wc.isremark, wb.workflowname FROM workflow_currentoperator wc INNER JOIN workflow_requestbase wr ON wc.requestid = wr.requestid LEFT JOIN workflow_base wb ON wr.workflowid = wb.workflowid WHERE wc.userid = ? AND wc.isover = 0 AND wr.status NOT IN (3, 4) AND wr.isfinish = 0 AND wc.isremark = 0 ORDER BY wr.createdate DESC

wc.userid需要用当前登录人的ID,而不是登录名,所以要先通过hrmresource表根据loginid查出idwr.status NOT IN (3, 4)是排除已撤销、已归档等无效状态,具体值在不同版本可能有差异,建议在测试环境先观察真实数据再定。wc.isremark = 0是只取待审批记录,如果需要包含知会,就把这个条件去掉,在前端分开展示。

5.3 自建接口最容易翻车的权限问题

自建接口最怕的就是越权。官方接口里的权限逻辑是黑盒,你自建接口以后,这道门就变成你自己守了。

我之前见有人图方便,在SQL里直接写死wc.userid = 1,接口只要有人调,返回的全是管理员账号的待办。后果就是普通员工在统一门户里看到了别人的审批单。这种问题在OA系统里属于严重事故级别,轻则被通报,重则会牵扯到流程数据泄露的责任。

所以我建议自建接口时加一道硬性校验:如果请求参数里的loginid和当前登录解析出来的loginid不一致,就必须判断调用者是否属于"待办查询管理员"角色,没有权限直接拒绝。这个逻辑不要在网关层做,要在接口层做,因为自建接口有可能绕开网关直接被内部系统调用。

6. 与外部业务系统打通:以泛微单点登录金蝶为例

统一待办做到后面,一定会遇到和外部系统打通的需求。热搜词里"泛微oa系统单点登录金蝶"出现频率很高,我就拿这个场景拆解一下。目标是:用户在泛微统一待办里看到金蝶的待办单据,点击之后免登录直接跳转到金蝶对应页面进行业务处理。

6.1 典型的集成链路

整体上分三步:

  1. 金蝶把它的待办数据推送给泛微(或者泛微定时拉取金蝶待办接口)。
  2. 泛微统一待办门户展示汇总后的待办列表。
  3. 用户点击某一条金蝶待办时,泛微拼接一个带登录票据的跳转地址,金蝶校验票据后自动登录并跳到对应单据页面。

核心难点在第1步和第3步。第3步的单点登录,很多金蝶版本支持通过tickettoken做免登。泛微侧生成一个临时票据,拼到金蝶的登录接口地址上,金蝶验证通过后建立会话并跳转。这里的票据必须设置有效期,比如5分钟,防止URL被别人拿走乱用。

6.2 待办状态同步的两种模式

外部系统的待办进入泛微统一待办后,最关键的问题是状态怎么同步:如果用户已经在金蝶里处理了这条待办,泛微这边的列表不能还显示未处理。

第一种是接口实时同步。用户点击跳转前,泛微调一次金蝶的接口确认单据状态;用户处理完回跳时,金蝶再通知泛微标记完成。这种模式体验最好,但对双方接口的健壮性要求高,任何一方接口抖动都会影响体验。

第二种是定时轮询。泛微每隔5分钟或10分钟拉一次金蝶的待办状态,批量更新本地数据。这种实现简单,但会有窗口期,用户可能处理完了,列表里还显示未处理,一般配合"已处理过一段时间后自动消失"的规则来缓解。

我做过的项目,如果金蝶那边有现成的待办查询接口和回调接口,我优先用实时同步;如果对方接口能力有限,就退而求其次用定时轮询,并在前端明确标注同步时间,避免用户误以为数据是实时的而产生投诉。

6.3 外部系统集成时的一个隐蔽坑:编码与账号映射

外部系统集成时,最容易出问题的不是接口而是账号映射。金蝶里的用户编码和泛微的loginid往往不一致,比如泛微里是zhangsan,金蝶里是00321。这个映射关系如果没建好,待办数据推过来后根本没法匹配到正确的待办人。

我的做法是单独建一张账号映射表,字段包括泛微loginid、金蝶用户编码、用户姓名、状态。在待办同步任务启动前,先把映射表核对清楚。不要试图在代码里硬编码这种映射关系,后续人员变动会让你改代码改到怀疑人生。

7. 移动端适配与高频问题排查

统一待办接口开发完,一定会遇到移动端的适配问题。E9自带的移动端和PC端在待办展示上体验还算接近,但如果是你们自己开发的小程序或者App,就会碰到一些接口层的小坑,我在这里集中说一下。

7.1 移动端待办展示的几个差异点

首先是字段长度。流程标题在PC端宽度充足,但在手机上最好截断,建议接口里直接返回一个shorttitle字段,控制在20个字符以内,避免前端每个页面都写截断逻辑。

其次是跳转方式。PC端通过浏览器地址跳转详情页没问题,但移动端可能是H5环境或者原生WebView,跳转前要确认有没有对应的移动端详情页路由。有些版本的移动端详情页路径和PC端完全不同,强行用PC端地址在手机里打开,体验很差。

最后是附件处理。移动端打开待办详情时经常要看附件,附件地址如果是content://这类本地协议,在外面集成时是不能用的,要把附件下载后转成可访问的HTTP地址,或者通过泛微自带的移动端附件预览接口处理。这个点很容易被忽略,上线前一定要拿真机实测一遍。

7.2 高频问题排查清单

我把做对接时最常遇到的问题整理成一个清单,按这个顺序查,基本能覆盖90%的情况:

现象排查方向
接口返回401或token无效先看token是否过期,再确认appid和appsecret配置是否正确
待办列表为空确认查询的userid对应的用户在当前测试流程节点上是否真的有待办,流程是否已归档
有重复待办检查是否同时走了缓存查询和数据库查询,或者是否在workflow_currentoperator中同一节点生成了多条记录
已处理的待办仍然显示确认isover字段是否真的更新为1,是否走的是自定义SQL查询漏了过滤条件
接口响应慢确认是否在业务高峰期拉全量数据,是否分页参数设置过大,建议加索引或用官方接口
中文乱码统一请求和响应的编码为UTF-8,注意Postman测试时不会暴露,但Java调用时会在Header处丢编码参数

最后一个乱码问题特别容易栽跟头。我在E9上对接统一待办时,用Postman调接口一切正常,换成Java代码调用后返回的中文全是乱码。排查了半天,发现是Java HttpClient在POST请求的时候没有显式指定Content-Type里的charset=UTF-8,服务端按默认编码解析请求体导致的。这个问题在POST接口里尤其明显,GET接口因为参数在URL上,反而不太会遇到。

做完这些排查,统一待办接口基本就稳定了。最后说一句我个人做集成的体会:接口对接本身不难,难点永远在数据和权限的边界上。你花一小时把接口调通,可能就要花一整天把各种极端情况处理干净——比如转办、加签、代理审批这些流程特性,在统一待办里都要有对应的展示和处理逻辑。先把这些业务规则梳理清楚,再动手写代码,比什么技巧都管用。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询