做星空二开,代码写在哪儿,决定了它能不能扛住下一次补丁。
我们项目里踩过最典型的一次:两年前在标准销售订单上直接加了字段、改了保存逻辑,上线很快。两年后标准产品升级,单据元数据被新补丁刷了一遍,自定义部分被覆盖,功能失效,而且因为改动散落在标准对象内部,排查花了很久。
同一个需求,如果当初写成挂在标准单据事件上的插件,升级时大概率不受影响。这篇文章把两种写法的差异落到代码层面讲清楚,附字段承载建议表和升级后常见报错处理表。
一、两种写法的代码差异
写法 A:直接改标准对象(不推荐)
在 BOS 设计器里打开标准单据,加自定义字段、改界面布局、重写业务逻辑。表面上看开发最快,但改动直接压在标准对象的元数据上。
写法 B:插件挂标准单据(推荐)
不动标准对象,新增一个插件类挂到标准单据的公开事件上。
using Kingdee.BOS.Core.Bill.PlugIn;
using Kingdee.BOS.Core.DynamicForm.PlugIn.Args;
using System;
using System.ComponentModel;
namespace JiuDie.Erp.PlugIn.SaleOrder
{
///
/// 销售订单 - 保存前校验客户等级底线折扣
/// 挂载方式:BOS 设计器 -> 销售订单 -> 单据插件 -> 保存(BeforeSave)
///
[Description(“销售订单-客户等级折扣底线校验”)]
public class SaleOrderDiscountCheckPlugIn : AbstractBillPlugIn
{
// 底线折扣率(百分数),低于该值不允许保存
private const decimal MinDiscountRate = 70m;
public override void BeforeSave(BeforeSaveEventArgs e) { base.BeforeSave(e); // 取当前单据上的字段值(示意:实际标识以现场元数据为准) var custLevel = Convert.ToString(this.Model.GetValue("F_CustLevel")); // 客户等级 var discountRate = Convert.ToDecimal(this.Model.GetValue("F_DiscountRate")); // 折扣率 if (string.IsNullOrWhiteSpace(custLevel)) { return; // 未填客户等级不拦截,交由其他校验处理 } // 仅对低等级客户做底线校验 if (custLevel == "C" && discountRate < MinDiscountRate) { throw new Exception(string.Format( "客户等级为 C 类,折扣率 {0}% 低于底线 {1}%,请走审批后保存。", discountRate, MinDiscountRate)); } } }}
这样写的关键点有三个:
- 不碰标准对象元数据。字段通过「引入自定义字段」的方式挂到单据上,而不是直接改建标准字段。补丁更新标准元数据时,自定义字段是独立的一份,不受影响。
- 挂公开事件。BeforeSave 是标准单据生命周期里的公开扩展点。要避开那些未公开、可能在新版本被调整的内部方法重写。
- 字段标识加前缀。自定义字段统一用 F_ 加业务前缀,避免和标准字段撞标识。
二、字段该承载在哪里
同一个需求,承载方式不同,升级风险差很多。实践中可以按这张表选:
需求类型
建议承载方式
说明
标准单据上的新增业务属性
自定义字段(不直接改建标准字段)
标识加业务前缀,避免与标准字段冲突
全新的业务单据
自定义业务对象
与标准对象解耦,标准产品升级一般无影响
标准单据的校验与联动
插件挂在公开事件上
事件点必须选公开且稳定的扩展点
界面布局调整
扩展方案 / 自定义布局
不直接改标准界面模板
跨系统数据进出
WebAPI
不动标准产品,升级基本无感
依赖标准内部算法的逻辑
改为调用公开服务或接口
内部实现可能随版本变化
最后一行值得展开:如果二开逻辑里直接引用了标准产品的内部类或内部方法,这类代码在升级中最容易断。正确的做法是找有没有对应的公开服务,没有的话再考虑把这段逻辑独立出来自己实现。
三、升级后常见报错与处理
这张表是我们项目里攒出来的,实际排查时能省不少时间。
升级后现象
常见原因
处理方式
单据保存报「字段不存在」
直接加在标准对象上的自定义字段被补丁覆盖
先查二开清单定位字段,用引入自定义字段的方式重建,同步修正代码里的字段标识
插件完全不触发
挂载的事件点在新版本被废弃或改名
查新版本的事件列表,改挂公开且仍在维护的事件
界面布局被还原
标准界面模板被补丁更新覆盖
自定义布局改走扩展方案,不直接改标准模板
金额或数量算错
逻辑依赖了标准内部算法,算法变了
改为调用公开服务,或把算法在本插件内独立实现
报表取数为空
直接读标准表,表结构或状态字段变了
改走业务对象视图或 WebAPI 取数,不直连标准表
列表过滤条件失效
过滤依赖的状态枚举值发生变化
改为引用标准枚举,不写死数值
四、发布前的自检清单
改完代码别急着上生产,这五项过一遍: - 这次改动有没有直接修改标准对象的元数据?
- 插件挂的事件点是不是公开的?
- 代码里有没有引用标准产品的内部类或内部方法?
- 新增字段的标识是否加了业务前缀?
- 这次改动的影响范围,有没有写进二开清单并给出回归测试点?
第 5 项经常被跳过,但它是升级时最值钱的一份东西。没有它,两三年后连当初改了什么都要靠猜。
五、关于升级影响评估
如果手上有历史二开、又准备升级标准产品,比较稳的做法是先做一次升级影响评估:把现有二开点按「是否触碰标准对象元数据」「是否依赖内部实现」两个维度过一遍,输出一份风险清单和回归测试点。
这类评估工作我们做得多一些,广州地区有相关需求可以交流,工程问题也可以直接留言讨论。