上一篇博文中,利用属性反射的特点,用两个方法完成了字符转实体,实体转字符的工作,但有些复杂的场景,上面方法就没那么好用了,就需要更复杂的方式来组装处理。
先来看一个接口文档,下面是接口的调用方式
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; } } }想要更快更方便的了解相关知识,可以关注微信公众号