DoubleLi

qq: 517712484 wx: ldbgliet

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

目标

由于医保是目前与HIS系统唯一需要接口的业务,且由于各地区的医保接口不相同,也不相通,但是主要的业务的处理模型基本上是相通的,即都需要进行收费明细处理.为满足能够灵活的支持各种医保接口且减少我们自己的HIS系统与医保接口的强藕合,则需要对医保的业务处理进行必要的抽象建模.

但是,由于B/S系统需要考虑交互上的问题,则对这个业务的需求就需要使用JS来完成.JS有一定的面向对象的机制,但不是很全面,目前的了解应该可以采用变通的方式实现抽象.

JS对象模型

1.对象图

这里,采用了类图的描述方式,但是实现的方式需要采用变通的方法

1.         OCXBase:OCX完成与对本地医保的接口调用封装,使JS可以与医保接口交互,每一种医保类型需要一个具体的实现,OCX对外暴露的接口允许变化,这是因为每种医保都有各自的业务规则且提供的接口形式也不尽相同,这个规则的变动只需要具体的JS业务类知道,针对于各个医保的JS对其变化进行了封装.

2.         服务器端业务:这个可以以WebService的形式或者页面来实现,主要是针对各种不同的医保返回的数据进行处理,例如需要保存各种医保的返回数据,以为将来的报表查询提供原始数据.另外,在这个业务处理过程还要处理我们自身的业务.

3.         JS业务类:因为是JS的实现,所以不可能将OCX和服务器端的业务类以引用的形式传给它,限于技术上的约束,这里对OCX和服务器端均需要采用字符串用来标识它们.JS业务类需要保持对象的对外暴露的接口,在其内部,它可以根据业务的需要来协调调用本地OCX控件与医保接口的接互,和对服务器端业务类的远程异步调用来完成医保数据与我们自身业务数据的处理.

2.调用关系时序图

调用首先由客户端页面来触发,JS得到其所需要的业务数据,然后将数据发送给OCX控件用来与医保接口交互和调用服务器端业务类进行数据的处理,服务器端的业务类即要处理医保的业务又要处理我们自身的业务.

在这里,调用OCX对象和异步远程调用服务器的业务类是可以由JS业务类来决定调用的顺序的,因为这里可能存在的变化点是根据医保接口的特点其处理的过程可能会因此而发生变化,那么也就是说,JS的这个业务处理的过程中将变化点封装在它的内部,而不需要暴露给页面的代码进行处理.

最后,如果全部处理成功,则需要异步回调当前页面,对当前页面的某些控件进行刷新,或者执行打印报表一类的操作.

3.对现有方案的思考

 

现有的方案是页面与医保的接口进行的强耦合,即由页面级的代码来区别各种医保类型然后加以处理,由于各种医保接口的不尽相同,则势必在页面级别编写各种类型的JS与各种类型的OCX进行交互,页面上会存在很多判断来确定是何种医保类型,然后才去决定应该进行何种的业务处理,如果医保的接口变动,或者新增医保类型,那么在页面上进行代码的修改将会变得很麻烦.另外,由于B/S的特殊性,动态执行某些功能时需要经常性的向页面注册脚本也会使处理过程变的更加复杂.

而采用与医保处理分离的方式,则可以页面与医保的处理独立进行,减小依赖,便于扩展和修改.

系统实现

依赖关系概述

从上面的分析可以确定,客户端的JS类和服务器端的业务类应该彼此了解对方的处理细节,它们之间是有很强的依赖性的,所以在实现具体的JS客户端业务类与具体的服务器端业务类时可以互相约定一些调用过程的细节.这里指的具体的含义是某种医保的业务类型,这就会形成某个业务类,会分别有具体的JS业务对象和服务器端的业务对象.

调用服务器端业务类的方式

JS客户端业务对象需要调用服务器端的业务处理逻辑,这里客户端的需要异步请求以不刷新页面,那么这个方案自然是要采用XMLHttpRequest,那么服务器端的业务处理以何种方式暴露给JS客户端业务对象呢?可选的方案有WebService,我们可以使用XMLHttpRequest可以传递Post或者Get参数,然后在WebService进行判断再处理.

但是这里存在一个问题,就是这里的业务类是需要处理我们自身业务的,即需要我们自身业务处理所需要的参数.我们倒着来演绎一下这个参数是从哪来的:

1.         服务器业务处理的这些参数从哪来呢?JS业务类传入?

2.         JS从哪里获取业务处理所需的数据呢?在调用JS业务类的方法时传进来.

3.         在调用JS业务类的方法时如何传进来?触发业务时组织数据传入.

