简介面向金蝶K/3 Cloud BOS插件开发初学者的入门文档围绕BOS产品应用汇总中的BOSIDE、套打、万能报表等应用背景讲解如何通过插件扩展标准业务、实现企业个性化与非标准化需求。资源包仅含1个docx文件大小约1.9MB以单一文档集中呈现环境配置、架构概念与开发入门内容便于按章节查阅。已有216人学习。文档涵盖服务器与客户端配置要求、数据库字符集与排序规则、BOS动态表单模型及四类插件动态表单、服务、校验规则、表单构建的定位并介绍Visual Studio 2012、Silverlight5、HttpWatch Pro等工具和公共/个人环境搭建方法还以Hello World及采购收料单为例梳理插件类定义、组件引用、方法重载、编译路径设置、IDE注册与运行测试流程适合需要快速建立K/3 Cloud插件开发整体认知的初学者。1. 一张采购收料单改到第三遍我才回头补 K/3 Cloud 插件开发基础接到「给采购收料单加个按钮、点一下弹提示」的需求时很多人的第一反应是找源码改。真上手才知道K/3 Cloud BOS 平台上的标准单据不能这么动正确路径是写一个插件挂上去用继承和重载把行为插进既有业务流。这份《初学者-k3cloud插件开发基础.docx》讲的就是这条路径从 SQL Server 排序规则、Visual Studio 2012 引用、Common.config 里的管理中心地址到四类插件的分工、动态表单视图与模型两个接口的边界、七个加载回调的先后顺序最后落到一个能编译、能注册、能点出对话框的 HelloWorld。适合已经能看懂 C#、但还没在 BOS IDE 里亲手注册过一次插件的人也适合带新人时当一份环境清单省得对方在排序规则上翻车。2. 环境与工程骨架把 Visual Studio 2012 到 Bin 目录这条链路搭通动手写第一行代码之前先把「代码写在哪、编译产物落到哪、运行时从哪加载」这三件事确定下来。插件开发的绝大部分低级故障都出在这条链路上而不是业务逻辑本身。这一章按配置基准、工程引用、输出路径、客户端依赖四个环节把链路一段一段接上。2.1 配置基准值别拿一台 4G 内存的笔记本硬扛 Oracle文档给出的配置建议区分了团队服务器和个人开发机两种口径这个区分很实用。团队环境里数据库服务器 CPU 双核 2.0 GHz 以上SQL Server 内存 8 GB、Oracle 内存 16 GB纯客户端双核 2.0 GHz 以上、4 GB 内存就够跑 IDE。个人开发机把客户端和数据库塞在一台机器上时SQL Server 版本给 4 GB 以上Oracle 版本直接给到 8 GB 以上。经验上 Oracle 那条线内存给少了光是实例启动加 BOS 服务加载就能把机器拖到没法编译。角色CPU内存备注团队数据库服务器4 核2.0 GHz 以上SQL Server 8 GBOracle 16 GB多人共用别省内存纯客户端双核2.0 GHz 以上推荐 4 GB只装 VS 和浏览器个人客户端 数据库双核2.0 GHz 以上SQL Server 4 GB 以上Oracle 8 GB 以上单机全栈磁盘 IO 是瓶颈运行环境的版本约束同样要提前对齐数据库侧 SQL Server 2008R2 以上标准版或企业版、Oracle 11g 以上操作系统 Windows Server 2008 以上Web 服务器 IIS 6.0 以上运行时 Microsoft .NET Framework 4.0。这几个版本号看着老但 BOS 平台的程序集就是按这套目标框架编的把 VS 工程的目标框架随手改成 4.5 以上编译能过运行时加载程序集照样出问题。数据库这一侧有两个硬指标必须在建库时就定死。用 SQL Server 时排序规则必须是Chinese_PRC_CI_AS用 Oracle 时数据库字符集必须是AL32UTF8国家字符集必须是AL16UTF16。这两条不是建议是前置条件。库建好、数据灌进去之后再想改排序规则基本等于重建库血泪经验就是这一步千万别抱着「先跑起来再说」的心态跳过。2.2 新建工程与引用Kingdee.BOS 和 Kingdee.BOS.Core 两个引用不能少打开 Visual Studio 2012新建一个类库工程命名按团队约定走文档里用的示例是MyDev.K3.SCM.Stock.Business.PlugIn从命名就能看出模块SCM、业务对象Stock、层Business和类型PlugIn。工程建好后加两个程序集引用Kingdee.BOS和Kingdee.BOS.Core。前者是平台基础类型后者承载动态表单、插件基类、事件参数这些二开最常碰的东西。!-- 工程文件里的引用片段HintPath 指向产品安装目录下的 Bin -- ItemGroup Reference IncludeKingdee.BOS HintPathD:\K3Cloud\K3CloudServer\Bin\Kingdee.BOS.dll/HintPath !-- 不要复制到输出目录避免覆盖平台自身的运行时文件 -- PrivateFalse/Private /Reference Reference IncludeKingdee.BOS.Core HintPathD:\K3Cloud\K3CloudServer\Bin\Kingdee.BOS.Core.dll/HintPath PrivateFalse/Private /Reference /ItemGroup这两个引用块的关键点在Private标签。设成False表示编译时不把平台 DLL 拷到输出目录。曾经有人图省事留了默认的True结果每次编译都往 Bin 里塞一份旧版本Kingdee.BOS.dll覆盖掉产品自带的文件整套站点起不来排查方向还容易跑到代码上去。引用路径建议直接用绝对路径指向产品安装目录别用相对路径或者 GAC 里的同名程序集版本对不上时现象极其隐蔽。目标框架保持 .NET Framework 4.0跟服务端运行环境一致。平台目标这一项没有强制要求但要注意跟 IIS 应用程序池的位数对齐应用程序池开了 32 位模式编译产物却是 AnyCPU 之外的其他配置加载时就会报类型初始化失败。这类报错信息通常不会直接告诉你位数不匹配只能靠对齐配置来避免。2.3 输出路径与管理中心地址Common.config 里那行 IP编译输出目录直接设成产品安装目录下的K3Cloud\K3CloudServer\Bin\。这一步决定了插件能不能被平台发现——IDE 里注册插件时浏览到的就是这个目录DLL 落在别处注册完运行起来必然是「找不到类型」或者干脆没反应。!-- 工程属性里的输出配置 -- PropertyGroup !-- 直接产出到平台加载目录省掉手工拷贝这一步 -- OutputPathD:\K3Cloud\K3CloudServer\Bin\/OutputPath TargetFrameworkVersionv4.0/TargetFrameworkVersion /PropertyGroup输出到平台目录有个副作用要提前知道只要站点在运行w3wp.exe就会把 Bin 下的 DLL 锁住编译时直接报「文件被另一个进程占用」。常见的处理办法是停掉对应站点或回收应用程序池再编译而不是去改输出路径绕开——绕开之后你还得手工拷回去反而多一步且容易忘。个人开发机还要改一处配置才能连上公共环境的管理中心。打开K3Cloud\K3CloudServer\App_Data\Common.config找到managementSiteUrl这个键把值改成公共环境里已经配置好管理中心的那台服务器地址。!-- K3Cloud\K3CloudServer\App_Data\Common.config -- appSettings !-- 末尾的斜杠不要省URL 拼接时少一个斜杠会出现 404 -- add keymanagementSiteUrl valuehttp://192.168.73.40:8000/ / /appSettings改完之后重启站点让配置生效。这一步顺序不能反先装产品再改地址改完再启动服务。有人先启动服务、再回头改文件却忘了重启登录时一直连到本机那个空的管理中心界面上什么数据中心都看不到还以为是数据库没配好。2.4 浏览器与 Silverlight 5客户端这一侧的隐性依赖服务端和 IDE 都通了最后一道坎在客户端。文档给出的浏览器支持矩阵是IE 8.0、9.032 位x86和 64 位x64都可以Firefox 12 及以上限 32 位 Windows 版本。两种浏览器都依赖 Silverlight 5 插件。客户端组件支持版本支持架构Internet Explorer8.0、9.032 位x86、64 位x64Firefox12 及以上32 位WindowsSilverlight5随浏览器位数匹配这里的坑在于位数匹配。64 位 IE 需要 64 位 Silverlight32 位 IE 需要 32 位 Silverlight装错版本的表现是页面能打开、但业务控件区域一片空白或者一直转圈控制台里未必有明确报错。另外别顺手把浏览器升到最新大版本Silverlight 插件在新版浏览器上是否还能正常加载取决于具体环境不在支持矩阵里的组合出问题时没有任何排查抓手。3. 四类插件与继承体系基类选错事件一个都不会进环境通了接下来是选型。BOS 平台的插件不是一个大而全的基类而是按介入位置切成几类继承错了你重载的方法根本不会被调用断点永远不命中。这一章把插件分类、继承关系、视图与模型两个接口、以及加载顺序讲清楚后面写代码才知道该往哪个方法里塞逻辑。3.1 动态表单插件、服务插件、校验规则插件、表单构建插件怎么分工文档按应用场景把插件分成四类这个划分是选型的起点。插件类型主要职责典型用途动态表单插件封装单据上的业务事件、操作和服务介入单据界面上的按钮响应、字段联动、数据校验提示服务插件基于动态表单服务端提供服务编程接口被外部服务直接调用的业务能力封装校验规则插件针对业务规则做校验保存、提交前的规则拦截表单构建插件对界面加载的控件和元数据动态调整按客户需求改界面结构属于较重的手段日常二开里绝大多数需求落在第一类。改个按钮、控个字段可见性、加个默认值都是动态表单插件的活。服务插件更偏「把能力暴露出去」校验规则插件适合把规则从代码里抽出来做成可配置项表单构建插件则是动界面骨架改动面大、影响面也大非必要不用。选型上有个判断顺序可以照抄先问「这个需求是不是在界面上发生的」是就走动态表单插件再问「是不是纯数据规则」是就考虑校验规则插件最后才问「是不是要动界面结构」是才上表单构建插件。跳过前两问直接上构建插件改出来的东西往往耦合度高、后续升级难。3.2 单据、基础资料、动态表单、列表AbstractBillPlugIn 这一族的继承链动态表单插件下面还要再分一次。文档给出的基类清单是四支单据插件用Kingdee.BOS.Core.Bill.PlugIn.AbstractBillPlugIn基础资料插件用Kingdee.BOS.Core.Base.PlugIn.AbstractBasePlugIn动态表单插件用Kingdee.BOS.Core.DynamicForm.PlugIn.AbstractDynamicFormPlugIn列表插件用Kingdee.BOS.Core.List.PlugIn.AbstractListPlugIn。继承体系上最上层是AbstractDynamicFormPlugIn单据插件和基础资料插件依次往下派生列表插件单独一支。这个层级的实际意义在于你继承AbstractBillPlugIn就同时拿到了动态表单层和单据层的事件继承AbstractDynamicFormPlugIn只有通用表单层的事件。采购收料单是业务单据所以继承AbstractBillPlugIn如果挂到基础资料上却继承了单据基类事件未必触发反过来也一样。命名空间也要对得上。AbstractBillPlugIn在Kingdee.BOS.Core.Bill.PlugIn事件参数在Kingdee.BOS.Core.DynamicForm.PlugIn.Args消息框类型MessageBoxType在Kingdee.BOS.Core.DynamicForm。这几个 using 漏一个编译报的错是「找不到类型或命名空间」看着像引用没加实际只是少了一行 using。3.3 IDynamicFormView 与 IDynamicFormModel视图管外观模型管数据动态表单模型把「长什么样」和「怎么算」拆开了。外观由视图View控制业务逻辑由模型Model控制两者分别持有布局元数据LayoutInfo和业务元数据BusinessInfo这两份元数据就是 IDE 里配的那些东西。运行时客户端发起请求先读元数据初始化BusinessInfo和LayoutInfo视图和模型再据此把表单渲染出来。插件能直接访问的是IDynamicFormView和IDynamicFormModel两个接口。前者提供访问菜单、修改控件样式、设置标题、刷新界面、打开表单、动态注册插件这些能力还负责操作转发后者围绕数据提供初始化、新增、复制、删除、定位当前分录行、设置值等方法。文档里提到的IDynamicFormViewService和IDynamicFormModelService是平台内部用的二开不用碰。模型侧最需要建立概念的是DataObject。它是当前表单的数据对象类型是DynamicObject包含单据头和单据体单据体是集合对象DynamicObjectCollection一张单据可以有多个单据体。平台默认用DynamicObject作数据承载类也允许通过DynamicObjectType.ClrType指定自定义类但要求这个类必须派生自DynamicObject。理解这一点后面看到GetEntityDataObject、SetValue(key, value, row)这类带行号的写法就不会懵。3.4 插件加载顺序七个回调的先后关系第一次访问表单时平台先加载元数据初始化视图和模型对象初始化页面然后创建数据包并绑定数据。二次开发能介入的时机就分布在这个过程里顺序是固定的。顺序回调时机说明1OnInitialize页面初始化可动态初始化控件数据源2CreateNewData动态表单数据包创建仅新增时触发3AfterCreateNewEntryRow新增分录行后每加一行触发一次4AfterCreateNewData数据包创建完成仅新增时触发5BeforeBindData绑定数据前可对数据做预处理改动不进状态管理器6AfterBindData绑定数据及控件状态后加载和界面刷新都会调用7BeforeClosed页面关闭前这张表的用法很直接需要在新增时给默认值找第 2 或第 4 步需要每加一行分录就补一个序号找第 3 步需要控制字段可见性、菜单可用性找第 6 步。把可见性逻辑写到第 2 步界面已经渲染完了才改属性用户看到的是闪一下再变体验和预期都对不上。4. HelloWorld 全流程从 BarItemClick 到 IDE 里勾选插件前面都是铺垫这一章把最小可运行闭环走完写一个继承AbstractBillPlugIn的类、重载菜单点击事件、编译到 Bin、在 IDE 里注册、运行验证。这条链路走通一次后面所有插件都是在这个骨架上加方法。4.1 定义插件类并重载 BarItemClick新建类ReceiptEdit继承AbstractBillPlugIn。需求是点菜单弹一个提示框对应重载BarItemClick。这个方法在菜单单击时触发参数BarItemClickEventArgs里的BarItemKey就是 IDE 中菜单项的唯一标识。using Kingdee.BOS.Core.Bill.PlugIn; using Kingdee.BOS.Core.DynamicForm; using Kingdee.BOS.Core.DynamicForm.PlugIn.Args; namespace MyDev.K3.SCM.Stock.Business.PlugIn { // 采购收料单属于业务单据继承单据插件基类 public class ReceiptEdit : AbstractBillPlugIn { public override void BarItemClick(BarItemClickEventArgs e) { // 先调基类保留平台自身的菜单处理逻辑 base.BarItemClick(e); // BarItemKey 与 IDE 中菜单项标识严格对应区分大小写 if (e.BarItemKey HelloWorld) { this.View.ShowMessage(Hello world!, MessageBoxType.Notice); } } } }代码本身只有几行三个细节值得单独说。第一base.BarItemClick(e)必须放在最前面平台内部的菜单处理、权限判断都在这条链上漏掉会出现「某些菜单点了没反应」的怪现象。第二BarItemKey是字符串硬比较写错一个字母就是不进分支而且不报错只能靠断点确认。第三弹框走this.View.ShowMessageMessageBoxType.Notice是提示类型需要警告或错误样式时换枚举值即可。4.2 编译输出到 K3CloudServer\Bin工程输出路径在 2.3 已经设成产品 Bin 目录直接生成解决方案。如果这一步报文件占用说明站点正在跑并且加载了这个 DLL回收应用程序池或停站点再编译。# 编译前先回收站点避免 Bin 下的 DLL 被 w3wp 锁住 # 站点名按实际环境替换回收后等几秒再执行编译 iisreset /stop # 编译完成后重新启动 iisreset /start用iisreset属于最省事的做法生产环境当然不能这么干本地开发机图快可以接受。更温和的方式是在 IIS 管理器里对单个应用程序池点「回收」影响面小、速度快。编译成功后去K3Cloud\K3CloudServer\Bin\下确认MyDev.K3.SCM.Stock.Business.PlugIn.dll的时间戳是刚才那一刻这一步比任何日志都直观——时间戳没变说明输出路径设错了或者编译走了别的配置。4.3 在 BOS IDE 设计器里注册插件注册这一步是新手最容易卡住的地方因为它不在代码里而在设计器里顺序错了会以为前面白写了。完整步骤是打开 IDE 设计器找到目标单据文档示例是采购收料单单据标识类似_Bill的属性项进入单据属性窗口找到插件列表点注册插件在弹窗里点浏览选中刚编译出来的 DLL勾选要启用的插件类确定返回最后保存单据。注册列表里可能已经存在其他插件文档里专门提醒了一句这些插件运行时会动态加载删除它们可能导致业务数据错误。这句话的分量很重。正确做法是只动自己新增的那一条不要去清理列表里看着陌生或者用不上的项——那些可能是标准产品的功能扩展点删掉之后表象是某些业务字段不刷新或者校验失效很难往「删了个插件」这个方向去想。保存单据是必须的一步。只勾选不保存注册信息只存在于当前设计器会话里刷新页面就没了。保存之后元数据才真正带上插件配置运行时才会按配置动态加载。4.4 运行验证与抓包定位刷新单据页面点 HelloWorld 菜单应该弹出提示框。如果没弹先按三个方向排查插件列表里勾的是不是当前这个类、DLL 时间戳是不是刚编译的、菜单项的 Key 是不是和代码里写的一致。这三个都对还不行就上抓包工具。文档里提到的跟踪工具是 HttpWatch Pro 6.0用途是看客户端和服务端之间到底发生了什么。插件里通过CreateNewData、AfterCreateNewData这类早期事件设置的值会随数据包一起下发到客户端在 HTTP 响应里能直接看到。反过来如果某个字段在界面上没值、但服务端日志显示赋过值抓包就能判断是下发阶段丢了还是客户端绑定阶段被覆盖了。这个工具在排查「值到底有没有出服务端」这类问题时比加一堆日志快得多。5. 避坑排查五个把我按在工位上的问题这些坑的共同点是报错信息指向的方向和真实原因基本无关。按「现象 → 原因 → 解决」记下来下次遇到能少绕半小时。现象一注册完插件点菜单毫无反应断点也不进。原因通常有两个层面。浅层是 DLL 没有真正落到K3Cloud\K3CloudServer\Bin\或者落的是上一版深层是 IDE 里勾选的插件类跟 DLL 里的类不是同一个改了命名空间或类名之后重新编译注册信息还指着老类型。解决先看 Bin 下 DLL 的时间戳再回 IDE 插件列表确认类名全称最后回收站点让新 DLL 真正被加载。改过类名的话把旧注册项取消勾选再勾新的别只是覆盖保存。现象二新增单据时默认值生效打开已有单据却不生效。CreateNewData和AfterCreateNewData只在新增时触发打开既有单据走的是加载已有数据的路径这两个回调根本不进来。把「打开时也要处理」的逻辑写在这里就等于只对新建单据有效。解决区分需求语义属于新建默认值的留在原处属于每次打开都要执行的比如根据当前组织刷新某个字段显示挪到AfterBindData那里加载和刷新都会调用。现象三在AfterCreateNewData里SetValue的值保存时没进库。这个阶段数据包刚创建完插件里设置的值不会进状态管理器值会随整个数据包一起发到客户端但从状态管理角度看这些字段没被「修改」过。如果后续逻辑依赖脏标记判断就会出现值看着有、保存却没带上。解决明确用途——这个回调适合做「初始展示」需要参与状态管理、需要触发后续校验和保存的赋值放到AfterBindData或者通过 IDE 里的实体服务规则配置让平台自己管状态。现象四清理了插件列表里几个不认识的插件业务数据开始出问题。单据属性里的插件列表可能包含标准产品注册的扩展点运行时会动态加载。删掉或者取消勾选表面上界面还能打开但某些字段联动、校验、下推逻辑就静默失效了。这类故障没有明确报错只能靠对比元数据发现差异。解决改动插件列表前先备份单据元数据只对自己新增的条目做增删其他条目一律不动。真需要排查某个插件的作用用取消勾选加单点验证的方式别直接删。现象五中文乱码、字符串比较结果异常或者安装检查直接不通过。根子在数据库。SQL Server 排序规则没设成Chinese_PRC_CI_ASOracle 的字符集不是AL32UTF8、国家字符集不是AL16UTF16都会在中文字段排序、比较、存储上出问题表现形式还特别分散容易误判成代码问题。解决建库阶段就按这两个值定死装完再改的代价接近重建库。接手别人搭好的环境时先查这两项再开始写代码能省掉后面大量说不清的排查。6. 事件里做减法的技巧把可见性、赋值和状态管理分开放AfterBindData是最容易被写成大杂烩的回调加载和界面刷新都会进来很多人把所有逻辑一股脑塞进去结果每次刷新都全量重算界面卡顿还查不出原因。我的习惯是把它拆成三块职责菜单和控件状态、字段可见性、依赖当前数据的展示逻辑各自独立成方法AfterBindData里只做编排。public override void AfterBindData(EventArgs e) { base.AfterBindData(e); // 主菜单项参数是工具栏按钮的 KeyIDE 里能看到 this.View.GetMainBarItem(tbNew).Visible false; // 分录菜单第一个参数是实体 Key第二个是菜单项 Key this.View.GetBarItem(FEntity, tbAdd).Enabled true; // 页签级隐藏第二个参数传 null 表示整个页签 if (_modelTypeId BASE) { this.View.StyleManager.SetVisible(FTab_Field, null, false); } }三个调用的参数口径不一样这是最容易写错的地方。GetMainBarItem只收一个菜单 Key管的是主工具栏GetBarItem要传实体 Key 加菜单 Key管的是分录工具栏StyleManager.SetVisible收控件 Key 加字段 Key第二个参数传null表示作用于整个容器。把GetBarItem的实体 Key 写成单据头标识运行时不会报错只是设置不生效这类静默失败只能靠对照 IDE 里的实体标识逐个核对。判断逻辑也别写死在代码里。上面那个_modelTypeId应该来自单据类型字段的实际值而不是常量需要按单据类型分支的场景用字段值判断比用单据标识判断更稳因为同一张单据挂不同单据类型时元数据是同一份。验证方式很简单改一个单据类型保存重新打开看页签和菜单是否按预期切换如果切了类型没变化多半是判断值取错了来源。还有一个我踩过的点一段代码不知道放哪个回调时先问「这个值需不需要进状态管理器」。需要就往后放让平台记录变更不需要只是给用户看的展示状态就往前放。想清楚这一条就不会把默认值、可见性、脏标记全挤在一个方法里。从那以后我每次新写插件都强制按「菜单状态 / 字段可见性 / 数据赋值」三块分开落方法再回头改需求时至少知道该动哪一块。希望帮到你。本文还有配套的精品资源点击获取