1. 项目背景与需求分析
在企业信息化建设过程中,OA系统与ERP系统的集成是提升业务流程效率的关键环节。最近在实施泛微OA-E9与金蝶ERP的对接项目时,遇到了一个典型需求:需要将金蝶ERP中的单据推送到OA系统进行审批,并在OA审批完成后将审批状态回写到金蝶系统。
这种跨系统状态同步的需求在企业级应用中非常普遍。以采购流程为例,当采购申请在ERP生成后,需要经过OA的多级审批,审批结果(通过/驳回)需要实时反馈给ERP系统,以便触发后续的财务或物流操作。传统的人工二次录入方式不仅效率低下,而且容易出错。
2. 技术方案设计
2.1 整体架构设计
实现系统间状态同步通常有两种主流方案:
- 主动查询方式:ERP系统定时轮询OA系统获取审批状态
- 回调通知方式:OA系统在审批动作发生时主动通知ERP系统
经过对比分析,我们选择了第二种回调通知方案,主要基于以下考虑:
- 实时性更高:状态变更立即触发通知
- 资源消耗更低:避免了无效的轮询请求
- 架构更合理:事件驱动模型符合业务逻辑
2.2 接口规范定义
与金蝶团队协商后,确定了以下接口规范:
- 调用方式:HTTP POST + JSON
- 接口地址:由金蝶在创建OA流程时通过hdurl字段传入
- 请求参数:
json复制{ "requestId": "OA流程唯一标识", "status": "3" // 3表示通过,0表示驳回 } - 响应格式:
json复制{ "bizcode": "10000", // 10000表示成功 "bizmsg": "成功", "data": "" }
3. 核心代码实现
3.1 审批通过处理类
java复制package weaver.interfaces.workflow;
import com.alibaba.fastjson.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import weaver.general.BaseBean;
import weaver.general.Util;
import weaver.interfaces.workflow.action.Action;
import weaver.soa.workflow.request.MainTableInfo;
import weaver.soa.workflow.request.Property;
import weaver.soa.workflow.request.RequestInfo;
import weaver.workflow.request.RequestManager;
public class AgreeAction extends BaseBean implements Action {
private static BaseBean log = new BaseBean();
public String execute(RequestInfo request) {
try {
// 1. 获取表单数据
Map<String, String> formData = extractFormData(request);
// 2. 准备回调参数
String callbackUrl = formData.get("hdurl");
String requestId = request.getRequestid();
String requestBody = buildRequestBody(requestId, "3"); // 3表示审批通过
// 3. 调用金蝶接口
String response = invokeK3Api(callbackUrl, requestBody);
// 4. 处理响应结果
return handleResponse(request, response);
} catch (Exception e) {
log.writeLog("审批通过回调异常:" + e);
setErrorMessage(request, "审批通过回调异常:" + e);
return "0"; // 返回0表示异常,阻断流程
}
}
private Map<String, String> extractFormData(RequestInfo request) {
Map<String, String> dataMap = new HashMap<>();
Property[] props = request.getMainTableInfo().getProperty();
for (Property prop : props) {
String fieldName = prop.getName().toLowerCase();
String fieldValue = Util.null2String(prop.getValue());
dataMap.put(fieldName, fieldValue);
}
return dataMap;
}
private String buildRequestBody(String requestId, String status) {
return String.format("{\"requestId\":\"%s\",\"status\":\"%s\"}",
requestId, status);
}
private String invokeK3Api(String url, String requestBody) throws IOException {
HttpURLConnection connection = null;
OutputStreamWriter writer = null;
BufferedReader reader = null;
try {
// 创建HTTP连接
connection = (HttpURLConnection) new URL(url).openConnection();
connection.setRequestMethod("POST");
connection.setDoOutput(true);
connection.setRequestProperty("Content-Type", "application/json");
// 发送请求
writer = new OutputStreamWriter(connection.getOutputStream(), "UTF-8");
writer.write(requestBody);
writer.flush();
// 读取响应
reader = new BufferedReader(
new InputStreamReader(connection.getInputStream(), "UTF-8"));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
return response.toString();
} finally {
// 资源清理
if (writer != null) writer.close();
if (reader != null) reader.close();
if (connection != null) connection.disconnect();
}
}
private String handleResponse(RequestInfo request, String response) {
JSONObject json = JSONObject.parseObject(response);
if ("10000".equals(json.getString("bizcode"))) {
return "1"; // 返回1表示成功,继续流程
}
String errorMsg = json.getString("bizmsg");
setErrorMessage(request, "金蝶接口返回错误:" + errorMsg);
return "0";
}
private void setErrorMessage(RequestInfo request, String message) {
request.getRequestManager().setMessage("-1");
request.getRequestManager().setMessagecontent(message);
}
}
3.2 审批驳回处理类
java复制package weaver.interfaces.workflow;
import com.alibaba.fastjson.JSONObject;
import java.io.IOException;
import java.util.Map;
import weaver.general.BaseBean;
import weaver.interfaces.workflow.action.Action;
import weaver.soa.workflow.request.RequestInfo;
public class RejectAction extends BaseBean implements Action {
private static BaseBean log = new BaseBean();
public String execute(RequestInfo request) {
try {
// 1. 获取表单数据
Map<String, String> formData = extractFormData(request);
// 2. 准备回调参数
String callbackUrl = formData.get("hdurl");
String requestId = request.getRequestid();
String requestBody = buildRequestBody(requestId, "0"); // 0表示审批驳回
// 3. 调用金蝶接口
String response = invokeK3Api(callbackUrl, requestBody);
// 4. 处理响应结果
return handleResponse(request, response);
} catch (Exception e) {
log.writeLog("审批驳回回调异常:" + e);
setErrorMessage(request, "审批驳回回调异常:" + e);
return "0";
}
}
// 以下方法与AgreeAction中相同,实际开发中可以提取到父类
private Map<String, String> extractFormData(RequestInfo request) { ... }
private String buildRequestBody(String requestId, String status) { ... }
private String invokeK3Api(String url, String requestBody) throws IOException { ... }
private String handleResponse(RequestInfo request, String response) { ... }
private void setErrorMessage(RequestInfo request, String message) { ... }
}
4. 部署与配置
4.1 文件部署步骤
-
编译代码:使用JDK 1.8编译Java文件
bash复制javac -cp ecology_classpath.jar AgreeAction.java RejectAction.java -
部署class文件:
- 在OA服务器上创建目录:
weaver/ecology/classbean/weaver/interfaces/workflow - 将编译后的
.class文件复制到该目录
- 在OA服务器上创建目录:
-
重启服务:
bash复制cd /weaver/ecology ./stop.sh ./start.sh
4.2 流程节点配置
- 打开流程设计器,找到目标流程
- 配置"同意归档"节点:
- 右键 → 节点属性 → 节点前附加操作
- 添加Java类:
weaver.interfaces.workflow.AgreeAction
- 配置"驳回归档"节点:
- 右键 → 节点属性 → 节点前附加操作
- 添加Java类:
weaver.interfaces.workflow.RejectAction
5. 测试与验证
5.1 测试方案设计
为确保集成可靠性,需要设计全面的测试用例:
| 测试场景 | 预期结果 | 验证方法 |
|---|---|---|
| 正常审批通过 | ERP状态更新为"已批准" | 检查金蝶系统数据 |
| 正常审批驳回 | ERP状态更新为"已驳回" | 检查金蝶系统数据 |
| 网络中断 | OA流程暂停并提示错误 | 观察OA界面提示 |
| ERP接口异常 | OA流程暂停并提示错误 | 查看OA日志 |
| 重复回调 | ERP正确处理重复请求 | 检查金蝶日志 |
5.2 日志监控要点
在ecology/logs/action.log中重点关注以下日志条目:
code复制OA调用异构系统传递的参数:{"requestId":"12345","status":"3"}
OA调用异构系统返回的结果:{"bizcode":"10000","bizmsg":"成功"}
6. 常见问题与解决方案
6.1 类加载问题
问题现象:重启后附加操作不生效,日志显示"Class not found"
解决方案:
- 检查class文件路径是否正确
- 确认文件权限(应为weaver用户可读)
- 检查包名是否与目录结构匹配
6.2 接口调用超时
问题现象:流程卡住,日志显示连接超时
优化方案:
- 增加超时设置:
java复制connection.setConnectTimeout(5000); // 5秒连接超时 connection.setReadTimeout(10000); // 10秒读取超时 - 添加重试机制(最多3次)
6.3 性能优化建议
- 使用连接池替代每次创建新连接
- 将JSON处理改为静态方法,减少对象创建
- 添加异步处理模式,避免阻塞主流程
7. 扩展思考
7.1 通用化设计
当前实现与金蝶系统强耦合,可以考虑抽象为通用回调框架:
- 通过配置表维护不同系统的回调规则
- 使用反射机制动态加载处理类
- 添加统一的异常处理模块
7.2 事务一致性保障
在更严格的场景下,需要考虑分布式事务:
- 引入本地消息表实现最终一致性
- 添加补偿机制(如定时任务检查未回调的记录)
- 重要业务可考虑接入企业服务总线(ESB)