分享

ExtJS+WebBroker搭建Web应用开发框架--毙掉Intraweb、UniGUI

 quasiceo 2016-12-18
标题:
ExtJS+WebBroker搭建Web应用开发框架--毙掉Intraweb、UniGUI 浏览:278
加入我的收藏
楼主: 前一阵我发个一个贴子说使用WYSISWYG WebBuilder+WebBroker搭建Web应用开发框架,现在将前端升级,将WYSISWYG WebBuilder换成Sencha ExtJS。这个方案更棒。
前端用ExtJS,目前的6.2版已经将桌面与手机开发统一到一套工具中,负责页面。
后端用Delphi内部自带的WebBroker,WebBroker只用了写JSON服务。
----------------------------------------------
-

作者:
男 bosoft (bobo) ▲▲▲▲▲ -
盒子活跃会员
2016-12-17 17:40:25
1楼: 对比intraweb好处在那里哟?运行速度会更快,可以更高并发性?给一个demo网址,让大伙一起分享你的喜悦啊按此在新窗口浏览图片
----------------------------------------------
-
作者:
男 FengLinYuShu (FengLinYuShu) ★☆☆☆☆ -
盒子活跃会员
2016-12-17 17:45:48
2楼: 来个DEMO?
----------------------------------------------
-
直接用Delphi开发网络应用程序
http;//www.web0000.com
作者:
男 c5soft (走遍天下) ▲▲△△△ -
注册会员
2016-12-17 18:04:36
2楼: 问题的关键是当前做Web开发,尤其是RIA,前端选哪个框架?选React/Angular,还处在婴幼儿阶段;选jQuery,零敲碎打,不成体系,插件虽多,Bug也多;以前误入了Dojo,体系不错,可惜没有很好的Grid。只有ExtJS,历经风雨,经久不衰,目前已被60%世界100强的企业采用。ExtJS版本发展到6.X,已经将移动开发与桌面开发统一成一个平台。Sencha ExtJS的控件无所不包,Grid与Chart开箱即用。Sencha Architect很像Dephi,支持RAD,提供一个可视化的界面开发环境。
----------------------------------------------
-
作者:
男 c5soft (走遍天下) ▲▲△△△ -
注册会员
2016-12-17 18:30:25
3楼: 相对前端来说,后端成熟方案有很多,TOMCAT、PHP、NodeJS都不错,ASP.NET也凑合,咱们用Delphi的,自然是在WebBroker与IntraWeb间选择。IW的优点与缺点不多说。使用WebBroker的好处是Delphi自身集成,可以写ISAPI的DLL与单独运行的EXE,实践中通过Nginx+WebBroker编译成EXE用于调试,IIS+WebBroker编译成ISAPI的DLL用于发布。生产环境中应用Delphi写的64位ISAPI规格的DLL运行起来非常稳定,支持上万的并发连接。

ExtJS+WebBroker方案的关键是要求开发者精通Javascript,光会Delphi是不行的。不像IntraWeb,只要开发者会Delphi就行了。
----------------------------------------------
-
作者:
男 bosoft (bobo) ▲▲▲▲▲ -
盒子活跃会员
2016-12-17 18:31:12
3楼: 让c5soft 说得很兴奋,马上查了一下webBroker 好象自身不带session管理,处理起来不会太麻烦吧
----------------------------------------------
-
作者:
男 c5soft (走遍天下) ▲▲△△△ -
注册会员
2016-12-17 18:41:27
4楼: WebBroker的确自动生成的代码的确没有Session管理,这很容易写。关键就是解析客户端传回来的cookie,在里面存储一个唯一的ID。通过这个ID到数据库中去存取Session相关数据。
----------------------------------------------
-
作者:
男 c5soft (走遍天下) ▲▲△△△ -
注册会员
2016-12-17 18:47:00
5楼: 下面是Session管理的关键代码:
 TBaseWebModule = class(TWebModule)
    procedure AppWebModuleDefaultAction(Sender: TObject; Request: TWebRequest;
      Response: TWebResponse; var Handled: Boolean);
    procedure WebModuleBeforeDispatch(Sender: TObject; Request: TWebRequest;
      Response: TWebResponse; var Handled: Boolean);
    procedure WebModuleAfterDispatch(Sender: TObject; Request: TWebRequest;
      Response: TWebResponse; var Handled: Boolean);
  protected
    FCookieIDForSession: string;
    FSessionClass: TSessionClass;
    FSession: TBaseSession;
    function GetUniqueSessionID: string; virtual;
    procedure OpenSession;
    procedure CloseSession;
    procedure SerializeSession; virtual;
    procedure DeserializeSession(const ASessionID: string); virtual;
  public
    property SessionClass: TSessionClass read FSessionClass;
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    function EchoIncoming: string;
  end;

