ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

泛微E9统一待办接口实战:从数据模型到多系统集成

泛微E9统一待办接口实战:从数据模型到多系统集成 先说个实际场景。公司上了泛微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、requestid、workflowid、nodeid、isremark是否待批、isreject是否驳回、isover是否已处理、receivedate接收时间。workflow_requestlog流程操作日志表记录每一步的审批意见、操作人、操作时间主要用于追溯和状态判断。待办的产生逻辑其实很朴素当流程流转到某个节点时E9会向workflow_currentoperator表写入对应处理人的记录这条记录的isover字段为0表示还没处理这个人登录OA后在待办事项里就能看到。处理完之后isover被置为1同时流程引擎会判断是进入下一节点、驳回还是结束并再次写新的workflow_currentoperator记录。所以统一待办接口本质上做的事情就是从这些表里把isover0的记录捞出来再关联上流程标题、发起人、紧急程度等业务信息组装成前端能直接展示的待办列表。1.2 为什么不建议直接查库做对接我之前见过不少团队图省事绕开接口直接对数据库跑SQL查询待办。短期内确实能出数据但很快会碰到几个硬问题。第一是权限。E9的权限模型很细同一个流程节点不同分部、不同部门的人看到的数据范围不一样。直接查库你很难把这个人只能看到他自己相关的那部分待办这个规则写清楚一旦写错就是越权在OA系统里这是大忌。第二是缓存。E9对流程数据有缓存机制你直接查库拿到的数据可能是旧的。我就遇到过明明流程已经走完了直连数据库查待办还能查出这条记录但通过官方接口查就正常因为接口会走E9的缓存刷新和状态过滤逻辑。第三是状态判断的复杂性。一个待办不只是简单的isover0还要排除流程已撤销、流程已归档、当前节点已跳转等异常状态。这些边界逻辑官方接口都封装好了自己写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 } }注意几个细节。第一appid和appsecret一般需要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是符合条件的总条数用于分页。分页参数一般叫pageindex和pagesize有的是从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?pageindex1pagesize20; HttpRequest request HttpRequest.newBuilder() .uri(URI.create(url)) .header(Content-Type, application/json) .header(token, accessToken) .GET() .build(); HttpResponseString response client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body());这里有个重要参数查询哪个人的待办。大部分统一待办场景下当前登录用户就是待办人接口可以通过token自动识别也可以在请求参数里显式传loginid或userid。如果你的统一门户是代用户查询那必须传对应的loginid而且后端要校验这个用户有没有权限查别人的待办不然就是越权。3.2 返回字段里哪些是真正用得上的接口返回的list数组里字段通常会比较多但真正做统一待办展示时核心字段就下面这几个字段含义使用场景requestid流程实例ID拼接详情地址、调处理接口workflowid流程模板ID判断流程类型、拼接详情地址nodeid当前节点ID判断当前审批到哪一步requestname流程标题待办列表主标题展示creater发起人姓名展示谁发起的createdate发起时间列表排序、展示urgentlevel紧急程度高亮显示、排序isremark是否待批区分审批和知会node name节点名称展示当前在哪个人/哪个节点这里特别提醒一下isremark这个字段。在E9里isremark1表示这条待办是知会性质不需要真正审批只是通知你看一下isremark0才是需要你操作的审批待办。做统一待办时一定要在列表里把这两个类型分开展示不然用户把知会当成审批去点流程操作会出问题。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?requestid100001workflowid12345注意这里的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 MapString, 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; } ListTodoItemVO todos todoService.queryTodoList(currentLoginId, pageIndex, pageSize); int total todoService.countTodoList(currentLoginId); MapString, 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 DESCwc.userid需要用当前登录人的ID而不是登录名所以要先通过hrmresource表根据loginid查出id。wr.status NOT IN (3, 4)是排除已撤销、已归档等无效状态具体值在不同版本可能有差异建议在测试环境先观察真实数据再定。wc.isremark 0是只取待审批记录如果需要包含知会就把这个条件去掉在前端分开展示。5.3 自建接口最容易翻车的权限问题自建接口最怕的就是越权。官方接口里的权限逻辑是黑盒你自建接口以后这道门就变成你自己守了。我之前见有人图方便在SQL里直接写死wc.userid 1接口只要有人调返回的全是管理员账号的待办。后果就是普通员工在统一门户里看到了别人的审批单。这种问题在OA系统里属于严重事故级别轻则被通报重则会牵扯到流程数据泄露的责任。所以我建议自建接口时加一道硬性校验如果请求参数里的loginid和当前登录解析出来的loginid不一致就必须判断调用者是否属于待办查询管理员角色没有权限直接拒绝。这个逻辑不要在网关层做要在接口层做因为自建接口有可能绕开网关直接被内部系统调用。6. 与外部业务系统打通以泛微单点登录金蝶为例统一待办做到后面一定会遇到和外部系统打通的需求。热搜词里泛微oa系统单点登录金蝶出现频率很高我就拿这个场景拆解一下。目标是用户在泛微统一待办里看到金蝶的待办单据点击之后免登录直接跳转到金蝶对应页面进行业务处理。6.1 典型的集成链路整体上分三步金蝶把它的待办数据推送给泛微或者泛微定时拉取金蝶待办接口。泛微统一待办门户展示汇总后的待办列表。用户点击某一条金蝶待办时泛微拼接一个带登录票据的跳转地址金蝶校验票据后自动登录并跳到对应单据页面。核心难点在第1步和第3步。第3步的单点登录很多金蝶版本支持通过ticket或token做免登。泛微侧生成一个临时票据拼到金蝶的登录接口地址上金蝶验证通过后建立会话并跳转。这里的票据必须设置有效期比如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里的charsetUTF-8服务端按默认编码解析请求体导致的。这个问题在POST接口里尤其明显GET接口因为参数在URL上反而不太会遇到。做完这些排查统一待办接口基本就稳定了。最后说一句我个人做集成的体会接口对接本身不难难点永远在数据和权限的边界上。你花一小时把接口调通可能就要花一整天把各种极端情况处理干净——比如转办、加签、代理审批这些流程特性在统一待办里都要有对应的展示和处理逻辑。先把这些业务规则梳理清楚再动手写代码比什么技巧都管用。
返回列表