
简介泛微E9 workflowServeice流程开发Demo是一份面向企业开发者的流程管理二次开发示例围绕流程模板的增删改查展示如何通过RESTful API对接E9平台适合正在集成泛微OA或需要快速上手流程服务接口的Java工程师。资源包共41个文件压缩包约24.75MB其中包含11个Java源码与12个编译后的class文件、8个jar依赖库、5个XML配置以及RSA密钥、说明文档等既可直接部署调试也能帮助理解流程服务的调用逻辑与鉴权机制。示例项目以workflow-restful-demo为主体结合readme说明和外部API文档压缩包完整覆盖从流程创建、修改到删除查询的典型操作场景并涉及fastjson、httpclient等常用库的实际运用。目前该资源已有1963人学习对于希望在泛微E9上构建自动化流程集成、提升审批流转效率的开发者是一份具备参考价值的实战Demo。1. 泛微 E9 workflowService 能做什么从一个集成需求说起在泛微E9的二次开发里workflowService 是绕不开的一套WebService接口有些同事搜索时把服务名拼成 workflowServeice实际服务端发布的服务名通常是 workflowService。一个ERP要直接往OA里推采购申请MES要自动获知某张审批单走到哪个节点或者用户填错单后想让外部系统帮你把单子撤回来这些需求最终都会归结为对流程实例的增、删、改、查。打开wsdl一看方法名一大片没有方向感的人很容易翻车。这篇笔记就把 workflowService 流程开发demo从头拆到尾先讲清楚接口背后的流程模型再给出创建、更新、查询、撤回的最小代码最后把那些让人血压升高的坑一次性列出来。适合刚接手OA集成的二开工程师也适合正在评估接口集成方案的技术负责人。2. workflowService 的接口模型与最小调用环境先看懂 requestid、workflowid 和字段标识2.1 三个核心IDworkflowid、requestid、nodeid 分别决定了什么调用workflowService之前先花五分钟把流程模型对齐否则后面写代码会一直拿错参数。泛微E9里的一个审批流存在三套关键IDworkflowid是流程模板的ID它决定了一个流程走哪些节点、用哪个表单requestid是流程实例ID也就是某一次具体审批单的身份证号后续的改、查、撤、删几乎都以它为唯一入口nodeid是当前节点ID它不只是在页面展示“走到第几步”更决定了哪些字段此刻可写、哪些字段被锁定。还有字段概念也要分清。表单字段有物理字段名和显示标签两层比如页面上写着“申请人姓名”底层字段名可能是dg0lb1。workflowService拼请求参数时用的是物理字段名。如果不先把这个映射关系摸清楚创建流程大概率会拿到一张字段全空的审批单。这也是为什么我每次接手新流程都先去表单设计器里把字段标识列表导出一份存到项目文档里备查。数据库里这些数据散在 workflow_requestbase流程头、workflow_requestlog流程日志、formtable_main_xxx业务表单表等几张表里。有人图省事直接拿SQL去增删改查把requestid一update就完事短期看不出问题等流程跟踪、消息待办、报表统计全部对不上时代价非常大。我的建议是SQL只能用来排查问题对流程实例的操作一律走接口。2.2 用 curl 跑通第一个 SOAP 查询确认服务活着先别急着写Java用curl把接口探一遍能省掉后面一大半的联调焦虑。泛微E9的workflowService通常以SOAP方式发布地址一般形如http://OA服务器端口/services/workflowService?wsdl具体路径以你们服务端实际发布为准。curl -X POST http://oa-server:8080/services/workflowService \ -H Content-Type: text/xml; charsetutf-8 \ -H SOAPAction: \getRequestInfo\ \ -d ?xml version1.0 encodingUTF-8? soapenv:Envelope xmlns:soapenvhttp://schemas.xmlsoap.org/soap/envelope/ xmlns:worhttp://webservice.ws.ecology9.com soapenv:Body wor:getRequestInfo loginIdadmin/loginId password你的密码/password requestid407/requestid ignoreuserfalse/ignoreuser /wor:getRequestInfo /soapenv:Body /soapenv:Envelope这段命令里SOAPAction对应你要调用的方法名 getRequestInfoBody里的入参按wsdl定义填写。ignoreuser 传 false 表示按登录账号的权限返回数据传 true 则绕过当前用户权限直接读流程信息但前提是账号本身有管理员授权。如果接口通了返回的XML里能看到 requestname、createdate、nodeid 等字段如果报 NoSuchMethod 或方法不存在去wsdl里搜一下有的小版本把查询方法命名为 getRequestInfoById 或 getWorkflowRequestInfo命名差异在E9各个补丁版本里很常见。用curl先跑通的意义在于先把网络、认证、地址三个变量排除掉之后写Java客户端时如果调用失败你就可以确定问题出在Java代码而不是OA那边根本没开发这个服务。我通常把这段curl保存成一个shell脚本后续每接一条新接口都先这样验一次。2.3 Java 侧最小客户端一个通用的 callWorkflowService 方法生产环境不会用curl还是要落到Java里。很多老项目用AXIS生成客户端桩也有用CXF的但如果你只是需要一个能快速跑通的demo我建议先封装一个通用的SOAP调用方法不绑定任何第三方库。import java.io.*; import java.net.HttpURLConnection; import java.net.URL; public class WorkflowSoapClient { public static String call(String endpoint, String soapAction, String soapXml) { try { URL url new URL(endpoint); HttpURLConnection conn (HttpURLConnection) url.openConnection(); conn.setRequestMethod(POST); conn.setRequestProperty(Content-Type, text/xml; charsetutf-8); conn.setRequestProperty(SOAPAction, \ soapAction \); conn.setDoOutput(true); conn.getOutputStream().write(soapXml.getBytes(UTF-8)); BufferedReader reader new BufferedReader( new InputStreamReader(conn.getInputStream(), UTF-8)); StringBuilder result new StringBuilder(); String line; while ((line reader.readLine()) ! null) { result.append(line); } return result.toString(); } catch (Exception e) { e.printStackTrace(); return null; } } }这段代码的核心就一件事把方法名和SOAP请求体发到 workflowService 地址拿回响应字符串。参数 endpoint 是wsdl里看到的服务地址soapAction 是方法名soapXml 是按SOAP格式拼好的请求体。注意请求头一定要带text/xml; charsetutf-8否则中文会出现乱码。调用失败时它会打印堆栈但不抛出异常demo阶段可以先保证看清返回内容正式项目里建议改成抛出可识别的业务异常。有了这个通用client下面的增删改查都围绕 call 方法展开。你可以把 endpoint 统一放到配置文件里不同环境切换OA地址时只改一处后面维护会轻松不少。3. 用 workflowService 做流程“增”和“改”createRequest 与 updateRequest 的实战参数3.1 创建流程createRequest 的参数拆解与 requestid 的获取创建流程是集成场景里最常用的能力。泛微发布采购单、推送请假单、自动生成报销审批走的都是同一个思路确定 workflowid拼好表单字段调 createRequest。public static String createRequest(String endpoint, String loginId, String password, String workflowid, String startUserId, MapString, String fields, String createTime) throws Exception { StringBuilder fieldXml new StringBuilder(); fieldXml.append(Data); for (Map.EntryString, String entry : fields.entrySet()) { fieldXml.append(Field name\).append(entry.getKey()) .append(\Value).append(entry.getValue()) .append(/Value/Field); } fieldXml.append(/Data); String soapXml ?xml version\1.0\ encoding\UTF-8\? soapenv:Envelope xmlns:soapenv\http://schemas.xmlsoap.org/soap/envelope/\ xmlns:wor\http://webservice.ws.ecology9.com\soapenv:Body wor:createRequest loginId loginId /loginId password password /password workflowid workflowid /workflowid startUserId startUserId /startUserId requestDataXml fieldXml /requestDataXml createTime createTime /createTime /wor:createRequest/soapenv:Body/soapenv:Envelope; String resp WorkflowSoapClient.call(endpoint, createRequest, soapXml); // 返回的resp里包含新增流程的requestid节点用DOM解析取出即可 return resp; }这段代码把字段 Map 转成标准的字段XML拼进 createRequest 的 requestDataXml 参数。字段值收口在 Map 里不再散落到业务代码各处。返回的 resp 里会带一个 requestid这是本次创建出来的流程实例唯一标识很多项目在这里翻车流程创建成功了但没把 requestid 保存下来后续查状态、改字段全部抓瞎只能去待办列表里人工翻。参数说明workflowid 是流程模板ID到“流程引擎-流程设计器”里看URL或属性就能找到startUserId 是OA里的登录账号必须是个真实存在的系统用户否则流程发起后找不到申请人createTime 不传时一般取当前时间如果业务上要补齐历史单据再把时间戳按格式传进去。requestDataXml 里的字段名必须是物理字段名不要用页面显示名这一步最容易出错。创建成功后我一般会立刻把 requestid 写回业务系统的关联表。下次业务系统再来查询或撤回时不需要再走一次模糊查找直接拿着 requestid 去调接口性能好也不容易错。这就是“泛微获取流程id”这条经验里最核心的一环。3.2 修改流程状态限制、字段权限与 updateRequestpublic static boolean updateRequest(String endpoint, String loginId, String password, String requestid, MapString, String fields) { StringBuilder values new StringBuilder(); for (Map.EntryString, String entry : fields.entrySet()) { values.append(Field name\).append(entry.getKey()) .append(\Value).append(entry.getValue()) .append(/Value/Field); } String soapXml ?xml version\1.0\ encoding\UTF-8\? soapenv:Envelope xmlns:soapenv\http://schemas.xmlsoap.org/soap/envelope/\ xmlns:wor\http://webservice.ws.ecology9.com\soapenv:Body wor:updateRequest loginId loginId /loginId password password /password requestid requestid /requestid requestValues values /requestValues /wor:updateRequest/soapenv:Body/soapenv:Envelope; String resp WorkflowSoapClient.call(endpoint, updateRequest, soapXml); return resp ! null resp.contains(flag\1\); }修改流程跟创建流程的最大区别是它受节点状态影响。流程还没提交时字段随便改一旦进入审批节点当前节点如果没给字段开放修改权限updateRequest 即使返回成功页面上的值也可能没变。所以我一般在调用 updateRequest 之前先用 getRequestInfo 看当前状态再决定这个更新要不要走。有些版本接口叫 doUpdateRequest参数顺序可能不同以 wsdl 为准。还有一个容易忽略的点updateRequest 改的是流程实例的表单值不是流程日志。想修改“审批记录”或“办理意见”那是另一个接口族不要在表单更新里硬塞。改字段前最好确认当前环节对字段是否有写权限这个用流程设计器里的节点权限配置判断。外部系统要做“审批中允许修改”的需求通常还需要流程设计器配合把对应节点字段设成可编辑。3.3 表单默认值、隐藏字段与加密字段在后端接口的处理创建流程时带上表单默认值是很多集成的隐性需求。比如请假单的开始日期默认当天、加班单的申请人默认当前登录人这些默认值不一定非得靠前端页面触发外部系统在拼 requestDataXml 时直接把值算好写进去流程进入后就是最终值比依赖表单公式稳定。“根据筛选框隐藏字段”也是E9上常见的交互需求。页面上常常通过一个下拉框决定某些字段显示还是隐藏如果外部系统把所有字段一股脑传进 workflowService被隐藏字段的值也会写入业务表审批人打开详情时看到“不该出现的数据”观感很糟。我习惯把隐藏逻辑前移到接口层外部系统在下拉框选某个值时就不拼对应字段到 requestDataXml 里或者传空值让OA端保持字段隐藏。前端再做一层同样的显隐控制双保险。字段加密设置则要格外小心。E9表单字段如果开了加密存储workflowService 返回的字段值很可能是密文尤其是手机号、身份证这类敏感字段。你把这个密文存到自己的库里后续数据校验和报表统计都很被动。常见做法是让管理员确认接口调用账号是否有查看明文的权限或者调用泛微的解密服务先解一遍再落库。不要自己去研究密文格式那是黑匣子猜不出来的。4. 流程“查”和“删”的接口组合状态查询、表单明细与安全撤单4.1 查流程状态getRequestInfo 的返回结构与解析要点public static String queryRequestInfo(String endpoint, String loginId, String password, String requestid) { String soapXml ?xml version\1.0\ encoding\UTF-8\? soapenv:Envelope xmlns:soapenv\http://schemas.xmlsoap.org/soap/envelope/\ xmlns:wor\http://webservice.ws.ecology9.com\soapenv:Body wor:getRequestInfo loginId loginId /loginId password password /password requestid requestid /requestid ignoreusertrue/ignoreuser /wor:getRequestInfo/soapenv:Body/soapenv:Envelope; return WorkflowSoapClient.call(endpoint, getRequestInfo, soapXml); }返回的XML里能看到 requestid、workflowid、requestname、creator、createdate、nodeid 这些字段。解析时不要硬用 String.indexOf 去截建议直接用 DocumentBuilder 把XML转成DOM按节点名取值。nodeid 就是当前审批节点配合流程设计器里维护的节点ID就能判断这条流程是刚到部门经理还是已经走到归档。集成系统做“流程进度”页面核心就是循环调用这个接口。注意 ignoreuser 的取值。外部系统集成时调用账号往往不是流程发起人如果 ignoreuser 传 false可能连这条流程都查不到。我一般会传 true让接口以账号的权限为基准只要账号有管理员角色就能查。权限管控严一点的公司可以给集成账号配置“流程查看”的角色再决定是否开启这个开关不要为了图省事随便放权。4.2 查表单明细getRequestData 与加密字段的两种返回getRequestData 是另一个高频接口它返回的是这张审批单的具体表单数据比如报销金额、出差城市、合同编号而不是流程头信息。实际项目里业务系统经常用这个接口把OA表单内容同步回自己的数据库做数据对账。public static String getRequestData(String endpoint, String loginId, String password, String requestid) { String soapXml ?xml version\1.0\ encoding\UTF-8\? soapenv:Envelope xmlns:soapenv\http://schemas.xmlsoap.org/soap/envelope/\ xmlns:wor\http://webservice.ws.ecology9.com\soapenv:Body wor:getRequestData loginId loginId /loginId password password /password requestid requestid /requestid /wor:getRequestData/soapenv:Body/soapenv:Envelope; return WorkflowSoapClient.call(endpoint, getRequestData, soapXml); }返回的字段XML结构跟创建时类似每个字段一个标签属性带字段名和值。加密字段在这里有两种可能一种是系统按调用账号权限直接返回明文另一种是返回密文串。判断标准是看字段值是否具备可读性比如手机号如果是11位数字那就是明文如果是一长串字母数字混合多半是密文。经验是普通集成账号拿到密文的概率比较大能配合管理员配置“返回明文”时尽量配掉或者用解密接口处理。getRequestInfo 和 getRequestData 的区别很多新手会记反前者查流程走到哪个环节后者查表单里的业务数据长什么样。区分清楚之后接口文档就不难翻了。另外注意如果表单里嵌了明细表返回XML里会出现一整套子节点不要按主表平铺的方式去解析。4.3 删和撤的区别cancelRequest 与 deleteRequest 的适用场景最后说“删除”。流程引擎里的删除是个敏感动作我的原则是能撤不删逻辑删除优先。申请人发现单子填错了应该走 cancelRequest 撤单接口流程终止数据保留审批记录还在后续能追溯。外部ERP想作废一笔订单对应的审批流程也可以调这个接口业务上完全说得通。public static String cancelRequest(String endpoint, String loginId, String password, String requestid) { String soapXml ?xml version\1.0\ encoding\UTF-8\? soapenv:Envelope xmlns:soapenv\http://schemas.xmlsoap.org/soap/envelope/\ xmlns:wor\http://webservice.ws.ecology9.com\soapenv:Body wor:cancelRequest loginId loginId /loginId password password /password requestid requestid /requestid /wor:cancelRequest/soapenv:Body/soapenv:Envelope; return WorkflowSoapClient.call(endpoint, cancelRequest, soapXml); }deleteRequest 则是管理员用来物理删除流程实例的接口删除后流程记录、日志和关联表单数据都会受影响。有些公司还会要求这类操作在集成平台上单独授权调用前必须写日志记录谁删的、为什么删、原 requestid 是多少。我这里强烈不建议外部系统默认调 deleteRequest因为一旦物理删除后续审计、报表、对账全部断层想找后悔药都找不到。先想清楚“撤单”和“删单”在业务上的区别再决定接口怎么接。撤回成功后流程状态会变成已撤销业务系统要做状态判重避免重复调用同一张单子。可以在业务表里记录撤回状态接口每次调用前先查一遍防止同一条流程被外部定时任务反复触发。5. workflowService 避坑记录5 个让集成翻车的典型问题没有踩过坑的集成项目是不完整的。这里我把 workflowService 开发中最常遇到的5个问题整理出来每条都按“现象、原因、解决”顺序写能直接当排查手册用。5.1 创建流程报“无权限”但页面操作完全正常现象管理员账号在OA页面上能正常发起流程但调用 workflowService 的 createRequest 时返回提示没有权限。 原因泛微E9里接口调用的权限和页面操作权限是两套体系。页面有权限不代表WebService接口对这个账号放行系统管理里一般还有独立的WebService接口授权。 解决到系统管理-接口安全/WebService配置里找到 workflowService把当前账号加入授权列表再重新调用。改完不用重启服务立刻生效。如果菜单位置找不到就在后台搜索“WebService”或“接口密钥”每个版本菜单命名略有不同但方向一致。5.2 流程创建成功表单业务字段却是空的现象createRequest 返回了 requestidOA里也能看到这条待办但打开审批详情表单上大量字段空白。 原因requestDataXml 里用的是表单显示名称而接口要求物理字段名另一种情况是字段属于明细表按主表字段平铺传值当然存不进去。 解决先在表单设计器里找到字段标识把显示标签映射为物理字段名。创建成功后写条测试数据去 formtable_main_xxx 表里查一遍确认哪些字段真正写入哪些字段还是空。明细表字段要用明细数据结构单独拼不要指望它能自动拆行识别。5.3 更新字段总不生效接口也没有报错现象updateRequest 调用后返回成功标识但审批页面打开字段值并没有变化。 原因流程已经进入审批节点该节点把目标字段设成只读接口写入被流程引擎拦截但返回值仍是成功。这个行为很容易让人误判为调用成功。 解决更新前先调 getRequestInfo看看当前节点ID和流程状态如果流程在途且必须改要去流程设计器把对应节点字段改为可编辑。外部系统展示“修改成功”前最好再查一次值确认生效了再给用户反馈不要只信接口返回标志位。5.4 加密字段取出来是一串密文没法直接用现象getRequestData 返回的手机号、身份证号是一串看起来像乱码的字符存进业务库后完全没法做条件查询。 原因表单字段开启了加密存储和加密显示接口按当前调用账号的权限决定是否返回明文。这不是bug是权限策略。 解决让管理员在后台给集成账号开放查看明文权限或调用解密组件做一遍解密。不要自己写固定算法去猜泛微的加密机制在不同版本上有差异猜错就会被带到黑匣子里。上线前用一个已知字段值测一次确认拿到的是明文再放心接。5.5 SOAP 调用中文乱码与特殊字符报错现象接口偶尔返回数据正常偶尔汉字变成乱码字段值里带了 、 等符号时请求直接失败。 原因SOAP请求没有统一指定UTF-8编码或者XML里的特殊字符没有转义。 解决所有请求头保持text/xml; charsetutf-8拼XML时对 做转义。这是最基础的坑但每次项目都有人踩提前写进工具类就能避免。字段值里出现英文单引号也会破坏XML结构建议在工具类里统一封装一个字段值编码方法。排查顺序建议是先看服务地址通不通再看账号授权有没有最后再怀疑XML拼写。很多问题表面是接口报错实际是前面的公共环节出错。6. 把 workflowService 封装成可复用工具类一个值得投入的工程习惯6.1 按“增删改查”收口的客户端接口demo跑通后不要急着接到业务代码里我建议先把接口收口成一个工具类。四个核心方法固定下来方法入参返回createworkflowid、字段Map、发起人账号requestidupdaterequestid、字段Map是否成功queryrequestid流程状态与表单明细cancelrequestid、撤回原因是否成功四个方法内部统一走同一个SOAP调用入口统一做异常处理、日志记录、字段转义。业务系统里不要再出现裸调 workflowService 的代码后续换接口版本、调整服务地址都只动这个类。6.2 封装后的验证顺序与长期维护封装好之后先用一张测试流程把四个方法按顺序走一遍创建流程拿到 requestid查状态改一个字段再查确认值变了最后撤单。这个过程能覆盖90%的集成问题。我自己的习惯是把这个验证脚本保留在项目里每次OA升级或者换了补丁版本就在测试环境重跑一遍看哪个接口行为变了。这比出了问题再翻日志舒服得多。长期维护时字段映射变化是最频繁的。建议把每个流程的物理字段名与业务字段名单独维护成映射表不要散落在业务代码里。以前我图省事哪里用到就在哪里写死后来表单一升级全项目都在改字段名改到怀疑人生。现在我会专门建一张映射配置增删改查统一从配置取值流程变更时只改配置代码不用动。这套做法不复杂但很值得做。希望帮到你。本文还有配套的精品资源点击获取