procedure TBaseWebModule.OpenSession;
var
    cSessionID: string;
  procedure SaveSessionIDToClient;
  var
      LCookie: TStringList;
  begin
    LCookie := TStringList.Create;
    LCookie.Add(FCookieIDForSession + '=' + cSessionID);
    Response.SetCookieField(LCookie, '', '',
{$IFDEF ISAPI} -1{$ELSE}0{$ENDIF}, False);
    LCookie.Free;
  end;

begin
  if not Assigned(FSession) then
      FSession := FSessionClass.Create(Self);
  cSessionID := Request.CookieFields.Values[FCookieIDForSession];
  if Length(cSessionID) = 0 then begin
    cSessionID := GetUniqueSessionID;
    SaveSessionIDToClient;
    FSession.Clear;
  end else begin
    DeserializeSession(cSessionID);
    FSession.Modified := False;
  end;
  FSession.SessionID := cSessionID;
end;

procedure TBaseWebModule.CloseSession;
begin
  if FSession.Modified then
  begin
    SerializeSession;
    FSession.Modified := False;
  end;
end;
----------------------------------------------
-
作者:
男 c5soft (走遍天下) ▲▲△△△ -
注册会员
2016-12-17 18:53:49
6楼: 这是后端路由的写法:

RouteMap('GET', '/rest/captcha',
  function(const Caller: TWebModule; const Request: TWebRequest;
    const Response: TWebResponse): Boolean
  var
    cs: TCaptchaStream;
    Self: TAppWebModule absolute Caller;
  begin
    cs := Captcha(RGB($F5, $F5, $F5));
    Self.Session.Captcha := cs.Captcha;
    Response.ContentType := cs.ContentType;
    Response.ContentStream := cs;
    Result := True;
  end);

RouteMap('GET', '/rest/login',
  function(const Caller: TWebModule; const Request: TWebRequest;
    const Response: TWebResponse): Boolean
  var
    cUserId, cUserPwd, cCaptcha: string;
    jo: TJSONObject;
    Session: TSession;
    Self: TAppWebModule absolute Caller;
  begin
    Session := Self.Session;
    jo := TJSONObject.Create;
    cCaptcha := Request.QueryFields.Values['captcha'];
    if not SameText(cCaptcha, Session.Captcha) then
    begin
      jo.S['errorMsg'] := ' 验证码有误,请重新输入!';
      jo.B['logined'] := False;
    end
    else
    begin
      cUserId := Request.QueryFields.Values['userId'];
      cUserPwd := Request.QueryFields.Values['userPwd'];
      if Session.Login(cUserId, cUserPwd) then
      begin
        jo.B['logined'] := True;
        jo.S['userId'] := cUserId;
        jo.S['userRole'] := Session.RoleName;
        jo.S['startPage'] := Session.StartPage;
      end
      else
      begin
        jo.B['logined'] := False;
        jo.S['errorMsg'] := Session.LoginFailMsg;
      end;
    end;
    Response.Content := jo.ToJSon();
    Response.ContentType := 'application/json; charset=utf-8';
    jo.Free;
    Result := True;
  end);

