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

先来看一个接口文档,下面是接口的调用方式

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;
        }
    }
}