首页 / 资讯中心 / 文章详情

Delphi三层MES系统:产线级DataSnap架构与连接池实战

Delphi三层MES系统:产线级DataSnap架构与连接池实战 ★ FEATURED ARTICLE
简介这是一套基于Delphi开发的三层架构MES生产管理系统源码面向制造业信息化开发者及ERP/MES系统二次开发工程师解决企业级应用中业务逻辑封装复杂、中间件依赖过重、权限与部署不灵活等实际问题。资源包共415个文件含115个核心Pas业务单元、93个Dfm界面设计文件、35个XML配置与接口定义、29个DLL动态库及FastReport报表.fr3、DevExpress皮肤.skincfg等关键组件整体73.27MB结构完整、模块解耦清晰开箱即可编译运行。已有4441人学习下载体现其在Delphi企业应用领域的实用认可度。用户可直接获取完整可运行的DataSnap服务端MS SQL客户端架构、SQLite轻量配置中心、免授权第三方控件集成方案以及登录认证、数据同步、报表生成等典型MES功能实现细节特别适合需要快速构建稳定、低耦合、易维护制造业管理系统的中高级Delphi开发者。1. 这不是个“Delphi老古董演示项目”一个能真正在产线跑起来的三层MES系统从数据库连接池到报工界面全开源某制造企业产线主管曾当着我的面删掉刚部署的商业MES试用版——不是因为功能少而是“点个报工按钮要等三秒工人摸鱼时间都够泡杯茶了”。而眼前这个 Delphi 三层框架 MES 生产管理系统源码包恰恰是为这种场景生的它用 DataSnap 构建标准三层通信服务端封装了轻量级连接池和事务控制客户端不依赖 BDE/ADO直接通过 TSQLConnection TClientDataSet 走 TCP 协议与中间层交互所有业务逻辑如工序流转校验、工单状态机、报工防重提交全部下沉到服务端 DLL 中执行客户端只做 UI 绑定和简单校验。它不追求炫酷大屏但每个按钮点击响应在 300ms 内完成它没集成 IoT 接口但预留了 OPC UA 设备数据接入桩它用的是 Delphi 10.4不是 Delphi 7支持 Unicode、原生 HTTPS、现代 Windows 主题渲染。如果你正被“Delphi 做不了工业级系统”的偏见卡住或者手头有个半死不活的 BDE 单机版 MES 想升级架构这份源码就是你该拆的第一份真实产线级 Delphi 工程。2. 架构解剖三层不是概念图是 client/datasnap/server 三个物理目录四类核心对象这个项目把“三层”落到了文件系统和进程模型上不是画在 PPT 上的分层图。整个工程由三个独立 Delphi 项目组成分别对应 ClientWin32 客户端、DataSnap ServerWindows 服务、Database Layer服务端数据访问封装它们之间通过 DataSnap REST/HTTP 协议通信而非传统 DCOM 或共享内存。这种设计让部署极其灵活服务端可单独部署在内网服务器客户端可离线缓存最近 3 天工单数据网络中断时仍能本地报工恢复后自动同步。2.1 目录结构即架构每个文件夹名都在告诉你它承担什么角色打开解压后的根目录你会看到三个主文件夹ClientApp\Win32 GUI 应用主窗体frmMain.pas所有 UI 控件绑定TClientDataSet数据源来自TSQLConnectionTDataSetProviderDataSnapServer\Windows Service 项目入口DSServer.exe核心是TServerMethods类uServerMethods.pas所有业务方法如GetWorkOrderList,SubmitProcessReport都定义在此DBAccessLayer\静态链接库项目.dll编译输出MESDBEngine.dll封装了TADOConnection连接池管理、SQL 参数化执行、事务嵌套控制BeginTransactionEx、以及关键的“工单状态变更原子性校验”逻辑。提示不要试图把DBAccessLayer编译成.bpl包加载——它被设计为显式 LoadLibrary 调用这样服务端崩溃时不会拖垮整个 DataSnap 进程符合工业系统“故障隔离”原则。2.2 四类核心对象它们才是三层通信的真正载体整个数据流靠四个 Delphi 对象驱动缺一不可对象类型所在位置关键作用配置要点TSQLConnectionClientApp\frmMain.pas客户端连接 DataSnap 服务的唯一通道ConnectionName : DSN_MES_Server必须与服务端注册名一致LoginPrompt : False认证走CustomHeaders传 tokenTDataSetProviderDataSnapServer\uServerMethods.pas将服务端TADOQuery结果转为客户端可消费的TClientDataSet格式Options : [poAllowCommandText, poUseQuoteChar]否则中文字段名会出错TServerMethodsDataSnapServer\uServerMethods.pas服务端业务门面所有public方法自动发布为 REST 端点方法必须返回TDataSet或string不能返回TObject否则序列化失败TADOConnectionPoolDBAccessLayer\uDBPool.pas服务端连接池实现非 Delphi 自带组件是本项目自研核心MaxConnections : 20IdleTimeout : 300秒超时连接自动回收下面这段代码就是TServerMethods.GetWorkOrderList的真实实现它展示了三层如何协作function TServerMethods.GetWorkOrderList(const WorkOrderNo: string; const Status: Integer): TDataSet; var Conn: TADOConnection; Qry: TADOQuery; begin // 1. 从连接池取连接非 new TADOConnection Conn : TADOConnectionPool.GetInstance.GetConnection; try Qry : TADOQuery.Create(nil); try Qry.Connection : Conn; // 2. 使用参数化查询防 SQL 注入产线系统生死线 Qry.SQL.Text : SELECT * FROM t_workorder WHERE wo_no LIKE ? AND status ?; Qry.Parameters.ParamByName(0).Value : % WorkOrderNo %; Qry.Parameters.ParamByName(1).Value : Status; Qry.Open; // 3. 返回 DataSetDataSnap 自动序列化为 JSON/二进制流 Result : Qry; except Qry.Free; raise; end; except TADOConnectionPool.GetInstance.ReleaseConnection(Conn); raise; end; end;逻辑说明第 1 步调用GetConnection是关键——它从TADOConnectionPool获取已预热、已验证可用的连接避免每次请求都新建连接导致数据库连接数爆炸第 2 步强制使用Parameters而非字符串拼接这是产线系统硬性要求防止操作员在工单号输入框里输 OR 11 --导致全表泄露第 3 步Result : Qry看似简单实则触发 DataSnap 的TDataSetAdapter序列化流程将结果集压缩为二进制流发给客户端比纯 JSON 体积小 40%对低带宽产线网络友好。2.3 数据流向图一个报工请求的真实旅程当你在客户端点击“提交报工”按钮背后发生的是跨进程、跨协议、跨线程的完整链路客户端btnSubmit.Click→TClientDataSet.ApplyUpdates→ 触发TSQLConnection.ExecuteDirect(SubmitProcessReport, Params)Params 是 JSON 字符串含工单号、工序ID、数量、操作员IDDataSnap ServerHTTP 请求被TDSServerClass拦截 → 解析为TServerMethods.SubmitProcessReport调用 → 方法内调用MESDBEngine.dll!SubmitProcessReportExDBAccessLayerDLL 中SubmitProcessReportEx先查t_process_report表确认该工序未被重复提交WHERE wo_no ? AND process_id ? AND operator_id ?→ 若存在则返回错误码ERR_DUPLICATE_REPORT→ 否则开启事务插入新记录 → 事务成功后更新t_workorder.status→ 最终返回{success: true, report_id: 12345}客户端收到响应后TClientDataSet.Refresh重新拉取最新工单状态UI 实时变色提示“已提交”。这个链路没有魔法每一步都可在日志中追踪服务端日志写入C:\MES\Logs\DSServer_20240520.log这也是它能落地产线的根本原因——可监控、可回溯、可压测。3. 运行实操从零部署只需 12 分钟但有 3 个配置项必须手改别被“三层”吓住。这个项目设计时就考虑了快速验证服务端编译后是绿色免安装 Windows 服务客户端是单 EXE数据库脚本一键执行。我用一台 Win10 虚拟机实测从解压到点击报工成功耗时 11 分 43 秒。但有 3 个配置项绝不能跳过否则你会卡在“连接被拒绝”或“登录失败”浪费两小时排查。3.1 数据库准备SQL Server 2016 或兼容的国产数据库项目默认适配 SQL Server但DBAccessLayer已预留 Oracle/PostgreSQL 接口uDBFactory.pas中的CreateDBEngine工厂方法。我们以 SQL Server 为例创建数据库MESDB字符集设为Chinese_PRC_CI_AS支持中文排序运行\DBScripts\CreateMESDB.sql共 8 张表含t_workorder,t_process_report,t_employee创建登录用户mes_app密码Mes2024赋予db_datareader和db_datawriter角色关键修改打开\DBAccessLayer\uDBConfig.pas找到DB_CONNECTION_STRING常量const DB_CONNECTION_STRING ProviderSQLOLEDB.1;Integrated SecuritySSPI;Persist Security InfoFalse;Initial CatalogMESDB;Data Source127.0.0.1\SQLEXPRESS;→ 将Data Source改为你的真实 SQL Server 实例名如WIN-ABC\SQLEXPRESS或192.168.1.100→ 若用 SQL 账号登录改为Data Source...;User IDmes_app;PasswordMes2024;→切记此处字符串不能有换行或空格否则TADOConnection解析失败日志只报“无效连接字符串”不提示具体哪错。3.2 服务端部署注册为 Windows 服务并启动编译DataSnapServer\DSServer.dpr生成DSServer.exe以管理员身份运行 CMD进入DataSnapServer\目录DSServer.exe /install DSServer.exe /start关键修改打开DSServer.exe.config找到appSettings节点add keyServerPort value8080 / add keyAuthMode valueToken / add keyTokenSecret valueMES_Secret_Key_2024 /→ServerPort可改但需与客户端TSQLConnection.Params.Values[HostName]一致→AuthMode必须为Token项目不支持 Basic Auth防密码明文传输→TokenSecret是 JWT 签名密钥必须与客户端代码中的密钥完全一致见ClientApp\uAuth.pas的TOKEN_SECRET常量否则所有请求返回 401。3.3 客户端配置3 个文件决定能否连上客户端无需安装但以下三个文件必须按环境修改文件路径修改项说明ClientApp\ClientApp.exe.configadd keyServerURL valuehttp://127.0.0.1:8080/datasnap/ /改为服务端真实 IP 和端口末尾/datasnap/不可省略ClientApp\uAuth.pasTOKEN_SECRET : MES_Secret_Key_2024;必须与服务端TokenSecret完全一致大小写敏感ClientApp\res\themes\default.themeThemeNameWin11若 Win10 系统显示异常改为Win10此文件控制 UI 主题不影响功能改完后双击ClientApp.exe首次启动会弹出登录框。默认账号admin/Admin2024密码在\ClientApp\uAuth.pas的DEFAULT_USERS数组里可查。4. 避坑指南这 4 个问题我替你踩过第 3 个让产线停了 2 小时部署不是复制粘贴就能完事。我在某电子厂陪产线调试时连续遇到 4 个高频翻车点每个都导致“功能看似正常实则数据错乱”。这里不讲原理只说现象、原因、解决——你照着做10 分钟内定位。4.1 现象客户端登录成功但所有列表为空日志显示 “Error 500: Internal Server Error”原因服务端DSServer.exe进程未以“本地系统账户”运行导致无法加载MESDBEngine.dllDLL 依赖的msvcr120.dll等 VC 运行库缺失解决services.msc打开服务管理器右键DSServer→ 属性 → 登录 → 选择“此账户” → 输入.\Administrator和密码重启服务验证任务管理器中看DSServer.exe进程的“用户名”列是否为Administrator不是Local System。4.2 现象报工成功但数据库t_process_report表里数量为 0且无错误提示原因客户端TClientDataSet的ProviderFlags设置错误导致ApplyUpdates时未将qty字段标记为“需提交”解决打开ClientApp\frmReport.pas找到cdsReport数据集在FormCreate事件中添加cdsReport.FieldByName(qty).ProviderFlags : [pfInUpdate, pfInWhere, pfInKey];为什么Delphi 默认只将主键字段加入pfInKey非主键数值字段需手动加pfInUpdate否则ApplyUpdates认为该字段未修改跳过提交。4.3 现象多台客户端同时报工同一工序数据库出现两条记录违反“单工序单次报工”业务规则原因DBAccessLayer的事务隔离级别为ReadCommitted默认在高并发下两个请求几乎同时执行SELECT COUNT(*) FROM t_process_report WHERE ...都查到 0 条于是都插入新记录解决打开DBAccessLayer\uDBEngine.pas在SubmitProcessReportEx方法开头将事务改为Serializable// 原代码 Conn.BeginTrans; // 改为 Conn.BeginTrans(tiSerializable); // tiSerializable 4代价性能下降约 15%但产线报工并发通常 50TPS可接受若需更高性能应改用UPDLOCK, HOLDLOCK提示见DBScripts\OptimizeLocking.sql。4.4 现象客户端切换语言为简体中文后日期控件显示为英文且导出 Excel 日期格式错乱原因Delphi 10.4 的TDateTimePicker和TExcelApplication依赖系统区域设置但项目未在启动时强制设置Locale解决打开ClientApp\ClientApp.dpr在Application.Initialize;后添加SetThreadLocale(2052); // 2052 Chinese (PRC) SetThreadUILanguage(2052);验证重启客户端在“系统设置”里切换语言日期控件立即变为“2024年5月20日”。5. 二次开发实战给报工流程加“拍照留证”功能3 小时从需求到上线产线最常提的需求不是“大屏看板”而是“报工时拍张照片”。这个功能看似简单实则涉及客户端摄像头调用、图片压缩、服务端存储、数据库关联、权限控制五层。我用这个项目做了完整实现全程基于现有架构不改 DataSnap 通信协议不新增 DLL只增 3 个文件、改 5 处代码。以下是可直接抄作业的步骤。5.1 客户端调用系统摄像头并压缩图片Delphi 10.4 原生不支持摄像头但我们用Windows.Media.CaptureCOM 接口无需第三方控件新建单元uCamera.pas引用ActiveX,ComObj,Winapi.Windows实现CapturePhotoToStream函数核心代码如下function CapturePhotoToStream(Stream: TStream): Boolean; var MediaCapture: OleVariant; PhotoCapture: OleVariant; InMemoryRandomAccessStream: OleVariant; BitmapEncoder: OleVariant; Buffer: OleVariant; begin Result : False; try // 1. 创建 MediaCapture MediaCapture : CreateOleObject(Windows.Media.Capture.MediaCapture); MediaCapture.InitializeAsync.Empty; // 2. 创建内存流 InMemoryRandomAccessStream : CreateOleObject(Windows.Storage.Streams.InMemoryRandomAccessStream); // 3. 拍照并保存到内存流 PhotoCapture : MediaCapture.CapturePhotoToStreamAsync( CreateOleObject(Windows.Graphics.Imaging.BitmapEncoder.JpegEncoderId), InMemoryRandomAccessStream ).GetResults; // 4. 读取流到 TMemoryStream Buffer : InMemoryRandomAccessStream.ReadAsync( CreateOleObject(Windows.Storage.Streams.Buffer).Create(1024*1024), 1024*1024, 0 ).GetResults; Stream.CopyFrom(InMemoryRandomAccessStream.AsType(IUnknown), Buffer.Length); Result : True; except on E: Exception do LogError(Camera capture failed: E.Message); end; end;参数说明Stream: 输出流传入TMemoryStream后续用于上传Buffer.Length: 图片原始大小我们后续会压缩到 ≤512KB产线网络限制LogError: 调用项目自带的日志函数日志写入C:\MES\Logs\ClientApp.log。5.2 服务端接收图片并存入文件系统DataSnap 默认不支持文件上传但我们用TWebRequest.ContentFields解析 multipart/form-data在DataSnapServer\uServerMethods.pas中新增方法UploadPhotofunction TServerMethods.UploadPhoto(const WorkOrderNo, ProcessID: string; const PhotoStream: TStream): string; var FileName: string; FileStream: TFileStream; SavePath: string; begin Result : ; try // 1. 生成唯一文件名WO20240520-001-PROC101-20240520142301.jpg FileName : Format(WO%s-%s-PROC%s-%s.jpg, [WorkOrderNo, GetOperatorID, ProcessID, DateTimeToStr(Now, yyyymmddhhnnss)]); // 2. 存入固定路径避免权限问题 SavePath : IncludeTrailingPathDelimiter(GetAppPath) Photos\ FileName; ForceDirectories(ExtractFilePath(SavePath)); // 3. 写入文件PhotoStream 已被 DataSnap 解析为内存流 FileStream : TFileStream.Create(SavePath, fmCreate); try PhotoStream.Position : 0; FileStream.CopyFrom(PhotoStream, PhotoStream.Size); Result : FileName; // 返回文件名供数据库记录 finally FileStream.Free; end; except on E: Exception do Result : ERROR: E.Message; end; end;关键点GetOperatorID: 从当前请求的 JWT Token 中解析操作员 IDuAuth.pas提供GetUserIDFromTokenForceDirectories: 确保Photos\目录存在否则写入失败返回FileName而非完整路径因客户端无需知道服务端路径只存数据库。5.3 数据库与 UI关联照片与报工记录修改t_process_report表增加photo_filename VARCHAR(255)字段修改ClientApp\frmReport.pas的btnSubmit.Click事件procedure TfrmReport.btnSubmitClick(Sender: TObject); var PhotoStream: TMemoryStream; PhotoName: string; begin if chkTakePhoto.Checked then begin PhotoStream : TMemoryStream.Create; try if CapturePhotoToStream(PhotoStream) then begin // 压缩图片到 ≤512KB CompressJpegStream(PhotoStream, 512*1024); // 调用服务端上传 PhotoName : ServerMethods.UploadPhoto( edtWO.Text, edtProcessID.Text, PhotoStream ); if PhotoName.StartsWith(ERROR) then raise Exception.Create(照片上传失败: PhotoName); // 将文件名写入 cdsReport cdsReport.FieldByName(photo_filename).AsString : PhotoName; end; finally PhotoStream.Free; end; end; cdsReport.ApplyUpdates(0); // 正常提交报工 end;CompressJpegStream 实现uImageUtils.pas用GDI压缩不依赖第三方库Delphi 10.4 原生支持procedure CompressJpegStream(Stream: TStream; MaxSize: Integer); var Bitmap: TGpBitmap; Encoder: TGpImageCodecInfo; Params: TGpEncoderParameters; NewStream: TMemoryStream; begin Bitmap : TGpBitmap.Create(Stream); try NewStream : TMemoryStream.Create; try Encoder : GetEncoderClsid(image/jpeg); Params : TGpEncoderParameters.Create(1); Params.Param[0].Guid : EncoderQuality; Params.Param[0].Type_ : EncoderParameterValueTypeLong; Params.Param[0].NumberOfValues : 1; Params.Param[0].Value : 75; // 压缩质量 75% Bitmap.Save(NewStream, Encoder, Params); if NewStream.Size MaxSize then begin Stream.Clear; Stream.CopyFrom(NewStream, 0); end; finally NewStream.Free; end; finally Bitmap.Free; end; end;最终效果报工成功后数据库t_process_report.photo_filename字段存入WO20240520-001-PROC101-20240520142301.jpg服务端C:\MES\Photos\下生成对应文件客户端在报工记录列表中点击“查看照片”按钮调用ShellExecute(0, open, PChar(file:/// PhotoPath), nil, nil, SW_SHOW)直接用系统看图器打开。从需求提出到产线验证通过我用了 2 小时 47 分钟。其中最耗时的不是写代码而是说服产线主管接受“照片存本地而非云存储”——因为他们的网络策略禁止外联。所以现在所有照片都存在服务端本地磁盘按月归档为 ZIP人工拷贝到 NAS。这很土但稳定。从那以后我每次接到“拍照”需求都强制走一遍“本地存储 定期归档”流程再不碰任何云存储 SDK。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站