RouteMap('GET', '/rest/TChangePwd',
  function(const Caller: TWebModule; const Request: TWebRequest;
    const Response: TWebResponse): Boolean
  var
    cAsk: string;
    jo, js : TJSONObject;
    Self: TAppWebModule absolute Caller;
  begin
    jo := TJSONObject.Create;
    cAsk := Request.QueryFields.Values['ask'];
    if cAsk = 'startInfo' then
    begin
      js := TJSONObject.Create;
      js.S['mainMenu'] := Self.MainMenu('TChangePwd.html');
      jo.O['elmHtmList'] := js;
    end;
    Response.Content := jo.ToJSon();
    Response.ContentType := 'application/json; charset=utf-8';
    jo.Free;
    Result := True;
  end);

RouteMap('POST', '/rest/TChangePwd',
  function(const Caller: TWebModule; const Request: TWebRequest;
    const Response: TWebResponse): Boolean
  var
    cAsk, cOldPwd, cNewPwd, cCaptcha: string;
    jo: TJSONObject;
    Session: TSession;
    Self: TAppWebModule absolute Caller;
  begin
    Session := Self.Session;
    jo := TJSONObject.Create;
    cAsk := Request.QueryFields.Values['ask'];
    cCaptcha := Request.ContentFields.Values['captcha'];
    if not SameText(cCaptcha, Session.Captcha) then
    begin
      jo.S['errorMsg'] := '验证码有误,请重新输入!';
    end
    else
    begin
      cOldPwd := Request.ContentFields.Values['oldPwd'];
      cNewPwd := Request.ContentFields.Values['newPwd'];
      if cOldPwd <> Session.Password then
        jo.S['errorMsg'] := '原密码有误,请重新输入!'
      else
      begin
        Session.ChangePassword(cNewPwd);
        jo.S['okMsg'] := '密码更改成功,下次登录请使用新密码!';
      end;
    end;
    Response.Content := jo.ToJSon();
    Response.ContentType := 'application/json; charset=utf-8';
    jo.Free;
    Result := True;
  end);
----------------------------------------------
-
作者:
男 c5soft (走遍天下) ▲▲△△△ -
注册会员
2016-12-17 19:00:14
7楼: 下面是SQL Server数据库session管理的表与储存过程:
IF EXISTS(SELECT 1 FROM sysobjects WHERE name='appSessions' AND type='U' AND uid=user_id('dbo'))
  DROP TABLE dbo.appSessions
GO
CREATE TABLE dbo.appSessions(
  sid      VARCHAR(40)  NOT NULL,
  session  VARCHAR(8000) NOT NULL,
  expires  DATETIME     NULL,
  CONSTRAINT pk_appSessions PRIMARY KEY(sid)
)
GO

IF EXISTS(SELECT 1 FROM sysobjects WHERE name='spSessionExists' AND type='P' AND uid=user_id('dbo'))
  DROP PROCEDURE dbo.spSessionExists
GO
CREATE PROCEDURE dbo.spSessionExists
@sid  VARCHAR(40),
@exists BIT OUTPUT
WITH ENCRYPTION 
AS
SET NOCOUNT ON
SET @exists=0
IF EXISTS(SELECT 1 FROM dbo.appSessions WHERE sid=@sid)
  SET @exists=1
SET NOCOUNT OFF
GO

IF EXISTS(SELECT 1 FROM sysobjects WHERE name='spSessionPurge' AND type='P' AND uid=user_id('dbo'))
  DROP PROCEDURE dbo.spSessionPurge
GO
CREATE PROCEDURE dbo.spSessionPurge
WITH ENCRYPTION 
AS
SET NOCOUNT ON
delete dbo.appSessions WHERE expires<GETDATE()
SET NOCOUNT OFF
GO

IF EXISTS(SELECT 1 FROM sysobjects WHERE name='spSessionGet' AND type='P' AND uid=user_id('dbo'))
  DROP PROCEDURE dbo.spSessionGet
