aiM18开发者中心 aiM18开发者中心
文档主页
  • 平台

    • 平台总体概述
    • 前端开发须知
    • 后台开发须知
    • EBI开发须知
    • JSF组件属性表
    • BPM扩展接口
    • 一查到底扩展接口
    • 手机App设置
  • ERP

    • ERP 后端扩展
    • ERP 应用配置
    • ERP 前端扩展
    • ERP 集团ERP扩展
  • 教程

    • 设置Eclipse开发环境
    • 创建App教程
  • 平台相关

    • API授权
    • 通用的数据对象
    • 核心的API
    • EBI相关接口
  • 企业资源规划
  • 人力资本管理
  • 业务流程处理
  • 日程管理
  • 文档管理
  • 教程

    • 通过API与aiM18进行交互
教程
GitHub (opens new window)
文档主页
  • 平台

    • 平台总体概述
    • 前端开发须知
    • 后台开发须知
    • EBI开发须知
    • JSF组件属性表
    • BPM扩展接口
    • 一查到底扩展接口
    • 手机App设置
  • ERP

    • ERP 后端扩展
    • ERP 应用配置
    • ERP 前端扩展
    • ERP 集团ERP扩展
  • 教程

    • 设置Eclipse开发环境
    • 创建App教程
  • 平台相关

    • API授权
    • 通用的数据对象
    • 核心的API
    • EBI相关接口
  • 企业资源规划
  • 人力资本管理
  • 业务流程处理
  • 日程管理
  • 文档管理
  • 教程

    • 通过API与aiM18进行交互
教程
GitHub (opens new window)
  • 平台

    • 平台总体概述
    • 前端开发须知
    • 后台开发须知
    • EBI开发须知
    • JSF组件属性表
    • BPM扩展接口
    • 一查到底扩展接口
    • 手机App设置
  • ERP

    • ERP EJB 接口
    • ERP XML 配置应用
    • ERP 装饰器(前端接口)
    • ERP 集团ERP扩展
      • 概述
        • 部署配置
        • 相关模块
        • 同步方式
      • 配置项
        • module.xml 的 param
        • navmenu.xml 的 param
      • 通用前提:跨服务器 id 对齐
      • 方式一:记录同步(FM 模块)
        • 1. 声明模块是否参与同步
        • 2. 调整同步的字段范围
        • 3. 特殊处理:grouperpSyncHandler
        • 4. 特殊处理:DataSwapHandler / SearchCodeHandler
      • 方式二:对象同步(配置模块)
        • 1. 检查你的 ObjectHandler
        • 2. 特殊处理:grouperpSyncHandler
      • 方式三:事件同步(Event)
        • 1. 编写 Event 类
        • 2. 编写界面 Listener
        • 3. 登记菜单
      • 调试与排错
        • 联调步骤
        • debug 模式
        • 常见问题
  • 教程

    • 设置Eclipse开发环境
    • 创建App教程
  • App中文
  • ERP
Multiable
2026-08-20
目录

ERP 集团ERP扩展

版本: 1.0 | 发布日期: 20/08/2026

# 概述

集团ERP(App 名 ce01_grouperp)用于多套独立部署的 aiM18 系统之间的配置同步:一台主服务器统一维护资料/配置类模块的增删改,通过 REST + OAuth2 推送到 N 台子服务器;子服务器上这些模块被强制只读,界面加锁并提供「前往主服务器」的跳转。

项目 说明
同步方向 单向:主服务器 → 子服务器
部署形态 互相独立的系统、独立的数据库

# 部署配置

服务器身份由 WildFly 的部署配置决定:

配置项 说明
caw.grouperp.master 当前这台是主服务器
caw.grouperp.slave 当前这台是子服务器
caw.grouperp.gerpKey 本机身份码,每台唯一。请求携带的 key 与本机不符即拒绝

程序中通过 GrouperpUtil.isMasterServer() / isSlaveServer() / getGerpKey() 读取。


# 相关模块

