☰
NC6自定义按钮开发全流程:模板、Action、权限与补丁
2026/10/27 17:10:10 网站建设 项目流程

标准产品把一个业务场景做到八成,剩下两成一定落在现场——这话放在 NC6 这类企业管理平台的交付里尤其准。项目做到收尾阶段,业务方提的往往不是什么大改造,而是"我就要在这个单据上多一个按钮,点一下把数据推过去"这种看似很小的需求。自定义按钮就是这么一类活:它不动平台底座,只在既有的功能节点上多挂一个交互入口,把校验、批量处理、外部对接、打印导出这些动作收敛成一次点击。

我这些年做过不少 NC6 的客户化扩展,自定义按钮属于出现频率最高、也最容易被低估的一类工作。说它频率高,是因为几乎每个模块都会碰到;说它容易被低估,是因为很多人以为在模板上加个按钮、写个 Action 就完事了,结果上线后按钮不显示、点了没反应、换了组织就置灰、打个标准补丁就被覆盖回去,问题一个接一个。这篇文章想讲的就是把这些坑提前填平:从需求判断、路径选型,到模板配置、Action 实现、权限注册、补丁发布,再到上线后的排查手段,我会按真实交付的顺序把它捋一遍。文中提到的 NC6 只是一个代称,泛指这一类基于 Java 富客户端 + 元数据模板的企业管理平台,思路对其他同类平台同样通用。适合谁看?做过一点 NC6 实施、被客户化需求追着跑的顾问,以及刚接手二次开发、还没搞清楚"模板层和代码层怎么对上"的开发同学。

1. 先把自定义按钮这件事拆到底层去

很多需求讨论会陷入一个误区:业务方说"加个按钮",开发者脑子里的第一反应是"写个 JButton 塞进去"。真正动手之前,必须先把 NC6 里一个按钮从"能看见"到"能干活"的完整链路拆开,否则你会在错误的位置上做正确的事。我自己总结下来,一个按钮在平台上至少要穿过三层:模板层负责它长什么样、显示在哪里;注册层负责它叫什么、归哪个功能管、受什么权限控制;代码层负责它被点击之后跑哪段逻辑。三层里任何一层断掉,症状都是"按钮不好使",但病因完全不同。

先看模板层。NC6 的单据界面不是画出来的,是"渲染"出来的——后台有一份单据模板(也叫界面元数据),里面定义了字段、区域、按钮、事件这些元素,客户端启动时拉取模板,按模板生成 Swing 组件。所以你在界面上看到的每一个按钮,背后一定有一条模板记录。这意味着加按钮的第一站永远是模板,而不是代码。这也解释了一个很常见的困惑:为什么有人写好了 Action 类、也编译进去了,界面上却什么都没有?因为他只做了代码层,模板层根本没这条记录,客户端压根不知道要渲染这个按钮。

再看注册层。模板只是"长什么样",功能节点才决定"谁能用"。NC6 的功能节点管理里,每个节点下都可以注册按钮权限项,按钮的编码就是权限项的标识。如果没有注册这一步,按钮虽然显示出来了,但在权限体系里是"黑户"——授不了权,也就意味着你没办法按角色控制谁能看见谁不能看见。很多现场的做法是干脆跳过权限,让所有人都能点,短期省事,长期是隐患:涉及批量改写、对外推送这种有副作用的操作,不做权限隔离,出问题追责都追不到人。

最后是代码层。NC6 的客户端是 Java 富客户端,按钮最终绑定的是一个 Action 类,这个类需要实现平台约定的接口(不同 UAP 版本略有差异,常见的是实现java.awt.event.ActionListener,或者继承平台提供的NCAction基类)。平台在渲染按钮时,会按模板里配置的 Action 类名去做类加载和实例化,然后把按钮的点击事件委托给它。所以类名、包名一旦写错,界面上通常表现为"按钮在,但点下去弹一堆堆栈"或者干脆静默无响应。

1.1 三条落地路径的横向对比

同样是加一个按钮,NC6 里至少有三种做法,我按"改动量从小到大"排一下,并且说说各自适合什么场景。

路径做法概要改动位置适合场景主要风险
纯配置路径模板加按钮 + 绑定已有标准 Action 或事件元数据调用现成能力、跳转、刷新、简单校验能力边界受限于标准动作
配置 + 自定义 Action模板加按钮 + 自写 Action 类元数据 + 客户端代码绝大多数客户化需求需处理部署与升级覆盖
纯代码注入在面板初始化后动态 add 组件客户端代码临时验证、极端场景可维护性差,不建议上线

纯配置路径是我最先考虑的。NC6 的模板里有大量"标准动作"可以直接绑,比如新增、删除、刷新、打印、附件管理,甚至一些通用的导入导出。如果业务诉求恰好能被标准动作覆盖,那最优解就是零代码——不用打包、不用发补丁、不用管升级覆盖,配置完刷新缓存就生效。我见过不少需求,本质上是"希望能一键跳到关联单据",这种用标准动作配置完全可以搞定,结果被做成了几百行自定义代码,后面每次平台升级都要重新适配,纯属自找麻烦。

配置 + 自定义 Action 是主力路径。绝大部分真实需求——批量回写、跨系统对接、复杂校验、生成对账文件——都得靠自写代码。这条路的成本主要在两端:前端要正确取到当前单据的数据上下文,后端要把业务逻辑放到合适的位置(是走服务端服务,还是客户端直连数据库?后面会细说)。

纯代码注入我不推荐上线用,但在两个场景里有价值:一是快速验证某个交互逻辑能不能跑通,二是平台提供的扩展点实在不够用时的兜底。动态往JPanel上add(new JButton(...))确实能让按钮出现,问题是这种按钮没有模板记录、没有权限标识、没有统一的事件管理,界面一重绘就可能丢,多语言也切不了。真到了非这么干不可的地步,也得把它的生命周期挂到面板的初始化回调上,而不是写死在构造函数里。

1.2 选型时先问清楚的四件事

选型不是拍脑袋,我一般会拿四个问题去问业务方和项目经理,答案基本决定了走哪条路。

第一,这个动作有没有副作用?只读的(查询、预览、导出)和写入的(回写状态、生成下游单据、推送外部系统)在实现上差别很大。有副作用的必须考虑事务、幂等、重复点击防护,不能简单地在按钮里直连数据库。

第二,数据量大不大?如果是列表页的批量操作,一次可能选中几千行,那按钮点击后必须异步执行 + 进度提示,否则客户端直接假死,用户会以为程序崩了。

第三,要不要按角色控制?只要答案是"要",权限注册这一步就绕不开,而且要在方案里就写清楚,别等到测试阶段被问到"为什么财务能点业务不能点"才开始补。

第四,以后平台升级怎么办?客户化代码被标准补丁覆盖是 NC6 交付里最经典的坑之一。如果这块业务是长期存在的,就得规划好补丁的独立目录和升级适配方案,而不是改标准文件。

注意:这四个问题里,只要"副作用"和"权限"同时命中,方案复杂度会跳一个台阶。我建议这类按钮单独拉一个小评审,把事务边界、失败重试、操作日志提前定下来,别边写边想。

1.3 前置条件:功能节点、模板归属与开发环境

动手之前还有一堆前置条件要确认,这些看起来琐碎,但每一条都能让你少走半天弯路。

首先要确认目标界面属于哪个功能节点。NC6 里同一个界面可能在多个节点下复用,不同节点可能挂不同的模板。如果你在 A 模板里加了按钮,测试时走的是 B 节点加载的 B 模板,那自然看不到。确认方法很简单:打开目标界面,看它加载的是哪份模板,或者从功能节点配置里反查。

其次要确认模板的归属组织。NC6 的模板支持按组织下发,集团级的模板和公司级的模板可能是继承关系。你在集团模板上加的按钮,子公司如果本地化过模板,可能不会自动生效。这块的经验是:优先在最高层级改,让下级继承;如果下级已经本地化,得单独同步一次。

第三是开发环境。自定义 Action 属于客户端代码,需要能编译并放到客户端的类路径下。我习惯在本地准备一套干净的客户化模块目录(比如modules/xxx_custom),编译产物直接扔进去,配合客户端缓存清理,改一次看一眼,效率比走完整补丁流程高得多。这里要提醒一句:客户端的类加载缓存比较顽固,改了代码没反应,先清缓存再怀疑代码。

