开发知识总结原创
金蝶云社区-sulivifer
sulivifer
8人赞赏了该文章 1,825次浏览 未经作者许可,禁止转载编辑于2022年12月16日 16:11:07
summary-icon摘要由AI智能服务提供

本文介绍了多个系统事件及其触发时机,包括在单据操作、数据校验、事务处理等不同阶段的事件,如`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"}


赞 8