近日(呵呵,这篇文章是去年写的)用了两个月开发了一个物流信息系统,这个系统是两层、三层相结合, C/S B/S 相结合的系统。虽然限于时间的紧张和人手的原因,系统规模不是很大,但是其中涉及的技术却很全面。在这个《开发技术篇》中我们将讲解我在开发系统中遇到的技术问题及解决方案,希望对大家有帮助。对于物流信息系统的分析设计问题,我将在另一篇文章《物流信息系统开发手记――系统构架篇》中讲解。
 <?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

一、 Midas 的安全问题。
    Midas 技术是 Delphi 中进行三层开发的首选技术,它不仅有纯 DCOM/COM+(COM+ 技术是 .NET 技术的基础 ) 的优点,而且也结合了 Delphi 的快速开发特性,可以快速开发出想要的系统,其开发速度是用 VC,PB 等开发 DCOM 的数十倍,把程序员从烦杂的代码中解脱出来,从而将更多的精力投入到业务逻辑的设计中去。
    但是 Midas 技术的一个最令人担忧的就是它的安全问题:
远端只要知道应用服务器的端口号即可访问到应用服务器,而一旦访问到应用服务器, TClientDataSet 即可获得 ProviderNames 列表。一旦知道了 ProviderNames 列表,这就相当于将数据库暴露在外了。
关于可轻易获得 ProviderNames 列表的问题,我使用下面的方法解决:
  在服务器端定义一个
LoginMTS(const AUserId, APassword: WideString): WordBool;
方法。初始状态下,所有的 DataSetProvider 和数据集的连接断开。用户必须调用 LoginMTS 并传递用户名和密码,登陆成功才将 DataSetProvider 和数据集的连接打开。这样如果用户验证没有通过,即使它获得了 ProviderNames 列表也没法调用接口中的方法对数据库进行操作。
二、 Midas 中主从表的实现
主从表的应用在信息系统中应用很广。在两层开发中我们可以通过直接建立两个数据集之间为主从关系来实现主从表;在三层中虽然我们仍然可以通过直接建立两个数据集之间为主从关系来实现主从表,但是这样就要求把数据库中所有相关的数据行都下载到本地,丧失了三层开发的优势。我在实际中使用下面的方法实现。这里我以实现入库单查询、添加、修改、删除 (CRUD) 为例来讲解:
1 )新建一个 MTS Data Module ,命名为 TmtsStockInListBiz, 增加如下方法:
    function QueryStockInListMasterById(const AId: WideString;
      var ADatas: OleVariant): WordBool; safecall;
    function QueryStockInListSlaveByMasterId(const AId: WideString;
      var ADatas: OleVariant): WordBool; safecall;
    procedure UpdataStockInListMaster(var ADatas: OleVariant); safecall;
    procedure UpdataStockInListSlave(var ADatas: OleVariant); safecall;
    function GenerateStockInListId: WideString; safecall;
 

QueryStockInListMasterById 作用是根据入库单单号查询入库单的基本信息(入库日期、负责人等), Aid 为入库单单号, Adatas 为返回值,其格式就是 Midas 的数据包,可以将其附给 ClientDatSet Data 属性。
QueryStockInListSlaveByMasterId 作用是根据入库单单号查询入库单的详细信息(商品条码,数量)
UpdataStockInListMaster 是对入库单主表进行删除、添加、修改操作。只要将 ClientDataSet Delta 属性做为传递即可。
UpdataStockInListSlave 是对入库单从表进行删除、添加、修改操作。
GenerateStockInListId 是产生一个唯一的入库单号。
下面是几个方法的代码,都很简单,就不多解释了,可以查看 Delphi 的帮助。
function TmtsStockInListBiz.QueryStockInListMasterById(
  const AId: WideString; var ADatas: OleVariant): WordBool;
begin
  result := false;
  ADatas := null;
  try
    cdsQuery.Close;
    cdsQuery.CommandText := 'select * from t_StockInListMaster where Id=:Id';
    cdsQuery.Params.ParamByName('Id').AsString := AId;
    cdsQuery.Open;
    if cdsQuery.RecordCount > 0 then
    begin
      result := true;
      ADatas := cdsQuery.Data;
    end;
  finally
    cdsQuery.Close;
  end;
end;
 

procedure TmtsStockInListBiz.UpdataStockInListMaster(
  var ADatas: OleVariant);