2. 配置线:把按钮从模板里"变"出来

配置这一步看着简单,实际上细节很多,我按操作顺序拆开讲。需要说明的是,不同 UAP 版本的界面措辞会有出入,但字段语义基本一致,你按语义对号入座就行。

2.1 模板中新增按钮的完整操作序列

进模板设计界面,找到目标单据的卡片模板或列表模板(这两个是分开的,卡片上的按钮和列表上的按钮要分别加,很多人只加了一边,然后疑惑为什么列表页看不到)。在按钮页签里新增一行,填写按钮的基本属性,保存后发布模板。

发布这件事有讲究。模板改动后不是立刻对所有人生效,需要清客户端缓存或者等缓存过期。测试阶段我一般会主动清一次缓存,避免"我明明改了却没生效"的误判。生产环境发布要挑时间窗,因为模板变更会影响所有在线用户的下一次界面加载,虽然大多数情况下无感,但赶上高峰期做,万一模板配置有误,回滚成本会放大。

操作过程中有几个细节值得单独提:

  • 按钮编码要遵循现场的命名规范,通常带上模块前缀,比如xx_batch_push。这个编码后面注册权限要用,一旦上线就别随便改,改了等于权限全部失效。
  • 按钮名称如果要多语言,得在模板里配置多语言条目,而不是在代码里写死中文。我吃过这个亏:代码里setText("批量推送"),结果客户是双语环境,切到英文界面这个按钮还是中文,很尴尬。
  • 图标建议用平台自带的图标库,自定义图标要考虑分辨率和客户端缩放,不然在 4K 屏上糊成一团。
  • 显示位置要选对按钮组,选错了按钮会跑到工具栏、状态栏或者根本看不见的地方。

2.2 按钮属性逐项拆解:哪些字段决定生死

模板里的按钮属性有十几个,但真正决定"能不能用"的就那么几个。我把关键字段列一下,附上我踩过的坑。

属性作用常见坑
按钮编码唯一标识,权限注册的依据重复编码导致权限串号;上线后修改导致授权丢失
显示名称界面上看到的文字硬编码中文,多语言环境穿帮
动作类型决定点击后走哪套机制(标准动作 / 自定义类 / 事件)选了自定义但类名写错,点击静默失败
Action 类全名指向客户端 Action 实现包名写错、类没打进客户端包
是否可见控制默认可见性与权限叠加后逻辑混乱,出现"该看见的看不见"
是否可用控制按钮是否置灰与业务状态联动缺失,导致误操作
快捷键键盘触发与平台内置快捷键冲突

重点说"是否可用"这一项。很多业务场景下,按钮不该一直可点——比如单据还没保存、状态已经是"已审核"、或者当前选中的是多行但业务只允许单行操作。这时候正确做法是让按钮根据业务状态动态置灰,而不是点了之后弹一个"不允许操作"的提示。前者是引导,后者是打断,体验差一个量级。实现上一般是在 Action 里监听数据变化,或者在面板的数据加载回调里判断当前单据状态,然后调平台的setEnabled接口。

还有一个隐藏坑:同一个模板被多个功能节点复用。你在模板上加的按钮,所有引用这个模板的节点都会出现。如果某个节点不该有这个按钮,要么给那个节点单独配模板,要么在 Action 里根据当前节点编码做可见性判断。后者更省事,但会把业务判断散落到代码里,我一般优先选前者。

2.3 多组织、多模板下的同步策略

做过集团型项目的同学应该有体会:一个按钮在测试环境配好了,推到生产,结果只有总部能看见,下面几十家子公司都没有。这不是 bug,是模板继承机制在起作用。

NC6 的模板通常有层级:集团级定义基础结构,公司级可以本地化覆盖。如果某家公司之前因为业务差异本地化过这份模板,那么集团后来新增的按钮不会自动出现在它的本地模板里,需要手工同步或者重新下发。处理这类问题的流程我一般是这样的:先在最高层级加按钮并发布,然后逐层检查哪些组织存在本地化模板,对存在本地化的,用模板对比工具把新增的按钮项同步过去,最后统一清缓存验证。

这里有个效率技巧:如果子公司很多,手工一家家点会崩溃,可以先把集团模板导出成文件,对比各组织的差异清单,只处理真正有差异的那几家。实际项目里,本地化过的组织往往只占少数,先把清单拉出来,工作量立刻从几十家降到三五家。

3. 代码线:写一个真能跑起来的 Action

配置搞定,按钮能看见了,接下来是让它干活。这一节是整篇的核心,我会把 Action 的骨架、上下文获取、后端调用、界面刷新这几个环节拆开讲,并且给出可以直接参考的代码结构。

3.1 Action 类的骨架与生命周期

先给一个可以直接抄的骨架。需要说明的是,NC6 不同版本的基类和方法名有差异,下面用的是比较通用的一种写法,你按自己环境的 API 微调即可。

package nc.ui.xx.action; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import nc.ui.pub.beans.MessageDialog; import nc.ui.pubapp.uif2app.model.BillManageModel; import nc.vo.pub.BusinessException; /** * 单据卡片上的自定义按钮动作 * 说明:具体注入方式按现场 UAP 版本调整 */ public class BatchPushAction implements ActionListener { private BillManageModel model; public void setModel(BillManageModel model) { this.model = model; } @Override public void actionPerformed(ActionEvent e) { try { // 1. 取当前单据 Object bill = model.getSelectedData(); if (bill == null) { MessageDialog.showWarnDlg("请先选择一张单据"); return; } // 2. 前置校验 if (!checkBeforePush(bill)) { return; } // 3. 二次确认 if (MessageDialog.showOkCancelDlg("确认推送当前单据?") != 0) { return; } // 4. 调后端服务 doPush(bill); // 5. 刷新界面 model.refresh(); MessageDialog.showInfoDlg("推送成功"); } catch (BusinessException ex) { MessageDialog.showErrorDlg("推送失败:" + ex.getMessage()); } catch (Exception ex) { MessageDialog.showErrorDlg("系统异常,请联系管理员"); // 这里必须记日志,别吞掉堆栈 } } private boolean checkBeforePush(Object bill) throws BusinessException { // 状态校验、必填校验等 return true; } private void doPush(Object bill) throws BusinessException { // 调用 nc.itf 定义的服务端接口 } }

这段代码有几处是有意为之的设计,值得展开说说。

第一,动作入口只做编排,不做业务。actionPerformed里全是流程控制:取数据、校验、确认、调服务、刷新、提示。真正的业务逻辑放在服务端,客户端不碰数据库。这是 NC6 客户化的铁律——客户端直连数据库看起来快,但一旦涉及事务、并发、权限,后面全是坑,而且平台升级时最容易被打断的就是这类代码。

第二,异常分两类处理。BusinessException是业务可预期的,直接把消息抛给用户看;其他Exception一律兜底成"系统异常",堆栈进日志。我见过太多现场代码把Exception的 message 直接弹给用户,结果用户看到一串NullPointerException,既看不懂又泄露了内部结构。

第三,二次确认不是多余。有副作用的按钮,尤其涉及外部系统推送的,确认框能挡掉相当一部分误点。确认框的文案要写清楚后果,别写"是否继续",要写"确认将当前单据推送至下游系统?推送后状态将变更为已推送"。

3.2 卡片与列表:两种上下文的取数差异

这一步是新手最容易栽跟头的地方。NC6 的卡片界面和列表界面,数据模型的取法是不一样的,很多人从卡片上抄了代码直接用到列表上,结果要么报空指针,要么只处理了一条。

卡片场景相对简单,当前界面就是一张单据,从BillManageModel或者面板的getModel()拿到当前的 VO 就行。但要注意,界面上可能有"未保存的修改",用户改了字段还没保存就点按钮,你取到的是内存里的对象还是数据库里的数据?这个要看具体取数方式。稳妥做法是先判断界面是否处于编辑状态,如果是,要么提示用户先保存,要么主动触发一次保存再取数,别让用户拿着半截数据去跑业务逻辑。

列表场景复杂得多。列表页可能选中多行,也可能一行没选;可能是分页加载的,当前页只有 50 条但总数上千。这时候按钮的行为必须定义清楚:是处理"选中的行",还是处理"当前查询条件下的全部数据"?这两个语义完全不同,而且后者往往不能靠客户端循环实现——上千条数据一条条调服务,性能直接崩。

我的做法是:如果业务语义是"处理全部",就把查询条件传到服务端,由服务端一次性处理,客户端只负责显示进度。如果语义是"处理选中",就先取选中集合,判断数量,超过阈值(我一般设 200 条)就走异步 + 进度条,低于阈值可以同步执行但要加等待光标。

// 列表场景:区分"选中处理"与"全量处理" Object[] selected = listModel.getSelectedData(); if (selected == null || selected.length == 0) { MessageDialog.showWarnDlg("请至少选择一条记录"); return; } if (selected.length > 200) { // 走异步任务,避免客户端假死 startAsyncTask(selected); } else { // 同步执行,配等待光标 doBatch(selected); }

顺带提一个我踩过的坑:列表的选中集合里,元素类型可能和卡片上的 VO 类型不完全一致,有时候是包装过的行对象,直接强转会报ClassCastException。稳妥写法是先判类型,或者通过主键去服务端重新查一遍完整 VO。多一次查询换来的是稳定,我觉得值。

3.3 调后端服务:客户端只做壳,逻辑放服务端

NC6 的标准架构是客户端 - 服务端分离,自定义按钮的逻辑也应该遵守这个分层。具体做法是:在接口层(nc.itf.*)定义服务接口,在实现层(nc.bs.*)写业务实现,客户端只负责调用。

package nc.itf.xx; import nc.vo.pub.BusinessException; public interface IPushToDownStream { /** * 推送单据至下游 * @param pkBill 单据主键 * @return 下游返回的业务流水号 */ String pushBill(String pkBill) throws BusinessException; }

服务端实现里要做几件事:开启事务、校验业务状态、组装报文、调用外部接口、回写状态、记操作日志。这里面接口调用的幂等性是重点——网络超时了到底推没推成功?如果没有幂等设计,用户点两次就产生两笔下游数据,对账时非常痛苦。常见做法是给每张单据生成一个唯一的推送流水号,下游按流水号去重;或者推送前先查一次下游状态,已存在就跳过。

回写状态这一步要放在事务里,但外部接口调用本身是事务外的。所以流程得设计成:先落一条"推送中"的中间状态并提交,再调外部接口,成功则更新为"已推送",失败则更新为"推送失败"并记录原因。这样即使中间宕机,也能从中间状态捞出来重试,而不是一笔糊涂账。

3.4 部署:客户端代码怎么放才算稳

代码写完,编译成 class 或 jar,接下来是放到客户端能加载的位置。NC6 的客户端有自己的模块目录结构,客户化代码一般放在独立的客户化目录下,不要往标准模块里塞。原因很简单:标准补丁升级时会覆盖标准目录,你的类就没了;放在独立目录,标准补丁一般不会动,升级风险小很多。

如果客户化类比较多,建议按模块打成一个独立的 jar,而不是散落的 class 文件。jar 的加载顺序和名字有关,命名上要保证客户化的 jar 排在标准 jar 之后,这样同名类会优先加载客户化的版本——这个特性在需要覆盖标准行为时很有用,但也是把双刃剑,用之前务必确认平台类加载器的具体策略,别指望它一定能覆盖。

部署后要验证三件事:一是类能加载(不报ClassNotFoundException);二是按钮能渲染;三是点击能走到你的断点(本地调试时)。线上环境没有断点,就靠日志——Action 入口打一行带单据主键的日志,服务端实现也打,出问题时顺着日志能还原整条调用链。

提示:客户端部署最容易忽略的是缓存。改了 jar 但没清客户端缓存,加载的还是旧类,症状是"代码明明改了却没生效"。养成习惯:改完代码先清缓存再测。

4. 权限、日志与发布:上线前必须补齐的三件事

功能跑通只是及格线,能不能安稳上线,取决于这三件事做得怎么样。我见过太多"测试环境一切正常,上线第一天就被投诉"的案例,问题基本都出在这里。

4.1 按钮权限的注册与授权

前面提过,按钮编码是权限注册的依据。注册的位置在功能节点管理里,找到对应节点,在按钮权限页签里新增一项,填上按钮编码和名称,保存。然后在角色管理里给相应角色授权。

这里有几个实操细节。第一,权限项的编码必须和模板里的按钮编码完全一致,包括大小写。差一个字符,按钮就是不显示,而且不报错,排查起来很费时间。第二,权限注册后需要重新登录或者清权限缓存才生效,很多现场"授了权还是看不见",八成是缓存。第三,如果一个按钮需要精细控制(比如只有财务主管能推送到资金系统),那要设计成独立的权限项,而不是和查看权限绑在一起。

授权策略上,我的建议是默认最小权限:新按钮默认只有管理员角色有,其他角色按需勾选。反过来做(默认全给,出事再收)在项目里很危险,因为业务方一旦习惯了能点,再收回去就是一场扯皮。

4.2 日志与可追溯性

有副作用的操作必须留痕,这不是可选项。日志要记三个层次:客户端入口记一条(谁、什么时候、在哪个界面、操作了哪张单据);服务端实现记一条(入参、出参、耗时);外部接口调用记一条(请求报文、响应报文、失败原因)。

日志写在哪里也有讲究。纯技术日志进应用日志文件,业务操作日志建议落到业务表里,因为业务方查问题的时候不会去看服务器日志文件,他们要的是"在界面上能查到谁什么时候推过"。这块如果时间紧,至少把业务操作日志做出来,技术日志靠平台默认的就行。

还有一个容易被忽略的点:批量操作的日志量。一次处理几千条,如果每条都写一条业务日志,日志表会迅速膨胀。做法是批量操作只记一条汇总日志(操作人、批次号、总数、成功数、失败数),明细失败原因记在附件或者单独的明细表里,按批次号关联。这样既满足追溯,又不会把表撑爆。

4.3 补丁管理与升级覆盖

这是长期项目里最痛的一块。NC6 的标准补丁升级会覆盖标准目录下的文件,如果你的客户化代码混在里面,升级一次就丢一次。规避方案我在前面提过——独立目录、独立 jar,但还有几个配套动作要做。

第一,把客户化改动清单维护成文档,记录每个改动点、涉及的文件、jar 名、版本。这份清单在升级前的差异分析里价值极高,能快速定位哪些改动需要重新验证。

第二,升级前做一次全量回归,重点验证自定义按钮。因为升级可能改变了平台的基类行为,你的 Action 编译能过、但运行时可能因为某个方法签名变了而抛异常。回归用例就写三条:按钮显示、点击执行、执行结果正确。

第三,给客户化 jar 加版本号,并在启动日志里打印出来。这样线上出问题时,第一眼就能确认"跑的是不是最新版本",避免在版本混淆上浪费时间。

5. 踩坑实录:那些文档里不会写的问题

这一节整理的是我在多个项目里反复遇到的典型症状和排查路径,做成了速查表,遇到问题先对照,能省下大量试错时间。

5.1 症状速查表

症状最可能的原因排查动作
按钮完全不显示模板没发布 / 缓存未清 / 功能节点加载的是另一份模板查当前界面的模板来源,清缓存重进
按钮显示但置灰权限未授 / 按钮"是否可用"配置为否 / 业务状态判断拦截先用管理员账号验证,区分权限问题还是逻辑问题
点击无任何反应Action 类名写错 / 类未加载 / 事件被上层拦截看客户端日志有无类加载异常,加断点或入口日志
点击报 ClassNotFoundjar 未部署 / 包名不一致 / 类加载器隔离确认 jar 位置与加载顺序
换组织后按钮消失组织级模板本地化,未同步新增按钮比对模板差异,同步按钮项
推送成功但界面没变未调用刷新 / 刷新的是错误的数据模型在刷新前后打日志确认模型实例
重复点击产生两条数据无幂等设计 / 按钮未做点击防抖加流水号去重 + 点击后临时禁用按钮
升级后按钮报错平台基类或 API 变更查升级说明,重新编译适配

5.2 几个值得单独说的坑

坑一:按钮加了,但只在卡片上,列表上没有。这个前面提过,卡片模板和列表模板是两份,很多人只改了一份。判断方法很简单:从列表双击进卡片,如果卡片上有按钮而列表工具栏没有,那就是列表模板没改。

坑二:管理员能看见,普通用户看不见,但权限明明授了。除了缓存,还有一种可能是权限项的"所属功能"配错了。权限项挂在 A 节点下,但用户实际是从 B 节点进入的界面,那自然不生效。查的时候把功能节点编码和权限项所属节点对一遍。

坑三:点击按钮后界面卡死十几秒。基本可以断定是同步执行了耗时操作。改造成异步 + 进度提示,或者干脆把执行逻辑挪到后台任务,客户端只负责提交和轮询结果。我做过一个批量推送,5000 条数据同步跑要 40 多秒,改成后台任务后客户端 1 秒内返回,用户体验天差地别。

坑四:按钮在多语言环境下文字错乱。原因是名称硬编码在代码里而不是走模板多语言。只要按钮文字是在模板里配的,切语言会自动跟着变;一旦代码里setText了,就绕过了多语言机制。这个改起来不难,但发现得晚就要回归一遍所有语言环境。

坑五:测试环境好好的,上线就报找不到服务。通常是服务端接口没有正确注册到平台的组件容器里,或者客户端调用时用的服务名和注册名不一致。上线前一定要在类生产环境(同版本、同配置)跑一遍完整链路,别拿开发机的单机环境当准。

5.3 一个完整场景的复盘

说一个我做过的真实类型的需求,把前面的东西串起来。某公司的销售出库单,业务方要求加一个"同步至外部仓储系统"的按钮。约束条件是:只能对已审核的单据操作;一次最多同步 50 张;同步失败要能看到原因;不同仓库的操作员只能同步自己仓库的单据。

方案拆解下来是这样的。配置层:在列表模板和卡片模板都加按钮,编码统一,注册权限项并挂到出库单功能节点下。代码层:Action 里取选中集合,先判断数量是否超限,再逐条判断单据状态是否已审核,然后按当前操作员的仓库权限过滤——这一步放在服务端做,客户端过滤不可信。服务端实现:开事务,逐条调用外部接口,成功的回写状态,失败的收集原因。因为要求"能看到失败原因",所以把结果做成了一个结果弹窗,成功多少条、失败多少条、失败的具体原因逐条列出。

上线后遇到两个问题。第一个是外部系统响应慢,一次 50 张同步要 20 多秒,用户以为死机了。解决办法是加进度条,并且把"同步中"的状态实时显示在按钮旁边。第二个是幂等问题——用户在等待期间又点了一次,导致部分单据重复推送。补的方案是服务端加流水号判重,同时按钮点击后立刻置灰,任务结束再恢复。

这两个问题都不是技术难题,但都是"不实际跑一遍想不到"的问题。所以我一直坚持一个观点:自定义按钮这种活,方案设计占三成,剩下七成全靠上线后的真实反馈来打磨。

6. 我个人的几条经验

做得多了,会形成一些不太写进文档但很管用的习惯,随手分享几条。

第一,先配后写。任何自定义按钮需求,先花十分钟看看能不能用标准动作或平台事件配出来,配不出来再写代码。这十分钟能省掉后面几天的部署和升级适配。

第二,入口只做编排。Action 里永远只有取数、校验、确认、调用、刷新、提示这六件事,业务逻辑一行都不放。这条守住了,代码在升级时的存活率会高很多。

第三,给按钮留一条日志。客户端入口打一行包含操作人、单据主键、时间戳的日志,成本几乎为零,但线上排查时是救命稻草。

第四,别信"应该没问题"。模板发布、权限缓存、jar 加载、组织同步,这四个环节每一个都可能让按钮失效,上线前老老实实按清单过一遍,比事后救火划算得多。

第五,文档写给自己看。每个自定义按钮记一页:需求来源、涉及模块、改动清单、部署位置、回滚方法。半年后接手的人(很可能就是你自己)会感谢现在的你。

这套东西说起来不复杂,真正难的是每次都能按流程走完,不因为"这次需求很简单"就跳过权限、跳过日志、跳过回归。我踩过的坑,十有八九都是跳步骤跳出来的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询