背景
刚开始和自助厂商做对接时候是给人提供dll调用,让他先调查询方法,然后再调打印方法,打印完了再调设置状态方法。或者查询和设置状态给对方提供webservice,查询成功后对方调用我们exe或dll。随着对接了几家自助机后,发现一方面对方实现全程需要检验开发配合,项目上不懂开发或者交互模式的很难跟进;另外一方面打印有问题难以排查,医院都是把问题甩给检验系统,最后还得检验拿出证据证明不是检验问题才找自助厂商,问题的责任划分也是项目人员难以做的。有次到深圳出差的时候也是要给自助机提供接口,构思之后发现既然都是要调用接口,何不就把双方接口压成一个,走webservice调用,自助厂商把读取的卡号和卡类型传给接口的Print方法。接口返回可打印,未出,已打印等信息,并且把可打印的报告在配置的目录生成PDF文件和图片文件,自助厂商按返回信息取文件打印就是了。
自助厂商疑问
1.为何不采用你们在服务器按指定名称规则生成好文件,我们按文件找报告打印的模式?
答:一方面都统一生成文件在医院规模扩大后生成效率的问题,就算一秒一个在医院报告上万时候集中审核或者批量审核时候速度也难跟上。第二方面报告单格式调整了文件已经生成了这部分文件有多少,是否都要看是否打印过找出来重新生成问题。第三方面对微生物手工计费在没交费前提下患者在自助机打印收费通知单,交完费再打印就打印阳性报告单的支持是这种生成文件模式无法满足的。包括拒收单、延迟报告单等等,所以默认不支持服务器生成文件的模式。第四方面就是有问题项目的人无法确定问题的归属。第五可能生成文件停止了不能及时发现。
2.为什么不提供个exe给我们调用exe打印,然后过几秒你们自动关闭呢?
答:exe模式的最大问题就是弹出exe的风格和自助界面的风格问题。另外不同医院还得做不同的界面来满足用户调整。另外的就是在有多个纸张多个打印机的时候我们的exe没有打印机和纸盒调度控制(A4和A5报告控制不同纸张打印),包括对缺纸、缺墨这些硬件信息的控制。我们毕竟不卖硬件,忙于这个事对产品没好处。有问题项目的人无法确定问题的归属。
3.为啥不提供dll给我们调呢,我们也是C#写的呢?
答:dll模式一样有exe模式的问题。另外提供dll对方的程序得引用我们dll和dll的依赖库,造成对方代码质量很低,在缺少环境的时候可能编译都无法通过。有问题项目的人无法确定问题的归属。
思路:以服务的发布方式把查询报告、生成文件、设置状态压缩到一个服务接口
接口服务化的好处(打印即服务)
1.对自助厂商调用来说网上调用wcf资料很多,也没有对我们程序环境的依赖,使自己代码整洁。
2.对项目人员来说打印有问题时候只需要把给自助厂商的包用提供的最简单的“打印服务调用事列.exe”,用卡号和卡类型调用试试,返回信息和生成文件没问题就不是检验方面的问题。
3.自助厂商不用担心我们程序随版本变化,服务接口不变就和系统调整没关,只要对接过的,哪个医院都是一样,只要问项目得到自助服务包就行。
4.只有不随着自助厂商怎么说怎么改,才能提供更稳定完善的报告打印服务.才能使接口服务向着版本化发展,一人一套的模式永远难以提供质量,最终打印的质量问题还推给检验系统。
5.也可要求其他厂商提供“打印即服务”模式,方便双方定责,减少双方依赖。为每个LIS厂商只需要一个调用服务的实现类适配逻辑,稳定自助机程序的发展。
6.不用繁琐的调服务,根据返回再又调exe或者dll什么的然后再设置状态等。用服务只需要有卡号读取到时候把卡号和卡类型传给服务的打印接口方法。服务会按config里配置的模式有以下反应:
- 没配置生成pdf路径的话直接调用打印机打印了(时候自助机打印机和纸盒打印,也不支持纸张控制的,省去写打印逻辑等,只需要调通就完成自助对接)
- 配置了生成pdf路径,服务程序不会调用打印机,会在配置的目录生成报告图片文件和pdf文件,自助厂商按接口返回信息里的文件路径取文件打印就是了。并且文件名称标明的A4和A5的信息。这种模式和上面的模式都是调用接口就设置了打印状态,再调用就是报告打印过了,不会生成文件了。
- 有的自助厂商说自助要像火车取票那样先查询出来,患者选择要打印的报告打印,这种模式项目把服务配置成患者选择模式。再调用服务的时候就不会生成文件。需要文件时候把卡号位置传要生成报告的主键用^拼接,卡类型传入File就给生成文件(这个模式会自动设置打印状态);卡类型传入Self也会生成文件只是不设置状态;如果打印成功再把卡类型传入Printed来自己设置状态(防止自助机缺纸、卡纸等情况我们生成了文件实际没打印报告,但是患者去别的机器打印又发现打印状态已经是打印了的情况)
自助打印服务模式只有这一个接口对外,采用wcf发布到127.0.0.1上
使用服务的自助流程
服务接口申明:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
namespace SelfPrintService
{
[ServiceContract(Namespace = "http://Microsoft.ServiceModel.Samples")]
public interface IPrint
{
/// <summary>
/// 打印服务接口
/// </summary>
/// <param name="cardNo">卡号</param>
/// <param name="cardType">卡类型</param>
/// <returns>打印信息串</returns>
[OperationContract]
string Print(string cardNo, string cardType);
/// <summary>
/// 查询能打印报告的信息
/// </summary>
/// <param name="startDate">开始日期,格式:yyytMMdd</param>
/// <param name="endDate">结束日期,格式:yyytMMdd</param>
/// <returns>返回信息</returns>
[OperationContract]
string QryCanPrintInfo(string startDate, string endDate);
}
}
返回信息说明(返回XML信息):
CanPrint:可打印数量
NotAuth:未出报告数量
CName:患者姓名
Remark:限制不让自助打印说明信息
DetailList:明细信息,下面详细说明明细意义
Location:科室
Ward:病区
Doctor:医师
Specimen:标本
TestSetDesc:医嘱
Remark:说明,检验维护不允许在自助机打印报告的提示信息或发生错误时候返回的详细信息
RegNo:登记号
VisitNumber:检验号
ReceiveDate:接收日期
ReceiveTime:接收时间
AcceptDate:核收日期
AcceptTime:核收时间
EntryDate:初审日期
EntryTime:初审时间
AuthDate:审核日期
AuthTime:审核时间
Printed:是否打印 0/1
SelfPrinted:是否自助打印 0/1
StatusDesc:状态
PDFPath:pdf地址
ImgPath:图片地址
XML返回信息实体
/// <summary>
/// 返回实体
/// </summary>
public class RetDto
{
/// <summary>
/// 能打印的报告
/// </summary>
public string CanPrint
{
get;
set;
}
/// <summary>
/// 未出的报告
/// </summary>
public string NotAuth
{
get;
set;
}
/// <summary>
/// 患者姓名
/// </summary>
public string CName
{
get;
set;
}
/// <summary>
/// 说明
/// </summary>
public string Remark
{
get;
set;
}
/// <summary>
/// 其他命令
/// </summary>
public string OtherCommand
{
get;
set;
}
/// <summary>
/// 详细信息集合
/// </summary>
public List<ReportDetail> DetailList
{
get;
set;
}
}
/// <summary>
/// 详细信息
/// </summary>
public class ReportDetail
{
/// <summary>
/// 报告主键
/// </summary>
public string ReportDR
{
get;
set;
}
/// <summary>
/// 科室
/// </summary>
public string Location
{
get;
set;
}
/// <summary>
/// 病区
/// </summary>
public string Ward
{
get;
set;
}
/// <summary>
/// 医师
/// </summary>
public string Doctor
{
get;
set;
}
/// <summary>
/// 标本
/// </summary>
public string Specimen
{
get;
set;
}
/// <summary>
/// 医嘱
/// </summary>
public string TestSetDesc
{
get;
set;
}
/// <summary>
/// 说明
/// </summary>
public string Remark
{
get;
set;
}
/// <summary>
/// 登记号
/// </summary>
public string RegNo
{
get;
set;
}
/// <summary>
/// 检验号
/// </summary>
public string VisitNumber
{
get;
set;
}
/// <summary>
/// 接收日期
/// </summary>
public string ReceiveDate
{
get;
set;
}
/// <summary>
/// 接收时间
/// </summary>
public string ReceiveTime
{
get;
set;
}
/// <summary>
/// 核收日期
/// </summary>
public string AcceptDate
{
get;
set;
}
/// <summary>
/// 核收时间
/// </summary>
public string AcceptTime
{
get;
set;
}
/// <summary>
/// 初审日期
/// </summary>
public string EntryDate
{
get;
set;
}
/// <summary>
/// 初审时间
/// </summary>
public string EntryTime
{
get;
set;
}
/// <summary>
/// 审核日期
/// </summary>
public string AuthDate
{
get;
set;
}
/// <summary>
/// 审核时间
/// </summary>
public string AuthTime
{
get;
set;
}
/// <summary>
/// 是否打印
/// </summary>
public string Printed
{
get;
set;
}
/// <summary>
/// 是否自助打印
/// </summary>
public string SelfPrinted
{
get;
set;
}
/// <summary>
/// 状态
/// </summary>
public string StatusDesc
{
get;
set;
}
/// <summary>
/// PDF路径
/// </summary>
public string PDFPath
{
get;
set;
}
/// <summary>
/// 图片路径
/// </summary>
public string ImgPath
{
get;
set;
}
/// <summary>
/// 患者姓名
/// </summary>
public string PatName
{
get;
set;
}
/// <summary>
/// 患者性别
/// </summary>
public string Sex
{
get;
set;
}
/// <summary>
/// 患者年龄
/// </summary>
public string Age
{
get;
set;
}
/// <summary>
/// 患者年龄
/// </summary>
public string AgeUnit
{
get;
set;
}
}
/// <summary>
/// 返回的打印参数
/// </summary>
public class RetPrintParas
{
public List<Para> PrintParas
{
get;
set;
}
}
/// <summary>
/// 参数
/// </summary>
public class Para
{
/// <summary>
/// ClassName
/// </summary>
public string ClassName
{
get;
set;
}
/// <summary>
/// FuncName
/// </summary>
public string FuncName
{
get;
set;
}
/// <summary>
/// 0号参数
/// </summary>
public string P0
{
get;
set;
}
/// <summary>
/// 1号参数
/// </summary>
public string P1
{
get;
set;
}
/// <summary>
/// 2号参数
/// </summary>
public string P2
{
get;
set;
}
/// <summary>
/// 3号参数
/// </summary>
public string P3
{
get;
set;
}
/// <summary>
/// 4号参数
/// </summary>
public string P4
{
get;
set;
}
/// <summary>
/// 5号参数
/// </summary>
public string P5
{
get;
set;
}
/// <summary>
/// 6号参数
/// </summary>
public string P6
{
get;
set;
}
/// <summary>
/// 7号参数
/// </summary>
public string P7
{
get;
set;
}
/// <summary>
/// 8号参数
/// </summary>
public string P8
{
get;
set;
}
/// <summary>
/// 9号参数
/// </summary>
public string P9
{
get;
set;
}
/// <summary>
/// 10号参数
/// </summary>
public string P10
{
get;
set;
}
/// <summary>
/// 11号参数
/// </summary>
public string P11
{
get;
set;
}
/// <summary>
/// 12号参数
/// </summary>
public string P12
{
get;
set;
}
/// <summary>
/// 13号参数
/// </summary>
public string P13
{
get;
set;
}
/// <summary>
/// Sessions
/// </summary>
public string Sessions
{
get;
set;
}
}
调用事列代码
private void btnPrint_Click(object sender, EventArgs e)
{
if (txtCardNo.Text == "")
{
MessageBox.Show("请输入卡号!");
return;
}
txtInfo.Text = "";
BasicHttpBinding bind1 = new BasicHttpBinding();
bind1.ReaderQuotas = new System.Xml.XmlDictionaryReaderQuotas();
bind1.ReaderQuotas.MaxArrayLength = int.MaxValue;
bind1.ReaderQuotas.MaxStringContentLength = int.MaxValue;
bind1.MaxReceivedMessageSize = int.MaxValue;
bind1.ReaderQuotas = new System.Xml.XmlDictionaryReaderQuotas();
bind1.ReaderQuotas.MaxStringContentLength = int.MaxValue;
EndpointAddress endpointAddress = new EndpointAddress("http://127.0.0.1:64567/PrintService");
ServiceReference1.PrintClient client = new ServiceReference1.PrintClient(bind1, endpointAddress);
string ret = client.Print(txtCardNo.Text, txtCardType.Text);
txtInfo.Text += ret;
}
包装的打印目录下文件的基类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;
using System.Drawing.Printing;
using System.Drawing;
namespace PatSelfPrint
{
///<summary NoteObject="Class">
/// [功能描述:调用检验打印服务基类,包装了扫描文件打印的细节] <para/>
/// [创建者:zlz] <para/>
/// [创建时间:2017年8月10日] <para/>
///<说明>
/// [说明:调用检验打印服务基类,包装了扫描文件打印的细节]<para/>
///</说明>
///<修改记录>
/// [修改时间:本次修改时间]<para/>
/// [修改内容:本次修改内容]<para/>
///</修改记录>
///<修改记录>
/// [修改时间:本次修改时间]<para/>
/// [修改内容:本次修改内容]<para/>
///</修改记录>
///</summary>
public static class LisPrintBase
{
/// <summary>
/// 打印报告
/// </summary>
/// <param name="pdfBasePath">pdf基地址</param>
public static void PrintReport(List<string> files, string pdfBasePath)
{
//有报告文件
if (files.Count > 0)
{
//遍历打印文件
foreach (var f in files)
{
PrintClass pc = new PrintClass(f);
}
}
}
/// <summary>
/// 获取指定目录下的所有在指定扩展名集合内的文件路径全名,传扩展名空参数就认为是所有文件
/// </summary>
/// <param name="directory">目录</param>
/// <param name="extensionList">扩展名集合,不带.</param>
/// <returns>文件路径集合</returns>
public static List<FileInfo> GetAllPerFileInfo(string directory)
{
List<FileInfo> paths = new List<FileInfo>();
if (Directory.Exists(directory))
{
DirectoryInfo drect = new DirectoryInfo(directory);
SeeFile(drect, paths);
}
return paths;
}
/// <summary>
/// 遍历文件
/// </summary>
/// <param name="di"></param>
private static void SeeFile(DirectoryInfo di, List<FileInfo> paths)
{
DirectoryInfo[] dArr = di.GetDirectories();
FileInfo[] fArr = di.GetFiles();
foreach (FileInfo f in fArr)
{
if (f.Extension.ToLower()==".bmp")
{
paths.Add(f);
}
}
foreach (DirectoryInfo d in dArr)
{
SeeFile(d, paths);
}
}
/// <summary>
/// 删除文件
/// </summary>
/// <param name="path">目录</param>
/// <returns></returns>
public static bool DeleteFiles(string path)
{
if (Directory.Exists(path)==false)
{
return false;
}
DirectoryInfo dir = new DirectoryInfo(path);
FileInfo[] files = dir.GetFiles();
try
{
foreach (var item in files)
{
File.Delete(item.FullName);
}
if (dir.GetDirectories().Length != 0)
{
foreach (var item in dir.GetDirectories())
{
if (!item.ToString().Contains("$") && (!item.ToString().Contains("Boot")))
{
DeleteFiles(dir.ToString() + "\\" + item.ToString());
}
}
}
dir.Delete();
return true;
}
catch (Exception)
{
return false;
}
}
}
/// <summary>
/// 打印类
/// </summary>
public class PrintClass
{
/// <summary>
/// 打印文档
/// </summary>
PrintDocument pdDocument = new PrintDocument();
/// <summary>
/// 打印的图片
/// </summary>
Image tmpImg = null;
/// <summary>
/// 图片名称
/// </summary>
string imgName = "";
public PrintClass(string fileName)
{
if (File.Exists(fileName))
{
imgName = fileName;
//订阅BeginPrint事件
pdDocument.BeginPrint += new PrintEventHandler(pdDocument_BeginPrint);
//定义打印内容
pdDocument.PrintPage += new PrintPageEventHandler(OnPrintPage);
//訂閱EndPrint事件,释放资源
pdDocument.EndPrint += new PrintEventHandler(pdDocument_EndPrint);
tmpImg = Image.FromFile(fileName);
pdDocument.Print();
}
}
/// <summary>
/// 开始打印
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void pdDocument_BeginPrint(object sender, PrintEventArgs e)
{
//设置纸张、打印机、送纸来源
if(imgName.Contains("A4"))
{
//A4报告
//打印机名称
string tPrinterName = System.Configuration.ConfigurationSettings.AppSettings["A4PrinterName"];
//设置缺省打印机
if (!String.IsNullOrEmpty(tPrinterName))
{
//获取所有打印机名称
foreach (string sPrint in PrinterSettings.InstalledPrinters)
{
//有这个打印机才设置
if (sPrint == tPrinterName)
{
pdDocument.DefaultPageSettings.PrinterSettings.PrinterName = tPrinterName;
}
}
}
//打印机送纸来源
string tPaperSource = System.Configuration.ConfigurationSettings.AppSettings["A4PaperSource"];
if (tPaperSource != "")
{
int iPaperSource = Convert.ToInt32(tPaperSource);
if (pdDocument.PrinterSettings.PaperSources.Count > iPaperSource)
{
///设置打印机送纸来源
pdDocument.DefaultPageSettings.PaperSource = pdDocument.PrinterSettings.PaperSources[iPaperSource];
}
}
bool bLandscape = false;
//打印机旋转方式
string tLandscape = System.Configuration.ConfigurationSettings.AppSettings["A4Landscape"];
if (tLandscape == "Landscape")
{
bLandscape = true;
}
pdDocument.DefaultPageSettings.Landscape = bLandscape;
}
else
{
//A5报告
//打印机名称
string tPrinterName = System.Configuration.ConfigurationSettings.AppSettings["A5PrinterName"];
//设置缺省打印机
if (!String.IsNullOrEmpty(tPrinterName))
{
//获取所有打印机名称
foreach (string sPrint in PrinterSettings.InstalledPrinters)
{
//有这个打印机才设置
if (sPrint == tPrinterName)
{
pdDocument.DefaultPageSettings.PrinterSettings.PrinterName = tPrinterName;
}
}
}
//打印机送纸来源
string tPaperSource = System.Configuration.ConfigurationSettings.AppSettings["A5PaperSource"];
if (tPaperSource != "")
{
int iPaperSource = Convert.ToInt32(tPaperSource);
if (pdDocument.PrinterSettings.PaperSources.Count > iPaperSource)
{
///设置打印机送纸来源
pdDocument.DefaultPageSettings.PaperSource = pdDocument.PrinterSettings.PaperSources[iPaperSource];
}
}
bool bLandscape = false;
//打印机旋转方式
string tLandscape = System.Configuration.ConfigurationSettings.AppSettings["A5Landscape"];
if (tLandscape == "Landscape")
{
bLandscape = true;
}
pdDocument.DefaultPageSettings.Landscape = bLandscape;
}
}
/// <summary>
/// 绘制打印
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnPrintPage(object sender, PrintPageEventArgs e)
{
e.Graphics.DrawImage(tmpImg, 0, 0);
tmpImg.Dispose();
//绘制完成后,关闭多页打印功能
e.HasMorePages = false;
}
/// <summary>
///5、EndPrint事件,释放资源
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void pdDocument_EndPrint(object sender, PrintEventArgs e)
{
}
}
}