.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