4.         触发业务时如何组织数据呢?这就回到原始的数据点了.

 

从功能上可以得知,触发事件的点是在客户端的页面中,那么获取数据可以通过DOM或者回服务器组织数据.通过DOM的方式,则需要与页面进行强藕合,且有可能组织不全处理所需的参数.回服务器组织参数,则需要后台的页类来处理,而后台的页面又如何知道具体的业务需要哪些数据呢?这就又造成了强藕合.即使通过服务器的业务类来组织数据,但是让一大堆参数数据传来传去也不是很爽,与现有的模型冲突太大.

那么有没有一种方案,能够做到让服务器端的页面完成完整的生命周期, 然后我们就可以读取各个控件的值了?有的,因为了解了AJAX的原理和ASP.Net2.0的异常回调模型后,就会发现这种方案是完全可行的,分析Asp.net2.0的异常回调模型是一个很大的议题,我在这里就不讨论它了.我将微软的JS代码修改后封装到WebForm_DoCallback中,使用这个函数进行提交的时候可以将ViewState和Form里的值提交回去,这样就会使后台页面的IsPostback为真,可以正常读取控件的值,然后再在页面级进行检查后,再调用业务类进行业务处理,参数传递自然也变成了一件简单的事情.

JS业务类上传服务器的消息格式

消息格式采用XML格式,如下所示:


<?xml version="1.0"?>

<Message>

 
<Class></Class>

 
<Command></Command>

 
<ClientParams></ClientParams>

 
<ClientReturn></ClientReturn>

 
<ServerParams></ServerParams>

 
<ServerReturn></ServerReturn>

</Message>

 

 

l         Class:标识了调用功能的JS的业务类标志.

 

l         Command:命令字标识

l         ClientParams:调用客户端JS函数时的传入参数.

l         ClientReturn:调用客户端JS与OCX交互时产生的返回值.

l         ServerParams:需要调用服务器的功能的参数

l         ServerReturn:服务器端的返回值,这个主要是保存上次调用时的返回值.

JS业务类的实现代码

医保基类


//<summary>构造函数</summary>
//
<param name="param">显示错误信息的容器</param>
//
<returns></returns>
function MedicareBase(errContainer)
{
    
this.ErrContainer=errContainer;
    
    
//保留属性定义
    
    
this._MessagePacketFlag="<?xml version=\"1.0\"?>";
    
    
this._BeginMessageFlag="<Message>";
    
this._EndMessageFlag="</Message>";
    
    
this._BeginClassFlag="<Class>";
    
this._EndClassFlag="</Class>";
    
    
    
this._BeginNoTransCommand="<Command Transaction='No'>";
    
this._BeginBeginTransCommand="<Command Transaction='Begin'>";
    
this._BeginCommitTransCommand="<Command Transaction='Commit'>";
    
this._BeginRollTransCommand="<Command Transaction='Rollback'>";
    
    
this._EndCommandFlag="</Command>";
    
    
this._BeginClientParamsFlag="<ClientParams>";
    
this._EndClientParamsFlag="</ClientParams>";
        
    
this._BeginClientReturnFlag="<ClientReturn>";
    
this._EndClientReturnFlag="</ClientReturn>";
    
    
this._BeginServerParamsFlag="<ServerParams>";
    
this._EndServerParamsFlag="</ServerParams>";
    
    
this._BeginServerReturnFlag="<ServerReturn>";
    
this._EndServerReturnFlag="</ServerReturn>";
    
    
this._JSClientCommand="JSClientCommand";
}

//<summary>获取以0或-1开头的真假值</summary>
//
<param name="param"></param>
//
<returns>true/false</returns>
MedicareBase.prototype._GetBoolean=function(str)
{
    
try
    {
        
if (str.substring(0,1)=='0')
        {
            
return true;
        }
        
else
        {
            
return false;
        }
    }
    
catch(e)
    {
        
return false;
    }
};

