金蝶云星空插件实战开发-新手保姆级入门教程-自定义WebAPI

本文为金蝶云星空自定义WebAPI开发的新手教程。内容为技术类文章,而且内容较长。做星空系统二次开发的新手朋友,可以收藏作为参考。非技术职业的朋友,可以直接划走,以免耽误您的宝贵时间。

文章末尾可以找到本教程源码仓库地址。

服务插件和表单插件的教程:

金蝶云星空插件实战开发-服务插件

金蝶云星空插件实战开发-新手保姆级教程-表单插件

阅读对象:云星空二次开发新手需求场景:使用Postman模拟第三方系统更新采购订单,提交并审核采购订单开发工具:Visual Studio 2019开发语言:C#星空版本:7.6.0插件类型:自定义WebAPI

说明:本文前提是开发机已经安装好金蝶云星空系统和金蝶BOS IDE。

金蝶云社区课程详情-课程详情-金蝶云社区官网

星空系统集成有以下三种方式:1. WebAPI2. WebService3. ClientProxy(客户代理接口)系统集成是个什么意思呢?

翻译一下就是星空系统与第三方系统进行数据对接。WebAPI常用的API接口:1. AuthService.ValidateUser(登录验证)2. View(查看单据)3. Save(保存单据)4. BatchSave(批量保存)5. Submit(提交单据)6. Audit(审核单据)7. UnAudit(反审核单据)8. Delete(删除单据)9. ExecuteBillQuery(单据数据查询)10. 自定义WebAPI11. Draft(暂存单据)12. Allocate(分配表单数据)13. Push(下推)14. GroupSave(分组保存)使用WebAPI通用的步骤是登录、构建和传递Model数据包。

WebAPI的工作原理通俗来说,就是在模拟录单的过程,根据传入的JSON数据包构造的每个字段值,按顺序逐一模拟录单填充对应的字段值,触发此字段的相关值更新事件、实体服务和插件逻辑。

构造完成整个单据的数据包之后,调用Save(保存)、Submit(提交)、Audit(审核),并对应触发保存、提交、审核的操作校验、操作逻辑、操作单据插件逻辑等整个过程。既然录单的顺序已经规定了,那么录入顺序也需要按照系统字段列表属性顺序,从前往后,否则就存在一定的相互覆盖值的情况。

熟悉MVC结构的研发应该都清楚,.Net框架的基础结构是通过基础管道中构建HttpApplication和HttpContext,对Http.Sys接收到的请求,进行管道处理。到达HttpModule进行拦截过后,通过路由节点转到正确的Controller进行业务处理。整个管道过程处理认证、授权等一系列问题,最后在星空的KDsvcHandle的HttpHandle拦截处理业务请求。星空的自定义WebAPI利用的也是这个思路,继承KDBaseService,请求路径是xxx.common.kdsvc,到达KDSvcHandle后,通过反射执行我们的操作服务。

步骤:1. 创建Visual C#类库2. 添加星空系统类库的引用3. 编写自定义WebAPI4. 编译代码生成dll文件5. 部署6. 重启IIS服务7. 使用Postman模拟请求

1.创建Visual C#类库

打开Visual Studio IDE,在启动窗口中选择“创建新项目”选项。如下图所示:

在Visual Studio IDE的项目类型列表中找到“类库(.NET Framework)”选项。如下图所示:

点击“下一步”按钮,配置项目信息。【重要】项目名称是一个比较重要的配置项,金蝶官方在《二次开发规范》中有说明。按照规范我们暂时将项目命名定为:Test.K3Cloud.SCM.WebAPI.ServerExtend【重要】框架选择 .NET Framework 4具体配置如下图所示:

2.添加星空系统类库的引用

在Visual Studio IDE的解决方案资源管理器窗口中,选择“引用”,点击右键,呼出右键菜单,选择“添加引用”选项。

打开“应用管理器”窗口,选择“Kingdee.BOS.dll“和”Kingdee.BOS.Core.dll”。如果窗口列表中没有此类库,可以点击窗口下方的“浏览”按钮,在星空系统的安装目录中找到此类库文件。

默认目录为:C:\Program Files (x86)\Kingdee\K3Cloud\WebSite\Bin

用同样的方式,我们再引入以下金蝶动态链接库文件:

