上一篇博文中,利用属性反射的特点,用两个方法完成了字符转实体,实体转字符的工作,但有些复杂的场景,上面方法就没那么好用了,就需要更复杂的方式来组装处理。
先来看一个接口文档,下面是接口的调用方式
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));}}}}#endregionreturn 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;}}
}