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

     
      想要更快更方便的了解相关知识,可以关注微信公众号 
     

  • 相关阅读:
    Mac Atom的PHP插件
    WebStorm mac下如何安装WebStorm + 破解
    PHP接收json格式的POST数据
    mysqldump 导出统一限制每张数据表导出的记录数
    centos7下git服务器端搭建
    nginx服务器常见错误代码500、501、502、503、504、505
    【原创】PHPstorm本地修改同步保存到远程服务器
    SVN Checkout 不包括源文件夹根目录
    mac终端显示日历信息命令
    PHP生成唯一RequestID类
  • 原文地址:https://www.cnblogs.com/axzxs2001/p/15854455.html
Copyright © 2020-2023  润新知