以上库文件都存在于C:\Program Files (x86)\Kingdee\K3Cloud\WebSite\Bin

3.编写自定义WebAPI

代码组织:1. BusinessService.cs2. Defineds.cs3. Checker.cs4. Formater.cs5. BillHandler.csBusinessService.cs 是程序逻辑的入口,进行参数检查和单据方法调用;Defineds.cs 声明和定义程序需要的常量、请求和返回参数的结构对象;Checker.cs 参数检查;Formater.cs 请求参数格式化和组织。将客户端请求的参数对WebAPI请求参数的要求和格式进行适配;BillHandler.cs 单据的保存、提交和审核操作。自定义WebAPI继承与AbstractWebApiBusinessService。

下面的代码是最基础的代码,我们后面的开发都是在这几行代码的基础来展开。我们首先将“Class1.cs”重命名为“BusinessService”。

当然我们也可以使用其他的名称。代码如下:

using System; using System.Collections.Generic; using System.Linq; using System.Text; using Kingdee.BOS.ServiceFacade.KDServiceFx; using Kingdee.BOS.WebApi.ServicesStub; namespace Test.K3Cloud.SCM.WebAPI.ServerExtend { public class BusinessService : AbstractWebApiBusinessService { // 构造函数 public BusinessService(KDServiceContext context) : base(context) { } } }

接下来我们新建一个名为“Defineds.cs”的类文件,这个类用于声明程序中需要用到的常量和公用的struct。如下图所示:

具体代码如下:

using System; using System.Collections.Generic; using System.Linq; using System.Text; using Newtonsoft.Json.Linq; namespace Test.K3Cloud.SCM.WebAPI.ServerExtend { public class Defineds { public const string DefaultSuccessMsg = "星空系统单据处理成功"; } /// <summary> /// API返回结果对象 /// </summary> public struct Result { public bool success;// 请求执行状态,成功:true;失败:false; public string msg;// 返回描述信息 public JObject data;// 请求返回的数据 } /// <summary> /// API请求参数的结构体 /// </summary> public struct Parameter { public string LocalFormId;// 上游单据ID public string EntryKey; // 下游单据明细单据单据ID public JObject Model; // 具体参数 } /// <summary> /// 保存接口请求参数 /// </summary> public struct RequestSave { public JArray NeedUpDateFields; // 需要更新的字段,数组类型,格式:[key1,key2,...] (非必录)注(更新单据体字段得加上单据体key public JArray NeedReturnFields; // 需返回结果的字段集合,数组类型,格式:[key,entitykey.key,...](非必录) 注(返回单据体字段格式:entitykey.key) public bool IsDeleteEntry;// 是否删除已存在的分录,布尔类型,默认true(非必录) public string SubSystemId;// 表单所在的子系统内码,字符串类型(非必录) public bool IsVerifyBaseDataField;// 是否验证所有的基础资料有效性,布尔类,默认false(非必录) public bool IsEntryBatchFill; // 是否批量填充分录,默认true(非必录) public bool ValidateFlag; // 是否验证标志,布尔类型,默认true(非必录) public bool NumberSearch; // 是否用编码搜索基础资料,布尔类型,默认true(非必录) public string InterationFlags;// 交互标志集合,字符串类型,分号分隔,格式:"flag1;flag2;..."(非必录) 例如(允许负库存标识:STK_InvCheckResult) public JObject Model; // 表单数据包,JSON类型(必录) } /// <summary> /// 提交接口请求参数 /// </summary> public struct RequestSubmit { public long CreateOrgId; // 创建者组织内码,字符串类型(非必录) public JArray Numbers; // 单据编码集合,数组类型,格式:[No1,No2,...](使用编码时必录) public string Ids; // 单据内码集合,字符串类型,格式:"Id1,Id2,..."(使用内码时必录) public long SelectedPostId;// 工作流发起员工岗位内码,整型(非必录) 注(员工身兼多岗时不传参默认取第一个岗位) public bool NetworkCtrl; // 是否启用网控,布尔类型,默认false(非必录) } /// <summary> /// 审核接口请求参数 /// </summary> public struct RequestAudit { public long CreateOrgId; // 创建者组织内码,字符串类型(非必录) public JArray Numbers;// 单据编码集合,数组类型,格式:[No1,No2,...](使用编码时必录) public string Ids;// 单据内码集合,字符串类型,格式:"Id1,Id2,..."(使用内码时必录) public string InterationFlags;// 交互标志集合,字符串类型,分号分隔,格式:"flag1;flag2;..."(非必录) 例如(允许负库存标识:STK_InvCheckResult) public bool NetworkCtrl;// 是否启用网控,布尔类型,默认false(非必录) } /// <summary> /// 接口返回数据 /// </summary> public struct ResponseData { public ResponseDataResult Result; } /// <summary> /// 返回数据结构 /// </summary> public struct ResponseDataResult { public ResponseDataResultStatus ResponseStatus; public long ID;// 单据内码 public string Number; // 单据编号 public JArray NeedReturnData; // 返回结果的字段集合 } /// <summary> /// 返回结果状态 /// </summary> public struct ResponseDataResultStatus { public bool IsSuccess;// 是否操作成功 public long ErrorCode; public List<ResponseDataResultErrors> Errors; // 失败错误信息 public List<ResponseDataResultSuccessEntitys> SuccessEntitys;// 操作成功的实体信息 public List<ResponseDataResultSuccessMessages> SuccessMessages;// 成功消息 public long MsgCode; // 消息代码 } /// <summary> /// 返回结果中错误信息结构 /// </summary> public struct ResponseDataResultErrors { public string FieldName; public string Message; public long DIndex; } /// <summary> /// 返回结果中成功的明细 /// </summary> public struct ResponseDataResultSuccessEntitys { public long Id; public string Number; public long DIndex; } /// <summary> /// 返回结果中成功的信息 /// </summary> public struct ResponseDataResultSuccessMessages { public string FieldName; public string Message; public long DIndex; } /// <summary> /// 返回结果,包含执行状态 /// </summary> public struct ResponseDataResultConvertStatus { public bool IsSuccess;// 是否操作成功 public long ErrorCode; public List<ResponseDataResultErrors> Errors; // 失败错误信息 public List<ResponseDataResultSuccessEntitys> SuccessEntitys;// 操作成功的实体信息 public List<ResponseDataResultSuccessMessages> SuccessMessages;// 成功消息 public long MsgCode; // 消息代码 } }

在场景需求中,我们要对“采购订单”进行更新、提交和审核的操作,所以上面的代码中,我们先定义了保存、提交和审核对应的WebAPI动作的参数结构。

同时我们也同时定义了客户端请求参数和处理结果的结构。保存、提交和审核的WebAPI请求参数的字段,代码中都有注释,当然我们也可以在官方文档中查阅到。在客户端请求参数的结构中“LocalFormId”是需要处理的单据的FormId。

“Model”是单据的实际数据。那么问题来了,这个FormId我们在哪里能找到呢?获取单据的FormId有两种比较直接和方便的方式:1. 在官方文档中查找2. 在BOS中查看本例中,我们需要操作的是“采购订单”,那么我们先从官方文档中找到FormId。

如下图所示:

在BOS中找到FormId,如下图所示:

我们可以看到,“采购订单”FormId的值是“PUR_PurchaseOrder”,这里我们先记下来,后面模拟客户端请求时,需要用到它。

EntryKey 是单据数据结构的明细的熟悉Key。

也可以在官方文档和BOS中找到。如下图所示:

这里我们可以看到,明细属性的Key的值为“FPOOrderEntry”。

这里我们先记下来,后面模拟客户端请求时,需要用到它。

接下来,我们开始处理业务逻辑。

处理业务逻辑,首先要做的就是对客户端传递过来的参数进行检查校验。

我们用上面同样的方式,新建一个名为“Checker.cs”的类文件。

我们将客户端传递过来的参数的校验的方法,放到这个类文件中。代码如下:

using System; using System.Collections.Generic; using System.Linq; using System.Text; using Kingdee.BOS.Util; namespace Test.K3Cloud.SCM.WebAPI.ServerExtend { public class Checker { /// <summary> /// 检查客户端传递过来的参数。 /// 客户端参数为JSON格式 /// </summary> /// <param name="parameters"></param> /// <returns></returns> public static Result clientParameterChecker(string parameters) { Result result = new Result(); if (parameters == null) { result.msg = "接口请求参数不能为空"; return result; } if (parameters.IsEmpty()) { result.msg = "接口请求参数不能为空"; return result; } result.success = true; return result; } /// <summary> /// 检查 Model /// </summary> /// <param name="parameter"></param> /// <returns></returns> public static Result checkModel(Parameter parameter) { Result result = new Result(); if (parameter.Model == null) { result.msg = "请求参数中固定参数 Model 不能为空"; return result; } result.success = true; return result; } /// <summary> /// 检查业务参数中的 FID 和 FBillNo /// </summary> /// <param name="parameter"></param> /// <returns></returns> public static Result checkFIDAndBillNo(Parameter parameter) { bool hasFID = false; bool hasFBillNo = false; if (parameter.Model.Property("FID") != null || Convert.ToInt64(parameter.Model["FID"]) > 0) { hasFID = true; } if (parameter.Model.Property("FBillNo") != null || Convert.ToString(parameter.Model["FBillNo"]).Length > 0) { hasFBillNo = true; } Result result = new Result(); if (!hasFID && !hasFBillNo) { result.msg = "单据内码(FID)或者单据编码(FBillNo)不能同时为空"; return result; } result.success = true; return result; } } }

本教程是入门教程,目的是为了帮助我们新手们了解自定义WebAPI的基本原理和流程。

因此,参数的校验没有做得很严格。

在实际的项目中需要对客户端传递的参数进行严格地校验,切记!切记!

接下来,我们编写WebAPI保存、提交和审核三个动作的API的请求参数的格式化方法。

用于后面调用系统标准WebAPI。

新建一个名为“Formater.cs”的类文件。代码如下:

using System; using System.Collections.Generic; using System.Linq; using System.Text; using Kingdee.BOS.Util; using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace Test.K3Cloud.SCM.WebAPI.ServerExtend { public class Formater { /// <summary> /// 更新 /// </summary> /// <param name="parameter"></param> /// <returns></returns> public static string Update(Parameter parameter) { RequestSave param = new RequestSave(); param.NeedUpDateFields = new JArray(); param.NeedReturnFields = new JArray(); param.IsDeleteEntry = false; param.SubSystemId = ""; param.IsVerifyBaseDataField = false; param.IsEntryBatchFill = true; param.ValidateFlag = true; param.NumberSearch = true; param.Model = new JObject(); if ((parameter.Model.Property("FID") == null || parameter.Model["FID"] == null) && (parameter.Model.Property("FBillNo") == null || parameter.Model["FBillNo"] == null)) { return JsonConvert.SerializeObject(param); } if (parameter.Model.Property(parameter.EntryKey) == null || parameter.Model[parameter.EntryKey] == null) { return JsonConvert.SerializeObject(param); } param.NeedReturnFields = NeedReturnFields(); // 组织需要更新的字段 JArray needUpDateFields = new JArray(); JArray entryData = parameter.Model[parameter.EntryKey] as JArray; foreach (JProperty item in parameter.Model.Properties()) { if (item.Name != "FID" && item.Name != "FBillNo" && item.Name != parameter.EntryKey) { needUpDateFields.Add(item.Name); } } needUpDateFields.Add(parameter.EntryKey); JObject entryDataZero = entryData[0] as JObject; foreach (JProperty item in entryDataZero.Properties()) { if (item.Name != "FEntryID") { needUpDateFields.Add(item.Name); } } param.NeedUpDateFields = needUpDateFields; param.Model = parameter.Model; return JsonConvert.SerializeObject(param); } /// <summary> /// 提交 /// </summary> /// <param name="parameter"></param> /// <returns></returns> public static string Submit(Parameter parameter) { RequestSubmit param = new RequestSubmit(); param.CreateOrgId = 0; param.Numbers = new JArray(); param.Ids = ""; param.SelectedPostId = 0; param.NetworkCtrl = true; param.Numbers = formatNumbers(parameter); if (param.Numbers.Count() == 0) { param.Ids = formatFID(parameter); } return JsonConvert.SerializeObject(param); } /// <summary> /// 审核 /// </summary> /// <param name="parameter"></param> /// <returns></returns> public static string Audit(Parameter parameter) { RequestAudit param = new RequestAudit(); param.CreateOrgId = 0; param.Numbers = new JArray(); param.Ids = ""; param.InterationFlags = ""; param.NetworkCtrl = true; param.Numbers = formatNumbers(parameter); if (param.Numbers.Count() == 0) { param.Ids = formatFID(parameter); } return JsonConvert.SerializeObject(param); } /// <summary> /// 格式化单据编号 /// </summary> /// <param name="parameter"></param> /// <returns></returns> private static JArray formatNumbers(Parameter parameter) { JArray jNumbers = new JArray(); if (parameter.Model.Property("FBillNo") == null && parameter.Model["FBillNo"] == null) { return jNumbers; } string numbers = Convert.ToString(parameter.Model["FBillNo"]); if (numbers.IsEmpty()) { return jNumbers; } if (numbers.IndexOf(",") > 0) { string[] numberList = numbers.Split(,); foreach (string number in numberList) { jNumbers.Add(new JValue(number.Trim())); } return jNumbers; } jNumbers.Add(new JValue(numbers)); return jNumbers; } /// <summary> /// 格式化单据内码 /// </summary> /// <param name="parameter"></param> /// <returns></returns> private static string formatFID(Parameter parameter) { string ids = ""; if (parameter.Model.Property("FID") == null && parameter.Model["FID"] == null) { return ids; } return Convert.ToString(parameter.Model["FID"]); } /// <summary> /// 需要返回的字段 /// </summary> /// <returns></returns> private static JArray NeedReturnFields() { JArray needReturnFields = new JArray { new JValue("FID"), new JValue("FBillNo") }; return needReturnFields; } /// <summary> /// 格式化WebAPI返回结果 /// </summary> /// <param name="res"></param> /// <returns></returns> public static ResponseData ConvertWebAPIResult(object res) { Dictionary<string, object> _res = res as Dictionary<string, object>; Dictionary<string, object> result = _res["Result"] as Dictionary<string, object>; ResponseData responseData = new ResponseData(); responseData.Result = ConvertWebAPIResultResponseDataResult(result); return responseData; } /// <summary> /// 格式化WebAPI返回结果中的Result /// </summary> /// <param name="res"></param> /// <returns></returns> public static ResponseDataResult ConvertWebAPIResultResponseDataResult(object res) { Dictionary<string, object> result = res as Dictionary<string, object>; ResponseDataResult responseDataResult = new ResponseDataResult(); if (result.ContainsKey("Id") && result["Id"] != null) { responseDataResult.ID = Convert.ToInt64(result["Id"]); } if (result.ContainsKey("Number") && result["Number"] != null) { responseDataResult.Number = Convert.ToString(result["Number"]); } responseDataResult.ResponseStatus = ConvertWebAPIResultData(result["ResponseStatus"]); if (result.ContainsKey("NeedReturnData") && result["NeedReturnData"] != null) { responseDataResult.NeedReturnData = result["NeedReturnData"] as JArray; } return responseDataResult; } /// <summary> /// 格式化WebAPI返回结果中的结果数据 /// </summary> /// <param name="res"></param> /// <returns></returns> public static ResponseDataResultStatus ConvertWebAPIResultData(object res) { Dictionary<string, object> result = res as Dictionary<string, object>; ResponseDataResultStatus responseDataResult = new ResponseDataResultStatus(); if (result.ContainsKey("IsSuccess")) { responseDataResult.IsSuccess = Convert.ToBoolean(result["IsSuccess"]); } else { responseDataResult.IsSuccess = false; } if (result.ContainsKey("MsgCode")) { responseDataResult.MsgCode = Convert.ToInt64(result["MsgCode"]); } else { responseDataResult.MsgCode = 0; } responseDataResult.Errors = ConvertWebAPIResultErrors(result["Errors"]); responseDataResult.SuccessEntitys = ConvertWebAPIResultSuccessEntitys(result["SuccessEntitys"]); responseDataResult.SuccessMessages = ConvertWebAPIResultSuccessMessages(result["SuccessMessages"]); return responseDataResult; } /// <summary> /// 格式化WebAPI返回结果中的错误信息 /// </summary> /// <param name="res"></param> /// <returns></returns> public static List<ResponseDataResultErrors> ConvertWebAPIResultErrors(object res) { if (res == null) { return null; } List<object> result = res as List<object>; List<ResponseDataResultErrors> responseDataResultErrors = new List<ResponseDataResultErrors>(); foreach (object item in result) { ResponseDataResultErrors value = new ResponseDataResultErrors(); Dictionary<string, object> _item = item as Dictionary<string, object>; value.DIndex = Convert.ToInt64(_item["DIndex"]); value.FieldName = Convert.ToString(_item["FieldName"]); value.Message = Convert.ToString(_item["Message"]); responseDataResultErrors.Add(value); } return responseDataResultErrors; } /// <summary> /// 格式化WebAPI返回结果中的成功的明细 /// </summary> /// <param name="res"></param> /// <returns></returns> public static List<ResponseDataResultSuccessEntitys> ConvertWebAPIResultSuccessEntitys(object res) { if (res == null) { return null; } List<object> result = res as List<object>; List<ResponseDataResultSuccessEntitys> responseDataResultSuccessEntitys = new List<ResponseDataResultSuccessEntitys>(); foreach (object item in result) { ResponseDataResultSuccessEntitys value = new ResponseDataResultSuccessEntitys(); Dictionary<string, object> _item = item as Dictionary<string, object>; value.DIndex = Convert.ToInt64(_item["DIndex"]); value.Id = Convert.ToInt64(_item["Id"]); value.Number = Convert.ToString(_item["Number"]); responseDataResultSuccessEntitys.Add(value); } return responseDataResultSuccessEntitys; } /// <summary> /// 格式化WebAPI返回结果中的成功的信息 /// </summary> /// <param name="res"></param> /// <returns></returns> public static List<ResponseDataResultSuccessMessages> ConvertWebAPIResultSuccessMessages(object res) { if (res == null) { return null; } List<object> result = res as List<object>; List<ResponseDataResultSuccessMessages> responseDataResultSuccessMessages = new List<ResponseDataResultSuccessMessages>(); foreach (object item in result) { ResponseDataResultSuccessMessages value = new ResponseDataResultSuccessMessages(); Dictionary<string, object> _item = item as Dictionary<string, object>; value.DIndex = Convert.ToInt64(_item["DIndex"]); value.FieldName = Convert.ToString(_item["FieldName"]); value.Message = Convert.ToString(_item["Message"]); responseDataResultSuccessMessages.Add(value); } return responseDataResultSuccessMessages; } } }

更新动作中,我们需要组织需要更新的字段。

我们无法确定哪个字段需要更新,所以在处理的时候,除了单据的FID(单据内码,换句话说就是单据的流水号)、单据的FBillNo(单据编号)、明细数据属性Key(采购订单明细Key为FPOOrderEntry)、明细数据的FEntryID(明细内码,明细数据流水号)。

其他的字段都作为更新的字段。具体处理代码为:

// 组织需要更新的字段 JArray needUpDateFields = new JArray(); JArray entryData = parameter.Model[parameter.EntryKey] as JArray; foreach (JProperty item in parameter.Model.Properties()) { if (item.Name != "FID" && item.Name != "FBillNo" && item.Name != parameter.EntryKey) { needUpDateFields.Add(item.Name); } } needUpDateFields.Add(parameter.EntryKey); JObject entryDataZero = entryData[0] as JObject; foreach (JProperty item in entryDataZero.Properties()) { if (item.Name != "FEntryID") { needUpDateFields.Add(item.Name); } }

在Formater.cs类文件中,我们使用一下函数方法来解析WebAPI返回的结果数据:

ConvertWebAPIResultConvertWebAPIResultResponseDataResultConvertWebAPIResultDataConvertWebAPIResultErrorsConvertWebAPIResultSuccessEntitysConvertWebAPIResultSuccessMessages

WebAPI返回结果数据的结构也可以在官方文档中查阅到。

当然也可以用其他方式来解析WebAPI返回结果,这不是本教程讨论的重点。

接下来,我们编写单据处理函数的方法。

我们再新建一个名为“BillHandler.cs”的类文件。

单据的处理方法都放到这个类文件中,当前我们只做一个方法,如果需要扩展其他的对单据的处理方法,也可以放在这个文件中。

代码组织方面的问题,我们不在这里讨论和展开。代码如下:

using System; using System.Collections.Generic; using System.Linq; using System.Text; using Kingdee.BOS; using Kingdee.BOS.WebApi.FormService; using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace Test.K3Cloud.SCM.WebAPI.ServerExtend { public class BillHandler { /// <summary> /// 保存和更新 /// </summary> /// <param name="context"></param> /// <param name="parameter"></param> /// <returns></returns> public static Result Save(Context context, Parameter parameter) { // 更新 var rs = WebApiServiceCall.Save(context, parameter.LocalFormId, Formater.Update(parameter)); ResponseData res = Formater.ConvertWebAPIResult(rs); Result result = new Result(); if (!res.Result.ResponseStatus.IsSuccess) { result.msg = JsonConvert.SerializeObject(res.Result.ResponseStatus.Errors) + "1"; return result; } result.data = new JObject { new JProperty("FID", res.Result.ID), new JProperty("Number", res.Result.Number) }; // 提交 if (parameter.Model.Property("FID") == null && Convert.ToInt64(parameter.Model["FID"]) == 0) { parameter.Model.Add(new JProperty("FID", res.Result.ID)); } if (parameter.Model.Property("FBillNo") == null && Convert.ToInt64(parameter.Model["FBillNo"]) == 0) { parameter.Model.Add(new JProperty("FBillNo", res.Result.Number)); } var rsSubmit = WebApiServiceCall.Submit(context, parameter.LocalFormId, Formater.Submit(parameter)); ResponseData rsSubmitData = Formater.ConvertWebAPIResult(rsSubmit); if (!rsSubmitData.Result.ResponseStatus.IsSuccess) { result.success = false; result.msg = JsonConvert.SerializeObject(rsSubmitData.Result.ResponseStatus.Errors) + "2"; return result; } // 审核 var rsAudit = WebApiServiceCall.Audit(context, parameter.LocalFormId, Formater.Audit(parameter)); ResponseData rsAuditData = Formater.ConvertWebAPIResult(rsAudit); if (!rsAuditData.Result.ResponseStatus.IsSuccess) { result.success = false; result.msg = JsonConvert.SerializeObject(rsAuditData.Result.ResponseStatus.Errors) + "3"; return result; } result.success = true; result.msg = Defineds.DefaultSuccessMsg; return result; } } }

上面代码中Save方法中,对单据做了保存、提交和审核的操作。

WebApiServiceCall 类中方法中实现了对单据的各种操作,在其他需求场景中,大家可以跟踪进去查看。

当然,对单据的操作还有其他方式,本教程为了方便大家对应官方文档理解,使用了WebApiServiceCall。

调用WebApiServiceCall 当然也是有缺陷的。

星空系统对单个的单据操作是有事务保护的,例如单据保存,如果在保存的操作中,出现了校验不通过的情况下,会触发事务回滚。

但是上面代码中保存、提交和审核这连续的三个动作,没有事务保护。

这种方式的优点是开发代码量少,开发效率高而且容易理解。

接下来我们在 BusinessService.cs类中调用Save方法,来更新、提交和审核采购订单。代码如下:

using System; using System.Collections.Generic; using System.Linq; using System.Text; using Kingdee.BOS; using Kingdee.BOS.ServiceFacade.KDServiceFx; using Kingdee.BOS.WebApi.ServicesStub; using Newtonsoft.Json; namespace Test.K3Cloud.SCM.WebAPI.ServerExtend { public class BusinessService : AbstractWebApiBusinessService { public BusinessService(KDServiceContext context) : base(context) { } /// <summary> /// 执行接口请求 /// </summary> /// <param name="parameters"></param> /// <returns></returns> public Result ExcuteService(string parameters) { parameters = parameters.Trim(); Result result = Checker.clientParameterChecker(parameters); if (!result.success) { return result; } return ServiceRouter(parameters); } /// <summary> /// 接口请求分发 /// </summary> /// <param name="parameters"></param> /// <returns></returns> private Result ServiceRouter(string parameters) { // 反序列化客户端传递的JSON数据 Parameter parameter = JsonConvert.DeserializeObject<Parameter>(parameters); Result checkRs = Checker.checkModel(parameter); // 业务参数不能为空 if (!checkRs.success) { return checkRs; } Context context = KDContext.Session.AppContext; return BillHandler.Save(context, parameter); } } }

ServiceRouter 为实际调用单据操作的方法。

因为我们还可以根据其他参数来处理不同的场景需求,所以命名为:ServiceRouter 。

当前我们教程中只调用了Save方法。

4.编译代码生成dll文件

在Visual Studio IDE菜单栏中选择“项目”选项,选择 “Test.K3Cloud.SCM.WebAPI.ServerExtend属性”选项,打开“属性配置”窗口。

选择窗口右侧的“生成”菜单选项,将输出路径设置为金蝶的安装目录的Website\bin目录。

默认路径为:C:\Program Files (x86)\Kingdee\K3Cloud\WebSite\Bin。

配置完成之后,点击“保存”。如下图所示:

点击菜单中的“生成”选项,在下拉菜单中选择“生成Test.K3Cloud.SCM.WebAPI.ServerExtend”选项。

生成“Test.K3Cloud.SCM.WebAPI.ServerExtend”动态链接库文件。

在Visual Studio IDE下方的输出窗口中显示生成成功,则说明Test.K3Cloud.SCM.WebAPI.ServerExtend.dll文件生成成功。如下图所示:

5.部署

我们在上面的项目属性设置中看到,配置了Test.K3Cloud.SCM.WebAPI.ServerExtend.dll文件的生成目录。

所以,当我们成功生成Test.K3Cloud.SCM.WebAPI.ServerExtend.dll文件之后就已经完成了部署工作。

如果没有做这个配置,我们可以将生成的这个文件复制到星空系统的安装目录。

默认目录为:C:\Program Files (x86)\Kingdee\K3Cloud\WebSite\Bin。

6.重启IIS服务

自定义WebAPI需要重启IIS服务。

打开IIS管理器。选择站点,然后点击右侧窗口中的“重新启动”按钮。重启IIS服务。

每次代码更新,编译之后,都需要重启IIS服务。如下图所示:

7.使用Postman模拟请求

在模拟请求之前,我们需要了解自定义WebAPI是对星空系统标准WebAPI的扩展,所以同样需要通过星空的身份验证。所以第一步需要进行登录操作。

登录需要的地址为:

登录时需要提交参数有:Acctid(账套ID)、userName(用户名)、password(密码)、lcid(语言ID)。

账套ID可以在星空客户端WebAPI测试工具界面找到。如下图所示:

第二步是测试请求我们的WebAPI。

根据WebAPI处理的方式我们组织的请求报文如下:

{ "parameters": [{ "LocalFormId": "PUR_PurchaseOrder", "EntryKey": "FPOOrderEntry", "Model": { "FID": , "FPOOrderEntry": [{ "FEntryID": , "FQty": "150", "FEntryNote": "测试明细备注1111" }] } }] }

上面报文中有以下几个涉及到单据的字段:FID:单据内码FEntryID:单据明细内码FQty:采购数量FEntryNote:明细备注的FID和FEntryID可以在数据库中查询到。

采购订单记录表为“t_PUR_POOrder”,

明细表为“t_PUR_POOrderEntry”。

备注说明:如果是使用金蝶官方给出的SDK来发起请求的话,就不需要{"parameters":[]}这一层。自定义WebAPI地址为:http://127.0.0.1/K3Cloud/Test.K3Cloud.SCM.WebAPI.ServerExtend.BusinessService.ExcuteService,Test.K3Cloud.SCM.WebAPI.ServerExtend.common.kdsvc

自定义接口URL地址格式说明:

接口命名空间.接口实现类名.方法,组件名.common.kdsvc

本例中接口

接口命名空间:Test.K3Cloud.SCM.WebAPI.ServerExtend

接口类名:BusinessService

方法名称:ExcuteService

组件名:编译后的dll名称,这里是Test.K3Cloud.SCM.WebAPI.ServerExtend首先我们在星空系统中创建一个采购订单,如下图所示:

接下来,我们使用Postman来模拟客户端请求。登录请求如下图所示:

如果返回结果与截图中一样,则说明登录成功。

Postman会自动维护登录Session会话,所以,登录成功之后,我们可以直接进行自定义WebAPI接口的测试。

如果返回结果中success为true,则说明执行成功。

教程内容较长,比前面的几篇教程复杂很多。大家可以在自己的开发环境中进行测试练习。

在学习的过程中,如果遇到了什么问题,可以留言。

对于新手来说,一次就成功完成教程,也有一定的难度,大家边做边思考边解决问题,这样我们的收获才是最大的。

虽然我给出了源代码,但是不建议大家直接拿来用,最好是自己敲一遍代码,这样才能发现自己的盲区,以便在今后的开发工作中顺利完成任务。

本教程源码地址:

更多精彩内容发布于:代码乾坤 (CoderLand)