菜单 模块 / 表 说明
[集团ERP服务器设定] grouperpServer 登记集团内每一台服务器的地址、身份码与集成账号,逐台验证连接;并指定各服务器负责的企业法人
[集团ERP模块设定] grouperpSetting 列出所有参与同步的模块与菜单,标注各自走 Excel / Object / Event;可为个别模块配置特殊设定
[集团ERP同步异常日志] grouperpaudittrail 同步失败的明细,含对端返回的错误与实际发出的报文,可勾选后重试
[集团ERP同步追踪日志] grouperpauditdata 同步成功的明细,可勾选后重新补录到指定子服务器

# 同步方式

方式 适用对象 底层机制 第三方要写什么
记录同步 FM 模块(资料/配置类的标准 module) 主服务器[数据导出] → 子服务器[数据导入] 一般只需在 module.xml 声明;有特殊需求才写 Handler
对象同步 配置模块(已实现 DataObjectHandler) 主服务器[创建Objects] → 子服务器[安装Objects] 检查并改造自己的 ObjectHandler
事件同步 界面按钮动作、非模块型配置 反射调用指定的类与函数 Event 类 + 界面 Listener


# 配置项

# module.xml 的 param

key 值 适用方式 说明
grouperpSyncAddFm true 记录 / 对象 让本模块参与同步
grouperpSyncSkipFm true 记录 / 对象 排除本模块参与同步
grouperpSyncLenientTable ; 分隔的表名 记录 把框架默认不导入的表拉进同步范围
grouperpSyncLenientField ; 分隔的 表名.列名 记录 把框架默认不导入的列拉进同步范围
grouperpSyncHandler 全类名 记录 / 对象 本模块的自定义同步处理类
grouperpSettingXhtml xhtml 路径 全部 本模块在[集团ERP模块设定]中的特殊设定弹窗

# navmenu.xml 的 param

key 值 适用方式 说明
grouperpSyncAddFm true 事件 让本菜单参与事件同步
grouperpSyncSkipFm true 事件 排除本菜单参与事件同步


# 通用前提:跨服务器 id 对齐

主/子服务器是两个独立数据库,两边同一条记录的自增 id 完全不同。三种同步方式都建立在同一个前提上:用 code 在对端反查本地 id。

因此参与同步的模块必须满足:

  • 主表有 code 栏位,且两边指向同一个业务实体——不能由本机流水号生成、不能包含本机 id、不能在同步后被本地改写;
  • 所引用的外键主档,在子服务器上必须存在同 code 的记录,否则报 cannot find lookup code。

模块天生没有 code 时(例如一对一的设定表),需要人工构造一个稳定 code,常见做法是 code = 模块名(单例表)或 code = 所属企业法人的 code(一 BE 一行的表)。

除 code 反查外,系统会自动给参与同步的主表补一个隐藏列 gerpSyncSId,存放该记录在主服务器上的 id,用于删除定位与「前往主服务器」跳转。第三方App 不需要声明它,也不应在业务逻辑里写它。



# 方式一:记录同步(FM 模块)

适用:FM 模块——资料/配置类的标准 module。系统默认会同步所有 FM 模块,不需要写任何代码。

机制上等同于主服务器做一次[数据导出]、子服务器做一次[数据导入]:主服务器把整条记录导成 xlsx(外加可选的 7z 附件包)推给子服务器,子服务器按标准导入流程落库,该模块自身的 Checker 会照常执行。


# 1. 声明模块是否参与同步

在自己 App 的 module.xml 里声明即可:

    <!-- 让本模块参与集团同步 -->
    <module name="myBaseData" mess="my3pd.myBaseData" mainTable="mybasedata" fmShare="N">
        <table name="mybasedata" key="code" initRow="1"/>
        <param key="grouperpSyncAddFm" value="true"/>
    </module>

    <!-- 让本模块不参与集团同步 -->
    <module name="myLocalConf" mess="my3pd.myLocalConf" mainTable="mylocalconf">
        <table name="mylocalconf" key="code" initRow="1"/>
        <param key="grouperpSyncSkipFm" value="true"/>
    </module>

# 2. 调整同步的字段范围

因为走的是[数据导出] / [数据导入],能同步的字段范围 = 这两个功能允许的范围。被标记 dataImport="false" 的表/列不会被同步。

如果第三方App 有必须与主服务器保持一致、但框架默认不允许导入的字段,用白名单强行拉进来:

    <module name="myBaseData" extend="true">
        <param key="grouperpSyncLenientTable" value="mybasedataext"/>
        <param key="grouperpSyncLenientField" value="mybasedataext.adminFlag;mybasedataext.apiKey"/>
    </module>
param 语法 说明
grouperpSyncLenientTable 表名;表名 整张表拉进同步范围
grouperpSyncLenientField 表名.列名;表名.列名 精确到列,不支持通配

安全提醒:白名单里的字段会随报文经 HTTP 发送到每一台子服务器,不要放明文密钥或明文密码。


# 3. 特殊处理:grouperpSyncHandler

当默认的导出/导入行为不够用时(需要额外传一个 lookup 的 code、按设定动态裁剪同步字段、落地前重映射外键),给模块指定一个自定义处理类:

    <module name="myBaseData" extend="true">
        <param key="grouperpSyncHandler" value="com.my3pd.erp.handler.MySyncHandler"/>
    </module>

系统启动时按模块把该类实例化并缓存,同步过程中用反射查找同名同签名的函数并调用,按需实现即可,不需要实现接口。与 Excel 相关的时点:

函数签名 执行侧 用途
void handlerExportExcel(DataExportConfig config, SqlEntity entity) 主 增删同步字段,或往 getParamMap() 塞值
void handlerImportExcel(DataImportConfig config, WorkbookMapping mapping, Long beId, String moduleName) 子 与上一条对称:裁剪导入字段、取回参数
void handlerImportExcelEntity(DataImportConfig config, SqlEntity entity, Long beId) 子 实体保存前的最后加工
void handlerSyncDto(SeSaveParam param, GrouperpSyncDto syncDto) 主 报文组装后补充自定义信息(Excel / Object 通用)
void updateSyncDto(GrouperpSyncDto syncDto) 子 落库前把 code 翻译成本机 id(Excel / Object 通用)

示例:模块 myBaseData 的主表上有外键 myTypeId,两边 id 不同,需要用 code 传递。

package com.my3pd.erp.handler;

public class MySyncHandler {

	/** 主服务器:把外键的 code 放进报文 */
	public void handlerExportExcel(DataExportConfig config, SqlEntity entity) {
		long myTypeId = entity.getMainData().getLong(1, "myTypeId");
		StLookupDto dto = StLookupLib.getDto("myType", myTypeId);
		if (dto != null) {
			config.getParamMap().put("myTypeCode", dto.getCode());
		}
	}

	/** 子服务器:把 code 翻译成本机 id */
	public void handlerImportExcelEntity(DataImportConfig config, SqlEntity entity, Long beId) {
		String code = ConvertLib.toString(config.getParamMap().get("myTypeCode"));
		if (!code.isEmpty()) {
			entity.getMainData().setValue(1, "myTypeId", GrouperpLookupLib.getIdByCode("myType", code));
		}
	}
}

类型陷阱:paramMap 是 Map<String, Object>,经 JSON 传输后类型会变(Java 对象变成 JSONObject、数字可能变成字符串)。放入与取出的代码必须成对编写,取值统一用 ConvertLib,不要直接强转。


# 4. 特殊处理:DataSwapHandler / SearchCodeHandler

grouperpSyncHandler 只作用于单个模块。以下 Handler 是跨模块的:

	// 在 serverBoot 中注册(com.multiable.core.share.handler.EntityUtil)
	EntityUtil.addDataSwap(new MyDataSwapHandler());     // implements DataSwapHandler
	EntityUtil.addSearchCode(new MySearchCodeHandler()); // implements SearchCodeHandler
接口 作用
DataSwapHandler 在导出/导入时增删虚拟列,做 id 与业务键之间的互换
SearchCodeHandler 接管「code → 本机 id」的反查逻辑


# 方式二:对象同步(配置模块)

适用:已实现 DataObjectHandler 的配置模块。系统默认会同步这类配置模块。

机制上等同于主服务器做一次[创建Objects]、子服务器做一次[安装Objects]。与方式一的关键差别在落地端:不走[数据导入],而是调用第三方App 自己的 ObjectHandler.install(...)。


# 1. 检查你的 ObjectHandler

检查第三方App 自己的 ObjectHandler,集团同步走一条「按 code 重映射外键」的路径:

DataObject param 说明
grouperpSyncFlag true 表示本次安装来自集团同步,而非用户手工安装
grouperpSyncCheck true 表示这是同步的预演阶段,不应产生副作用(写库、发通知、发邮件)
	// 在 ObjectHandler.install(...) 中分流
	boolean syncFlag  = ConvertLib.toBoolean(ErpDataObjectLib.getParamValue(dsDto, "grouperpSyncFlag"));
	boolean syncCheck = ConvertLib.toBoolean(ErpDataObjectLib.getParamValue(dsDto, "grouperpSyncCheck"));

	// 手工安装走原有逻辑;集团同步走「按 code 重映射外键」的逻辑
	boolean status = !syncFlag ? genFkTableRecord(entity) : grouperpSyncSave(entity, syncCheck);

grouperpSyncSave 的典型写法:用 code 在本机缓存表里查出本机 id,再把实体上的外键逐个换掉;syncCheck 为 true 时跳过一切写操作。


# 2. 特殊处理:grouperpSyncHandler

与方式一使用同一个 grouperpSyncHandler 参数,只是命中的是另外两个时点:

函数签名 执行侧 用途
void handlerExportObject(Long beId, String moduleName, DataObjectBaseDto dto) 主 往数据对象里追加参数
void handlerImportObject(Long beId, String moduleName, DataObjectBaseDto dto, List<DsInstallDto> insList, Object detail) 子 安装前重映射,或保留子服务器的本地值
void handlerSyncDto(SeSaveParam param, GrouperpSyncDto syncDto) 主 报文组装后补充自定义信息(Excel / Object 通用)
void updateSyncDto(GrouperpSyncDto syncDto) 子 落库前把 code 翻译成本机 id(Excel / Object 通用)


# 方式三:事件同步(Event)

适用:界面按钮触发的动作,以及不是标准 module 的配置(数据直接落在自己的表里、或需要在子服务器上调用某个 API)。

事件同步不传数据表,而是传一条「调用指令」:主服务器告诉子服务器「实例化哪个类、调用哪个函数、带什么载荷」,子服务器反射调用。第三方App 需要写两样东西:一个 Event 类 + 一个界面 Listener。


# 1. 编写 Event 类

一个类同时承担两侧的职责:static 函数在主服务器发起,实例函数在子服务器落地。

package com.my3pd.erp.share.event;

import java.util.List;

import com.multiable.core.share.lib.ConvertLib;
import com.multiable.core.share.message.CheckMsg;
import com.multiable.core.share.message.CheckMsgLib;
import com.multiable.erp.grouperp.share.entity.GrouperpEvent;
import com.multiable.erp.grouperp.share.lib.GrouperpEJBLib;
import com.multiable.logging.CawLog;

public class MySettingEvent {

	/** 主服务器:发起事件 */
	public static List<CheckMsg> syncMySetting(String settingValue) {
		GrouperpEvent event = new GrouperpEvent();
		event.setEjbClass(MySettingEvent.class.getName());   // 处理类全类名
		event.setEjbMethod("handlerMySetting");              // 处理函数名
		event.setData(settingValue);                         // 载荷
		event.setMenuCode("mySetting");                      // 日志显示用
		event.setActionMess("core.save");                    // 动作 messCode
		return GrouperpEJBLib.getCommonEJB().syncEvent(event);
	}

	/** 子服务器:处理事件 */
	public CheckMsg handlerMySetting(Object data) {
		CheckMsg msg = null;
		try {
			MySettingLib.save(ConvertLib.toString(data));
		} catch (Exception e) {
			CawLog.logException(e);
			msg = CheckMsgLib.createErrorMsg(e.toString());
		}
		return msg;
	}
}

GrouperpEvent字段

名称 类型 说明 必填
ejbClass String 子服务器上要实例化的类的全类名 Y
ejbMethod String 要调用的函数名 Y
data Object 载荷,会被 JSON 序列化后传输 Y
moduleName String 模块名,用于同步日志与告警文案 N
menuCode String 菜单代码,用于同步日志与告警文案 N
actionMess String 动作的 messCode,显示在同步日志的「动作」列 N
dto StLookupDto 指向具体记录(id + code),让同步日志能定位到记录 N
user UserDto 操作人,构造时自动取当前用户;子服务器按 usercode 映射本机 uid 自动

