2026/10/30 17:02:09

DELPHI 制作 WEB 数据库服务系统:架构选型与实战避坑指南

DELPHI 制作 WEB 数据库服务系统:架构选型与实战避坑指南 简介这份文档资料面向具备一定Delphi基础的开发者讲解如何用Delphi构建Web数据库服务系统解决从桌面应用转向Web端数据交互的开发需求。内容围绕Web浏览器、Web应用服务器、数据库服务器与DBMS的协作架构展开重点说明ISAPI、CGI、WinCGI三种服务程序类型的选型以及TWebModule、Request与Response属性、Action事件处理机制的使用方法。资源包共1个doc文件约32KB属于轻量级技术文档便于快速通读与查阅。文中结合通讯录管理实例演示了TPageProducer、TQueryTableProducer、TDataSetPageProducer等组件如何将查询结果转换为HTML页面并借助TSession管理多用户会话覆盖数据浏览、搜索与录入等典型场景。目前已有93人学习适合希望掌握Delphi Web开发框架、理解ODBC与BDE桥接思路、并需要可落地组件化方案的中级开发者参考。1. 用 DELPHI 制作 WEB 数据库服务系统老工具为什么还能打接手过不少工厂内部管理系统甲方预算有限、服务器只有一台 Windows Server、IT 管理员只会装 exe这种场景下用 DELPHI 做 WEB 数据库服务系统反而是最稳的路子。标题里的三个词拆开看DELPHI 是开发工具WEB 是访问方式数据库服务系统是最终交付形态。合起来就是——用 DELPHI 写一个能通过浏览器访问、背后连着数据库、能增删改查的业务系统。它解决的核心问题是让不装客户端的用户也能用上 DELPHI 写出来的业务逻辑同时保留 DELPHI 在数据库操作上的成熟生态。适合谁适合手里有存量 DELPHI 代码、团队只有一两个熟手、又必须交付 B/S 架构的从业者。热搜里 DELPHI、WEB、数据库三个词反复出现说明搜的人多半卡在“怎么把桌面端那套搬到浏览器”这一步。2. 先想清楚架构DELPHI 在 WEB 数据库服务系统里站哪个位置2.1 三种主流架构的取舍用 DELPHI 做 WEB 数据库服务系统绕不开一个根本问题DELPHI 代码跑在哪一端。常见做法有三种选错了后面全是返工。第一种是 CGI/ISAPI 模式。DELPHI 编译出 dll 或 exe挂到 IIS 或 Apache 下浏览器请求进来DELPHI 代码处理完直接吐 HTML。优点是部署简单一个 dll 丢进站点目录就能跑缺点是每次请求都要走一遍进程/线程调度并发一高就顶不住而且页面逻辑和业务逻辑容易搅在一起。适合内部几十人用的系统。第二种是中间层模式。DELPHI 写一个独立的服务端程序常见用 Indy 或 DataSnap 做 HTTP 服务浏览器通过 AJAX 调接口DELPHI 负责查数据库、拼 JSON 返回。前端可以是纯 HTMLJS也可以是别的框架。这种模式把 DELPHI 的强项数据库访问、业务计算和 WEB 的展示层分开了维护起来清楚得多。我一般会推荐这种尤其是系统还要活好几年的情况。第三种是 WebBroker 模板引擎。DELPHI 自带的 WebBroker 框架配合 PageProducer能把 HTML 模板和动态数据拼起来。适合页面结构固定、不需要复杂前端交互的后台管理类系统。选哪种看三个指标并发用户数、前端交互复杂度、团队里有没有人懂前端。三个都不高第一种够用有一个高就往第二种靠。2.2 数据库连接层的选型DELPHI 连数据库的组件不少选哪个直接影响后面写代码的手感。组件方案适用数据库特点注意点ADOSQL Server / AccessWindows 原生部署省事依赖系统 OLE DB 驱动dbExpressMySQL / Oracle / SQL Server跨平台轻量驱动要单独配dbx 数据库工具可辅助调试FireDAC主流数据库全覆盖功能最全性能好学习曲线略陡UniDAC主流数据库全覆盖第三方直连不依赖客户端需要授权热搜里出现 dbx 数据库工具说明不少人在用 dbExpress 这条线。dbExpress 的好处是 Delphi 自带、不需要额外装数据库客户端库配好驱动就能连。缺点是某些冷门数据库支持一般。如果目标库是 MySQL 或 SQL ServerdbExpress 够用如果要连多种库或者对性能有要求FireDAC 更稳。连接字符串这块有个血泪经验不要把密码硬编码在代码里。常见做法是放在配置文件或者环境变量里代码里只读配置。后面换服务器、改密码的时候不用重新编译。2.3 最小可跑的服务端骨架下面这段代码用 Indy 的 TIdHTTPServer 起一个最简 HTTP 服务收到请求后查数据库返回 JSON。这是中间层模式的最小骨架能跑通之后再往上加业务。// 服务端最小骨架Indy HTTP Server FireDAC 查库返回 JSON procedure TMainForm.IdHTTPServerCommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); var Conn: TFDConnection; Query: TFDQuery; JsonArr: TJSONArray; JsonObj: TJSONObject; begin // 1. 建立数据库连接实际项目应复用连接池 Conn : TFDConnection.Create(nil); try Conn.DriverName : MySQL; Conn.Params.Add(Server127.0.0.1); Conn.Params.Add(Databasetestdb); Conn.Params.Add(User_Nameappuser); Conn.Params.Add(Password******); // 从配置文件读取不要硬编码 Conn.Connected : True; // 2. 根据请求路径分发 Query : TFDQuery.Create(nil); try Query.Connection : Conn; if ARequestInfo.Path /api/users then begin Query.SQL.Text : SELECT id, name, email FROM users LIMIT 100; Query.Open; JsonArr : TJSONArray.Create; while not Query.Eof do begin JsonObj : TJSONObject.Create; JsonObj.AddPair(id, Query.FieldByName(id).AsInteger); JsonObj.AddPair(name, Query.FieldByName(name).AsString); JsonObj.AddPair(email, Query.FieldByName(email).AsString); JsonArr.AddElement(JsonObj); Query.Next; end; AResponseInfo.ContentType : application/json; charsetutf-8; AResponseInfo.ContentText : JsonArr.ToString; JsonArr.Free; end else begin AResponseInfo.ResponseNo : 404; AResponseInfo.ContentText : {error:not found}; end; finally Query.Free; end; finally Conn.Free; end; end;这段代码的逻辑说明TIdHTTPServer 的 OnCommandGet 事件在每次 HTTP GET 请求时触发ARequestInfo.Path 拿到请求路径按路径分发到不同查询。查完结果逐行拼成 JSON 数组返回。参数说明DriverName 按实际数据库改MySQL 就写 MySQLSQL Server 写 MSSQLParams 里的 Server、Database、User_Name、Password 按实际环境填LIMIT 100 是防止一次拉太多数据把内存撑爆实际项目要加分页。注意上面每次请求都新建连接生产环境必须改成连接池否则并发一上来数据库连接数就爆了。FireDAC 可以用 TFDManager 配连接池或者用中间件层统一管理。3. 把数据库增删改查搬到浏览器接口设计与前端对接3.1 REST 风格的接口约定中间层模式的核心是接口设计。我一般按 REST 风格来定路径和 HTTP 方法对应操作GET /api/users —— 查列表GET /api/users/1 —— 查单条POST /api/users —— 新增PUT /api/users/1 —— 修改DELETE /api/users/1 —— 删除DELPHI 这边在 OnCommandGet 里处理 GET在 OnCommandPost 里处理 POSTPUT 和 DELETE 需要看 Indy 版本是否支持老版本可能要在 POST 里用 _method 参数模拟。这个约定定下来之后前端不管用什么框架对接逻辑都是一样的。请求体和响应体统一用 JSON。响应格式固定成{code:0,data:...,msg:}前端只判断 code 是不是 0不用去猜 HTTP 状态码。这个约定看起来简单但能省掉大量前后端扯皮的时间。3.2 参数化查询防注入数据库增删改查里最容易翻车的就是 SQL 注入。DELPHI 里用参数化查询不要用字符串拼接。// 参数化查询示例新增用户 procedure TUserService.AddUser(const AName, AEmail: string); var Query: TFDQuery; begin Query : TFDQuery.Create(nil); try Query.Connection : FConn; Query.SQL.Text : INSERT INTO users (name, email, created_at) VALUES (:name, :email, NOW()); Query.ParamByName(name).AsString : AName; Query.ParamByName(email).AsString : AEmail; Query.ExecSQL; finally Query.Free; end; end;逻辑说明SQL 语句里用:name、:email占位实际值通过 ParamByName 传入。数据库驱动会自动处理转义用户输入里带单引号也不会破坏 SQL 结构。参数说明AsString 对应字符串类型如果是整数用 AsInteger日期用 AsDateTime。注意 NOW() 是 MySQL 的函数SQL Server 要用 GETDATE()。前端这边用 fetch 调接口// 前端调用示例新增用户 async function addUser(name, email) { const resp await fetch(/api/users, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ name: name, email: email }) }); const result await resp.json(); if (result.code ! 0) { alert(新增失败 result.msg); return false; } return true; }逻辑说明fetch 发 POST 请求body 是 JSON 字符串。服务端 DELPHI 在 OnCommandPost 里读 ARequestInfo.PostStream 拿到请求体解析 JSON 后调 AddUser。参数说明Content-Type 必须写 application/json否则服务端可能按表单解析。返回值统一判断 code。3.3 分页和排序的处理列表查询一定要分页不然数据一多浏览器直接卡死。DELPHI 这边接收 page 和 pageSize 两个参数拼到 SQL 里。// 分页查询 Query.SQL.Text : SELECT id, name, email FROM users ORDER BY id DESC LIMIT :offset, :limit; Query.ParamByName(offset).AsInteger : (APage - 1) * APageSize; Query.ParamByName(limit).AsInteger : APageSize;参数说明offset 是跳过的行数limit 是每页条数。MySQL 用 LIMIT offset, limit 语法SQL Server 要用 OFFSET FETCHOracle 要用 ROWNUM 嵌套。排序字段不要直接拼用户传的值要在服务端做白名单映射防止 ORDER BY 注入。前端分页组件拿到总条数后算总页数翻页时重新请求。总条数可以单独一个接口也可以在列表接口里带回来看数据量决定。4. 避坑与排查DELPHI WEB 数据库服务系统最常见的五个翻车点4.1 中文乱码现象浏览器显示的中文全是问号或方块。原因DELPHI 默认字符串编码和 HTTP 响应的 charset 不一致或者数据库连接字符集没设对。解决三处都要设——数据库连接参数加CharacterSetutf8mb4HTTP 响应头设ContentType : application/json; charsetutf-8返回的字符串用 UTF8Encode 转一遍。三处缺一处都可能乱码。4.2 并发一高就崩现象几个人同时用没事十几个人同时点就报连接超时或程序无响应。原因每次请求新建数据库连接连接数超过数据库上限或者 Indy 默认是单线程处理请求排队。解决数据库连接用连接池FireDAC 的 TFDManager 或自己维护一个连接队列Indy 的 TIdHTTPServer 设置 MaxConnections 并确认是线程池模式。如果还顶不住考虑换 DataSnap 或上 ISAPI。4.3 编译报内存错误现象DELPHI 编译时报内存错误尤其是大项目。原因常见的是第三方组件版本冲突、编译器缓存损坏、或者项目文件里引用了不存在的路径。解决先清理项目目录下的 dcu 文件和 __history 目录重新编译还不行就检查组件包是否都正确安装特别是 dbExpress 驱动和 Indy 版本是否匹配。热搜里 delphi 编译程序报内存错误这个词出现频率不低多半是环境问题不是代码问题。4.4 部署到服务器后连不上数据库现象开发机跑得好好的部署到服务器就报数据库连接失败。原因服务器上没装数据库客户端库或者防火墙拦了数据库端口或者连接字符串里的 Server 写的是 localhost 但数据库在另一台机器。解决确认服务器能 ping 通数据库主机telnet 数据库端口通不通如果用 ADO 或 dbExpress确认服务器上装了对应的驱动连接字符串里的 Server 改成实际 IP。4.5 前端跨域被拦现象前端页面和 DELPHI 服务不在同一个端口浏览器报 CORS 错误。原因浏览器同源策略DELPHI 服务端没返回 CORS 头。解决在 OnCommandGet 里给响应加Access-Control-Allow-Origin、Access-Control-Allow-Methods、Access-Control-Allow-Headers三个头。如果前端带 cookieAllow-Origin 不能写*要写具体域名。生产环境最好前后端同源部署用反向代理把 /api 转发到 DELPHI 服务省掉跨域问题。5. 进阶技巧让这套系统跑得更稳的几个习惯5.1 日志要记全但别记密码DELPHI 服务端出问题的时候没有日志基本等于黑匣子。我一般会在三个地方打日志请求进来时记路径和参数、数据库操作前记 SQL 和参数、异常时记堆栈。用 TStringList 或者第三方日志库写到文件里按天切分。注意SQL 参数里如果有密码字段记日志前要脱敏不然日志文件泄露等于密码泄露。5.2 接口版本化系统上线后需求一定会变接口不能随便改。常见做法是在路径里带版本号比如/api/v1/users。新需求加/api/v2/users老接口保留一段时间让前端迁移。DELPHI 这边在路径分发的地方按版本号走不同分支。这个习惯能省掉大量“改了接口前端全挂”的事故。5.3 数据库结构变更要有后悔药热搜里 mysql 数据库修改结构这个词说明很多人在这上面吃过亏。我的习惯是任何表结构变更都写成 SQL 脚本文件按日期编号存到版本控制里。改之前先备份改完在测试库跑一遍确认没问题再上生产。DELPHI 这边如果用了 ORM 或者数据模块字段变更后要同步更新 FieldDefs不然运行时报字段找不到。5.4 性能验证的简单方法系统上线前用简单的压测工具比如 ab 或者自己写个循环请求的脚本跑一下核心接口看响应时间和错误率。DELPHI 服务端可以在代码里加计时记录每个请求的处理耗时超过阈值的记到慢查询日志里。数据库那边开慢查询日志看哪些 SQL 拖了后腿。这两个日志对着看基本能定位大部分性能问题。5.5 一个具体技巧用 WebBroker 做静态资源服务如果前端页面不多可以直接用 DELPHI 的 WebBroker 把静态文件也一起服务了不用单独装 Nginx。在 OnCommandGet 里判断路径如果是 /static/ 开头就去读磁盘文件返回ContentType 按扩展名映射。这样部署的时候只有一个 exe拷贝过去就能跑对只有一台 Windows 服务器的场景特别友好。注意路径要做安全校验防止../目录穿越。这套方案我前后在几个工厂内部系统上用过最久的跑了五年没大改。DELPHI 做 WEB 数据库服务系统不是最时髦的路子但在特定约束下它是最省心的。关键是想清楚架构选型把连接池、参数化查询、日志这三件事做扎实剩下的就是业务代码的体力活。希望帮到你。本文还有配套的精品资源点击获取