.NET架构小技巧(3)——反射,架构人员法宝I

  如题,这是我的心声,反射在我的开发中用的频次还是比较高的,有一本万利的感觉,一段复杂的代码,可以节省大量的时间;但带来的一个问题性能相对较差,所以要选择适合的场景使用。

  关于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系统配合使用,就是一个时刻只有一个接口在调用,对性能的容忍度还是够用的。

    想要更快更方便的了解相关知识,可以关注微信公众号    .NET架构小技巧(3)——反射,架构人员法宝I

 

 

上一篇:Pro/E PTC CREO许可限制,许可加入白名单


下一篇:IDEA springboot外置Tomcat使用