标题: |
ExtJS+WebBroker搭建Web应用开发框架--毙掉Intraweb、UniGUI |
浏览:278 |
|
加入我的收藏 |
楼主: |
前一阵我发个一个贴子说使用WYSISWYG WebBuilder+WebBroker搭建Web应用开发框架,现在将前端升级,将WYSISWYG WebBuilder换成Sencha ExtJS。这个方案更棒。 前端用ExtJS,目前的6.2版已经将桌面与手机开发统一到一套工具中,负责页面。 后端用Delphi内部自带的WebBroker,WebBroker只用了写JSON服务。
----------------------------------------------
- |
作者: |
|
2016-12-17 17:40:25 |
1楼: |
对比intraweb好处在那里哟?运行速度会更快,可以更高并发性?给一个demo网址,让大伙一起分享你的喜悦啊
----------------------------------------------
-
|
作者: |
|
2016-12-17 17:45:48 |
2楼: |
来个DEMO?
----------------------------------------------
- 直接用Delphi开发网络应用程序 http;//www.web0000.com
|
作者: |
|
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,提供一个可视化的界面开发环境。
----------------------------------------------
-
|
作者: |
|
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就行了。
----------------------------------------------
-
|
作者: |
|
2016-12-17 18:31:12 |
3楼: |
让c5soft 说得很兴奋,马上查了一下webBroker 好象自身不带session管理,处理起来不会太麻烦吧
----------------------------------------------
-
|
作者: |
|
2016-12-17 18:41:27 |
4楼: |
WebBroker的确自动生成的代码的确没有Session管理,这很容易写。关键就是解析客户端传回来的cookie,在里面存储一个唯一的ID。通过这个ID到数据库中去存取Session相关数据。
----------------------------------------------
-
|
作者: |
|
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;
----------------------------------------------
-
|
作者: |
|
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);
----------------------------------------------
-
|
作者: |
|
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
----------------------------------------------
-
|
作者: |
|
2016-12-17 20:42:25 |
8楼: |
来个demo?
----------------------------------------------
-
|
作者: |
|
2016-12-17 20:44:04 |
9楼: |
这两个被毙掉的都是可视化组件拖放开发的,没有门槛直接进入,你说的却不可以,难度高多了。
----------------------------------------------
-
|
作者: |
|
2016-12-17 21:17:41 |
10楼: |
非常感谢c5soft马上回复这个session非常实用的解决方法,这个好贴造福delpher
----------------------------------------------
-
|
作者: |
|
2016-12-17 21:20:34 |
10楼: |
从入门走向专业,迈过几道门槛是必须的。迈过门槛,展现在眼前的是一片风光无限的自由天地。
----------------------------------------------
-
|
作者: |
|
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
----------------------------------------------
-
|
作者: |
|
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的数据读写情况非常有帮助。 |
|