zoukankan      html  css  js  c++  java
  • .NET架构小技巧(4)——反射,架构人员法宝II

      上一篇博文中,利用属性反射的特点,用两个方法完成了字符转实体,实体转字符的工作,但有些复杂的场景,上面方法就没那么好用了,就需要更复杂的方式来组装处理。
      先来看一个接口文档,下面是接口的调用方式

    long  OltpTransData(unsigned long msgType,unsigned long packageType,
          unsigned long packageLength,char *str,LPTSTR com);

    I.msgType:业务请求类型;

    II.packageType:数据解析格式类型,系统重组数据时使用

    III.packageLength:数据串的长度;

    IV. str:数据串;调用时,通过数据串传入参数;函数返回时,数据串中包含返回的数据,数据按字符串方式组合,并且在字符串第一位保留一个空格;另外,除了16位日期右补空格,其他的字段均以左空格补位

    V. com:数据请求串口

    业务请求类型

    数据解析格式类型

    数据串最小长度

    说明

    1001

    101

    126

    实时验卡(读卡、验卡)

    1002

    12

    610

    实时结算

    1003

    7

    291

    实时明细数据传输

    1004

    9

    253

    实时住院登记数据传输

    1005

    8

    309

    实时医嘱数据传输

    1006

    12

    610

    实时结算预算

    1007

    2

    1101

    实时住院首次病程记录传输

    1009

    504

    85

    医师信息查询

    1010

    510

    331

    出入院标准传输

    读卡

    序号

    定义

    数据原型

    起始位置

    数据长度

    备注

    数据填充

    1

    个人编号

    CHAR

    1

    8

    医保编号,以’E’开头的为异地社保卡,详见说明

    院端

    2

    姓名

    CHAR

    9

    20

     

    中心

    3

    身份证号

    CHAR

    29

    18

    18位或15位

    中心

    4

    IC卡号

    CHAR

    47

    9

     

    院端

    5

    治疗序号

    NUM

    56

    4

     

    中心

    6

    职工就医类别

    CHAR

    60

    1

    A在职、B退休、L事业离休、T特诊、Q企业离休、E退老、N农民工、X未成年居民、O老年居民(老年居民、低收入人员、残疾人)、D低保人员、S  三无人员、U 大学生

    中心

    7

    基本个人帐户余额

    NUM

    61

    10

     

    中心

    8

    补助个人帐户余额

    NUM

    71

    10

    现用于公务员单独列帐

    中心

    9

    统筹累计

    NUM

    81

    10

     

    中心

    10

    门诊慢病统筹累计

    NUM

    91

    10

     

    中心

    11

    月缴费基数

    NUM

    101

    10

    月缴费工资

    中心

    12

    帐户状态

    CHAR

    111

    1

    A正常、B半止付、C全止付、D销户

    中心

    13

    参保类别1

    CHAR

    112

    1

    是否享受高额:

    0 不享受高额、1  享受高额、2 医疗保险不可用

    中心

    14

    参保类别2

    CHAR

    113

    1

    是否享受补助(商业补助、公务员补助):0  不享受、1 商业、2  公务员、3事业离休差额拨款人员

    中心

    15

    参保类别3

    CHAR

    114

    1

    0 企保、1 事保、2企业慢病、3事业慢病、4异地就医,详见说明

    中心

    16

    参保类别4

    CHAR

    115

    1

    0或2生育不可用、1或3生育可用

    中心

    17

    参保类别5

    CHAR

    116

    1

    0工伤不可用、1工伤可用

    中心

    18

    住院次数累计

    NUM

    117

    4

     

    中心

    19

    家床次数累计

    NUM

    121

    4

     

    中心

    查询医师

    序号

    定义

    数据原型

    起始位置

    数据长度

    备注

    填写方式

    1

    医师编码

    CHAR

    1

    8

     

    院端

    2

    医师姓名

    CHAR

    9

    20

     

    中心

    3

    身份证号

    CHAR

    29

    18

     

    中心

    4

    可出诊医院编号

    CHAR

    47

    20

    详见说明

    中心

    5

    终止日期

    DATETIME

    67

    16

    YYYYMMDDHHMMSS

    中心

    6

    有效标志

    CHAR

    83

    1

    ‘0’无效,‘1’有效

    中心

      这个接口是拼接方式,每个数据项都有固定的长度,有些数据也有固定的格式,比如日期,还有很多类型,都是有固定值固定含义的。这种情况下需要更多的信息来告诉两个转换方法该怎么转换,这里就引出了Attribute——一个专门来给类或属性方法加特征的知识点。

     /// <summary>
        /// 发送报文类型
        /// </summary>
        [AttributeUsage(AttributeTargets.Class)]
        public class PackageTypeAttribute : Attribute
        {
            /// <summary>
            /// 发关报文实体类属性特性类
            /// </summary>
            /// <param name="SN">属性的序号,从1开始</param>
            /// <param name="Length">属性转成字符串后长度</param>
            public PackageTypeAttribute(uint OperationType, uint DataFormaterType, uint MinLength)
            {
                this.OperationType = OperationType;
                this.DataFormaterType = DataFormaterType;
                this.MinLength = MinLength;
            }
            /// <summary>
            /// 业务请求类型
            /// </summary>
            public uint OperationType
            { get; private set; }
            /// <summary>
            /// 数据解析格式类型
            /// </summary>
            public uint DataFormaterType
            { get; private set; }
            /// <summary>
            /// 数据串最小长度
            /// </summary>
            public uint MinLength
            { get; private set; }
        }
    
        /// <summary>
        /// 报文中属性的顺序SN和长度Length
        /// </summary>
        [AttributeUsage(AttributeTargets.Property)]
        public class PackageAttribute : Attribute
        {
            /// <summary>
            /// 序号,从1开始
            /// </summary>
            public int SN
            { get; private set; }
            /// <summary>
            /// 转成字符串后的长度
            /// </summary>
            public int Length
            { get; private set; }
            /// <summary>
            /// 发关报文实体类属性特性类
            /// </summary>
            /// <param name="SN">属性的序号,从1开始</param>
            /// <param name="Length">属性转成字符串后长度</param>
            public PackageAttribute(int SN, int Length)
            {
                this.SN = SN;
                this.Length = Length;
            }
            /// <summary>
            /// 是否是时间类型,因为时间类型是左对齐,右补空格
            /// </summary>
            public bool IsDateTime
            { get; set; }
        }
    
        /// <summary>
        /// 取枚举的值还是
        /// </summary>
        [AttributeUsage(AttributeTargets.Enum)]
        public class EnumValeuNumberAttribute : Attribute
        {
            /// <summary>
            /// 是否把枚举类型属性的的值数转成Char类型
            /// </summary>
            public bool IsChar
            { get; set; }
        }

      定义了三个特性类,PackageTypeAttribute主用来区分不同的交易类型,从实体类上获取不同交易的函数参数;PackageAttribute是在实体类的属性上的,是核心特性类,它标记了属性的序号,和每个属性的长度,和属性是否是DateTime类型;EnumValeuNumberAttribute是用来专门处理枚举类型的属性的。

     /// <summary>
        /// 医师信息查询
        /// </summary>
        [PackageType(1009, 504, 85)]
        public class DoctorQuery : Entity
        {
            /// <summary>
            /// 医师编码
            /// </summary>
            [Package(1, 8)]
            public virtual String DoctorCode
            {
                get; set;
            }
            /// <summary>
            /// 医师姓名
            /// </summary>
            [Package(2, 20)]
            public virtual String DoctorName
            { get; set; }
    
            /// <summary>
            /// 身份证号
            /// </summary>
            [Package(3, 18)]
            public virtual string PersonID
            { get; set; }
    
            ///编号为0002的医院, 下属有编号为0113的定点, 在总院注册登记的医师可以在这样的2家医院出诊, 则“可出诊医院编号”为00020113,若长度不足20位则前补空格。
            /// <summary>
            /// 可出诊医院编号
            /// </summary>
            [Package(4, 20)]
            public virtual string CanVisitHospitalCode
            { get; set; }
    
            /// <summary>
            /// 终止日期
            /// </summary>
            [Package(5, 16, IsDateTime = true)]
            public virtual string TerminationTime
            { get; set; }
    
            /// <summary>
            /// 有效标志
            /// </summary>
            [Package(6, 1)]
            public virtual DLYBAvailableMarker DLYBAvailableMarker
            { get; set; }
        }
    
        /// <summary>
        /// 有效标志
        /// </summary>
        [EnumValeuNumber]
        public enum DLYBAvailableMarker
        {
            /// <summary>
            /// 无效
            /// </summary>
            nullity = 0,
            /// <summary>
            /// 有效
            /// </summary>
            Valid = 1
        }
    
        /// <summary>
        /// 实时验卡(读卡、验卡)
        /// </summary>
        [PackageType(1001, 101, 126)] 
        public  class QueryCardEntity : Entity
        {        
            /// <summary>
            /// 个人编号
            /// </summary>
            [Package(1, 8)]
            public virtual string PersonNumber
            { get; set; }
    
            /// <summary>
            /// 姓名
            /// </summary>
            [Package(2, 20)]
            public virtual string Name
            { get; set; }
    
            /// <summary>
            /// 身份证号
            /// </summary>
            [Package(3, 18)]
            public virtual string PersonID
            { get; set; }
    
            /// <summary>
            /// IC卡号
            /// </summary>
            [Package(4, 9)]
            public virtual string ICCardNumber
            { get; set; }
    
            long therapyNumber;
            /// <summary>
            /// 治疗序号
            /// </summary>
            [Package(5, 4)]
            public virtual long TherapyNumber
            {
                get
                {
                    return therapyNumber;
                }
                set
                {
                    if (value >= 0 && value <= 9999)
                    {
                        therapyNumber = value;
                    }
                    else
                    {
                        throw new Exception("治疗号在0-9999之间");
                    }
    
                }
            }
    
            /// <summary>
            /// 职工就医类别
            /// </summary>
            [Package(6, 1)]
            public virtual string TherapyCategory
            { get; set; }
    
            /// <summary>
            /// 基本个人帐户余额
            /// </summary>
            [Package(7, 10)]
            public virtual decimal BasePersonBalance
            { get; set; }
    
            /// <summary>
            /// 补助个人帐户余额
            /// </summary>
            [Package(8, 10)]
            public virtual decimal SubsidyPersonBalance
            { get; set; }
    
            /// <summary>
            /// 统筹累计
            /// </summary>
            [Package(9, 10)]
            public virtual decimal PlannerTotal
            { get; set; }
    
            /// <summary>
            /// 门诊慢病统筹累计///新
            /// </summary>
            [Package(10, 10)]
            public virtual decimal MZSlowDisease
            { get; set; }
    
            /// <summary>
            /// 月缴费基数
            /// </summary>
            [Package(11, 10)]
            public virtual decimal BaseFeeByMonth
            { get; set; }
    
            /// <summary>
            /// 帐户状态
            /// </summary>
            [Package(12, 1)]
            public virtual string AccoutState
            { get; set; }
    
            /// <summary>
            /// 参保类别1
            /// </summary>
            [Package(13, 1)]
            public virtual string InsuranceCategory1
            { get; set; }
    
            /// <summary>
            /// 参保类别2
            /// </summary>
            [Package(14, 1)]
            public virtual string InsuranceCategory2
            { get; set; }
    
            /// <summary>
            /// 参保类别3
            /// </summary>
            [Package(15, 1)]
            public virtual string InsuranceCategory3
            { get; set; }
    
            /// <summary>
            /// 参保类别4
            /// </summary>
            [Package(16, 1)]
             public virtual string InsuranceCategory4
            { get; set; }
    
            /// <summary>
            /// 参保类别5
            /// </summary>
            [Package(17, 1)]
            public virtual string InsuranceCategory5
            { get; set; }
    
            /// <summary>
            /// 住院次数累计////新
            /// </summary>
            [Package(18, 4)]
            public virtual int ZYAddNumber
            { get; set; }
    
            /// <summary>
            /// 家床次数累计////新
            /// </summary>
            [Package(19, 4)]
            public virtual int AddBedNumber
            { get; set; }
        }

      上面的实体类分别使用了特性类,参照文档就OK。

     public static class StringExtension
        {  
            /// <summary>
            /// 右边不够长度补空格,汉字算两个空格
            /// </summary>
            /// <param name="str"></param>
            /// <param name="length">设定长度</param>
            /// <returns></returns>
            public static string ChineseCharacterLeft(this string str, int length)
            {
                var len = Encoding.Default.GetBytes(str).Length;
                if (len < length)
                {
                    for (int i = 0; i < length - len; i++)
                    {
                        str = " " + str;
                    }
                }
                return str;
            }
    
            /// <summary>
            /// 右边不够长度补空格,汉字算两个空格
            /// </summary>
            /// <param name="str"></param>
            /// <param name="length">设定长度</param>
            /// <returns></returns>
            public static string ChineseCharacterRight(this string str, int length)
            {
                var len = Encoding.Default.GetBytes(str).Length;
                if (len < length)
                {
                    for (int i = 0; i < length - len; i++)
                    {
                        str += " ";
                    }
                }
                return str;
            }
    
            /// <summary>
            /// 切除字符串
            /// </summary>
            public static string ChineseCharacterSubstring(this string str, int length, out string remaining)
            {
                var arr = Encoding.Default.GetBytes(str);
                var barr = arr.Take(length).ToArray();
                var valuestr = Encoding.Default.GetString(barr);
                barr = arr.Skip(length).ToArray();
                remaining = Encoding.Default.GetString(barr); ;
                return valuestr;
            }
        }

      上面代码是对某些属性的对齐方式作了处理。

     /// <summary>
        /// 报文类的父类
        /// </summary>
        public abstract class Entity
        {
            /// <summary>
            /// 组装发送报文格式
            /// </summary>
            /// <returns></returns>
            public override string ToString()
            {
                var pros = this.GetType().GetProperties();
                var sortPro = new SortedList<int, PropertyInfo>();  
                foreach (var pro in pros)
                {
                    foreach (var att in pro.GetCustomAttributes(false))
                    {
                        if (att is PackageAttribute)
                        {
                            var packageAtt = att as PackageAttribute;   
                            sortPro.Add(packageAtt.SN, pro);
                        }
                    }
                }
                var content = new StringBuilder();
                #region 组合发送字符串
                //遍历属性 
                foreach (var pro in sortPro)
                {
                    //遍历属性上的特性                
                    foreach (var att in pro.Value.GetCustomAttributes(false))
                    {
                        //判断是否为自定义的PackageAttribute类型
                        if (att is PackageAttribute)
                        {
                            //转换属性上的特性类
                            var packageAtt = att as PackageAttribute;
                            //取拼接时字符长度
                            var length = packageAtt.Length;
                            //取属性的值
                            var proValue = pro.Value.GetValue(this, new Object[0]);
    
                            //对decimal作处理
                            if (pro.Value.PropertyType.Name.ToLower() == "decimal")
                            {
                                proValue = Math.Round(Convert.ToDecimal(proValue), 2);
                                if (Encoding.Default.GetByteCount(proValue.ToString()) > length)
                                {
                                    proValue = "0";
                                }
                            }
                            //判断字符串长度过长
                            if (proValue != null && (pro.Value.PropertyType.Name.ToLower() == "string"))
                            {
                                if (System.Text.Encoding.Default.GetBytes(proValue.ToString()).Length > length)
                                {
                                    throw new Exception(string.Format("属性{0}的值{1},长度超过{2}", pro.Value.Name, proValue, length));
                                }
                            }
                            //如果值为非空
                            if (proValue != null)
                            {
                                //日期是右补空格,其他是左补空格
                                if (!packageAtt.IsDateTime)
                                {
                                    //这里注册,有些属性是枚举类型,有些属性拼接枚举的值,有些取枚举值对应的枚举数值,这里是从该属性类型上的EnumValeuNumberAttribute特性的IsValue属性来判断的,IsValue为true,就取枚举的值,为false取该值对应的枚举数
                                    if (pro.Value.PropertyType.IsEnum)
                                    {
                                        foreach (var eatt in pro.Value.PropertyType.GetCustomAttributes(false))
                                        {
                                            if (eatt is EnumValeuNumberAttribute)
                                            {
                                                var enumVaNu = eatt as EnumValeuNumberAttribute;
                                                if (enumVaNu.IsChar)
                                                {
                                                    var enumNumber = ((char)(int)Enum.Parse(pro.Value.PropertyType, proValue.ToString())).ToString();
                                                    content.Append(enumNumber.ChineseCharacterLeft(length));
                                                }
                                                else
                                                {
                                                    var enumNumber = ((int)Enum.Parse(pro.Value.PropertyType, proValue.ToString())).ToString();
                                                    content.Append(enumNumber.ChineseCharacterLeft(length));
                                                }
                                            }
                                        }
                                    }
                                    else
                                    {
                                        content.Append(proValue.ToString().ChineseCharacterLeft(length));
                                    }
                                }
                                else//日期类型右补空格
                                {
                                    content.Append(proValue.ToString().ChineseCharacterRight(length));
                                }
                            }
                            else
                            {
                                content.Append("".ChineseCharacterLeft(length));
                            }
                        }
                    }
                }
                #endregion
                return content.ToString();
            }
    
    
            /// <summary>
            /// 把一个字符串转成一个对象
            /// </summary>
            /// <param name="content"></param>
            /// <returns></returns>
            public  Entity ToEntity(Type entityType,string content)
            {
                var pros = entityType.GetProperties();
                //按照特性类上的SN序号把属性名存入集合proPackageList中
                List<PropertyInfo> proPackageList = new List<PropertyInfo>(pros.Length);
                //初始化属性集合
                for (int i = 0; i < pros.Length; i++)
                {
                    foreach (var att in pros[i].GetCustomAttributes(false))
                    {
                        if (att is PackageAttribute)
                        {
                            proPackageList.Add(null);
                            break;
                        }
                    }
                }
                //按属性顺序排列属性
                foreach (var pro in pros)
                {
                    foreach (var att in pro.GetCustomAttributes(false))
                    {
                        if (att is PackageAttribute)
                        {
                            var packageAtt = att as PackageAttribute;
                            var index = packageAtt.SN - 1;
                            proPackageList[index] = pro;
                        }
                    }
                }
    
                //创建实体对象
                var constructor = entityType.GetConstructor(new Type[0]);
                var entity = constructor.Invoke(new object[0]);
                foreach (var pro in proPackageList)
                {
                    //遍历属性上的特性
                    foreach (var att in pro.GetCustomAttributes(false))
                    {
                        //判断是否为自定义的PackageAttribute类型
                        if (att is PackageAttribute)
                        {
                            //转换属性上的特性类
                            var packageAtt = att as PackageAttribute;
    
                            var length = packageAtt.Length;
    
                            var valuestr = content.ChineseCharacterSubstring(length, out content).Trim();
    
                            if (pro.PropertyType.IsEnum)
                            {
                                foreach (var eatt in pro.PropertyType.GetCustomAttributes(false))
                                {
                                    if (eatt is EnumValeuNumberAttribute)
                                    {
                                        var eat = eatt as EnumValeuNumberAttribute;
                                        if (eat.IsChar)
                                        {
                                            var chr = Convert.ToChar(valuestr);
    
                                            var value = Convert.ChangeType(Enum.Parse(pro.PropertyType, ((int)chr).ToString()), pro.PropertyType);
                                            pro.SetValue(entity, value, null);
                                        }
                                        else
                                        {
                                            var value = Convert.ChangeType(Enum.Parse(pro.PropertyType, valuestr), pro.PropertyType);
                                            pro.SetValue(entity, value, null);
                                        }
                                        break;
                                    }
                                }
                            }
                            else
                            {
                                var value = Convert.ChangeType(valuestr, pro.PropertyType);
                                pro.SetValue(entity, value, null);
                            }
                        }
                    }
                }
                return (Entity)entity;
            }
        }

      这两个方法核心里通过反射属性上的特性,取特性中定义的固定值,来生成接口要求的字符串,合理的设计特性,可以使两个转换方法更优雅,更简便,在开发过程中,也需要不断调整理,适配,逐渐完善。

      可以用下面的代码完成测试

    using System;
    namespace ArchitectureDemo04
    {
        class Program
        {
            static void Main(string[] args)
            {
                var backQueryCard = Send(new QueryCardEntity { PersonNumber = "0000001", ICCardNumber = "C00000001" });
    
                var backDoctorQuery = Send(new DoctorQuery { DoctorCode = "0001" });
            }
            /// <summary>
            /// 发送
            /// </summary>
            /// <param name="entity"></param>
            /// <returns></returns>
            static Entity Send(Entity entity)
            {
                try
                {
                    foreach (var att in entity.GetType().GetCustomAttributes(false))
                    {
                        if (att is PackageTypeAttribute)
                        {
                            var attPackage = att as PackageTypeAttribute;    
                            Console.WriteLine($"入参:");
                            Console.WriteLine(entity);
                            Console.WriteLine("模拟函数调用:");
                            Console.WriteLine($"OltpTransData({attPackage.OperationType},{attPackage.DataFormaterType},{attPackage.MinLength},{entity})");
                            var backContent = BackOperation(entity);
                            var backEntity = entity.ToEntity(entity.GetType(),backContent);
                            return backEntity;
                        }
                    }
                    return null;
                }
                catch
                {
                    throw;
                }
            }
            /// <summary>
            /// 模拟医保中心返回
            /// </summary>
            /// <param name="entity">参数</param>
            /// <returns></returns>
            static string BackOperation(Entity entity)
            {
                switch (entity.GetType().Name)
                {
                    case "QueryCardEntity":
                        return " 0000001                Jack210213198411113111C00000001   1A   1000.66         0         0         0      1800A00131   0   0"; 
                    case "DoctorQuery":
                        return "    0001            DcotorLi210211198707182233            0002011320201029190850  1";
                }
                return null;
            }
        }
    }
      
      想要更快更方便的了解相关知识,可以关注微信公众号 
     
  • 相关阅读:
    用SNMP协议实现系统信息监控--Windows Server 2008
    Apache与Tomcat区别联系
    Oracle数据库的创建与验证
    oracle监听服务开启
    【VS Code 开发工具】在VS Code中使用Markdown语法
    【SQLServer数据库】SQLServer分库分表
    【SQLServer数据库】SQLServer死锁与优化
    【SQLServer数据库】SQLServer悲观锁和乐观锁
    【HTTP】HTTP Body
    【SQLServer数据库】SQLServer视图
  • 原文地址:https://www.cnblogs.com/ljknlb/p/15854619.html
Copyright © 2011-2022 走看看