做了这么多年企业内部系统集成,泛微Ecology9是我用得最多的一套OA底座,也是踩坑最多、积累最厚的一套东西。很多刚接触泛微的兄弟一上来就懵,因为Ecology9的接口体系不像普通互联网应用那样一个Swagger文档搞定,它分好几套入口、好几套认证方式,而且不同版本的路径和参数还有差异。这篇博文我就把自己从后台配置到Java代码调用全流程摸通的经验完整拆一遍,重点讲清楚接口怎么申请、Token怎么拿、业务流程怎么设计、Java代码怎么写才不出幺蛾子,帮你少走我走过的弯路。如果你是系统集成工程师、Java开发,或者企业信息化负责人,这篇内容应该能直接帮上忙。
1. 泛微Ecology9接口体系概览:对接前先搞清楚有什么牌可打
1.1 三种主流对接方式:从JSON到XML的取舍
泛微Ecology9这套产品我在集成时常用的对接方式主要有三类,它们各自解决不同的问题,也各有各的脾气。
第一类是泛微自带的Restful API,走/api/ec/dev/...这类路径,返回JSON格式数据,开发体验最好。以我接触过的多个E9版本来看,Token获取走/api/ec/dev/auth/applytoken,创建流程、查询流程这些接口则分散在流程引擎、内容管理等模块下。这个体系的优点是结构清晰,前后端分离,Java、Python、甚至Node.js都能轻松对接;缺点是不同版本、不同补丁包打完之后,接口路径可能会发生变化,升级时容易踩坑。
第二类是WorkflowXML WebService接口,地址一般是http://{host}/services/WorkflowXML,这是泛微老牌工作流接口,通过SOAP协议传XML字符串来创建流程、提交审批、查询待办。这套接口非常稳定,我见过不少企业用了七八年都没动过,但它开发体验确实难受——你要手动拼XML、解析返回的SOAP XML,调试起来比Restful费劲不少。不过如果你的核心需求是“发起流程”“审批流转”“查待办”,WorkflowXML依然是很靠谱的选择。
第三类是Ecode平台提供的前端集成能力,可以在泛微页面里写自定义脚本,也可以在后端写Java插件。严格来说它不算“外部接口调用”,但对于需要在OA内部做数据处理、按钮扩展、页面增强的场景,Ecode往往是最高效的路径。我个人的经验是,外部第三方系统对接优先考虑Restful,老系统稳定优先考虑WorkflowXML,定制页面和内部逻辑增强优先考虑Ecode。
1.2 场景决定技术路线:你的需求该走哪条路
接口选型不是越新越好,也不是越熟越好,关键看业务场景。我整理了一张选型对照表,基本上覆盖了我做集成时遇到的大多数需求类型:
| 业务场景 | 推荐接口 | 主要原因 |
|---|---|---|
| 第三方系统发起流程创建 | Restful / WorkflowXML | 两个都能做,Restful开发快,WorkflowXML稳定 |
| 查询流程进度、审批日志 | WorkflowXML | doGetWorkflowRequestLogs接口成熟,字段全 |
| 读取OA表单数据、文档内容 | Restful | JSON结构解析方便,数据层级清晰 |
| 待办待阅同步到企业微信/钉钉 | Restful | 高频调用,Token机制更适合连接池复用 |
| 页面按钮触发调用第三方系统 | Ecode | 不需要后端单独部署,直接在OA页面逻辑里对接 |
| 批量导入历史数据 | 数据库接口 / Restful | 数据量大时直接走数据库更可控,但要求熟悉表结构 |
打个比方,Restful像是一辆配置齐全的新车,开着舒服但更新换代快;WorkflowXML像是一台老款越野车,外表糙但皮实耐用,翻山越岭从不掉链子;Ecode则像是原厂改装件,只有在原厂体系里才能发挥最大价值。实际项目中,我经常在一个集成方案里同时用到两三种接口,比如用Restful获取外部系统推送的数据,用WorkflowXML创建流程,再用Ecode在审批结束后触发回调,这套组合在真实企业环境里非常常见。
2. 后台配置与Token机制:接口调用的地基工程
2.1 接口密钥申请:不是随便填填就完事
很多新手拿到泛微Ecology9地址就直接写代码调接口,结果第一步就卡住了——返回401或者appid invalid。原因是泛微的接口必须有密钥才能调,而这个密钥需要在OA后台申请。
登录OA系统管理员账号之后,入口一般在“集成中心”或者“客户化”模块下面的“接口密钥管理”,不同版本的菜单名会有差异,但里面的要素大同小异。进去之后选择新增密钥,需要填应用名称、联系人、授权模块这几个关键信息。这里要注意,授权模块决定了你能调哪些接口,比如你只想做流程创建,那就勾选工作流相关模块;如果你勾选范围过大,安全审核时容易被卡。如果只勾选了流程模块,后面想查文档数据又会报权限不足,所以申请前最好列一个接口清单,按清单勾权限。
提交申请之后,系统会生成一对密钥,一个是appid,一个是secret,有的版本还会额外生成一个appkey。这三个词的叫法在不同补丁版本里略有差别,但逻辑上appid是应用标识,secret相当于密码。我见过不少内部系统在代码里硬编码了secret,这是非常危险的——secret一旦泄露,别人就能以你这个应用的身份读取或操作系统数据,生产环境的secret一定要放到配置中心或环境变量里,不要写进代码仓库。
提示:申请密钥的时候,尽量一个外部系统申请一个独立的应用,不要所有系统共用一个
appid。否则后面想单独回收某个第三方系统的权限时,你会发现牵一发动全身。
2.2 双Token机制拆解:access_token和refresh_token的恩怨
申请完密钥,下一步就是通过密钥换取Token。泛微Ecology9的Token机制和主流开放平台类似,采用access_token + refresh_token双Token设计。调用业务接口时,请求头或参数里带上access_token;access_token过期后,用refresh_token换取新的access_token,避免用户重新走密钥认证流程。
我截取一个典型的获取Token响应体:
{ "code": 0, "msg": "success", "data": { "access_token": "eyJhbGciOiJIUzI1NiIs...", "refresh_token": "dGhpcyBpcyByZWZyZXNo...", "expires_in": 3600, "refresh_expires_in": 604800 } }这里expires_in是access_token的有效期,单位秒,常见的是3600秒(1小时);refresh_expires_in是refresh_token的有效期,常见的是7天。很多开发者以为只需要在过期后重新获取新Token就行,但从安全设计上讲,正确的做法是:access_token快过期时用refresh_token去换,而不是重新用appid和secret去申请。这样既减少了密钥的传输频率,也方便后台做统一的生命周期管理和吊销控制。
不过我在实际项目里遇到过一个坑:有些泛微版本对refresh_token的调用频率有限制,频繁刷新可能会被风控拦截。所以代码层面必须做好缓存和并发控制——多个线程同时发现token过期、同时去刷新,就会产生雪崩效应。稍后第4章我会给出一个线程安全的TokenManager方案。
2.3 签名算法与安全边界:防人篡改的那点事
泛微Ecology9在申请Token时,很多版本要求携带签名参数token。这个签名的作用是防止请求参数在传输过程中被篡改。我见过比较常见的签名算法是:将appid、secret、timestamp按固定顺序拼接后做MD5,具体拼接顺序和加密方式每个补丁版本可能有差异,但核心思想是一致的——用只有客户端和服务端知道的secret参与计算,服务端收到请求后重新计算一遍,两个签名一致才会放行。
实际调试时,我建议先在泛微后台找一下“接口文档”或者“开发示例”页面,里面有现成的签名示例。如果后台没有文档,可以抓一个OA前端页面发起的请求看看它怎么生成签名,照葫芦画瓢。这个办法我在好几个版本上都用过,百试百灵。
关于安全边界,还有一个容易被忽略的点:很多生产环境的泛微OA直接对外网开放了业务接口,风险很大。如果条件允许,建议在防火墙层面对接口路径做白名单限制,只允许固定IP段的服务器访问。如果泛微必须对公网开放,至少要把/api/ec/dev/这几个认证相关路径加严格访问控制,并且定期轮换密钥。
3. 业务流程设计:从ERP发起报销流程看调用闭环
3.1 真实场景:ERP审批通过后自动发起OA报销流程
接口调用不能只看单个请求,要站在业务流程闭环的高度去设计。我这里分享一个我真实做过的案例:客户公司用ERP管理采购报销,审批在ERP里完成后,需要自动在泛微OA里发起一条报销流程,流程走完后,审批结果还要回传给ERP。
这个场景里,泛微其实是“被调用方”,ERP是“调用方”。整个链条可以拆成四段:
- ERP提交审批通过后,触发一个消息事件;
- ERP系统的集成服务收到事件,组装泛微创建流程所需的参数;
- 调用泛微Restful接口或WorkflowXML接口,创建一条报销流程,并拿到requestId;
- 泛微流程审批结束后,再通过接口或回调把结果返回给ERP。
很多开发在做第3步时只关心“流程有没有创建成功”,忽略了第1步和第4步的设计,结果流程是建起来了,但审批到哪一步了、有没有被退回,ERP那边一概不知,最后还是靠人肉查OA,集成就失去了意义。
3.2 调用链路拆解:5个环节串起一次完整请求
我把一次完整的第三方发起泛微流程的调用拆成5个环节,每个环节都有自己必须注意的细节:
环节一:准备Token。在真正发起流程创建前,先确认本地缓存的access_token是否还有效。无效则用refresh_token刷新,刷新失败再走密钥申请逻辑。
环节二:组装业务数据。这一步是把ERP系统的数据映射成泛微表单字段。常见的坑是字段类型不匹配,比如ERP里的金额是字符串"1234.5",泛微表单的小数字段类型是decimal,直接传会导致流程创建失败。
环节三:调用创建流程接口。通过HTTP客户端把JSON请求体发送到泛微接口。请求体里一般有三个核心部分:工作流标识(workflowId)、流程标题(requestName)、表单数据(formData)。有的场景还需要指定流程发起人(creatorId),这个字段在接口升级后可能有不同的传法,需要以实际文档为准。
环节四:解析响应拿requestId。创建成功后,泛微会返回业务流水号requestId,这是后续追踪流程的唯一标识,必须保存到ERP的业务表里。
环节五:回调与状态同步。泛微流程审批结束后,通过Ecode写回调,或者定时轮询WorkflowXML的doGetWorkflowRequestLogs查询流程状态,把结果返回ERP。轮询方案实现简单,但存在延迟;回调方案实时性好,但需要处理网络异常、重试等问题。
3.3 表单字段映射:接口参数与表单控件的前世今生
表单字段映射是整个流程设计里最容易出问题的地方。泛微Ecology9的表单分为标准字段和自定义字段,标准字段比如申请人、申请日期、部门这些,对应FormData里的固定key;自定义字段则是你们OA管理员在建模引擎里建的控件,字段名以field加编号之类的形式命名,比如field12345、field67890。
我见过不少新人直接拿表单控件的中文标签去做接口参数,比如把“报销金额”作为key传进去,结果泛微根本识别不了。正确做法是在泛微后台的建模引擎查看每个字段的实际物理名称,或者用抓包工具抓一下前端实际提交的表单结构,那个才是接口能识别的字段名。
字段映射还有一个细节:附件的处理。如果流程表单里需要带附件,一般的做法是先把附件上传到泛微文档中心拿到docid,再在创建流程时把docid放进表单字段。上传接口和文档中心的关联逻辑在不同版本差异较大,建议先在测试环境验证一遍,再把代码固化下来。
4. Java实战解析:手写一个可上生产的调用客户端
4.1 工程依赖与工具类准备
Java对接泛微Ecology9,我建议直接使用OkHttp + Fastjson这套组合。OkHttp连接池管理好,性能稳定;Fastjson序列化反序列化方便,解析泛微返回的JSON非常顺手。如果你的项目对HTTP库有统一规范,用Spring的RestTemplate或HttpClient也行,核心逻辑都一样。
Maven依赖如下:
<dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>4.12.0</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>2.0.32</version> </dependency> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> <version>1.15</version> </dependency>我还习惯封装一个最简HTTP工具类,把POST JSON、GET请求这些常用方法收敛起来,业务代码里就不用每个地方都写一遍OkHttp的Builder了:
public class OkHttpUtils { private static final OkHttpClient CLIENT = new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .writeTimeout(30, TimeUnit.SECONDS) .build(); public static String postJson(String url, String json) throws IOException { RequestBody body = RequestBody.create(json, MediaType.parse("application/json; charset=utf-8")); Request request = new Request.Builder().url(url).post(body).build(); try (Response response = CLIENT.newCall(request).execute()) { return response.body() != null ? response.body().string() : ""; } } public static String get(String url) throws IOException { Request request = new Request.Builder().url(url).get().build(); try (Response response = CLIENT.newCall(request).execute()) { return response.body() != null ? response.body().string() : ""; } } }超时时间不要设置太短,泛微有些流程操作涉及大量数据组装,响应超过10秒很正常。读超时30秒是比较稳妥的起始值。
4.2 获取Token:代码里的细节决定成败
获取Token是实现调用闭环的第一步。先看一段我自己在多个E9版本上验证过的核心代码:
public class TokenManager { private static volatile TokenManager instance; private final String appid; private final String secret; private final String tokenUrl; private volatile String accessToken; private volatile String refreshToken; private volatile long expireAt; private TokenManager(String appid, String secret, String tokenUrl) { this.appid = appid; this.secret = secret; this.tokenUrl = tokenUrl; } public static TokenManager getInstance() { if (instance == null) { synchronized (TokenManager.class) { if (instance == null) { instance = new TokenManager("your-appid", "your-secret", "http://oa.example.com/api/ec/dev/auth/applytoken"); } } } return instance; } public String getAccessToken() throws Exception { // 提前60秒过期,避免token刚好在请求过程中失效 if (accessToken == null || System.currentTimeMillis() >= expireAt - 60000) { synchronized (this) { if (accessToken == null || System.currentTimeMillis() >= expireAt - 60000) { doRefreshToken(); } } } return accessToken; } private void doRefreshToken() throws Exception { long timestamp = System.currentTimeMillis(); // 请注意:不同版本的泛微签名串拼接顺序可能不同,以官方接口文档为准 String sign = DigestUtils.md5Hex(appid + secret + timestamp); Map<String, Object> params = new HashMap<>(); params.put("appid", appid); params.put("secret", secret); params.put("timestamp", timestamp); params.put("token", sign); String json = OkHttpUtils.postJson(tokenUrl, JSON.toJSONString(params)); JSONObject result = JSON.parseObject(json); if (result.getIntValue("code") != 0) { throw new RuntimeException("获取Token失败: " + json); } JSONObject data = result.getJSONObject("data"); this.accessToken = data.getString("access_token"); this.refreshToken = data.getString("refresh_token"); long expireIn = data.getLongValue("expires_in"); this.expireAt = System.currentTimeMillis() + expireIn * 1000L; } }这段代码我有几个设计想法特意说明一下:
第一,用了双重检查锁保证并发安全,多个线程同时调用getAccessToken()时不会重复刷新Token——这是生产环境最容易踩的并发坑,不做并发控制的话,高峰期瞬间几十个线程一起去刷新,泛微后台会限流,反过来又拖慢业务请求。
第二,提前60秒过期,这是一个经验值。网络请求有耗时,如果token刚好在发请求的瞬间过期,服务端返回401,你就得重试一次,白白增加耗时。提前1分钟刷新,能有效规避这个问题。
第三,刷新Token的方法和初次申请Token的方法我合成了同一个doRefreshToken(),因为很多版本的泛微认证接口并不区分首次申请和刷新,统一走applytoken即可。如果你的版本区分两个接口,那就在getAccessToken()里先判断refreshToken是否为空,再决定走哪个接口。
4.3 创建流程实例:从JSON拼装到响应解析
拿到Token之后,接下来的核心操作就是创建流程。我以比较容易理解的创建请求接口为例,写一段典型的Java代码:
public class WorkflowApiClient { private static final String HOST = "http://oa.example.com"; public String createWorkflow(int workflowId, String requestName, String creatorId, Map<String, Object> formData) throws Exception { String url = HOST + "/api/ec/dev/workflow/paService/grampusworkflow/createWorkflow"; String token = TokenManager.getInstance().getAccessToken(); Map<String, Object> workflowBaseInfo = new HashMap<>(); workflowBaseInfo.put("workflowid", workflowId); workflowBaseInfo.put("requestName", requestName); workflowBaseInfo.put("creatorId", creatorId); Map<String, Object> payload = new HashMap<>(); payload.put("workflowBaseInfo", workflowBaseInfo); payload.put("formData", formData); String requestJson = JSON.toJSONString(payload); System.out.println("请求参数: " + requestJson); String responseJson = OkHttpUtils.postJsonWithToken(url, requestJson, token); JSONObject result = JSON.parseObject(responseJson); if (result.getIntValue("code") != 0) { throw new RuntimeException("创建流程失败: " + responseJson); } // 生产环境这里的requestId请存库,后续查询状态要用 String requestId = result.getJSONObject("data").getString("requestId"); return requestId; } }这里有几个关键点值得展开。
首先是请求体结构。workflowBaseInfo承载的是流程的基础信息,formData承载的是表单数据。不同版本的formData字段结构可能有差异,有的版本是Map直接映射字段,有的版本要求一个FormField数组。我建议大家在开发时先抓一个前端实际发起流程的请求,照抄它的结构,成功率会直线上升。
其次是creatorId的处理。有的场景下,第三方系统代发起流程,但流程发起人应该是某个具体的OA用户,这时要传该用户在泛微里的用户ID,而不是appid对应的系统用户。如果泛微版本不允许指定发起人,就需要用系统管理员的身份来调用,通过onBehalfUser之类的参数指定,具体以你们版本文档为准。
最后是响应解析。泛微返回的requestId是流程实例的唯一标识,类似数据库的主键ID。我在代码里刻意把它单独提取出来返回,就是为了让调用方在业务逻辑里可以明确感知到“这条流程已经属于某个业务主数据了”。后续ERP要用这个requestId去查询审批状态、做关联归档,所以无论如何都要保存下来。
4.4 查询流程状态与日志:闭环不能只发不管
流程创建成功以后,业务闭环并没有结束,还需要查询流程走到哪个节点了。最方便的方式是在ERP的定时任务里调用泛微的流程日志查询接口。
我在老项目中通常用WorkflowXML的doGetWorkflowRequestLogs方法,这个方法传入requestId,返回该流程的所有审批日志,包括各个节点的审批人、审批意见、审批时间、最终状态等。这个方法的返回是SOAP XML格式,解析起来有一点繁琐,我贴一个简化版的调用思路:
public String getWorkflowLogs(String requestId) throws Exception { String wsdlUrl = HOST + "/services/WorkflowXML"; List<String> requestMessage = new ArrayList<>(); requestMessage.add("<Request><RequestId>" + requestId + "</RequestId></Request>"); // 构造SOAP请求体,具体命名空间以WSDL为准 String soapBody = buildSoapBody("doGetWorkflowRequestLogs", requestMessage); String responseXml = OkHttpUtils.postXml(wsdlUrl, soapBody); return parseSoapResult(responseXml); }SOAP接口拼XML比较丑,但在企业环境里你绕不开它。我见过有人为了不用SOAP,专门去找新版Restful流程查询接口,这当然可以,但如果你们环境是旧版本,那WorkflowXML就是唯一选择了。在解析XML时,我建议直接用DocumentHelper或XPath取节点,不要用正则去抠,效率低还容易出错。
补充一句,doGetWorkflowRequestLogs返回的日志里,workflowRequestStatus字段比较关键,它表示流程当前状态:比如0是审批中、1是已完成、2是已作废或退回等,具体枚举值不同版本有差异,业务判断时尽量用常量映射而不是硬编码数字。
4.5 封装一个EcologyApiClient:单例、重试与线程安全
当你的项目里不只一个地方需要调泛微接口时,散落各处写HttpClient代码就会变得很难维护。我的建议是,把所有和泛微相关的调用收敛到一个EcologyApiClient类里,对外只暴露业务方法,内部统一处理Token、重试、异常转换。
一个典型的设计可以是:
public class EcologyApiClient { private final TokenManager tokenManager = TokenManager.getInstance(); public String createExpenseWorkflow(ExpenseData data) throws Exception { Map<String, Object> formData = new HashMap<>(); formData.put("field_expense_amount", data.getAmount()); formData.put("field_expense_reason", data.getReason()); formData.put("field_applicant", data.getApplicant()); // 业务方法内部统一走重试逻辑 return retryOnTokenExpired(() -> createWorkflow( data.getWorkflowId(), data.getRequestName(), data.getCreatorId(), formData)); } private <T> T retryOnTokenExpired(Callable<T> action) throws Exception { try { return action.call(); } catch (RuntimeException e) { if (e.getMessage() != null && e.getMessage().contains("token")) { tokenManager.forceRefresh(); return action.call(); } throw e; } } }这个设计的核心价值有两点:一是把业务数据和接口的映射逻辑集中在Client内部,上层系统不需要关心workflowBaseInfo到底怎么组;二是做了Token过期自动重试,当接口返回token expired或401时,先强制刷新Token再重试一次,对上层业务透明。这个重试逻辑对网络抖动、令牌刚好过期这类的偶发问题很有效。
有一点要提醒:重试逻辑要控制次数,一般重试1次就够了,重试太多次反而会给泛微服务端造成压力,尤其在接口性能比较差的环境里。
5. 常见问题与排查技巧实录
5.1 高频报错速查表:一眼定位问题
我把这些年对接泛微Ecology9时遇到的高频报错整理成了表格,照着这个表排查,大部分问题能快速定位:
| 报错现象 | 可能原因 | 解决思路 |
|---|---|---|
| 401 Unauthorized | Token缺失、过期、或者请求头里没带 | 检查Token缓存逻辑,提前刷新;确认请求头key拼写正确 |
| appid invalid | appid填错,或者该应用没有接口权限 | 后台核对密钥,确认授权模块 |
| 签名校验失败 | 签名串拼接顺序不对,或timestamp误差过大 | 严格按照接口文档生成签名,检查服务器时间同步 |
| 表单字段不存在 | formData里的key和表单物理字段名不一致 | 到后台建模引擎查看字段物理名,或抓包确认 |
| 流程创建但无权限 | 当前账号不具备该流程的发起权限 | 确认creatorId是否对该流程可见可发起,换个授权账号测试 |
| 返回code非0但HTTP是200 | 业务逻辑错误,比如workflowId不存在 | 输出完整响应体,定位业务错误码信息 |
| 连接超时 | 泛微处理慢或网络隔离 | 适当调大超时配置,检查泛微服务器负载 |
| 跨域报错 | 浏览器调用泛微接口时CORS拦截 | 改为后端调用,不要在浏览器里直接调OA接口 |
5.2 排查三板斧:日志、抓包、接口文档
遇到问题不要瞎猜,排接口问题我有三板斧。
第一板斧是看日志。我这里说的不是单纯看控制台,而是看请求参数和响应体的完整日志。我自己习惯在代码里用日志记录每次调用的URL、请求JSON、响应JSON,但要注意生产环境打日志时必须脱敏,绝对不能把secret、token明文打出来。建议封装工具类,对敏感字段做掩码处理。
第二板斧是抓包。当泛微前端页面能正常操作,但你的代码就是调不通时,用Fiddler或Charles抓一下前端页面实际发出的HTTP请求,看看它调的URL、请求头、请求体长什么样子,和你的代码对比差异。这个方法多次帮我解决了版本差异问题,比看接口文档快得多。注意用真机或测试环境抓包,不要在公网入口抓。
第三板斧还是接口文档。泛微Ecology9后台通常会有一个“接口文档”或“开发说明”页面,里面能看到当前版本支持的接口列表、请求参数说明和Java示例代码。这个文档偶尔和实际行为有出入,但多数情况下还是靠谱的。判断接口文档和实际行为不一致时,以抓包结果为准。
5.3 我踩过的三个坑:长期困扰你的往往是小地方
第一个坑是关于集群部署下的Token管理。客户OA是泛微集群环境,负载均衡后面有多台E9节点,Token认证逻辑在各个节点之间应该是共享的,但如果你在代码里把Token缓存到本地内存,集群刷新的时机又没控制好,就会出现部分请求token失效、部分请求正常的现象。解决办法是在TokenManager里加分布式锁,或者干脆把Token缓存到Redis,确保集群环境下所有请求节点拿到的是同一份Token。
第二个坑是回调地址的网络隔离。我在做流程审批结束后回调外部系统时,刚开始怎么也调不通,最后发现是泛微服务器到外部系统的网络端口没有放通。这个问题不是代码问题,但很容易让人怀疑代码写错了。做集成联调之前,先检查你的目标服务器和对方服务器的网络连通性,能省下大量排查时间。
第三个坑是金额字段的精度问题。ERP传过来的金额是BigDecimal类型,序列化后的字符串可能是1234.50,泛微表单里如果没有设置对应的小数位精度,流程创建时会把多余的小数位截断,导致报销金额对不上。解决思路是在组装formData之前,统一按表单精度格式化金额字段,保留两位小数。
最后分享两个小经验
我自己做下来最大的体会是,泛微Ecology9接口本身并不算难,难的是各种版本差异和环境问题。你在写代码之前一定要先花半天时间把当前环境属于哪个版本、有哪些接口文档、签名算法是什么、字段物理名是什么这些信息沉淀下来,把地基打牢,后面的开发会顺畅很多。
另外一个小技巧是,泛微接口联调时千万不要在生产环境直接试,一定要在测试环境把完整流程跑通了再切生产。测试环境的初始化数据和生产不一定完全一致,你可能会遇到表单配置、人员权限、流程分类这类只在生产环境存在的问题,但至少能把代码层面的问题全部过滤掉。等你切换到生产环境后,剩下的就是配置同步和环境参数调整,风险小得多。