背景
由于我们检验对串口仪器已经有成熟的方案。而且是数据库脚本语言通过串口转网口连的。省去了连仪器要配连接电脑的开销,同时不用在电脑部署环境和程序,用脚本调试也方便。对于仪器在电脑生成数据文件的、或者监听对方数据库、或者需要画图的等就不适合用串口转网口走TCP的模式。为此需要写EXE运行在电脑上监听处理数据。
碰到的问题有:
1.仪器数据格式各种各样,需要监听各种格式的数据(可能不同项目文件后缀都不同)。
2.仪器数据存放结构各种各样,有都在一级目录的,有在多级目录的。
3.监听非简单文本数据(PDF、Excel、csv、直接数据生成mdb的等)
4.仪器厂商数据库设计参差不齐(有的给LIS留了标记字段,有的连主键都难找出)
5.可能要按数据取指定地方的图片,也可能要按数据绘图,也肯能要选图、截图
6.监听数据文件有的仪器的你不是监听完删掉(需要监听变化)。监听数据库的要有重传的问题。
7.怎么确保监听的兼容性,项目程序丢了用最新的肯定兼容老的,确保程序向着稳定发展,新加的公共功能之前所有的接口都能获得,而不是越写越散。
8.减少开发难度,避免陷入这些重复的繁琐逻辑里。
对于以上问题,如果采用碰到个仪器就从相似的仪器拷贝代码写一个EXE的做法。首先每个开发都要面临处理上面这些繁琐问题,基于拷贝程序带过来的bug,新的修复了老的程序的bug还存在,接口质量得不到提高。新加的功能也无法惠及老的实现。虽然每个EXE的复杂度没有统一模式的主程序大,不能实现产品化的把控。仔细观察不难发现,对文件的监听最后都可以提取为一行行的数据读取解析。对数据库的监听最后都可提取为一行行数据行的读取解析。对要上传给仪器数据的最后都可提取为查询返回的一行行检验数据,按返回数据做不同操作(写文件或者执行SQL等)。不同的仪器只是解析逻辑不同,对这些共性问题大家都是一致的。因此提取两个接口:
仪器给检验数据处理接口
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
namespace PreDeal.Interface
{
///<summary NoteObject="Class">
/// [功能描述:数据前处理接口,所有前处理改实现该接口,配置实现类实现相应的前处理] <para/>
/// [创建者:zlz] <para/>
/// [创建时间:2015年10月25日] <para/>
///<说明>
/// [说明:数据前处理接口,所有前处理改实现该接口,配置实现类实现相应的前处理]<para/>
///</说明>
///<修改记录>
/// [修改时间:本次修改时间]<para/>
/// [修改内容:本次修改内容]<para/>
///</修改记录>
///<修改记录>
/// [修改时间:本次修改时间]<para/>
/// [修改内容:本次修改内容]<para/>
///</修改记录>
///</summary>
public interface IPreDeal
{
/// <summary>
/// 前处理,实时处理用GetDataJSON方法调M及时响应,用PreDeal.Util.TxtUtil读写文本
/// </summary>
/// <param name="result">结果串</param>
/// <param name="machID">仪器ID</param>
/// <param name="dealProcess">处理程序</param>
/// <param name="index">当前文件的第几行</param>
/// <param name="fileName">当前读取的文件全名</param>
/// <returns>是否继续后处理,true是,false否</returns>
bool PreDeal(string result,string machID, string dealProcess,int index,string fileName);
/// <summary>
/// 前处理数据库,实时处理用GetDataJSON方法调M及时响应,用PreDeal.Util.TxtUtil读写文本
/// </summary>
/// <param name="row">当前行数据</param>
/// <param name="machID">仪器ID</param>
/// <param name="dealProcess">处理程序</param>
/// <param name="index">当前文件的第几行</param>
/// <returns>是否继续后处理,true是,false否</returns>
bool PreDealDataBase(DataRow row, string machID, string dealProcess, int index,string otherPara);
}
}
检验给仪器数据处理接口
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
namespace PreDeal.Interface
{
///<summary NoteObject="Class">
/// [功能描述:上传数据前处理接口,所有上传前处理改实现该接口,配置实现类实现相应的前处理] <para/>
/// [创建者:zlz] <para/>
/// [创建时间:2015年10月25日] <para/>
///<说明>
/// [说明:数据前处理接口,所有前处理改实现该接口,配置实现类实现相应的前处理]<para/>
///</说明>
///<修改记录>
/// [修改时间:本次修改时间]<para/>
/// [修改内容:本次修改内容]<para/>
///</修改记录>
///<修改记录>
/// [修改时间:本次修改时间]<para/>
/// [修改内容:本次修改内容]<para/>
///</修改记录>
///</summary>
public interface IUPPreDeal
{
/// <summary>
/// 前处理
/// </summary>
/// <param name="labno">检验号</param>
/// <param name="labnoInfo">标本信息</param>
/// <param name="patInfo">患者信息</param>
/// <param name="upPara">配置的上传前处理参数</param>
/// <param name="machID">仪器</param>
/// <param name="dealProcess">处理程序</param>
/// <returns>是否继续后处理,true是,false否</returns>
bool UPPreDeal(string labno, string labnoInfo, string patInfo, string upPara, string machID, string dealProcess);
}
}
关系图
通过以上结构实现的监听将有以下特点:
1.主程序会随发展趋于稳定化。
2.主程序加入的功能所有接口实现都会获得。
3.主程序修复的bug所有接口实现都会修复。
4.只要接口实现提交代码,监听可以保证承诺的兼容性(新的程序可以替换老的)。
5.统一的日志服务,统一的配置。
6.连仪器只要关注业务实现接口,不用关心别的繁琐的处理。
要点
1.要面向接口编程
2.从各种差异里抽取出大家的共性
3.结构比实现更重要,良好构思结构能朝预期方向良性发展,无结构只会越改越烂
实现事列
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PreDeal.Interface;
using LIS.Model.Bussiness;
using LIS.DAL.DataAccess;
using PreDeal.Ftp;
using PreDeal.Attributes;
namespace PreDeal
{
///<summary NoteObject="Class">
/// [功能描述:西森美康带图片的处理,处理数据格式] <para/>
/// [创建者:zlz] <para/>
/// [创建时间:2015年10月25日] <para/>
///<说明>
/// [说明:西森美康带图片的处理,处理数据格式
/// 0,2015-10-26,00:00:00,9999,XT1800I,41
/// 1,BASO#,2,0.01,,,,,
/// 1,BASO%,2,0.20,,,,,
/// 1,EO#,2,0.04,,,,,
/// 1,EO%,2,0.70,,,,,
/// 1,HCT,2,38.00,,,,,
/// 1,HFR,2,0,,,,,
/// 1,HGB,2,128.00,,,,,
/// 1,IRF,2,0,,,,,
/// 1,LFR,2,0,,,,,
/// 1,LYMPH#,2,1.52,,,,,
/// 1,LYMPH%,2,27.70,,,,,
/// 1,MCH,2,30.00,,,,,
/// 1,MCHC,2,337.00,,,,,
/// 1,MCV,2,89.20,,,,,
/// 1,MFR,2,0,,,,,
/// 1,MONO#,2,0.44,,,,,
/// 1,MONO%,2,8.00,,,,,
/// 1,MPV,2,9.80,,,,,
/// 1,NEUT#,2,3.48,,,,,
/// 1,NEUT%,2,63.40,,,,,
/// 1,NRBC#,2,0,,,,,
/// 1,NRBC%,2,0,,,,,
/// 1,P-LCR,2,23.50,,,,,
/// 1,PCT,2,23.00,,,,,
/// 1,PDW,2,11.40,,,,,
/// 1,PLT,2,234.00,,,,,
/// 1,RBC,2,4.26,,,,,
/// 1,RDW-CV,2,12.70,,,,,
/// 1,RDW-SD,2,40.80,,,,,
/// 1,RET#,2,0,,,,,
/// 1,RET%,2,0,,,,,
/// 1,WBC,2,5.49,,,,,
/// 3,,HPLT,D:\测试图片\00000009999_151026145540_HPLT.gif,2
/// 3,,HRBC,D:\测试图片\00000009999_151026145540_HRBC.gif,2
/// 3,,SBASO,D:\测试图片\00000009999_151026145540_SBASO.gif,2
/// 3,,SDIFF,D:\测试图片\00000009999_151026145540_SDIFF.gif,2
/// 3,,SNRBC,D:\测试图片\00000009999_151026145540_SNRBC.gif,2
/// 3,,SPLT,D:\测试图片\00000009999_151026145540_SPLT.gif,2
/// 3,,SPLT-O,D:\测试图片\00000009999_151026145540_SPLT-O.gif,2
/// 3,,SRET,D:\测试图片\00000009999_1510265540_SRET.gif,2
/// 3,,SRET-E,D:\测试图片\00000009999_151026145540_SRET-E.gif,2
/// 9,41
/// ]<para/>
///</说明>
///<修改记录>
/// [修改时间:本次修改时间]<para/>
/// [修改内容:本次修改内容]<para/>
///</修改记录>
///<修改记录>
/// [修改时间:本次修改时间]<para/>
/// [修改内容:本次修改内容]<para/>
///</修改记录>
///</summary>
[Remark(Remark = "西施美康带图片的处理,处理数据格式。\n0,2015-10-26,00:00:00,9999,XT1800I,41\n1,BASO#,2,0.01,,,,,\n1,BASO%,2,0.20,,,,,\n3,,HPLT,D:\\测试图片\00000009999_151026145540_HPLT.gif,2")]
public class DealImage : BaseDeal, IPreDeal
{
/// <summary>
/// 存流水号
/// </summary>
private static string episNo = "";
/// <summary>
/// 仪器代码
/// </summary>
private static string machCode = "";
/// <summary>
/// 前处理
/// </summary>
/// <param name="result">结果</param>
/// <param name="machID">仪器ID</param>
/// <param name="dealProcess">处理程序</param>
/// <param name="index">当前文件的第几行</param>
/// <param name="fileName">当前读取的文件全名</param>
/// <returns></returns>
public bool PreDeal(string result, string machID, string dealProcess, int index, string fileName)
{
string[] strArr = result.Split(',');
if (strArr != null && strArr.Length > 0)
{
//第一行数据时清空流水号
if (index == 0)
{
episNo = "";
machCode = "";
}
if (index == -1)
{
DealData("", machID, episNo, dealProcess, index);
}
if (strArr[0] == "0")
{
//第一行数据提取流水号
episNo = result.Split(',')[3];
machCode = result.Split(',')[5];
DealData(DealNotSeeChar(result + "," + machCode), machID, episNo, dealProcess, index);
return false;
}
else if (strArr[0] == "1" || strArr[0] == "4" || strArr[0] == "9")
{
DealData(DealNotSeeChar(result + "," + machCode), machID, episNo, dealProcess, index);
return false;
}
else if (strArr[0] == "3")
{
//得到配置的处理程序处理数据
try
{
//图片路径
string imgPath = strArr[3];
if (System.IO.File.Exists(imgPath))
{
string ftpPath = "";
//得到ftp
FtpService ftp = GetFtpHelper(machID, dealProcess, out ftpPath);
//上传图片
ftp.Upload(imgPath);
System.IO.FileInfo fInfo = new System.IO.FileInfo(imgPath);
//保存图片
SaveImg(machID, episNo, strArr[2], ftpPath.Split('^')[3] + fInfo.Name, dealProcess);
}
else
{
LIS.Core.Util.LogUtils.WriteDebugLog("文件不存在:" + imgPath);
}
}
catch (Exception ex)
{
LIS.Core.Util.LogUtils.WriteExceptionLog("获得数据处理程序失败", ex);
}
return false;
}
}
return true;
}
/// <summary>
/// 处理数据库数据
/// </summary>
/// <param name="row">当前行数据</param>
/// <param name="machID">仪器ID</param>
/// <param name="dealProcess">处理程序</param>
/// <param name="index">当前数据的第几行</param>
/// <param name="otherPara">其他参数</param>
/// <returns></returns>
public bool PreDealDataBase(System.Data.DataRow row, string machID, string dealProcess, int index, string otherPara)
{
throw new NotImplementedException();
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PreDeal.Interface;
using PreDeal.Attributes;
using System.IO;
using PreDeal.Ftp;
namespace PreDeal.UPDeal
{
///<summary NoteObject="Class">
/// [功能描述:爱康唐筛上传糖筛数据] <para/>
/// [创建者:zlz] <para/>
/// [创建时间:2015年10月25日] <para/>
///<说明>
/// [说明:默认的处理,配置改实现时实现读一行传一行的默认实现]<para/>
///</说明>
///<修改记录>
/// [修改时间:本次修改时间]<para/>
/// [修改内容:本次修改内容]<para/>
///</修改记录>
///<修改记录>
/// [修改时间:本次修改时间]<para/>
/// [修改内容:本次修改内容]<para/>
///</修改记录>
///</summary>
[Remark(Remark = "默认的处理,配置改实现时实现读一行传一行的默认实现")]
public class UpAiKangTangShai : BaseDeal, IUPPreDeal, IPreDeal
{
/// <summary>
/// 前处理
/// </summary>
/// <param name="labno">检验号</param>
/// <param name="labnoInfo">标本信息</param>
/// <param name="patInfo">患者信息</param>
/// <returns>是否继续后处理,true是,false否</returns>
public bool UPPreDeal(string labno, string labnoInfo, string patInfo, string upPara, string machID, string dealProcess)
{
LIS.DAL.ORM.EntityManager.EntityManagerImpl manager = new LIS.DAL.ORM.EntityManager.EntityManagerImpl("OTAccessBaseDbFactory");
string[] infoArr = patInfo.Split('~');
string err;
//认为m层组装了sql语句
if (infoArr.Length > 1 && infoArr[1] != "")
{
LIS.Core.Util.LogUtils.WriteDebugLog("插入唐筛数据");
LIS.Core.Util.LogUtils.WriteDebugLog("执行SQL:" + infoArr[1]);
bool ret = manager.ExecUnSelectSQL(infoArr[1], out err);
if (err != "")
{
LIS.Core.Util.LogUtils.WriteDebugLog("插入唐筛数据出错:" + err);
return false;
}
return true;
}
string sql = "insert into InputTable(Rep_No,Pat_Zip,Rep_Date,Pat_Name,Pat_Birthday,Pat_Addr,Pat_tel,Pat_Weight,Pat_Index,Rep_SampleDate,It_HCG,Pat_GestWeek,Pat_GestDay,Rep_Methor) values('{0}','{1}','{2}','{3}','{4}','{5}','{6}','{7}','{8}','{9}','{10}','{11}','{12}','{13}')";
string[] patArr = patInfo.Split('^');
string[] labArr = labnoInfo.Split(',');
string LisPatientID = patArr[8];
if (LisPatientID == "")
{
LisPatientID = labno;
}
string LisPatientName = patArr[9];
string LisBirthday = patArr[13].Split(' ')[0];
string LisSampleID = labno+"^"+patArr[28];
string LisSamplingDate = patArr[0];
string LisTestDate = patArr[0].Split(' ')[0];
string LisDoctorName = patArr[6];
string LisLocation = patArr[5];
sql = String.Format(sql, LisSampleID, patArr[28], LisTestDate, LisPatientName, LisBirthday, patArr[29], patArr[30], patArr[31], patArr[32], LisTestDate, patArr[33], patArr[34], patArr[35], patArr[36]);
bool ret1 = manager.ExecUnSelectSQL(sql,out err);
if (err != "")
{
LIS.Core.Util.LogUtils.WriteDebugLog("插入唐筛数据出错:"+err);
return false;
}
return true;
}
bool IPreDeal.PreDeal(string result, string machID, string dealProcess, int index, string fileName)
{
throw new NotImplementedException();
}
/// <summary>
/// 处理数据库
/// </summary>
/// <param name="row"></param>
/// <param name="machID"></param>
/// <param name="dealProcess"></param>
/// <param name="index"></param>
/// <param name="otherPara"></param>
/// <returns></returns>
bool IPreDeal.PreDealDataBase(System.Data.DataRow row, string machID, string dealProcess, int index, string otherPara)
{
try
{
LIS.Core.Util.LogUtils.WriteDebugLog("绘制上传唐筛图片");
string episNo = row["ColA"].ToString();
string T21 = "1:" + row["ColL"].ToString();
string T18 = "1:" + row["ColM"].ToString();
string Name21 = DrawTangSaiImg("1:270", "T21", T21, "1:690", episNo);
LIS.Core.Util.LogUtils.WriteDebugLog("生成图片文件:" + Name21);
string Name18 = DrawTangSaiImg("1:350", "T18", T18, "1:6200", episNo);
LIS.Core.Util.LogUtils.WriteDebugLog("生成图片文件:" + Name18);
string ftpPath = "";
//得到ftp
FtpService ftp = GetFtpHelper(machID, dealProcess, out ftpPath);
LIS.Core.Util.LogUtils.WriteDebugLog("获得FTP地址:" + ftpPath);
if (File.Exists(Name21))
{
System.IO.FileInfo fInfo21 = new System.IO.FileInfo(Name21);
//上传图片
ftp.Upload(Name21);
System.IO.File.Delete(Name21);
//保存图片
SaveImg(machID, episNo, "T21", ftpPath.Split('^')[3] + fInfo21.Name, dealProcess);
}
if (File.Exists(Name18))
{
System.IO.FileInfo fInfo18 = new System.IO.FileInfo(Name18);
//上传图片
ftp.Upload(Name18);
System.IO.File.Delete(Name18);
//保存图片
SaveImg(machID, episNo, "T18", ftpPath.Split('^')[3] + fInfo18.Name, dealProcess);
}
}
catch (Exception ex)
{
LIS.Core.Util.LogUtils.WriteExceptionLog("处理唐筛图片异常", ex);
}
return true;
}
}
}