尧图精选

NC65自定义参照按财务组织动态过滤的实现与踩坑记录

🕒 发布时间:2026/9/16 1:13:39 📁 来源:尧图网络
如果你们公司的NC65项目里碰到了这种需求自己做一个自定义参照然后这个参照里能选的数据还得跟着当前单据上选的财务组织走——组织变了参照里的数据也得跟着变。这个需求听起来不算复杂但真正落地的时候会踩到不少平台细节上的坑。我前阵子刚在一个财务共享项目里完整走了一遍这个流程今天把整个思路、实现路径和踩坑记录都整理出来。先说清楚背景。NC65的参照Reference本质上就是一个带查询和回填能力的弹窗数据源界面上表现为一个输入框加放大镜按钮。平台默认给很多基础档案比如客商、人员、部门都配好了参照但项目上经常会遇到需要自定义参照的场景。比如我们这次要做的是一个“费用承担中心”的参照数据来源是我们自己导入的一张自定义表而且要求当单据表头的财务组织从A公司切换到B公司的时候这个参照里只能出现B公司名下的费用承担中心A公司的数据不能漏出来。这个“按财务组织过滤”才是真正的难点。1. 需求场景与方案选型1.1 这个需求本质上是在解决什么问题先把这个需求拆开看。它其实包含了三个递进的要求第一存在一张非平台原生的业务表需要做成参照供单据选择第二参照查询时不能一次性返回全部数据必须根据当前操作上下文缩小范围第三这个缩小的条件不是固定的而是由用户在单据上选择的财务组织动态决定的。第三个要求最关键因为它意味着过滤条件不能在配置阶段写死必须在运行时从表单界面取到“当前选中的组织主键”然后传递到参照查询的服务端。如果只是做一个不带过滤的自定义参照那在NC65里属于非常基础的操作一旦牵涉到动态过滤就得同时管好前端事件、参照模型、查询模板三个环节。这类需求在财务类项目中极其常见因为财务组织本身就是一个天然的数据隔离维度。像费用报销单选成本中心、采购订单选收料部门、资产卡片选使用组织基本都是同一个套路。掌握了这个思路相当于掌握了一种通用的“参照级数据权限”写法。1.2 两条技术路线的取舍NC65里做自定义参照并实现动态过滤主流有两条路。第一条路是“纯配置 二次开发组合拳”先用基础数据节点把自定义档案建好然后在元数据平台生成对应的自定义参照再通过扩展开发对参照查询的SQL追加过滤条件。优点是档案、参照、权限体系都能复用平台能力后期维护成本低缺点是配置步骤多而且受平台元数据生成规则限制如果要过滤的字段不在标准字段里就得多绕一步。第二条路是“纯代码自定义参照”自己写一个继承AbstractRefModel的Java类重写getRefQuerySql和getRefCountQuerySql直接返回带组织过滤条件的SQL。优点是完全放得开想怎么过滤就怎么过滤字段来源、关联表、排序规则全部可控缺点是要自己处理分页、模糊匹配、权限拼接等一系列平台原本已经做好的事情。我这次选的是第一条路但也不是完全按平台默认来而是把过滤逻辑写在了参照模型层。原因有两个一是我们的数据来源本身就是一张自定义业务表没有现成的自定义档案可用用纯配置方案还得先做数据同步多一层麻烦二是需求里明确提到后续还要根据费用类型再做二级过滤代码方案扩展起来更灵活。实际上最后落地的时候我是在平台提供的可配置参照基础上用Java代码覆盖了查询SQL属于两条路的结合。2. 核心机制拆解NC65参照与组织过滤原理2.1 参照从点选到回填的完整链路要把过滤做对先得搞清楚NC65里一个参照从用户点击到数据回填中间经过了哪些环节。整个链路可以简化成四步。第一步界面渲染。单据模板上的字段如果配置了refcode前端组件会在初始化时根据这个refcode去查找对应的参照模型RefModel并准备好弹窗的查询UI和数据源。第二步弹窗查询。用户点击放大镜后前端发起一个查询请求这个请求会带上当前的查询条件、分页信息以及一组额外的上下文参数一般放在paramMap里。后端收到请求后RefModel会根据这些参数拼SQL查询数据库返回JSON数据。第三步选择回填。用户在弹窗结果里选中一行确认后系统会把该行对应字段的值映射回单据字段。比如参照显示的是“成本中心编码”实际回填的可能还有一个主键。第四步联动刷新。如果有其他字段监听了这个字段的变化会触发对应的afterEvent事件去做后续处理——比如带出名称、带出上级组织、清空下游字段。对于“按财务组织过滤”来说最关键的环节是第二步我们必须在RefModel生成查询SQL的时候把“当前选中的财务组织主键”作为条件拼进去。而这个主键需要在第四步的联动机制里想办法传给RefModel。2.2 过滤条件应该加在哪一层很多刚接触NC65二次开发的人容易上来就去改单据模板的前端事件企图在界面上直接拦截参照的数据源。比如在组织字段的afterEvent里把参照组件的sql直接set进去。这个做法在简单场景下确实能跑通但问题也很明显参照弹窗组件在每次打开时都会重新初始化如果你只是改变了refModel的某个属性下次打开时极有可能被平台重新置空导致过滤“时灵时不灵”。我推荐把过滤条件加在RefModel的SQL生成层。具体来说就是自定义一个参照模型类继承平台已有的可配置参照模型然后重写getRefQuerySql/getRefCountQuerySql这两个方法。在生成SQL时从上下文的参数Map里取出财务组织主键拼到WHERE条件里。这样做的好处有三个过滤对前端完全透明不会出现界面状态被重置导致过滤失效的问题后端强制过滤即使有人绕过界面直接调接口也拿不到其他组织的数据。这里的核心逻辑是界面上某个字段变化了并不直接去操作SQL而是把变化的值作为参数存到参照模型的上下文里参照模型每次生成SQL时都检查一下这个参数存不存在存在就过滤。也就是说界面只负责“传参”数据隔离交给“SQL注入”。3. 实操从建档案到实现按组织过滤3.1 准备环节自定义表与数据初始化我们这次的需求数据源是一张平台外的业务表用来存放费用承担中心的信息。表结构大致是主键、编码、名称、所属财务组织org_pk、生效状态、创建时间。在正式做参照之前我先把这张表在NC65的数据库里建好并在表里灌了一部分测试数据涵盖了两个不同财务组织的记录这样后面验证过滤效果的时候才能看出来差异。有一点要特别提醒如果你们的数据源不是平台标准档案而是自己建的表建议表结构里加上组织字段并且这个字段最好直接使用NC65的组织主键org_pk。不要用组织编码或者组织名称来做关联因为NC65内部很多地方只认主键编码和名称都可能重复。我们当时就因为这个吃了亏第一版用的组织编码结果集团下有公司编码不规范过滤出来的数据串了。后来全部改成主键关联才解决。3.2 创建自定义参照的两种具体方式在NC65里建自定义参照我最常用的有两种方式先按操作路径列一下。方式一如果你用的是“基础数据-自定义档案”节点先建一个档案分类和档案类型然后在档案类型下面新增档案项。建完档案项之后直接使用“自定义参照”按钮生成参照平台会自动在元数据平台生成一个对应的refmodel配置。方式二如果你像我们这样数据不在标准档案里可以直接在元数据管理节点下手动创建一个“高级参照”。具体路径一般是客户化-元数据管理-查询模板管理先增一个查询模板然后在“客户化-模板管理-参照模板”里把这个查询模板包装成参照模板最后在单据模板的字段属性上配置refcode指向这个参照模板。我这次用的是方式二具体来说是在查询模板里以自定义表为数据源查询字段就是编码、名称、组织主键这几个然后把它包装成参照。这里有一个细节参照模板的SQL并不是一个静态SQL它是存在平台配置表里的可以被Java代码覆盖。这就给我们后面做动态过滤留了口子。3.3 界面事件接住组织变化并传参参照建好之后先在单据模板上把字段挂上refcode到这一步不动任何代码参照是能用的。但如果现在打开单据参照会返回所有组织的数据。所以接下来要做的是把财务组织字段的变化事件接住。NC65的单据开发框架里监听字段变化事件有好几种写法。如果你用的是UAP2.0框架也就是nc.ui.pubapp.uif2app下的BillForm最直接的方式是给表头字段加上ItemListenerpublic class FeeCenterRefFilterListener implements ItemListener { private BillForm billForm; public FeeCenterRefFilterListener(BillForm billForm) { this.billForm billForm; } Override public void itemStateChanged(ItemEvent e) { if (e.getStateChange() ItemEvent.SELECTED) { String orgPk (String) billForm.getBillCardPanel() .getHeadItem(pk_org).getValueObject(); // 将组织主键写入参照模型上下文 RefModel refModel billForm.getBillCardPanel() .getRefModel(pk_feecenter); if (refModel ! null) { refModel.getParamsMap().put(pk_org, orgPk); } } } }这段代码的关键动作就两个从表头取出当前财务组织主键把它put到参照模型的paramsMap里。为什么放在paramsMap而不是直接修改refModel的sql因为paramsMap在每次参照打开时都会被平台读取一遍稳定性最好直接改sql的话下次初始化时大概率被重置。然后在单据初始化的时候注册这个监听器billForm.getBillCardPanel().getHeadItem(pk_org) .addItemListener(new FeeCenterRefFilterListener(billForm));这里有两个坑。第一个坑是getHeadItem返回的组件类型不一定是nc.ui.ufo.controls.RichComboControl有的项目里组织字段用的是自定义控件事件写法略有区别。第二个坑是监听器注册的时机一定要在界面初始化完成后注册否则字段还没创建好直接空指针。建议放在afterShow或者默认值设置完成之后。3.4 参照模型注入过滤条件前端把组织主键放进paramsMap之后接下来就要让参照模型在拼SQL时把这个参数用起来。我这次是写了一个自定义参照模型类继承平台的可配置参照模型基类然后重写SQL生成方法。大致骨架如下public class FeeCenterRefModel extends AbstractRefModel { Override public String getRefQuerySql() { String baseSql super.getRefQuerySql(); String orgPk (String) getParamsMap().get(pk_org); if (StringUtils.isBlank(orgPk)) { // 如果没有传组织直接返回空集防止越权 return select * from (select 1 as pk, as code, as name from dual) t where 10; } // 把组织过滤条件拼到baseSql上 if (baseSql.toLowerCase().contains(where)) { return baseSql and org_pk orgPk ; } return baseSql where org_pk orgPk ; } Override public String getRefCountQuerySql() { String baseCountSql super.getRefCountQuerySql(); String orgPk (String) getParamsMap().get(pk_org); if (StringUtils.isBlank(orgPk)) { return select count(1) from (select 1 from dual) t where 10; } if (baseCountSql.toLowerCase().contains(where)) { return baseCountSql and org_pk orgPk ; } return baseCountSql where org_pk orgPk ; } }然后在参照配置里把这个模型类注册进去。NC65里注册自定义参照模型可以在参照模板配置界面指定一个Java类名也可以在初始化的时候通过代码动态替换。我用的是前者配置文件里直接把refmodel类指到FeeCenterRefModel。需要注意的是getParamsMap()这个方法是平台提供的返回的就是前端传过来的一组参数。前端put进去的key这里是“pk_org”要和这里get的key保持一致。如果有多个过滤条件可以多put几个key多拼几个条件。另外有一点务必注意你自己拼SQL时防SQL注入这根弦不能松。组织主键虽然一般是从参照或树形选择里来的不会拼什么恶意字符串但后台接口如果有人直接模拟请求传一个带单引号的pk_org进来就存在被注入的可能性。NC65平台虽然在很多公共入口做了参数校验但这种自定义拼SQL的代码安全责任完全在自己身上。建议至少做一层过滤用正则校验主键格式只允许字母、数字、下划线和中划线或者干脆用参数绑定而不是字符串拼接。4. 常见问题与排查实录4.1 高频问题速查表整个开发过程中我们团队前后遇到了不少问题下面把最典型的几个列出来如果你也在做类似功能可以直接对着排查。现象可能原因解决办法参照弹窗不刷新切组织后还是显示全部数据监听器没触发或paramsMap没有写入成功在监听器里打断点确认pk_org是否真的取到了值确认注册监听器的时机参照能过滤但分页的总数不对只重写了getRefQuerySql没重写getRefCountQuerySql两个方法都要重写分页总数走的是countSql第一次打开参照正常第二次打开过滤失效在界面上直接修改了参照的sql属性被平台重置改成用paramsMap传参而不是直接setSql过滤条件拼上了但报“ORA-00933: SQL命令未正确结束”baseSql里已经带了where且末尾可能有分号或换行打印最终SQL人工检查拼接位置拼条件前先把末尾分号去掉参照显示正常但回填时字段值错位查询模板里列的别名和回填字段不一致检查查询模板中每个字段的key确保与单据模板字段的关联正确这里我想详细说下第二个问题也就是分页相关。NC65的参照查询默认是分页的平台在执行分页查询前会先执行count查询拿到总记录数再根据当前页取数据。如果两条SQL的过滤条件不一致就会出现很诡异的情况第一页有数据但翻到第二页或者输入关键字搜索时结果跟预期对不上。因为列表右下角的“共多少条”是根据countSql算出来的而当前页数据走的是getRefQuerySql。刚开始我只重写了getRefQuerySql结果界面显示总条数一直不变化排查了半天才发现是countSql没同步加条件。4.2 大数据量与性能优化的实操建议当我们把组织过滤做上去之后测试环境数据量不大一切都很流畅。结果切到预生产环境有一张自定义表里面存了五十多万条费用承担中心数据问题立刻暴露出来参照弹窗打开要三到五秒输入关键字搜索甚至要八秒以上才出结果。后来定位下来主要有三个原因。第一个原因是查询SQL没有走索引。我们表里的org_pk虽然是逻辑外键但在数据库里没有建立索引。加上组织过滤后每次查询都要全表扫一遍再过滤。这个解决起来最简单给org_pk建一个普通索引就能明显改善。第二个原因是NC65参照默认的逻辑是把所有数据都查回来再做模糊匹配数据量一大就非常慢。优化方向是重写参照模型里的关键字查询逻辑利用数据库层面的LIKE或者INSTR来做裁剪而不是把全量数据拉到内存里过滤。具体来说在getRefQuerySql里判断一下paramsMap里有没有查询关键字如果有就在SQL层追加and (code like %关键字% or name like %关键字%)。这样网络传输量和内存开销都会大大降低。第三个原因是平台自带的查询接口在数据量大时会有默认的查询上限不同版本略有差异可能你的参照一共有几十万条数据但只返回了前几百条。解决方式是重写查询方法显式处理分页参数确保用户翻页时能获取到后续数据。这次我们就是因为没有注意这个限制一度以为组织过滤“把数据过滤没了”后来才发现是页大小限制导致的假象。4.3 组织值变化后旧数据缓存的处理这个坑很隐蔽但实际项目里几乎一定会遇到。用户先在界面上选择了A组织参照里选了A组织的费用承担中心返回后界面上显示了一条A组织的数据。这时候用户把财务组织切换到B组织参照弹窗确实只显示B组织的数据了但单据表头上原来那行A组织的数据没有自动清空。严格来说这不算参照本身的问题而是一种数据联动遗漏看起来就像旧数据“残留”了。处理方式是在财务组织的变化事件里加一步判断组织变了之后主动清理或者校验下游字段。如果旧值不属于新的过滤范围就置空如果属于则保留。因为我们这次的数据字典比较简单直接选择了组织一变化就把费用承担中心字段清空然后在用户重新选择后再带出。从用户体验看这个处理方式最直接也最不容易产生脏数据。4.4 用户权限与数据口径一致性的提醒最后还想多说一句。自定义参照里做的组织过滤本质上是一种“界面级数据隔离”。但如果用户自己写报表、走其他查询接口去访问这张自定义表还是能看到全量数据的。NC65平台本身有数据权限体系比如“业务单元隔离”这种授权策略但那是平台标准能力跟我们代码里自己拼的过滤条件互不相通。我们这次项目上线前专门做了个内部评审确认了这条边界代码过滤负责“单据操作场景下的数据选择正确性”如果业务上还要求“这个角色的人只能看自己组织的数据”那必须再去配置标准数据权限两件事不能互相替代。项目里有同事一开始以为参照过滤做了就一劳永逸后来发现报表平台还是能查全量差点上线前翻车。这一点写出来也算给后面做类似需求的朋友提个醒。5. 这个功能还可以怎么扩展顺着“自定义参照 动态过滤”这个思路其实还能延伸出不少实用的变体简单说两个我试过的方向。第一个方向是级联过滤。也就是参照的过滤条件不只有一个组织而是根据界面上的多个字段共同决定。比如你先选公司再选部门参照只能出现该公司该部门下的数据。实现方式和我们前面讲的一模一样无非是在前端事件里多put两个key在参照模型里多拼两个条件。但要注意如果参与过滤的字段本身就存在依赖关系比如部门发生了变化而公司没变甚至部门变了之后公司也要跟着调整那还需要额外处理联动逻辑复杂度会往前走一步。第二个方向是过滤条件反查。比如界面上财务组织没有直接暴露主键而是一个编码字段用户输入的是编码而不是主键。这时候前端取到的值就要先做一层转换去组织档案表里查一次主键再把主键传给参照模型。或者干脆在参照模型的SQL里直接子查询“通过编码找主键再过滤”少走一次前端逻辑。这两个思路本质相同根据你的代码习惯选一种即可。还有一点是关于版本兼容的。NC65在项目现场有各种小版本不同版本对自定义参照的封装和配置入口会有些差异。我这边写的类名和方法名是基于比较常见的NC65小版本如果你们现场的版本里找不到同样的基类可以先查一下平台自带的参照模型代码看当前版本里面提供的是哪些扩展点。大方向不变细节以现场版本为准。就我个人这段调试经验来说NC65这种老牌企业级平台最大的特点就是“配置能覆盖80%的场景但剩下20%的个性化需求得靠读懂它内部的扩展机制”。自定义参照这个东西平台给你铺好了路但动态过滤这种需求还是得自己动手往SQL层去深挖一层。只要把前端事件传参、参照模型SQL注入、分页一致性这三个关键点打通了后面再做类似的功能就非常顺手了。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →