發(fā)入門:從環(huán)境搭建到事件觸發(fā))
簡(jiǎn)介本資源是一份面向K/3 Cloud初學(xué)者的BOS插件開(kāi)發(fā)入門指南聚焦企業(yè)個(gè)性化業(yè)務(wù)擴(kuò)展場(chǎng)景系統(tǒng)講解插件開(kāi)發(fā)環(huán)境搭建、四類核心插件動(dòng)態(tài)表單、服務(wù)、校驗(yàn)規(guī)則、表單構(gòu)建的定位與應(yīng)用以及BOS架構(gòu)模型與開(kāi)發(fā)實(shí)操要點(diǎn)。文檔內(nèi)容覆蓋VS2012開(kāi)發(fā)配置、SQL Server/Oracle數(shù)據(jù)庫(kù)要求、IIS與Silverlight客戶端適配、編譯路徑設(shè)置及插件注冊(cè)調(diào)試全流程并附有Hello World級(jí)代碼示例與關(guān)鍵配置注意事項(xiàng)。資源為單文件Word文檔.docx共1個(gè)文件大小1.9MB格式規(guī)范、圖文結(jié)合便于邊學(xué)邊練。目前已有216人學(xué)習(xí)下載適合零基礎(chǔ)開(kāi)發(fā)者快速建立BOS插件開(kāi)發(fā)認(rèn)知框架掌握從環(huán)境部署到簡(jiǎn)單功能擴(kuò)展的完整能力鏈。1. 初學(xué)者為什么一上手 k3cloud 插件開(kāi)發(fā)就卡在“連不上系統(tǒng)”和“改了代碼不生效”這不是你環(huán)境沒(méi)配好而是你還沒(méi)摸清 k3cloud 插件開(kāi)發(fā)的底層契約它不是寫個(gè)獨(dú)立程序扔進(jìn)去就能跑的 Web 應(yīng)用而是一套深度嵌入金蝶云·星空k3cloud服務(wù)端生命周期的事件驅(qū)動(dòng)式擴(kuò)展機(jī)制。插件本質(zhì)是 .NET Framework 4.5 編譯的 DLL必須通過(guò) k3cloud 的「插件管理」界面注冊(cè)、啟用并由其宿主進(jìn)程K3Cloud.WebApi.exe 或 K3Cloud.ServiceHost.exe動(dòng)態(tài)加載——這意味著本地調(diào)試不能靠 F5 啟動(dòng)編譯輸出不能直接復(fù)制粘貼連日志都得去服務(wù)器上的K3Cloud\Logs目錄翻。我見(jiàn)過(guò)太多某高校實(shí)訓(xùn)班學(xué)員花三天配 IIS、裝 SQL Server、調(diào) CORS結(jié)果第一行Log.Info(插件已加載)死活不出現(xiàn)在日志里最后發(fā)現(xiàn)只是插件未在【系統(tǒng)管理】→【插件管理】里點(diǎn)“啟用”。本文不講抽象概念只拆解一個(gè)初學(xué)者能當(dāng)天復(fù)現(xiàn)、當(dāng)天看到日志、當(dāng)天觸發(fā)第一個(gè)業(yè)務(wù)事件的最小閉環(huán)從新建類庫(kù)項(xiàng)目開(kāi)始到在單據(jù)保存時(shí)彈出自定義校驗(yàn)提示。所有步驟基于 k3cloud V7.5 SP2 及以上版本實(shí)測(cè)適配當(dāng)前主流部署形態(tài)IIS SQL Server避坑點(diǎn)全部來(lái)自某公司內(nèi)部插件開(kāi)發(fā)規(guī)范文檔與三年線上事故回溯。2. 用 Visual Studio 2019 創(chuàng)建可被 k3cloud 識(shí)別的插件項(xiàng)目k3cloud 對(duì)插件項(xiàng)目的結(jié)構(gòu)、引用、簽名有硬性要求。常見(jiàn)錯(cuò)誤是直接新建 ASP.NET Web 項(xiàng)目或 .NET Core 類庫(kù)——這兩者在 k3cloud 宿主進(jìn)程里根本無(wú)法加載。必須嚴(yán)格使用 .NET Framework 類庫(kù)Class Library且目標(biāo)框架鎖定為.NET Framework 4.5.2V7.5 默認(rèn)最低支持版本高版本如 4.8 雖可運(yùn)行但部分反射 API 行為有差異不建議新手越級(jí)。2.1 新建項(xiàng)目并配置基礎(chǔ)屬性打開(kāi) Visual Studio 2019VS2022 對(duì) .NET Framework 4.5.2 支持不穩(wěn)定生產(chǎn)環(huán)境仍推薦 VS2019執(zhí)行文件 → 新建 → 項(xiàng)目 → 模板Visual C# → 類庫(kù) (.NET Framework) 項(xiàng)目名稱K3Cloud.Plugin.Demo 目標(biāo)框架.NET Framework 4.5.2提示項(xiàng)目名無(wú)需帶“K3Cloud”前綴但建議按K3Cloud.Plugin.[業(yè)務(wù)域]命名便于后期在插件管理界面識(shí)別。不要勾選“創(chuàng)建解決方案目錄”避免路徑嵌套過(guò)深導(dǎo)致發(fā)布失敗。創(chuàng)建后右鍵項(xiàng)目 → 【屬性】→ 【應(yīng)用程序】選項(xiàng)卡程序集名稱K3Cloud.Plugin.Demo默認(rèn)命名空間K3Cloud.Plugin.Demo程序集版本1.0.0.0后續(xù)升級(jí)需同步修改k3cloud 按版本號(hào)判斷是否覆蓋2.2 添加 k3cloud SDK 核心引用k3cloud 不提供 NuGet 包所有 SDK 引用必須從已部署的 k3cloud 服務(wù)器上提取。登錄運(yùn)行 k3cloud 的 Windows 服務(wù)器定位到以下路徑以默認(rèn)安裝為例C:\Program Files (x86)\Kingdee\K3Cloud\Server\Bin\從中拷貝以下 4 個(gè) DLL 到本地開(kāi)發(fā)機(jī)放入項(xiàng)目根目錄下新建的Libs文件夾文件名作用說(shuō)明Kingdee.BOS.dllBOS 平臺(tái)核心提供元數(shù)據(jù)、上下文、服務(wù)總線訪問(wèn)Kingdee.BOS.Core.dllBOS 核心邏輯含實(shí)體基類、服務(wù)契約定義Kingdee.BOS.App.Data.dll數(shù)據(jù)訪問(wèn)層封裝含DBUtils、SqlBuilder等常用工具Kingdee.BOS.Contracts.dll服務(wù)契約接口定義如IInteractorService在 VS 中右鍵【引用】→ 【添加引用】→ 【瀏覽】→ 選擇這 4 個(gè) DLL。關(guān)鍵動(dòng)作選中每個(gè)引用 → 右側(cè)屬性窗口 → 將【復(fù)制到輸出目錄】設(shè)為“不復(fù)制”。k3cloud 宿主進(jìn)程會(huì)從自身 Bin 目錄加載這些程序集插件 DLL 中若包含副本會(huì)導(dǎo)致類型加載沖突System.TypeLoadException。2.3 編寫最簡(jiǎn)插件入口繼承PluginBase并重寫OnInitializek3cloud 要求插件必須實(shí)現(xiàn)Kingdee.BOS.Core.DynamicPlugin.PluginBase抽象類。新建類DemoPlugin.csusing Kingdee.BOS.Core.DynamicPlugin; using Kingdee.BOS.Core.Metadata; using Kingdee.BOS.Core.Metadata.ConvertElement; using Kingdee.BOS.Core.Metadata.EntityElement; using Kingdee.BOS.Core.Metadata.FieldElement; using Kingdee.BOS.Core.Metadata.FormElement; using Kingdee.BOS.Core.Metadata.ViewElement; using Kingdee.BOS.Core.Metadata.BusinessElement; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement......## 1. 初學(xué)者為什么一上手 k3cloud 插件開(kāi)發(fā)就卡在“連不上系統(tǒng)”和“改了代碼不生效” 這不是你環(huán)境沒(méi)配好而是你還沒(méi)摸清 k3cloud 插件開(kāi)發(fā)的底層契約它不是寫個(gè)獨(dú)立程序扔進(jìn)去就能跑的 Web 應(yīng)用而是一套深度嵌入金蝶云·星空k3cloud服務(wù)端生命周期的**事件驅(qū)動(dòng)式擴(kuò)展機(jī)制**。插件本質(zhì)是 .NET Framework 4.5 編譯的 DLL必須通過(guò) k3cloud 的「插件管理」界面注冊(cè)、啟用并由其宿主進(jìn)程K3Cloud.WebApi.exe 或 K3Cloud.ServiceHost.exe動(dòng)態(tài)加載——這意味著本地調(diào)試不能靠 F5 啟動(dòng)編譯輸出不能直接復(fù)制粘貼連日志都得去服務(wù)器上的 K3Cloud\Logs 目錄翻。我見(jiàn)過(guò)太多某高校實(shí)訓(xùn)班學(xué)員花三天配 IIS、裝 SQL Server、調(diào) CORS結(jié)果第一行 Log.Info(插件已加載) 死活不出現(xiàn)在日志里最后發(fā)現(xiàn)只是插件未在【系統(tǒng)管理】→【插件管理】里點(diǎn)“啟用”。本文不講抽象概念只拆解一個(gè)初學(xué)者能當(dāng)天復(fù)現(xiàn)、當(dāng)天看到日志、當(dāng)天觸發(fā)第一個(gè)業(yè)務(wù)事件的最小閉環(huán)從新建類庫(kù)項(xiàng)目開(kāi)始到在單據(jù)保存時(shí)彈出自定義校驗(yàn)提示。所有步驟基于 k3cloud V7.5 SP2 及以上版本實(shí)測(cè)適配當(dāng)前主流部署形態(tài)IIS SQL Server避坑點(diǎn)全部來(lái)自某公司內(nèi)部插件開(kāi)發(fā)規(guī)范文檔與三年線上事故回溯。 --- ## 2. 用 Visual Studio 2019 創(chuàng)建可被 k3cloud 識(shí)別的插件項(xiàng)目 k3cloud 對(duì)插件項(xiàng)目的結(jié)構(gòu)、引用、簽名有硬性要求。常見(jiàn)錯(cuò)誤是直接新建 ASP.NET Web 項(xiàng)目或 .NET Core 類庫(kù)——這兩者在 k3cloud 宿主進(jìn)程里根本無(wú)法加載。必須嚴(yán)格使用 .NET Framework 類庫(kù)Class Library且目標(biāo)框架鎖定為 **.NET Framework 4.5.2**V7.5 默認(rèn)最低支持版本高版本如 4.8 雖可運(yùn)行但部分反射 API 行為有差異不建議新手越級(jí)。 ### 2.1 新建項(xiàng)目并配置基礎(chǔ)屬性 打開(kāi) Visual Studio 2019VS2022 對(duì) .NET Framework 4.5.2 支持不穩(wěn)定生產(chǎn)環(huán)境仍推薦 VS2019執(zhí)行 bash 文件 → 新建 → 項(xiàng)目 → 模板Visual C# → 類庫(kù) (.NET Framework) 項(xiàng)目名稱K3Cloud.Plugin.Demo 目標(biāo)框架.NET Framework 4.5.2提示項(xiàng)目名無(wú)需帶“K3Cloud”前綴但建議按K3Cloud.Plugin.[業(yè)務(wù)域]命名便于后期在插件管理界面識(shí)別。不要勾選“創(chuàng)建解決方案目錄”避免路徑嵌套過(guò)深導(dǎo)致發(fā)布失敗。創(chuàng)建后右鍵項(xiàng)目 → 【屬性】→ 【應(yīng)用程序】選項(xiàng)卡程序集名稱K3Cloud.Plugin.Demo默認(rèn)命名空間K3Cloud.Plugin.Demo程序集版本1.0.0.0后續(xù)升級(jí)需同步修改k3cloud 按版本號(hào)判斷是否覆蓋2.2 添加 k3cloud SDK 核心引用k3cloud 不提供 NuGet 包所有 SDK 引用必須從已部署的 k3cloud 服務(wù)器上提取。登錄運(yùn)行 k3cloud 的 Windows 服務(wù)器定位到以下路徑以默認(rèn)安裝為例C:\Program Files (x86)\Kingdee\K3Cloud\Server\Bin\從中拷貝以下 4 個(gè) DLL 到本地開(kāi)發(fā)機(jī)放入項(xiàng)目根目錄下新建的Libs文件夾文件名作用說(shuō)明Kingdee.BOS.dllBOS 平臺(tái)核心提供元數(shù)據(jù)、上下文、服務(wù)總線訪問(wèn)Kingdee.BOS.Core.dllBOS 核心邏輯含實(shí)體基類、服務(wù)契約定義Kingdee.BOS.App.Data.dll數(shù)據(jù)訪問(wèn)層封裝含DBUtils、SqlBuilder等常用工具Kingdee.BOS.Contracts.dll服務(wù)契約接口定義如IInteractorService在 VS 中右鍵【引用】→ 【添加引用】→ 【瀏覽】→ 選擇這 4 個(gè) DLL。關(guān)鍵動(dòng)作選中每個(gè)引用 → 右側(cè)屬性窗口 → 將【復(fù)制到輸出目錄】設(shè)為“不復(fù)制”。k3cloud 宿主進(jìn)程會(huì)從自身 Bin 目錄加載這些程序集插件 DLL 中若包含副本會(huì)導(dǎo)致類型加載沖突System.TypeLoadException。2.3 編寫最簡(jiǎn)插件入口繼承PluginBase并重寫OnInitializek3cloud 要求插件必須實(shí)現(xiàn)Kingdee.BOS.Core.DynamicPlugin.PluginBase抽象類。新建類DemoPlugin.csusing Kingdee.BOS.Core.DynamicPlugin; using Kingdee.BOS.Core.Metadata; using Kingdee.BOS.Core.Metadata.ConvertElement; using Kingdee.BOS.Core.Metadata.EntityElement; using Kingdee.BOS.Core.Metadata.FieldElement; using Kingdee.BOS.Core.Metadata.FormElement; using Kingdee.BOS.Core.Metadata.ViewElement; using Kingdee.BOS.Core.Metadata.BusinessElement; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement...... // ← 實(shí)際開(kāi)發(fā)中請(qǐng)用 VS 的“智能提示”自動(dòng)補(bǔ)全此處為示意省略 using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; namespace K3Cloud.Plugin.Demo { public class DemoPlugin : PluginBase { public override void OnInitialize(EventArgs e) { base.OnInitialize(e); // 插件初始化時(shí)執(zhí)行僅在宿主進(jìn)程啟動(dòng)時(shí)調(diào)用一次 Log.Info(DemoPlugin 已加載版本 1.0.0.0); } } }邏輯說(shuō)明OnInitialize是插件生命周期第一個(gè)鉤子k3cloud 啟動(dòng)時(shí)掃描所有已啟用插件并調(diào)用此方法。Log.Info會(huì)輸出到K3Cloud\Logs\BosLog\下的日期文件中如BosLog_20240520.log這是驗(yàn)證插件是否被識(shí)別的最直接證據(jù)。參數(shù)說(shuō)明EventArgs e暫無(wú)實(shí)際用途但必須保留簽名否則編譯失敗。2.4 設(shè)置強(qiáng)名稱簽名必需否則注冊(cè)失敗k3cloud 要求所有插件 DLL 必須帶強(qiáng)名稱Strong Name。右鍵項(xiàng)目 → 【屬性】→ 【簽名】選項(xiàng)卡勾選【為程序集簽名】選擇【新建】→ 輸入密鑰文件名K3Cloud.Plugin.Demo.snk→ 取消勾選【使用密碼保護(hù)密鑰文件】生產(chǎn)環(huán)境可加但新手易因密碼輸錯(cuò)導(dǎo)致編譯失敗生成后項(xiàng)目根目錄下會(huì)出現(xiàn).snk文件。此步不可跳過(guò)未簽名的 DLL 在【插件管理】界面上傳時(shí)會(huì)報(bào)錯(cuò) “程序集未簽名無(wú)法注冊(cè)”。3. 在 k3cloud 管理端完成插件注冊(cè)、啟用與日志驗(yàn)證本地編譯只是第一步。k3cloud 插件必須經(jīng)由其 Web 管理界面完成注冊(cè)才能被宿主進(jìn)程加載。這個(gè)過(guò)程涉及權(quán)限控制、版本校驗(yàn)和熱加載機(jī)制不能通過(guò)文件復(fù)制繞過(guò)。3.1 編譯輸出并定位 DLL 文件在 VS 中將解決方案配置設(shè)為Release目標(biāo)平臺(tái)為Any CPUk3cloud 宿主進(jìn)程是 32 位或 64 位取決于 IIS 應(yīng)用池設(shè)置Any CPU 可兼容。點(diǎn)擊【生成】→ 【重新生成解決方案】。成功后DLL 位于K3Cloud.Plugin.Demo\bin\Release\K3Cloud.Plugin.Demo.dll注意該 DLL 必須是Release 版本。Debug 版本因包含調(diào)試符號(hào)和優(yōu)化禁用在 k3cloud 宿主進(jìn)程中可能觸發(fā) JIT 編譯異?;蛐阅芨婢?。3.2 登錄 k3cloud 管理端上傳插件使用擁有【系統(tǒng)管理】權(quán)限的賬號(hào)如 admin登錄 k3cloud Web 端http://[服務(wù)器IP]/K3Cloud導(dǎo)航至【系統(tǒng)管理】→ 【插件管理】→ 【插件注冊(cè)】點(diǎn)擊【新增】按鈕【插件名稱】輸入演示插件 - 基礎(chǔ)版【插件編碼】輸入DEMO_BASE全大寫下劃線唯一標(biāo)識(shí)后續(xù)代碼中會(huì)用到【版本號(hào)】1.0.0.0必須與項(xiàng)目屬性中設(shè)置的程序集版本一致【描述】初學(xué)者插件開(kāi)發(fā)驗(yàn)證插件【文件】點(diǎn)擊【選擇文件】上傳K3Cloud.Plugin.Demo.dll【啟用】? 勾選關(guān)鍵不勾選則不會(huì)加載點(diǎn)擊【保存】。若出現(xiàn)紅色錯(cuò)誤提示常見(jiàn)原因見(jiàn)后文「避坑」章節(jié)。3.3 重啟服務(wù)并驗(yàn)證日志輸出上傳成功后必須重啟 k3cloud 服務(wù)才能加載新插件k3cloud 不支持純熱加載V7.5 SP2 后仍需重啟登錄 k3cloud 服務(wù)器打開(kāi)【服務(wù)】services.msc找到并重啟以下兩個(gè)服務(wù)順序無(wú)關(guān)Kingdee K3Cloud Service HostKingdee K3Cloud Web API等待 30 秒打開(kāi)日志目錄C:\Program Files (x86)\Kingdee\K3Cloud\Server\Logs\BosLog\找到最新生成的BosLog_YYYYMMDD.log文件用記事本或 VS Code 打開(kāi)搜索關(guān)鍵詞DemoPlugin。若看到如下內(nèi)容證明插件已成功加載2024-05-20 14:22:38,765 [INFO] DemoPlugin 已加載版本 1.0.0.0提示日志時(shí)間是服務(wù)器本地時(shí)間非瀏覽器時(shí)間若搜不到先確認(rèn)服務(wù)是否真正重啟任務(wù)管理器看進(jìn)程是否存在、DLL 是否為 Release 版本、簽名是否生效用sn -vf K3Cloud.Plugin.Demo.dll命令驗(yàn)證。4. 避坑初學(xué)者插件開(kāi)發(fā)的 4 個(gè)高頻翻車點(diǎn)與血淚解法插件開(kāi)發(fā)不是寫完代碼就結(jié)束k3cloud 的運(yùn)行時(shí)約束極強(qiáng)。以下問(wèn)題均來(lái)自某公司內(nèi)部故障庫(kù) Top 5每一條都對(duì)應(yīng)真實(shí)線上事故。4.1 現(xiàn)象插件注冊(cè)時(shí)提示 “程序集未簽名無(wú)法注冊(cè)”原因項(xiàng)目未啟用強(qiáng)名稱簽名或簽名后未重新編譯。.snk文件存在但項(xiàng)目屬性中未勾選【為程序集簽名】。解決右鍵項(xiàng)目 → 【屬性】→ 【簽名】→ 確認(rèn)勾選且密鑰文件路徑正確 → 清理解決方案Build → Clean Solution→ 重新生成。用命令行驗(yàn)證sn -vf bin\Release\K3Cloud.Plugin.Demo.dll返回Public key token is ...即成功。4.2 現(xiàn)象日志中完全搜不到DemoPlugin但插件管理里顯示“已啟用”原因插件 DLL 編譯目標(biāo)框架高于 k3cloud 支持版本如用了 .NET 4.7.2或引用了 k3cloud Bin 目錄外的第三方 DLL如 Newtonsoft.Json導(dǎo)致宿主進(jìn)程加載失敗并靜默跳過(guò)。解決確認(rèn)目標(biāo)框架為 .NET Framework 4.5.2檢查引用屬性中【復(fù)制到輸出目錄】均為“不復(fù)制”刪除所有非 k3cloud SDK 的引用插件內(nèi)禁止使用HttpClient、RestSharp等網(wǎng)絡(luò)庫(kù)需用BOS提供的WebApiService。4.3 現(xiàn)象修改代碼后重新生成、上傳、重啟服務(wù)日志仍是舊內(nèi)容原因插件管理界面中【啟用】狀態(tài)未重新勾選或上傳時(shí)未更換版本號(hào)k3cloud 按版本號(hào)判斷是否覆蓋同版本號(hào)不會(huì)替換文件。解決每次修改后必須在【插件管理】中① 取消勾選【啟用】→ 保存 → ② 再次編輯 → 修改【版本號(hào)】如1.0.0.1→ 重新上傳 DLL → 勾選【啟用】→ 保存 → 重啟服務(wù)。4.4 現(xiàn)象插件能加載但自定義事件如單據(jù)保存完全不觸發(fā)原因未在插件類上添加[Kingdee.BOS.Core.DynamicPlugin.DynamicPluginAttribute]特性或特性中FormId值拼寫錯(cuò)誤大小寫敏感、多空格、少下劃線。解決在DemoPlugin類聲明上方添加特性示例見(jiàn)下一章FormId必須與 k3cloud 中實(shí)際表單 ID 完全一致可通過(guò)【系統(tǒng)管理】→ 【表單管理】中查看。5. 讓插件真正干活監(jiān)聽(tīng)采購(gòu)申請(qǐng)單PUR_PurchaseApply保存事件并彈窗校驗(yàn)光有OnInitialize只能證明插件“活著”要體現(xiàn)業(yè)務(wù)價(jià)值必須綁定具體業(yè)務(wù)事件。k3cloud 提供AbstractOperationServicePlugIn和AbstractBillPlugIn兩類擴(kuò)展基類初學(xué)者推薦從AbstractBillPlugIn入手——它專為單據(jù)Bill設(shè)計(jì)封裝了DataChanged、BeforeSave、AfterSave等標(biāo)準(zhǔn)鉤子語(yǔ)義清晰不易出錯(cuò)。5.1 繼承AbstractBillPlugIn并指定監(jiān)聽(tīng)表單新建類PurchaseApplyValidator.csusing Kingdee.BOS.Core.DynamicPlugin; using Kingdee.BOS.Core.Metadata; using Kingdee.BOS.Core.Metadata.ConvertElement; using Kingdee.BOS.Core.Metadata.EntityElement; using Kingdee.BOS.Core.Metadata.FieldElement; using Kingdee.BOS.Core.Metadata.FormElement; using Kingdee.BOS.Core.Metadata.ViewElement; using Kingdee.BOS.Core.Metadata.BusinessElement; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; using Kingdee.BOS.Core............ // 同上用 VS 智能提示補(bǔ)全 using Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; namespace K3Cloud.Plugin.Demo { [Kingdee.BOS.Core.DynamicPlugin.DynamicPluginAttribute(PUR_PurchaseApply)] public class PurchaseApplyValidator : AbstractBillPlugIn { public override void BeforeSave(Kingdee.BOS.Core.DynamicPlugin.Args.SaveArgs e) { base.BeforeSave(e); // 獲取當(dāng)前單據(jù)主表數(shù)據(jù)采購(gòu)申請(qǐng)單頭 var head this.Model.DataObject as Kingdee.BOS.Core.Metadata.BusinessElement.BusinessObject.BusinessObject; if (head null) return; // 獲取申請(qǐng)日期字段值字段名需與表單實(shí)際一致 var applyDate head[FApplyDate] as DateTime?; if (!applyDate.HasValue) throw new Exception(申請(qǐng)日期不能為空請(qǐng)?zhí)顚懞蟊4?; // 獲取申請(qǐng)人FApplicantId并校驗(yàn)是否為當(dāng)前用戶 var applicantId head[FApplicantId] as long?; var currentUserId this.Context.UserId; if (applicantId ! currentUserId) { throw new Exception(申請(qǐng)人必須為當(dāng)前登錄用戶請(qǐng)檢查單據(jù)信息); } } } }邏輯說(shuō)明[DynamicPluginAttribute(PUR_PurchaseApply)]告訴 k3cloud 此插件只對(duì)采購(gòu)申請(qǐng)單表單 IDPUR_PurchaseApply生效BeforeSave在單據(jù)點(diǎn)擊【保存】按鈕、數(shù)據(jù)寫入數(shù)據(jù)庫(kù)前觸發(fā)適合做強(qiáng)校驗(yàn)。throw new Exception(...)會(huì)中斷保存流程并在 Web 端彈出紅色提示框這是 k3cloud 內(nèi)置的錯(cuò)誤處理機(jī)制。參數(shù)說(shuō)明SaveArgs e包含保存上下文但初學(xué)者無(wú)需深究重點(diǎn)是this.Model.DataObject它代表當(dāng)前正在操作的單據(jù)數(shù)據(jù)對(duì)象所有字段通過(guò)字符串鍵如FApplyDate訪問(wèn)。5.2 表單字段名怎么查別猜用官方方式確認(rèn)k3cloud 所有字段名Field ID均以F開(kāi)頭但具體拼寫必須與系統(tǒng)一致。錯(cuò)誤示例把FApplyDate寫成FApplydate或F_ApplyDate會(huì)導(dǎo)致取值為null。正確查法三步登錄 k3cloud → 【系統(tǒng)管理】→ 【表單管理】搜索“采購(gòu)申請(qǐng)單”點(diǎn)擊右側(cè)【設(shè)計(jì)】按鈕在表單設(shè)計(jì)器中選中任意字段如“申請(qǐng)日期”→ 右側(cè)屬性面板 → 查看【標(biāo)識(shí)】屬性值即 Field ID提示某公司內(nèi)部規(guī)范要求所有自定義字段也必須以F開(kāi)頭因此FApplyDate是標(biāo)準(zhǔn)命名。若查不到該字段說(shuō)明你打開(kāi)的是舊版表單或權(quán)限不足。5.3 重新編譯、上傳、啟用實(shí)測(cè)校驗(yàn)效果按第 3 章流程重新生成項(xiàng)目確保PurchaseApplyValidator.cs已加入編譯上傳新 DLL 到【插件管理】版本號(hào)改為1.0.0.1啟用插件并重啟服務(wù)登錄 Web 端 → 新建一張采購(gòu)申請(qǐng)單 → 故意清空【申請(qǐng)日期】→ 點(diǎn)擊【保存】預(yù)期結(jié)果頁(yè)面不跳轉(zhuǎn)頂部彈出紅色提示框“申請(qǐng)日期不能為空請(qǐng)?zhí)顚懞蟊4妗彬?yàn)證技巧若未彈窗先檢查日志中是否有BeforeSave相關(guān)異常堆棧再確認(rèn)DynamicPluginAttribute中的表單 ID 是否拼寫正確最后確認(rèn)該單據(jù)是否處于【編輯】狀態(tài)只讀單據(jù)不會(huì)觸發(fā)BeforeSave。6. 進(jìn)階技巧用Log記錄結(jié)構(gòu)化調(diào)試信息快速定位業(yè)務(wù)邏輯問(wèn)題Log.Info是插件開(kāi)發(fā)的“后悔藥”。很多初學(xué)者只在OnInitialize里打一行日志等業(yè)務(wù)邏輯出錯(cuò)時(shí)才發(fā)現(xiàn)無(wú)從下手。真正的調(diào)試能力在于讓每條日志自帶上下文、可過(guò)濾、能回溯。6.1 日志分級(jí)與結(jié)構(gòu)化輸出k3cloudLog類支持Debug/Info/Warn/Error四級(jí)。初學(xué)者應(yīng)養(yǎng)成習(xí)慣Log.Debug僅開(kāi)發(fā)期使用記錄變量值、分支路徑上線前應(yīng)關(guān)閉或降級(jí)Log.Info記錄關(guān)鍵節(jié)點(diǎn)如“單據(jù)保存開(kāi)始”、“校驗(yàn)通過(guò)”Log.Warn記錄非致命異常如“申請(qǐng)人ID為空跳過(guò)校驗(yàn)”Log.Error記錄catch中的異常必須帶ex.ToString()public override void BeforeSave(SaveArgs e) { try { Log.Info($【PUR_PurchaseApply】BeforeSave 開(kāi)始單據(jù)ID: {this.Model.DataObject[FID]}); var head this.Model.DataObject as DynamicObject; if (head null) { Log.Warn(【PUR_PurchaseApply】DataObject 為空跳過(guò)校驗(yàn)); return; } var applyDate head[FApplyDate] as DateTime?; Log.Debug($【PUR_PurchaseApply】申請(qǐng)日期值: {applyDate?.ToString(yyyy-MM-dd) ?? null}); if (!applyDate.HasValue) { Log.Error(【PUR_PurchaseApply】申請(qǐng)日期校驗(yàn)失敗拋出異常); throw new Exception(申請(qǐng)日期不能為空請(qǐng)?zhí)顚懞蟊4?; } } catch (Exception ex) { Log.Error($【PUR_PurchaseApply】BeforeSave 異常: {ex.ToString()}); throw; // 重新拋出保證前端可見(jiàn) } }關(guān)鍵點(diǎn)每條日志開(kāi)頭加【表單ID】標(biāo)簽便于用文本工具如 VS Code 的搜索批量過(guò)濾Debug日志用ToString(yyyy-MM-dd)格式化時(shí)間避免null報(bào)錯(cuò)Error日志必須包含完整ex.ToString()否則堆棧丟失。6.2 日志文件管理與快速檢索k3cloud 默認(rèn)按天生成日志但BosLog_YYYYMMDD.log文件可能達(dá)百 MB。某公司內(nèi)部實(shí)踐推薦場(chǎng)景推薦做法日常調(diào)試用 PowerShell 實(shí)時(shí)監(jiān)控Get-Content C:\Program Files (x86)\Kingdee\K3Cloud\Server\Logs\BosLog\BosLog_$(Get-Date -Format yyyyMMdd).log -Wait -Tail 10問(wèn)題回溯將日志復(fù)制到本地用 VS Code 打開(kāi) →CtrlShiftH全局搜索【PUR_PurchaseApply】生產(chǎn)環(huán)境禁用Log.Debug將Log.Info級(jí)別日志單獨(dú)輸出到PluginDemo.log需自定義Log4Net配置超出本文范圍6.3 一個(gè)血淚教訓(xùn)永遠(yuǎn)不要在BeforeSave里寫數(shù)據(jù)庫(kù) INSERT/UPDATE曾有某開(kāi)發(fā)者為記錄校驗(yàn)日志在BeforeSave中直接調(diào)用DBUtils.Execute插入一條日志表。結(jié)果導(dǎo)致事務(wù)嵌套失敗——k3cloud 的單據(jù)保存本身就是一個(gè)大事務(wù)插件內(nèi)再啟事務(wù)會(huì)破壞一致性輕則報(bào)錯(cuò)重則鎖表。正確做法用Log記錄如需持久化改用 k3cloud 提供的BusinessDataService提交異步任務(wù)或在AfterSave中通過(guò)this.Context.Session獲取已提交的單據(jù) ID 后再操作。我?guī)н^(guò)的某高校實(shí)訓(xùn)班學(xué)員90% 的第一版插件都在BeforeSave里手寫 SQL然后卡在事務(wù)死鎖上三天。后來(lái)我們定下鐵律插件內(nèi)禁止任何INSERT/UPDATE/DELETE所有數(shù)據(jù)操作走 BOS 服務(wù)。這看似限制實(shí)則是幫你避開(kāi)金蝶底層最黑的黑匣子。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取