本文共 19437 字,大约阅读时间需要 64 分钟。
上一篇博文中,利用属性反射的特点,用两个方法完成了字符转实体,实体转字符的工作,但有些复杂的场景,上面方法就没那么好用了,就需要更复杂的方式来组装处理。
先来看一个接口文档,下面是接口的调用方式
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——一个专门来给类或属性方法加特征的知识点。
////// 发送报文类型 /// [AttributeUsage(AttributeTargets.Class)] public class PackageTypeAttribute : Attribute { ////// 发关报文实体类属性特性类 /// /// 属性的序号,从1开始 /// 属性转成字符串后长度 public PackageTypeAttribute(uint OperationType, uint DataFormaterType, uint MinLength) { this.OperationType = OperationType; this.DataFormaterType = DataFormaterType; this.MinLength = MinLength; } ////// 业务请求类型 /// public uint OperationType { get; private set; } ////// 数据解析格式类型 /// public uint DataFormaterType { get; private set; } ////// 数据串最小长度 /// public uint MinLength { get; private set; } } ////// 报文中属性的顺序SN和长度Length /// [AttributeUsage(AttributeTargets.Property)] public class PackageAttribute : Attribute { ////// 序号,从1开始 /// public int SN { get; private set; } ////// 转成字符串后的长度 /// public int Length { get; private set; } ////// 发关报文实体类属性特性类 /// /// 属性的序号,从1开始 /// 属性转成字符串后长度 public PackageAttribute(int SN, int Length) { this.SN = SN; this.Length = Length; } ////// 是否是时间类型,因为时间类型是左对齐,右补空格 /// public bool IsDateTime { get; set; } } ////// 取枚举的值还是 /// [AttributeUsage(AttributeTargets.Enum)] public class EnumValeuNumberAttribute : Attribute { ////// 是否把枚举类型属性的的值数转成Char类型 /// public bool IsChar { get; set; } }
定义了三个特性类,PackageTypeAttribute主用来区分不同的交易类型,从实体类上获取不同交易的函数参数;PackageAttribute是在实体类的属性上的,是核心特性类,它标记了属性的序号,和每个属性的长度,和属性是否是DateTime类型;EnumValeuNumberAttribute是用来专门处理枚举类型的属性的。
////// 医师信息查询 /// [PackageType(1009, 504, 85)] public class DoctorQuery : Entity { ////// 医师编码 /// [Package(1, 8)] public virtual String DoctorCode { get; set; } ////// 医师姓名 /// [Package(2, 20)] public virtual String DoctorName { get; set; } ////// 身份证号 /// [Package(3, 18)] public virtual string PersonID { get; set; } ///编号为0002的医院, 下属有编号为0113的定点, 在总院注册登记的医师可以在这样的2家医院出诊, 则“可出诊医院编号”为00020113,若长度不足20位则前补空格。 ////// 可出诊医院编号 /// [Package(4, 20)] public virtual string CanVisitHospitalCode { get; set; } ////// 终止日期 /// [Package(5, 16, IsDateTime = true)] public virtual string TerminationTime { get; set; } ////// 有效标志 /// [Package(6, 1)] public virtual DLYBAvailableMarker DLYBAvailableMarker { get; set; } } ////// 有效标志 /// [EnumValeuNumber] public enum DLYBAvailableMarker { ////// 无效 /// nullity = 0, ////// 有效 /// Valid = 1 } ////// 实时验卡(读卡、验卡) /// [PackageType(1001, 101, 126)] public class QueryCardEntity : Entity { ////// 个人编号 /// [Package(1, 8)] public virtual string PersonNumber { get; set; } ////// 姓名 /// [Package(2, 20)] public virtual string Name { get; set; } ////// 身份证号 /// [Package(3, 18)] public virtual string PersonID { get; set; } ////// IC卡号 /// [Package(4, 9)] public virtual string ICCardNumber { get; set; } long therapyNumber; ////// 治疗序号 /// [Package(5, 4)] public virtual long TherapyNumber { get { return therapyNumber; } set { if (value >= 0 && value <= 9999) { therapyNumber = value; } else { throw new Exception("治疗号在0-9999之间"); } } } ////// 职工就医类别 /// [Package(6, 1)] public virtual string TherapyCategory { get; set; } ////// 基本个人帐户余额 /// [Package(7, 10)] public virtual decimal BasePersonBalance { get; set; } ////// 补助个人帐户余额 /// [Package(8, 10)] public virtual decimal SubsidyPersonBalance { get; set; } ////// 统筹累计 /// [Package(9, 10)] public virtual decimal PlannerTotal { get; set; } ////// 门诊慢病统筹累计///新 /// [Package(10, 10)] public virtual decimal MZSlowDisease { get; set; } ////// 月缴费基数 /// [Package(11, 10)] public virtual decimal BaseFeeByMonth { get; set; } ////// 帐户状态 /// [Package(12, 1)] public virtual string AccoutState { get; set; } ////// 参保类别1 /// [Package(13, 1)] public virtual string InsuranceCategory1 { get; set; } ////// 参保类别2 /// [Package(14, 1)] public virtual string InsuranceCategory2 { get; set; } ////// 参保类别3 /// [Package(15, 1)] public virtual string InsuranceCategory3 { get; set; } ////// 参保类别4 /// [Package(16, 1)] public virtual string InsuranceCategory4 { get; set; } ////// 参保类别5 /// [Package(17, 1)] public virtual string InsuranceCategory5 { get; set; } ////// 住院次数累计新 /// [Package(18, 4)] public virtual int ZYAddNumber { get; set; } ////// 家床次数累计新 /// [Package(19, 4)] public virtual int AddBedNumber { get; set; } }
上面的实体类分别使用了特性类,参照文档就OK。
public static class StringExtension { ////// 右边不够长度补空格,汉字算两个空格 /// /// /// 设定长度 ///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; } /// /// 右边不够长度补空格,汉字算两个空格 /// /// /// 设定长度 ///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; } /// /// 切除字符串 /// 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; } }
上面代码是对某些属性的对齐方式作了处理。
////// 报文类的父类 /// public abstract class Entity { ////// 组装发送报文格式 /// ///public override string ToString() { var pros = this.GetType().GetProperties(); var sortPro = new SortedList (); 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(); } /// /// 把一个字符串转成一个对象 /// /// ///public Entity ToEntity(Type entityType,string content) { var pros = entityType.GetProperties(); //按照特性类上的SN序号把属性名存入集合proPackageList中 List proPackageList = new List (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" }); } ////// 发送 /// /// ///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; } } /// /// 模拟医保中心返回 /// /// 参数 ///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; } }}
转载地址:http://vskdi.baihongyu.com/