var
  eCount: Integer;
  OwnerData: OleVariant;
begin
  DCOMConStockInList.GetServer.AS_ApplyUpdates('dspStockInListMaster',
    ADatas, -1, eCount, OwnerData);
end;
 

function TmtsStockInListBiz.GenerateStockInListId: WideString;
var
  LPrior: string;
  i: Integer;
begin
  cdsQuery.Close;
  cdsQuery.CommandText := 'select top 1 id from t_StockInListMaster order by id desc';
  cdsQuery.Open;
  LPrior := cdsQuery.FieldByName('Id').AsString;
  i := StrToIntDef(RightStr(LPrior,8),0);
  Inc(i);
  result := 'RK' + FormatFloat('00000000',i);
  cdsQuery.Close;
end;
 

2 )、新建一个应用程序,通过 DCOMConnection SocketConnection 等连接到 MTS 组件,然后就可以调用 MTS 的相应的方法实现客户端功能了。
放入 cdsStockInListMaster cdsStockInListSlave 两个 ClientDataSet 控件,在控件上点击右键,选择“ FieldsEditor ”新建于服务器中的字段同样的字段,然后再次在控件上单击右键,选择“ CreateDataSet ”,建立一个本地数据库。
3
根据入库单号查询入库单的方法实现:
procedure TFormStockInList.BtnFindClick(Sender: TObject);
var
  v,vs: OleVariant;
begin
  if SocketConStockInList.AppServer.QueryStockInListMasterById(Trim(LEdtId.Text), v) then
  begin
    cdsStockInListMaster.Data := v;// 显示入库单主表(主要信息)
 

    if SocketConStockInList.AppServer.QueryStockInListSlaveByMasterId(Trim(LEdtId.Text), vs) then
      cdsStockInListSlave.Data := vs; ;// 显示入库单从表(明细信息)
  end
  else
    ShowMessage(' 此单不存在! ');
end;
4 )新建入库单的实现
procedure TFormStockInList.BtnNewClick(Sender: TObject);
var
  LId: string;
begin
  ClearCDSRecord;
  cdsStockInListMaster.Open;
  cdsStockInListMaster.Insert;
  LId := SocketConStockInList.AppServer.GenerateStockInListId;
  LEdtId.Text := LId;
  cdsStockInListMaster.FieldByName('Id').AsString := LId;
  cdsStockInListMaster.FieldByName('GenerateDate').AsDateTime := Now();
end;
5 )提交功能的实现
procedure TFormStockInList.BtnPostClick(Sender: TObject);
var
  LQuerymts: ImtsQueryObjDisp;
  LBar: string;
begin
  SetSocketConnectionConnect(SocketConQuery);
  LQuerymts := ImtsQueryObjDisp(SocketConQuery.GetServer);
 

  SocketConQuery.Close;
 

  if cdsStockInListMaster.RecordCount > 0 then
    SocketConStockInList.AppServer.UpdataStockInListMaster(cdsStockInListMaster.Delta);
  if cdsStockInListSlave.RecordCount > 0 then
  SocketConStockInList.AppServer.UpdataStockInListSlave(cdsStockInListSlave.Delta);
end;
注:本文中 ClientDataSet 控件的名称开头一般为 cds TsocketConnection 控件的名称开头一般为 SocketCon
三、动态设置 TsimpleObjectBroker 的服务器列表
procedure SetSocketConnectionConnect(AValue: TSocketConnection);
  procedure FillAppServerList(ABroker: TSimpleObjectBroker);
  var
    sl: TStringList;
    i, n: Integer;
  begin
    sl := TStringList.Create;
    从配置文件中读取服务器列表,并保存到 sl ;
    n := sl.Count - 1;
    ABroker.ServerData := null;
    for i := 0 to n do
    begin
      ABroker.Servers.Add;
      ABroker.Servers[i].ComputerName := sl.Strings[i]
    end;
    sl.Free;
  end;
var
  LBroker: TSimpleObjectBroker;
begin
  LBroker := TSimpleObjectBroker.Create(nil);
    FillAppServerList(LBroker);
    AValue.ObjectBroker := LBroker;
    try
      AValue.Connected := true;
    except
      raise Exception.Create(' 应用服务器连接错误! ');
    end;
    LBroker.Free;
end;