1. 程式人生 > 其它 >.NET架構小技巧(4)——反射,架構人員法寶II

.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;
        }
    }
}
     想要更快更方便的瞭解相關知識,可以關注微信公眾號 ****歡迎關注我的asp.net core系統課程****
《asp.net core精要講解》 https://ke.qq.com/course/265696
《asp.net core 3.0》 https://ke.qq.com/course/437517
《asp.net core專案實戰》 https://ke.qq.com/course/291868
《基於.net core微服務》 https://ke.qq.com/course/299524