//<summary>去掉成功与否的标记,获取Server的返回值</summary>
//
<param name="param"></param>
//
<returns></returns>
MedicareBase.prototype._GetReturnString=function(str)
{
    
var digits = "0123456789"
    
try
    {
        
if (str.length==0)
        {
            
return str;
        }
        
        
if (str.charAt(0== "e" || str.charAt(0== "s")
        {
            
return str.substring(1);
        }
        
        
else
        {
            
for(i=0;i<str.length;i++)
            {
                
if (str.substring(i,1)!='-' && digits.indexOf(str.substring(i,1))==-1)
                {
                    
break;
                }
            }
        }
        
        
return str.substring(i);
    }
    
catch(e)
    {
        
return str;
    }
};

//<summary>去掉成功与否的标记,获取Server的返回值</summary>
//
<param name="param"></param>
//
<returns></returns>
MedicareBase.prototype._GetReturnFlag=function(str)
{
    
var digits = "0123456789"
    
try
    {
        
if (str.length==0)
        {
            
return str;
        }
        
var i=0;
        
if (str.charAt(0== "e" || str.charAt(0== "s")
        {
            
return '-1';
        }
        
else
        {
            
for(i=0;i<str.length;i++)
            {
                
if (str.substring(i,1)!='-' && digits.indexOf(str.substring(i,1))==-1)
                {
                    
break;
                }
            }
        }
        
return str.substring(0,i-1);
    }
    
catch(e)
    {
        
return str;
    }
};

//<summary>获取首行</summary>
//
<param name="param"></param>
//
<returns></returns>
MedicareBase.prototype._GetFirstLine=function(str)
{
    
var i=0;
    
for(i=0;i<str.length;i++)
    {
        
if (str.substring(i,i+1)=='\r' || str.substring(i,i+1)=='\n')
        {
            
break;
        }
    }
    
    
return str.substring(0,i);
};

//<summary>显示成功后的信息</summary>
//
<param name="param">消息文本</param>
//
<returns></returns>
MedicareBase.prototype._ShowSuccessMsg=function(str)
{
    
//判断一下是否为空,为空则不显示提示,将来确定何时来显示这个框时可以在参数里加标志
    if (str!="")
    {
        alert(str);
    }
};

//<summary>显示失败后的信息</summary>
//
<param name="param">消息文本</param>
//
<returns></returns>
MedicareBase.prototype._ShowFailurMsg=function(str)
{
    
if (typeof(errContainer)=="undefined")
    {
        alert(str);
    }
    
else
    {
        
var control=document.getElementById(errContainer);
        
if (typeof(control)=="undefined")
        {
            alert(str);
        }
        
else
        {   
            control.value
=str;
        }
    }
};

    
//默认读卡返回真
MedicareBase.prototype.ReadCard=function()
{
    
return true;
};

    
//默认挂号返回真
MedicareBase.prototype.Registration=function()
{
    
return true;
};

医保具体类


//构造函数 
    //objID:OCX控件ID 
    //remoteService:远程后台服务  
    //callBack:目前为AjaxManagerID 
    //userName:操作员
    //errContainer:错误信息的容器
    function SYMedicare(objID,callBack,userName,errContainer)
    {
        MedicareBase.call(
this,errContainer);                    //使属性继承
        
        
this.ObjID=objID;
        
this.UserName=userName;
        
this.CallBack=callBack;
        
        
        
//私有属性
        this._personAccountInfo="";
        
    }
    
//方法定义 SYMedicare.prototype=new MedicareBase();必需是定义方法前的第一行
    SYMedicare.prototype=new MedicareBase();
    
    
//<summary>向服务器发送信息,将来可以根据是发送到WS还是后台页面来做修改</summary>
    //<param name="param">发送的信息</param>
    //<returns>[0,-1]|ReturnMessage</returns>
    SYMedicare.prototype._SendDataToServer=function(param)
    {
        
//使用ASP.net2.0的WebForm_DoCallback异步机制,使用修改后的函数
        var result=WebForm_DoCallback(param);
        
return this._GetFirstLine(result);
    }
    
    
//<summary>重置状态</summary>
    //<returns></returns>
    SYMedicare.prototype._Reset=function()
    {
        
this._personAccountInfo="";
    }
    
    
//<summary>对发送的消息打包</summary>
    //<param name="clientParams">调用客户端时的参数</param>
    //<param name="clientReturn">调用客户端产生的返回值</param>
    //<param name="serverParams">调用服务器端需要的业务的参数</param>
    //<param name="serverReturn">上一次服务器端的返回值</param>
    //<param name="rollBack">是否为回滚命令</param>
    //<param name="commandText">命令字</param>
    //<returns></returns>
    MedicareBase.prototype._PacketMessage=function(clientParams,clientReturn,
            serverParams,serverReturn,commandType,commandText)
    {
        
var msg=new StringBuilder();
        
        msg.append(
this._MessagePacketFlag);
        msg.append(
this._BeginMessageFlag);
        
        msg.append(
this._BeginClassFlag);
        msg.append(
"SYMedicare");
        msg.append(
this._EndClassFlag);
        
        msg.append(commandType);
        msg.append(commandText);
        msg.append(
this._EndCommandFlag);

        msg.append(
this._BeginClientParamsFlag);
        msg.append(clientParams
= (typeof(clientParams)=="undefined")?"":clientParams);
        msg.append(
this._EndClientParamsFlag);
        
        msg.append(
this._BeginClientReturnFlag);
        msg.append(clientReturn
= (typeof(clientReturn)=="undefined")?"":clientReturn);
        msg.append(
this._EndClientReturnFlag);
        
        msg.append(
this._BeginServerParamsFlag);
        msg.append(serverParams
= (typeof(serverParams)=="undefined")?"":serverParams);
        msg.append(
this._EndServerParamsFlag);
        
        msg.append(
this._BeginServerReturnFlag);
        msg.append(serverReturn
= (typeof(serverReturn)=="undefined")?"":serverReturn);
        msg.append(
this._EndServerReturnFlag);
        
        msg.append(
this._EndMessageFlag);
        
return msg.toString();
    };
    
    
//<summary>读卡</summary>
    //<param name="param">数值型1~7或者x+y的形式</param>
    //<param name="callbackFlag">是否回调 true/false,默认为true</param>
    
    
//<returns>读卡产生的消息或者出错信息</returns>
    SYMedicare.prototype.ReadCard=function(param,callbackFlag,callServer)
    {
        callbackFlag
= (typeof(callbackFlag)=="undefined")?true:callbackFlag;
        callServer
= (typeof(callServer)=="undefined")?true:callServer;
        
        
var control=document.getElementById(this.ObjID);
        
var objMsg="";
        
        
//对参数要进行判断,如果是a+b+c形式的,需要多次读卡,并将读出的信息用符号~连接起来
        var readPara=String(param).split('+');
        
for(var i=0;i<readPara.length;i++)
        {
            
var m=control.ReadCard(readPara[i]);
            
if (control.GetBoolean(m))
            {
                
if (objMsg=="")
                {
                    objMsg
=m;
                }
                
else
                {
                    objMsg
+="~"+m;
                }
                
            }
            
else
            {
                
if (callbackFlag==true)
                {
                    
this._ShowFailurMsg(control.GetReturnString(m));
                }
                
return m;
            }
        }

        
if (callServer==true)
        {
            
var serverResult=false;
            
//调用服务器端业务逻辑处理读卡
            
            
var s=this._PacketMessage(param,objMsg,"","",this._BeginNoTransCommand,"ReadCard");
            
            
var serverMsg=this._SendDataToServer(s);
            serverResult
=this._GetBoolean(serverMsg);
            
            
if (serverResult==true)
            {
                
this._ShowSuccessMsg(this._GetReturnString(serverMsg));
            }
            
else
            {
                
this._ShowFailurMsg(this._GetReturnString(serverMsg));
            }
            
            s
=this._PacketMessage(param,objMsg,"",serverResult,this._BeginNoTransCommand,"ReadCard");
        }
        
if (callbackFlag==true)
        {
            
var managerName=this.CallBack.toString();
            window[managerName].AjaxRequest(
this._JSClientCommand+','+s);
        }
        
return objMsg;
    }
    
    
//<summary>挂号服务</summary>
    //<param name="param">参数约定: MedType|BillNO|InHosNo|SysDate|DiseaseNO|DiseaseName</param>
    //<param name="callbackFlag">是否回调 true/false,默认为true</param>
    //<param name="transType">挂号类别</param>
    //<returns>挂号产生的消息或者出错信息</returns>
    SYMedicare.prototype._RegistrationAll=function(param,callbackFlag,commandText,transType)
    {
        callbackFlag
= (typeof(callbackFlag)=="undefined")?true:callbackFlag;
        
        
var objMsg="";
        
var control=document.getElementById(this.ObjID);
        
        
var strParam=new String(param);
        
var paramArray=strParam.split("|");
        
if (paramArray.length<6)
        {
            
this._ShowFailurMsg("挂号参数传递错误");
            
return;
        }

        objMsg
=this.ReadCard(7,false,false);
        
if (control.GetBoolean(objMsg)==false)
        {
            
this._ShowFailurMsg(control.GetReturnString(objMsg));
            
return objMsg;
        }
        
        
this._personAccountInfo=control.GetReturnString(objMsg);
        
        objMsg
=control.Registration(this._personAccountInfo,transType,
                        paramArray[
0],paramArray[1],paramArray[2],paramArray[3],
                        paramArray[
4],this.UserName,paramArray[5]);
        
        
if (control.GetBoolean(objMsg)==true)
        {
            
var serverResult=false;
            
//调用服务器端业务逻辑处理挂号
            
            
var s=this._PacketMessage(param,objMsg,"","",this._BeginBeginTransCommand,commandText);
            
var serverMsg=this._SendDataToServer(s);
            serverResult
=this._GetBoolean(serverMsg);
                
            
//如果服务器端返回成功,则提交本地
            if (serverResult==true)
            {
                objMsg
=control.CommitTrans();
                
//判断接口是否提交成功,不成功则调用服务器端撤销业务
                if (control.GetBoolean(objMsg)==false)
                {
                    s
=this._PacketMessage(param,objMsg,"",serverMsg,this._BeginRollTransCommand,commandText);
                    
this._SendDataToServer(s);
                    
                    
this._ShowFailurMsg(control.GetReturnString(objMsg));
                    
return objMsg;
                }
                
else
                {
                    
//这里应该再次调用服务器端确认提交数据,暂时未做
                    
                    
this._ShowSuccessMsg(this._GetReturnString(serverMsg));
                    
return serverMsg;
                }
            }
            
else
            {
                control.RollbackTrans();
                
this._ShowFailurMsg(this._GetReturnString(serverMsg));
                
return serverMsg;
                
            }
            
//回调
            if (callbackFlag==true)
            {
                window[
this.CallBack].AjaxRequest('Registration,'+paramArray[0]+','+objMsg+","+serverMsg);
            }
            
return objMsg;
        }
        
else
        {
            
this._ShowFailurMsg(control.GetReturnString(objMsg));
            
return objMsg;
        }
    }
    
    
//<summary>挂号</summary>
    //<param name="param">参数约定: MedType|BillNO|InHosNo|SysDate|DiseaseNO|DiseaseName</param>
    //<param name="callbackFlag">是否回调 true/false</param>
    //<returns>挂号产生的消息或者出错信息</returns>
    SYMedicare.prototype.Registration=function(param,callbackFlag)
    {
        
return this._RegistrationAll(param,callbackFlag,"Registration","1");
    }
    
    
//<summary>退号</summary>
    //<param name="param">参数约定: MedType|BillNO|InHosNo|SysDate|DiseaseNO|DiseaseName</param>
    //<param name="callbackFlag">是否回调 true/false</param>
    //<returns>挂号产生的消息或者出错信息</returns>
    SYMedicare.prototype.UnRegistration=function(param)
    {
        
return this._RegistrationAll(param,callbackFlag,"UnRegistration","-1");
    }

服务器端的实现代码

消息解析器

Code

业务类简单工厂

 

Code

业务类基类

Code

业务类具体类

Code

页面的处理过程

因为使用的是ASP.Net2.0的异步回调模型,所以要实现ICallbackEventHandler接口.

1.组织功能调用:通过触发客户端的页面上的提交事件(例如控钮触发),使服务器端接收到请求,然后需要组织JS业务类方法中所需的参数,这里需要Ajax技术的支持,以使页面不被刷新.
某按钮中的代码片断如下:

Code

 

这里需要根据页面上的选择来确定使用哪个医保的服务器端业务类,通过传递必要的参数后,由服务器端业务组织成JS业务端所需要的参数,最后动态注册JS脚本,使其返回时被执行,JS业务端执行时可以中断处理,然后产生一个对服务器端业务的请求.


2.接收JS业务端产生的请求:JS业务类处理完成后需要通知服务器进行处理,服务器可以在ICallbackEventHandler的接口方法的RaiseCallbackEvent(string eventArgument)会被调用.在这里就可以通过多态来屏蔽细节了.示例代码如下:

Code

其中_returnMsg是页面级的变量,是供异步返回时使用的.这个回调完成后,JS业务端得到返回值用来判断是否可以继续处理,如果出错则可以进行对客户端的回滚,如果成功则继续处理,处理完毕后需要调用客户端进行界面上的刷新.
3.接收JS业务端请求,完成界面的刷新:这里需要Ajax的配合,目前我使用的是Rad的AjaxManager,它提供一个回调的方法,我可以在服务器端接收到这个请求,示例代码如下:

Code

总结
通过使用这种模型,将业务处理逻辑分配到JS的业务类和服务器端的业务类,减少了页面对于业务处理的代码,达到了一定的业务抽象,便于扩展其它的医保类型.同时,对于JS的异步调用也解决了”中断函数->同步请求服务->根据返回值再继续处理”的技术问题,简化了与由于B/S处理这种与本地交互的而产生的复杂过程.

一点说明:为什么在标题中要嵌入英文?原因是为了能够让国外的网友能查询到这篇文章。平常在Google上查资料的时候,经常参考国外网友的博客,帮助我解决了很多问题,所以我也想让他们能够参考我写的内容。当然文中我不可能全部译为英文,所以我尽量把代码粘全,靠代码说话吧。
 
 
posted on 2012-12-05 17:49  DoubleLi  阅读(5053)  评论(0编辑  收藏  举报