处理函数由反射调用,形态是固定的:

要求 说明
参数 Object,不能写成 JSONObject data 或 String data
返回值 必须是 CheckMsg(返回其它类型不报错,但错误无法回传给操作人)
位置 处理类必须在子服务器的 classpath 上,即放在 p-share 或 p-ejb,不能放 p-jsf

# 2. 编写界面 Listener

Listener 负责两件事:在子服务器上把界面锁成只读,在主服务器上点按钮时发起事件。

若要为 FRD 已有的界面追加同步行为,通过 cawweb.xml 注册 listener(做法见后台开发须知与ERP 前端扩展)。

public class MySettingListener extends ViewBean {

	MySettingBean sourceBean = null;

	@Override
	public void initialized() {
		super.initialized();
		sourceBean = (MySettingBean) GrouperpWebUtil.getCurrentBeanInstance("mySetting");

		// 子服务器上锁成只读
		if (GrouperpUtil.isSlaveServer() && !UserCc.isSuper()) {
			WebUtil.setDisabled(true, "save");
		}
	}

	@Override
	public void actionPerformed(ViewActionEvent vae) {
		super.actionPerformed(vae);
		if ("save".equals(vae.getActionCommand())) {
			if (!GrouperpUtil.isMasterServer()) {   // 只在主服务器发起同步
				return;
			}
			GrouperpWebUtil.postMessage(MySettingEvent.syncMySetting(sourceBean.getValue()));
		}
	}
}

# 3. 登记菜单

菜单需要登记,才能出现在[集团ERP模块设定]中,并在子服务器上被加锁:

    <menu code="mySetting" mess="my3pd.mySetting">
        <param key="grouperpSyncAddFm" value="true"/>
    </menu>


# 调试与排错

# 联调步骤

集团同步必须在两套系统上联调,无法在单机验证。

  1. 准备两套独立部署的 aiM18(各自独立数据库),部署同一版本的第三方App;
  2. 一台配 caw.grouperp.master=true,另一台配 caw.grouperp.slave=true,两台各配一个互不相同的 caw.grouperp.gerpKey;
  3. 在主服务器的[集团ERP服务器设定]中登记两台服务器,逐条点「验证」直到通过;
  4. 打开[集团ERP模块设定],确认第三方App 的模块/菜单已出现在清单里,且标注的方式(Excel / Object / Event)与预期一致——没出现就说明没参与同步;
  5. 在主服务器上做一次保存,到子服务器上核对数据;
  6. 失败看[集团ERP同步异常日志],成功看[集团ERP同步追踪日志],两张表的 eventData 列就是实际发出的报文。

# debug 模式

开启框架 debug 模式后,主/子服务器都会打印完整同步报文,且同步产生的临时文件不会被删除——可直接到 <jboss>/excel/、<jboss>/cawobj/(主服务器)和 <jboss>/sync/(子服务器)解包逐字段核对。这是验证「Handler 有没有生效」「某栏位到底传没传」最直接的手段。


# 常见问题

症状 先检查
完全不同步、无任何日志 caw.grouperp.master / slave 配置
某个模块从来不同步 是否声明了 grouperpSyncAddFm;param key 有没有拼错
某几个栏位不同步 该栏位 dataImport="false",需用 grouperpSyncLenientField 显式声明
自定义 Handler 不执行 ① 签名不匹配(尤其 beId 要用装箱的 Long);② 模块本身没参与同步
子服务器上外键挂错了记录 对象同步的 ObjectHandler.install 没有按 grouperpSyncFlag 分流,直接用了主服务器的 id
子服务器日志里是 Java 异常堆栈 主/子服务器的第三方App 版本不一致,或处理函数的参数/返回值不符合约定
报 Unmatched server code 请求携带的 gerpKey 与子服务器的 caw.grouperp.gerpKey 不一致
Last Updated: 2026/08/20, 10:51:19
ERP 装饰器(前端接口)
设置Eclipse开发环境

← ERP 装饰器(前端接口) 设置Eclipse开发环境→

Theme by Vdoing | Copyright © 1990-2026 Multiable | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式