本文介绍了多个系统事件及其触发时机,包括在单据操作、数据校验、事务处理等不同阶段的事件,如`onPreparePropertys`、`onAddValidators`、`beforeExecuteOperationTransaction`等,并详细说明了插件在这些事件中可以执行的操作。此外,还讨论了系统架构中的后台任务调度与执行分离机制、MQ的使用、服务注册与调用方式,以及插件命名规范、数据状态管理、报表排序、接口白名单等内容。同时,还提供了调试建议、SQL查询示例和单点登录权限验证的解决方案。
onPreparePropertys 事件
事件触发时机
操作执行,加载单据数据包之前,触发此事件;
在单据列表上执行单据操作,传入的是单据内码;
操作引擎需要先根据传入的单据内码,加载单据数据包,其中只包含操作要用到的字段,然后再执行操作;在加载单据数据包之前,触发此事件;
在单据维护界面,执行单据操作时,传入的是单据数据包,不需要操作引擎自行加载单据,不会触发此事件;
插件需要在此事件,添加需要用到的字段;否则,操作引擎加载出的单据数据包,可能没有插件要用到的字段值,从而引发中断
onAddValidators 事件
事件触发时机
构建好操作校验器之后,执行操作校验之前,触发此事件;
插件可以在此事件,增加自定义操作校验器,或者去掉内置的校验器。
beforeExecuteOperationTransaction 事件
事件触发时机
操作校验通过之后,开启事务存储数据之前,触发此事件;
插件可以在此事件,对已经通过校验的数据,进行整理,或者取消操作的执行。
这个事件触发时,还没有启动事务保护,请勿在此修改数据库数据。
beginOperationTransaction 事件
事件触发时机
操作校验通过,开启了事务之后,还没有把数据提交到数据库之前触发此事件;
可以在此事件,进行数据同步处理。
这个事件触发时,系统还没有调用 ORM 引擎存储单据:
此时访问数据库,拿到的单据数据是旧的;
单据数据包中的脏标志也没有被复位;
可能会执行失败的同步处理,如和第三方系统的对接,放在这个事件比较合适。
endOperationTransaction 事件
事件触发时机
单据数据已经提交到数据库之后,事务未提交之前,触发此事件;
可以在此事件,进行数据同步处理;
这个事件触发,系统已经调用 ORM 引擎存储了单据,数据已经入库,单据数据包中的脏标志已经复位。
可能执行失败的同步处理,不适合放在这个事件执行,事务回滚比较麻烦。
特别说明:
系统会根据脏标志,判断出数据是否来自数据库,有没有更改:脏标志被复位后,调用 ORM 数据存储引擎,存储数据包,会被 ORM 引擎直接略过,不再更新入库。
rollbackOperation 事件
事件触发时机
操作事务提交失败,事务回滚之后触发此事件;
该方法在事务异常后执行,插件可以在此事件,对没有事务保护的数据更新进行补偿。
kd.bos.mservice.form.FormServiceImpl#batchInvokeAction
kd.bos.mvc.form.FormView#invokeOperation(java.lang.String)
kd.bos.form.operate.FormOperate#execute
kd.bos.entity.operate.DefaultEntityOperate#callEntityOperate
kd.bos.entity.operate.Submit#callBillOperationService
kd.bos.service.operation.OperationServiceImpl#localInvokeOperation(java.lang.String, kd.bos.dataentity.entity.DynamicObject[], kd.bos.dataentity.OperateOption)
kd.bos.service.operation.EntityOperateService#doExcete
kd.bos.entity.plugin.OperationServicePlugInProxy#fireAfterExecuteOperationTransaction
afterExecuteOperationTransaction 事件
事件触发时机
操作执行完毕,事务提交之后,触发此事件;
插件可以在此事件,对操作结果进行整理,或者执行其他无需事务保护的逻辑。
这个事件触发时,事务已经完成并提交,没有了事务保护,请勿在此事件更新数据库。
FormAction batchInvokeAction -----> result = (String)DispatchServiceHelper.invokeBOSServiceByAppId(appId, "FormService", "batchInvokeAction", new Object[]{pageId, params});
FormController batchInvokeAction
FormServiceImpl invokeAction
Control ctl = formView.getControl(key);
#这个里面又缓存得Control,然后就得到操作得类了
kd.bos.form.AbstractFormView#cacheControls
FormView.initiService()
\BillController loadData() createModelData() AbstractFormDataModel ModelEventProxy.fireCreateNewData
这里会把页面配置的插件都pluginProxy.registerPlugins
FormView.initiService(){initiPluginProxy()}
FormViewPluginProxy这个里面会把插件fire
fireAfterBindData
fireBeforeBindData一般都是在这个类FormViewPluginProxy触发插件的方法
updateView中包含 fireBeforeBindData fireAfterBindData
BillController loadData BillView updateView this.getPluginProxy().fireAfterBindData(
操作服务bos-metadata OperationType_bos.xml
调用registerListener
FormController registerListener FormViewPluginProxy fireRegisterListener
调用createNewData
BillController loadData model.createNewData() AbstractFormDataModel createNewData ModelEventProxy fireAfterCreateNewData
DispatchServiceHelper.invokeService("hihn.bd.sbd.servicehelper", "sbd", "IReceiveAddressService", "getWarehouseByAddressId", paramMap);
kd.bos.servicehelper.DispatchServiceHelper#serviceLookup()
Map<String, Object> result = DispatchServiceHelper.invokeBizService("isc", "iscb", "ISCDataCopyService", "execute", objs);
String factory = String.format("kd.%s.%s.servicehelper.ServiceFactory", cloudId, appId);
获取对应得ServiceFactory,之后把
serviceObject = factory.getMethod("getService", String.class).invoke((Object)null, serviceName);
this.serviceObjectMap.put(serviceKey, serviceObject);
kd.bos.service.lookup.ServiceLookup#lookup(DispatchService.class, serviceAppId)
kd.bos.mservice.spi.rpc.MServiceLookup#lookup
kd.bos.dubbo.DubboServiceLookup#lookupImpl(DispatchService.class, serviceAppId)
kd.bos.service.DispatchServiceImpl#invoke
kd.bos.mservice.service.MServiceStartService#start
->
kd.bos.mservice.service.MServiceStartService#registerDefault
ServiceRegister.registerConsumer(key, text);
->
kd.bos.mservice.rpc.dubbo.DubboServiceRegister#registerConsumer
->
kd.bos.mservice.rpc.dubbo.DubboBeanManager#registerConfig
->
kd.bos.mservice.rpc.dubbo.DubboBeanManager#initReferenceConfig
kd.bos.servicehelper.DispatchServiceHelper.invokeBizService(String, String, String, String, Object...)
--> kd.bos.servicehelper.DispatchServiceHelper.serviceLookup(String)
--> kd.bos.service.lookup.ServiceLookup.lookup(Class<T>, String)
MServiceLookup --> DubboServiceLookup 通过配置实现微服务的注册方式,目前使用的dubbo方式
--> kd.bos.mservice.rpc.dubbo.DubboServiceLookup.lookup(Class<T>, String)
--> kd.bos.mservice.rpc.dubbo.DubboServiceLookup.doLookupImpl(Class<T>, String)
--> kd.bos.mservice.rpc.dubbo.ProxyFactory.createProxy(Class<T>, String) 这里创建了一个代理
--> kd.bos.mservice.rpc.dubbo.ProxyFactory.Handler.invoke(Object, Method, Object[]) 调用的时候获取远程的bean,bean = config.get()
--> com.alibaba.dubbo.config.ReferenceConfig.get()
--> com.alibaba.dubbo.config.ReferenceConfig.init()
com.alibaba.dubbo.rpc.proxy.InvokerInvocationHandler#invoke
Object result = method.invoke(bean, args);
bean 为代理对象InvokerInvocationHandler
RpcInvocation [methodName=invoke, parameterTypes=[class java.lang.String, class java.lang.String, class java.lang.String, class [Ljava.lang.Object;], arguments=[kd.bos.service.ServiceFactory, FormService, batchInvokeAction, [Ljava.lang.Object;@33c4dc5c], attachments={}, invocationContext=null]
kd.bos.form.field.TextEdit#fireClick 点击事件
kd.bos.form.control.AbstractGrid#addHyperClickListener 超链接
kd.bos.form.field.BasedataEdit#addBeforeF7SelectListener F7
一、Scheduler Server 后台任务产生器 根据配置的调度信息 生成job发送到MQ Executor 执行器监听队列 执行JOB返回到Server Zookeeper执行监听和注册
1、Schedule Server管理多个Executor Server
2、Executor Server通过Zookeeper注册到Schedule Server,实现服务资源管理
3、MQ主要用于Job消息的推送,属于轻量级应用。极端情况,可以用Redis的pub/sub机制实现
二、为什么要基于MQ
调度与执行分离,可以灵活配置多个执行端
Server端与业务解耦,executor端与业务捆绑
三、Schedulable 接口
可调度的任务需要实现该接口
execute(context ctx) 入口
status() 当前状态
progress() 当前进度
stop() 取消任务
kd.bos.framework.lifecycle.LifecycleManager#start
kd.bos.framework.lifecycle.Service#start
kd.bos.mq.init.MQService#start
sysServices = {Service[16]@5756}
0 = {ConfigurationService@5758}
1 = {ClusterAppidsService@5759}
2 = {StorageReporterService@5760}
3 = {MServiceStartService@5761}
4 = {HAService@5762}
5 = {DtsInitService@5763}
6 = {MonitorServerService@5764}
7 = {EyeServerService@5765}
8 = {ShardingInitService@5766}
9 = {AppStarterService@5767}
10 = {MQService@5768}
11 = {StartupPKTempTableClearService@5769}
12 = {PreHeatService@5770}
13 = {DtsService@5771}
14 = {SessionManagerService@5772}
15 = {MsgJetSubscribeService@5773}
kd.bos.mq.init.MQInit#init
五、存储
需要实现分表机制,将历史数据与活动数据隔离
Executor Server端,需要存储异常信息,防止消息与业务数据事务不一致的问题
插件类命名:
表单插件:{插件名}Plugin 如:MainPagePlugin
单据插件:{插件名}Edit 如:PurchaserEdit
列表插件:{插件名}List 如:PurchaserList
操作插件:{插件名}Op 如:PurchaserOp
报表插件:{插件名}Rpt 如:PurchaserRpt
启动时候你打个断点在UsageConfig.java 里面loadmqconfigfiles这个方法看看有没有加载你的配置文件,然后检查一下你的配置路径有没有问题
\\172.17.6.170\feature_share\devhwcbg\share\biz-build\biz-dev-env\bat
采购收货单、采购订单----》采购入库单
销售订单------》销售出库单
集成服务云 主要有4种方式 https restful的方式、webservice、MQ、ESB
OPENAPI
操作服务和自定义服务、AI服务
注册第三方应用 根据新增的第三方应用获取调用方法获取apptoken,获取之后就去登录,登录需要获取accestoken
调用服务需要accestoken
单据保存时,插件怎么识别单据体分录某一行是否发生变化,然后更新该行的修改时间
数据包DynamicObject下有个状态对象,会记录数据包的各种状态,其中有改动的字段清单
1. 取到行数据包row (DynamicObject)
2. 调用row.getDataEntityState().getDirtyFlags() 获取改动的字段索引
接口服务器
正常是不允许在二开中搞和平台一样的全类名
在多个jar包中有全限定类名相同的类的情况下,jar文件排序靠前的会被优先加载到
报表漏斗排序
setSortAndFilter
执行费用集成失败的 重试之后需要试用 发票
集成 数据更新和集成同步都要修改
分录序号 ${seq}
8b4t_white 接口白名单
苍穹单据集成eas将启用配置的形式, 目前差旅报销单已启用次方式, 一周观察期, 后续其他单据也将启用, 另外单据集成如果要换用服务流的话, 只需要在单据集成配置上修改一下单据集成方案的编码就可以了
protected void filterContainerInit(FilterContainerInitEvent contInitEvent, ReportQueryParam queryParam) {
// 进入列表时,默认设置公司为当前用户主职位公司
String isInitfilter = this.getPageCache().get("isInitfilter");
if (StringUtils.isBlank(isInitfilter) || isInitfilter.equals("true")) {
String mainCompanyId = getMainCompanyId();
if (StringUtils.isNotBlank(mainCompanyId)) {
filterColumn.setDefaultValue(String.valueOf(mainCompanyId));
}else {
filterColumn.setDefaultValue("");
}
this.getPageCache().put("isInitfilter", "false");
}
}
【调试断点】
AbstractFormDataModel setValue可以看到是哪里调用
kd.bos.entity.datamodel.AbstractFormDataModel.setValue(String, Object, int, int)
kd.bos.form.AbstractFormView.setVisible(Boolean, String...)
kd.bos.form.field.FieldEdit#setMustInputClient
this.view.updateControlMetadata(this.getKey(), prop);
8b4t_zhservicelog 接口日志
bos_adminorg 组织
工作流
select * from t_wf_jobrecord where fbusinesskey = '1159379237128402944' and fhandlertype not in ('async-todo','async-taskRuleAnalysis');
select * from t_wf_hitaskinst where fprocinstid = 1159379249132495872;
select * from t_wf_execution where fbusinesskey = '1159379237128402944';
select * from t_wf_task where fprocinstid = 1159379249132495872 and fexecutiontype='byHand' and fbusinesskey='1159379237128402944';
select * from t_wf_timerjob where fbusinesskey = '1159379237128402944';
select * from t_wf_deadletterjob where fbusinesskey = '1159379237128402944';
后面大家如果有碰到通过url单点登录进苍穹, 出现拿费用核算应用验权提示无权限的时候, 可以在url后面配置个这个玩意儿来指定验权的应用
&customParams={"checkRightAppId":"8b4t_cmhk_zh_exp"}
示例: https://ip:port/index.html?formId=formId&customParams={"checkRightAppId":"8b4t_cmhk_zh_exp"}