TMS Web core 踩雷笔记
💣 陷阱一:带有前端鉴权的初始页面(Unit1)过早渲染
问题现象:
Unit1应当仅作为一个入口过渡页面用于实现鉴权或者展示一个加载中,不可实现实际业务窗体,如主要的业务功能直接编写在初始加载的 Unit1(通常是应用程序的 Main Form)中会导致应用进入主页时主要功能组件已经开始渲染。此时没有任何合适的鉴权时机导致出现数据泄露、组件状态异常或触发各种难以追踪的时机(Timing)问题。

根本原因:
TMS Web Core 在启动时会自动实例化并挂载主窗体(Main Form)。在传统的 Delphi VCL/FMX 中,界面的创建是同步的;但在 Web 环境中,界面的 DOM 渲染与网络请求(如鉴权 API 调用)是并行的。如果主窗体包含了大量业务 DOM,它们会在鉴权完成前就被插入到页面中。
解决方案:引入专用的路由/加载页(Router / Loading Page)
将 Unit1 剥离所有业务逻辑,使其纯粹作为一个路由分发器(Router)或加载过渡页(Splash/Loading Screen)。
在此页面中发起异步鉴权请求,根据结果动态加载登录页或主业务页。
参考伪代码实现:
{ 行号仅为示例参考 }
01: unit UnitRouter;
02:
03: interface
04:
05: uses
06: System.SysUtils, System.Classes, WEBLib.Graphics, WEBLib.Controls,
07: WEBLib.Forms, WEBLib.Dialogs;
08:
09: type
10: /// <summary>
11: /// 应用程序入口路由窗体,仅负责鉴权与状态分发,不包含任何核心业务 UI。
12: /// </summary>
13: TRouterForm = class(TWebForm)
14: procedure WebFormCreate(Sender: TObject);
15: private
16: /// <summary>
17: /// 执行异步鉴权逻辑
18: /// </summary>
19: [async] procedure CheckAuthentication;
20: end;
21:
22: var
23: RouterForm: TRouterForm;
24:
25: implementation
26:
27: uses
28: UnitMain, UnitLogin; // 实际的业务主页和登录页
29:
30: {$R *.dfm}
31:
32: procedure TRouterForm.WebFormShow(Sender: TObject);
33: begin
34: // 窗体显式时启动鉴权,此时页面可仅展示一个 Loading 动画
35: CheckAuthentication;
36: end;
37:
38: procedure TRouterForm.CheckAuthentication;
39: var
40: LIsAuthenticated: Boolean;
41: begin
42: // 模拟异步请求鉴权状态 (Await 用于等待 Promise 解析)
43: LIsAuthenticated := await(Boolean, AuthService.CheckAuthAsync());
44:
45: if LIsAuthenticated then
46: begin
47: // 鉴权通过,动态创建并显示业务主页,隐藏/销毁路由页
48: ShowMain;
49: end
50: else
51: begin
52: // 鉴权失败显示失败页面
53: ShowError('鉴权错误','Token鉴权错误!');
54: end;
55: end;
56:
57: end.
💣 陷阱二:在 Create 事件中创建新的 Form 引发 DOM 混乱
问题现象:
在某个窗体(如主窗体或父窗体)的构造函数(Create)或 OnCreate 事件中,直接实例化创建其他的子窗体(Forms)。这会导致页面中被强行插入多个平级的 div 元素(Web Core 底层将 Form 映射为 div),引发奇怪的层叠问题、UI 错乱或 CSS 样式失效。
根本原因:
在 TMS Web Core 中,Pascal 对象的创建(Object Instantiation)与底层 HTML DOM 元素的挂载(DOM Mounting)存在生命周期差异。当父窗体还在执行 Create 时,它自己的 DOM 容器可能尚未在浏览器的 DOM 树(DOM Tree)中完全就绪并定位。此时如果强行创建子窗体,子窗体的 DOM 元素找不到正确的父级挂载点,框架就会将其直接附加到 document.body 或发生不可预期的元素重叠。
解决方案:遵循 DOM 渲染生命周期(延时加载或事件驱动)
绝对避免在父窗体的创建阶段(Synchronous Creation Phase)实例化子窗体。应当遵循按需加载(Lazy Loading)原则,或在父窗体明确已渲染完成的事件(如用户点击、或者特定的生命周期回调)中进行创建。
错误示范(需严格避免):
procedure TMainForm.WebFormCreate(Sender: TObject);
var
LChildForm: TChildForm; // 局部变量
begin
// ⚠️ 危险逻辑:在父窗体尚未完全挂载到 DOM 时创建子窗体
LChildForm := TChildForm.Create(Self);
LChildForm.Show;
end;

正确示范(按需/延时创建):
{ 推荐在用户交互或明确的业务需要时进行实例化 }
{ 鉴权时机应当在OnShow中,这也是陷阱一说的问题 }
procedure TForm1.WebFormShow(Sender: TObject);
VAR
LPageToken: STRING;
begin
//注意,不可在Create创建窗体,否则必bug
FServiceWebClient := TServiceWebClient.Create('http://localhost:8088/api/');
// 提取浏览器地址栏中的 ?token= 对应的值
LPageToken := GetQueryParam('token');
if LPageToken = '' then
begin
ShowError('鉴权错误','连接不正确,请检查!');
end
else
begin
FServiceWebClient.CheckCodeAsync(LPageToken,
procedure(const AResult: string)
begin
if AResult <> '' then
begin
window.sessionStorage.setItem('UserToken',AResult);
ShowMain;
end
else
begin
ShowError('鉴权错误','Token鉴权错误!');
end;
end,
procedure(const AErrorMsg: string)
begin
ShowError('错误','服务器维护中..');
end
);
end;
end;
💣 陷阱三:后端接口必须进行鉴权
问题现象:
在前端开发中所有与后端的交互逻辑均暴露在用户端,任何未鉴权的接口访问都可能被恶意利用。

根本原因:
在 TMS Web Core 这样的前端框架中,所有的 Pascal 代码最终都会被编译为 JavaScript 运行在用户的浏览器中。这意味着前端没有任何秘密可言:API 的路由地址、参数结构、甚至是前端的加密算法,对恶意用户来说都是透明的。
解决方案:
遵循 “零信任(Zero Trust)”原则,由于前端的任何逻辑对用户都是透明的,我们绝对不能依赖前端的路由或 UI 限制来保护数据。必须在后端服务的入口处(如路由分发层)引入全局的拦截器(Interceptor)或中间件(Middleware)。在任何具体的业务逻辑被触发之前,统一且强制地校验 HTTP 请求头中的身份凭证(如 JWT Token)。这种统一拦截的设计不仅能彻底封堵未授权的恶意 API 调用,还能将鉴权机制与核心业务逻辑完全解耦,确保系统在持续重构和扩展时,安全防线依然稳固且没有遗漏。
同时对于前端传递来的数据保持绝对警惕进行严格校验,绝对禁止没有严格校验的数据进入后端的下层处理。
💣 陷阱四:如何正确的创建一个页面
问题现象:
第一次使用TMS Web core动态创建页面总是踩到各种莫名其妙的问题。
解决方案:
使用Application.CreateForm。
procedure TForm1.ShowMain;
begin
if Assigned(lfrom) then
begin
lfrom.Free;
lfrom := nil;
end;
Application.CreateForm(TForm4,'body',lfrom);
lfrom.Show;
end;
浙公网安备 33010602011771号