GO
CREATE PROCEDURE dbo.spSessionGet
@sid  VARCHAR(40),
@session VARCHAR(8000) OUTPUT
WITH ENCRYPTION 
AS
SET NOCOUNT ON
DECLARE @expires DATETIME
SELECT @session=session,@expires=expires FROM dbo.appSessions WHERE sid=@sid
IF @expires<GETDATE() BEGIN
  SET @session=NULL
  DELETE dbo.appSessions WHERE sid=@sid
END ELSE 
  UPDATE dbo.appSessions SET expires=GETDATE()+1 WHERE sid=@sid
SET NOCOUNT OFF
GO

IF EXISTS(SELECT 1 FROM sysobjects WHERE name='spSessionSet' AND type='P' AND uid=user_id('dbo'))
  DROP PROCEDURE dbo.spSessionSet
GO
CREATE PROCEDURE dbo.spSessionSet
@sid  VARCHAR(40),
@session VARCHAR(8000)
WITH ENCRYPTION 
AS
SET NOCOUNT ON
DECLARE @expires DATETIME
SET @expires=GETDATE()+1
IF EXISTS(SELECT 1 FROM dbo.appSessions WHERE sid=@sid) BEGIN
  UPDATE dbo.appSessions SET session=@session,expires=@expires WHERE sid=@sid
END ELSE
  INSERT INTO dbo.appSessions VALUES(@sid,@session,@expires)  
SET NOCOUNT OFF
GO
----------------------------------------------
-
作者:
男 ldjssoft (ldissoft) ▲△△△△ -
注册会员
2016-12-17 20:42:25
8楼: 来个demo?
----------------------------------------------
-
作者:
男 ldjssoft (ldissoft) ▲△△△△ -
注册会员
2016-12-17 20:44:04
9楼: 这两个被毙掉的都是可视化组件拖放开发的,没有门槛直接进入,你说的却不可以,难度高多了。
----------------------------------------------
-
作者:
男 bosoft (bobo) ▲▲▲▲▲ -
盒子活跃会员
2016-12-17 21:17:41
10楼: 非常感谢c5soft马上回复这个session非常实用的解决方法,这个好贴造福delpher
----------------------------------------------
-
作者:
男 c5soft (走遍天下) ▲▲△△△ -
注册会员
2016-12-17 21:20:34
10楼: 从入门走向专业,迈过几道门槛是必须的。迈过门槛,展现在眼前的是一片风光无限的自由天地。
----------------------------------------------
-
作者:
男 xuyb19870303 (xuyb19870303) ▲△△△△ -
注册会员
2016-12-17 23:22:53
11楼: 肖老尸,session和路由还可以参考下DelphiMVCFramework https://github.com/danieleteti/delphimvcframework ,这个框架正在把system.json换成jsondataobject ,进一步提升性能,而且这个框架的restful也很不错。

另外就是session存数据库对性能影响还是很大吧,改用redis可行不 https://github.com/danieleteti/delphiredisclient
----------------------------------------------
-
作者:
男 c5soft (走遍天下) ▲▲△△△ -
注册会员
2016-12-17 23:38:20
12楼: delphimvcframework是个不错的框架,就是稍微复杂了一点。我的这个设计非常精简,核心代码不到500行。delphi服务端负责返回的主要是JSON数据+少量的动态图片(验证码captcha)。MVC框架交给ExtJS去做了,新版的ExtJS支持更好的MVVM框架(数据双向绑定,与Angular相同)。
ExtJS是一个单页面应用(SPA),前端的页面跳转通过视图切换实现。我这里所说的路由不过是不同的取数路径而已。

session用redis当然没问题了。我用sql server主要是降低服务器构建的复杂性。为了避免性能损失,session只在数据发生变化时才写入后端数据库。

实践中使用UniDAC,没发现因为session的读写影响了应用的速度。UniDAC的DBMonitor对监控ISAPI的数据读写情况非常有帮助。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多