如题,这是我的心声,反射在我的开发中用的频次还是比较高的,有一本万利的感觉,一段复杂的代码,可以节省大量的时间;但带来的一个问题性能相对较差,所以要选择适合的场景使用。
关于C#中的反射基本用法,这里不作详细介绍,官网有详细的说明。
下面是在网上找了一个医保接口的案件,比如有两个业务接口,033,027,这个接口的传输内容类似xml,但又是有区别的,不是严格意义上的xml格式。
医院就诊卡接口规范(业务:033)
输入参数:
<Request> <TradeCode>业务编号</TradeCode> <BeginDate>开始日期</BeginDate> <EndDate>结束日期</EndDate> </Request>
输出参数:
<Response> <PatientId>病历编号</PatientId> <SiHisOrderNo>HIS端结算流水</SiHisOrderNo> <PubCost>医保统筹支付(元)</PubCost> <PayCost>医保帐户支付(元)</PayCost> <OwnCost>患者个人自付(元)</OwnCost> <TotCost>本次结算总额(元)</TotCost> <TransType>结算类别(1消费,0退费)</TransType> <OperCode>操作人员编号</OperCode> <OperName>操作人员姓名</OperName> <PayType>支付方式</PayType> <Invoices> <INum>单据编号</INum> <IType>单据类别</InvoiceType> <ISum>单据金额(元)</ISum> </Invoices> </Request>
医院就诊卡接口规范(业务:027)
输入参数:
<Request> <TradeCode>交易码(见上表交易代码)</TradeCode> <Date>交易日期(YYYYMMDD)</Date> <Time>交易时间(HHMMSS)</Time> <InvoiceNo>发票号</InvoiceNo> <TransType>交易类别</TransType> </Request>
输出参数:
<Response> <TradeCode>业务编号</TradeCode> <Result>返回值:0 成功,其他失败</Result> <Err>错误描述信息</Err> <HospitalTransNO>本次交易流水</HospitalTransNO> <Fees> <Fee> <fybm>收费项目编码</fybm> <fymc>收费项目名称</fymc> <ksbm>开立科室编码</ksbm> <ksmc>开立科室名称</ksmc> <ysbm>开立医生编码</ysbm> <ysxm>开立医生姓名</ysxm> <kdsj>开单时间(yyyy-MM-dd HH:mm:ss)</kdsj> <sfsj>收费时间(yyyy-MM-dd HH:mm:ss)</sfsj> <fysl>数量</fysl> <yxbz>有效标志</yxbz> <czy>收费人员编号</czy> <zxks>执行科室编号</zxks> <InvoiceNo>单据编号</InvoiceNo> </Fee> ...... </Fees> </Response>
这个医保接口的对接方式入参都是一个Request节点,出参都是Response节点,至少是调用dll,还是通过什么协议发送参数,接收参数,是另外一会事,所以咱们重点来说架构小技巧。
首先思考的是,我们在.net中是面向对应编程,这些接口的输入参数,输出参数应该对应成实体,当然用字符串拼接也可以,但当接口众多时,一点一点拼接,势必会出错率高,同时个性化处理非常麻烦,另一方面,显得比较low。这时,反射大显身手的机会就来了,假如我只要按输入参数,输出参数的属性定义好实体类,再造一个神器,能把实体转成输入参数字符串,也能把输出参数转成实体类,对我们来说,就是完全的OOP了,“辛苦两个转换方法,幸福所有接口”,当然对开发一个完整的医保接口来说,另一个难点是,从现有系统中组织输入参数实体类,和回写输出参数实体,但也不是架构技巧的重点。
还是上代码吧:
using System; using System.Collections; using System.Collections.Generic; using System.Text; using System.Xml; namespace ArchitectureDemo03 { class Program { static void Main(string[] args) { var readCard033 = new ReadCard033 { TradeCode = "033", BeginDate = DateTime.Now, EndDate = "20200102" }; var readcard033Back = Send<ReadCard033Back>(readCard033); Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(readcard033Back)); var readCard027 = new ReadCard027 { Date = "20201212", InvoiceNo = "abcd", Time = "121212", TradeCode = "003", TransType = "123" }; var readCard027Back = Send<ReadCard027Back>(readCard027); Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(readCard027Back)); } /// <summary> /// 模拟输入函数与输出xml对应集合,以作mock /// </summary> static Dictionary<string, string> BackDic = new Dictionary<string, string> { {"ReadCard033" ,@"<Response> <PatientId>病历编号</PatientId> <SiHisOrderNo>HIS端结算流水</SiHisOrderNo> <PubCost>1</PubCost> <PayCost>2</PayCost> <OwnCost>3</OwnCost> <TotCost>4</TotCost> <TransType>1</TransType> <OperCode>操作人员编号</OperCode> <OperName>操作人员姓名</OperName> <PayType>支付方式</PayType> <Invoices> <INum>单据编号</INum> <InvoiceType>单据类别</InvoiceType> <ISum>5</ISum> </Invoices> </Response>"}, { "ReadCard027",$@"<Response> <TradeCode>业务编号</TradeCode> <Result>0</Result> <Err>错误描述信息</Err> <HospitalTransNO>本次交易流水</HospitalTransNO> <Fees> <Fee> <fybm>收费项目编码</fybm> <fymc>收费项目名称</fymc> <ksbm>开立科室编码</ksbm> <ksmc>开立科室名称</ksmc> <ysbm>开立医生编码</ysbm> <ysxm>开立医生姓名</ysxm> <kdsj>2020-12-12 13:14:15</kdsj> <sfsj>2020-12-12 13:14:15</sfsj> <fysl>10</fysl> <yxbz>有效标志</yxbz> <czy>收费人员编号</czy> <zxks>执行科室编号</zxks> <InvoiceNo>单据编号</InvoiceNo> </Fee> <Fee> <fybm>收费项目编码</fybm> <fymc>收费项目名称</fymc> <ksbm>开立科室编码</ksbm> <ksmc>开立科室名称</ksmc> <ysbm>开立医生编码</ysbm> <ysxm>开立医生姓名</ysxm> <kdsj>2020-12-12 13:14:15</kdsj> <sfsj>2020-12-12 13:14:15</sfsj> <fysl>10</fysl> <yxbz>有效标志</yxbz> <czy>收费人员编号</czy> <zxks>执行科室编号</zxks> <InvoiceNo>单据编号</InvoiceNo> </Fee> </Fees> </Response>"} }; /// <summary> /// 封装调用接口 /// </summary> /// <typeparam name="T">输出参数类型</typeparam> /// <param name="request">输入参数</param> /// <returns></returns> static T Send<T>(Request request) where T : class { Console.WriteLine("输入参数:"); Console.WriteLine(request.ToXML()); var backXML = BackDic[request.GetType().Name]; return request.ToResponse(typeof(T), backXML) as T; } } /// <summary> /// 请求父类 /// </summary> abstract class Request { public override string ToString() { var requestSB = new StringBuilder(); var type = this.GetType(); //遍历属性,获取属性值 foreach (var pro in type.GetProperties()) { //处理Request的子类,所有输入参数都应该继承Request if (pro.PropertyType.IsSubclassOf(typeof(Request))) { requestSB.AppendLine($"<{pro.Name}>"); requestSB.AppendLine($"{pro.GetValue(this)}"); requestSB.AppendLine($"</{pro.Name}>"); } else { //处理DateTime类型属性 if (pro.PropertyType.IsAssignableFrom(typeof(DateTime))) { var value = Convert.ToDateTime(pro.GetValue(this)).ToString("yyyyMMddHHMMSS"); requestSB.AppendLine($"<{pro.Name}>{value}</{pro.Name}>"); } else { requestSB.AppendLine($"<{pro.Name}>{pro.GetValue(this)}</{pro.Name}>"); } } } return requestSB.ToString().Trim(); } /// <summary> /// 输成xml输入参数 /// </summary> /// <returns></returns> public string ToXML() { return $"<Request>\n{this}\n</Request>"; } /// <summary> /// 输出参数xml转成实体类 /// </summary> /// <param name="type">输出参数类型</param> /// <param name="xml">输出参数xml</param> /// <returns></returns> public object ToResponse(Type type, string xml) { xml = $@"<?xml version=""1.0"" encoding=""utf-8""?>{xml}"; var xmlDoc = new XmlDocument(); xmlDoc.LoadXml(xml); var instance = Activator.CreateInstance(type); foreach (var pro in type.GetProperties()) { //自定义实体类属性,利用在同一个命名空间里来解瘊这个事情 if (pro.PropertyType.Namespace == this.GetType().Namespace) { var xmlElement = xmlDoc.GetElementsByTagName(pro.Name); if (xmlElement.Count > 0) { var response = ToResponse(pro.PropertyType, $"<{pro.Name}>{xmlElement[0].InnerXml}</{pro.Name}>"); pro.SetValue(instance, response); } } else { //泛型集合实体类属性 if (pro.PropertyType.IsGenericType) { //获取泛型集合属性的类型 var subType = pro.PropertyType.GetGenericArguments()[0]; var xmlElement = xmlDoc.GetElementsByTagName(pro.Name); if (xmlElement.Count > 0) { //生成泛型集合属性的实体类 var list = Activator.CreateInstance(pro.PropertyType) as IList; //把输出字符串中的列表对应数据添加到泛型属性集合中 foreach (XmlNode childItem in xmlElement[0].ChildNodes) { if (childItem.ChildNodes.Count > 0) { var subInstance = ToResponse(subType, $"<{subType.Name}>{xmlElement[0].ChildNodes[0].InnerXml}</{subType.Name}>"); list.Add(subInstance); } } //设置泛型集合属性的值 pro.SetValue(instance, list); } } else { //普通属性 var xmlElement = xmlDoc.GetElementsByTagName(pro.Name); if (xmlElement.Count > 0) { var value = Convert.ChangeType(xmlElement[0].InnerText, pro.PropertyType); pro.SetValue(instance, value); } } } } return instance; } } /// <summary> /// 医院就诊卡接口规范(业务:033) /// </summary> class ReadCard033 : Request { /// <summary> /// 业务编号 /// </summary> public string TradeCode { get; set; } /// <summary> /// 开始日期 /// </summary> public DateTime BeginDate { get; set; } /// <summary> /// 结束日期 /// </summary> public string EndDate { get; set; } } /// <summary> /// 医院就诊卡接口规范(业务:033)输出参数 /// </summary> class ReadCard033Back { /// <summary> /// 病历编号 /// </summary> public string PatientId { get; set; } /// <summary> /// HIS端结算流水 /// </summary> public string SiHisOrderNo { get; set; } /// <summary> /// 医保统筹支付(元) /// </summary> public decimal PubCost { get; set; } /// <summary> /// 医保帐户支付(元 /// </summary> public decimal PayCost { get; set; } /// <summary> /// 患者个人自付(元) /// </summary> public decimal OwnCost { get; set; } /// <summary> /// 本次结算总额(元) /// </summary> public decimal TotCost { get; set; } /// <summary> /// 结算类别(1消费,0退费) /// </summary> public int TransType { get; set; } /// <summary> /// 操作人员编号 /// </summary> public string OperCode { get; set; } /// <summary> /// 操作人员姓名 /// </summary> public string OperName { get; set; } /// <summary> /// 支付方式 /// </summary> public string PayType { get; set; } /// <summary> /// 单据信息 /// </summary> public Invoices Invoices { get; set; } } /// <summary> /// 单据 /// </summary> class Invoices { /// <summary> /// 单据编号 /// </summary> public string INum { get; set; } /// <summary> /// 单据类别 /// </summary> public string InvoiceType { get; set; } /// <summary> /// 单据金额(元) /// </summary> public decimal ISum { get; set; } } /// <summary> /// 医院就诊卡接口规范(业务:027) /// </summary> class ReadCard027 : Request { /// <summary> /// 交易码(见上表交易代码) /// </summary> public string TradeCode { get; set; } /// <summary> /// 交易日期(YYYYMMDD /// </summary> public string Date { get; set; } /// <summary> /// 交易时间(HHMMSS) /// </summary> public string Time { get; set; } /// <summary> /// 发票号 /// </summary> public string InvoiceNo { get; set; } /// <summary> /// 交易类别 /// </summary> public string TransType { get; set; } } /// <summary> /// 医院就诊卡接口规范(业务:027)输出参数 /// </summary> class ReadCard027Back { /// <summary> /// 业务编号 /// </summary> public string TradeCode { get; set; } /// <summary> /// 返回值:0 成功,其他失败 /// </summary> public string Result { get; set; } /// <summary> /// 错误描述信息 /// </summary> public string Err { get; set; } /// <summary> /// 本次交易流水 /// </summary> public string HospitalTransNO { get; set; } /// <summary> /// 明细列表 /// </summary> public List<Fee> Fees { get; set; } } /// <summary> /// 明细 /// </summary> class Fee { /// <summary> /// 收费项目编码 /// </summary> public string fybm { get; set; } /// <summary> /// 收费项目名称 /// </summary> public string fymc { get; set; } /// <summary> /// 开立科室编码 /// </summary> public string ksbm { get; set; } /// <summary> /// 开立科室名称 /// </summary> public string ksmc { get; set; } /// <summary> /// 开立医生编码 /// </summary> public string ysbm { get; set; } /// <summary> /// 开立医生姓名 /// </summary> public string ysxm { get; set; } /// <summary> /// 开单时间(yyyy-MM-dd HH:mm:ss) /// </summary> public string kdsj { get; set; } /// <summary> /// 收费时间(yyyy-MM-dd HH:mm:ss) /// </summary> public string sfsj { get; set; } /// <summary> /// 数量 /// </summary> public string fysl { get; set; } /// <summary> /// 有效标志 /// </summary> public string yxbz { get; set; } /// <summary> /// 收费人员编号 /// </summary> public string czy { get; set; } /// <summary> /// 执行科室编号 /// </summary> public string zxks { get; set; } /// <summary> /// 单据编号 /// </summary> public string InvoiceNo { get; set; } } }
在上面代码中,重点是Request这个抽象类,其实原ToXML中把实体类转成输入参数字符串,ToResponse是把输出字符串转成实体供程序使用。这两个方法的具体实现,在代码中有注释,这是使用反射属性来实现转换的,当然这种转换,用XmlSerializer也可以实现,但这样就失去了对属性的灵活控制,比如demo中的时间类型属性的转换。
“辛苦两个转换方法,幸福所有接口”,就是ToXML和ToResponse,如果这个医保还有几十个接口,那就只用定义每个业务函数对应的输入参数,输出参数实体类就可以了。这样简化了程序模块的耦合,层次也很清晰,但你可能在问性能了,确实,反射带来灵活的同时就会损失性能,但大部分医保接口都是以dll的形式存在,和his系统配合使用,就是一个时刻只有一个接口在调用,对性能的容忍度还是够用的。
想要更快更方便的了解相关知识,可以关注微信公众号