
簡介面向Delphi 12.3開發(fā)者的TMS XData v5.21.0.0控件包是一套基于REST架構的數(shù)據(jù)訪問框架用于快速構建可提供增刪改查接口的服務端應用解決網(wǎng)絡服務開發(fā)中架構選型與組件集成的常見痛點。壓縮包為7z格式體積僅3.2MB共582個文件包含249個pas源代碼、33個dpr工程文件和29個dpk包文件用于編譯安裝76個dfm窗體布局文件37個html說明頁面及chm編譯幫助另有css/js等前端資源與少量pdf/txt說明。整體并非僅僅是庫文件而是完整的組件工程與文檔集合。目前已有67人學習下載對于正在選型或準備上手TMS XData的Delphi開發(fā)者具有直接參考價值。通過249個pas源碼可深入理解組件內部實現(xiàn)借助dpr/dpk示例工程和chm幫助手冊能快速建立RESTful服務支持JSON/XML數(shù)據(jù)交換包內html/css/js等Web資源可用于前端聯(lián)調配合dfm窗體布局文件可降低界面與接口的對接成本。無論初學者還是資深Delphi程序員都能減少底層網(wǎng)絡通信的重復勞動更專注于業(yè)務邏輯顯著縮短數(shù)據(jù)服務開發(fā)周期。1. TMS XData v5.21.0.0在 Delphi 12.3 里讓業(yè)務模型直接長出 REST API 的控件做 Delphi 后端的人應該都碰過這種局面Aurelius 實體已經寫好了數(shù)據(jù)庫表也定完了但前端要調一個GET /customers還得單獨搭一套 HTTP 服務寫路由、寫 JSON 序列化、寫鑒權折騰半天。TMS XData v5.21.0.0 就是專門解決這個重復勞動的控件集它基于 Delphi 12.3 編譯鏈直接把 Aurelius ORM 實體暴露成標準 REST/JSON 資源自動生成 CRUD 端點順帶把 JWT 鑒權、CORS、日志這些服務端基礎設施也放進組件里。適合已經用 Delphi 做業(yè)務系統(tǒng)、需要把數(shù)據(jù)開放給 Web/H5/移動端復用的團隊也適合想在不換技術棧的前提下做輕量中臺的場景。2. 環(huán)境與安裝先讓 Delphi 12.3 認識 TMS XData v5.21.0.02.1 版本匹配為什么是 v5.21.0.0 而不是手寫 HTTP 服務先說結論TMS XData v5.21.0.0 這份資源是按 Delphi 12.3 Athens 編譯的項目的源碼路徑、BPL 包名、設計期注冊邏輯都對齊了這一代 IDE。如果你還在用 Delphi 10.3 或 11直接裝大概率會報DCU版本不匹配或者找不到System.Net的某個類型這不是資源有問題而是編譯工具鏈不匹配后面避坑部分會詳細說。那為什么不手寫一個 WebBroker 服務我早年也這么干過在TWebModule里自己解析Request.PathInfo、手寫Response.Content再處理 URL 編碼和日期格式。短期內接口少還能扛一旦實體超過十個每個實體都要寫 CRUD 路由、參數(shù)校驗、分頁、排序代碼量翻著倍走而且所有接口的 JSON 格式靠人肉保證前端稍微挑點格式問題就得來回返工。XData 在這個場景下更像一個資源映射器實體類就是 API 資源屬性就是字段路由、序列化、HTTP 方法映射全部自動完成。2.2 安裝步驟解壓、加庫路徑、編譯安裝拿到的是 .7z 壓縮包不是 installer所以安裝要做幾步手工操作但每一步都可控。先把壓縮包釋放到一個不帶空格的目錄我的習慣是放D:\Libs\tms\xdata_521不建議丟到系統(tǒng)盤或者帶中文名的路徑Delphi 的庫路徑對中文路徑支持一直不太穩(wěn)定。接下來打開 Delphi 12.3做三件事步驟操作說明1Tools Options Environment Variables里新增變量變量名TMS_XDATA指向解壓根目錄后續(xù)庫路徑可以直接引用2Tools Options Delphi Options Library Library Path追加源碼路徑把$(TMS_XDATA)\source或對應版本目錄加進去讓 IDE 能索引到單元文件3打開包工程組并順序編譯在packages目錄下找到 Delphi 12 對應的.groupproj先編譯運行時包.dpk再編譯設計期包并右鍵Install注意第三步的順序很關鍵XData 的運行時包依賴 Aurelius 的運行時包如果直接把設計期包拿去 Install會因為找不到底層 DCU 報錯。正確順序是從下往上編譯先Aurelius再XData核心最后才是設計期注冊包。看到Design package installed的提示并且 Tool Palette 里出現(xiàn)TMS XData分類就說明裝上了。2.3 驗證安裝用自帶 Demo 建立第一個服務器XData 安裝包通常會帶示例工程路徑一般在demos或samples目錄下。優(yōu)先找名字帶WebModule或者Server的示例打開后編譯運行瀏覽器訪問http://localhost:8080/tms/xdata/info如果返回一段 JSON列出 XData 版本號和服務狀態(tài)就說明整條鏈路是通的。這里有個關鍵的驗證點/tms/xdata是 XData 的默認根路徑后面跟的info是框架自帶的服務信息接口。如果瀏覽器訪問能看到類似product : TMS XData的字段說明不僅是組件裝好了連接池和請求分發(fā)也正常。如果這一步就 404優(yōu)先查端口是不是被別的服務占了再查 WebModule 有沒有被正確加載。3. 模型先行用 Aurelius 實體把數(shù)據(jù)表變成 REST 資源3.1 數(shù)據(jù)模型即 APIXData 如何做映射XData 的核心設計理念是模型即接口它不做獨立的接口編排層而是直接讀取 Aurelius 實體的元數(shù)據(jù)。實體類上的[Entity]、屬性上的[Column]這些注解在運行期被 XData 翻譯成資源的名稱、字段的 JSON 鍵名和類型。也就是說你寫實體類的時候接口文檔其實已經同步確定了。這意味著兩件事第一字段名、類型、是否可空都會直接影響 API 表現(xiàn)設計實體類時要站在前端視角想這個字段能不能為 null、應該返回字符串還是整數(shù)第二XData 端點的資源名默認是實體類名的復數(shù)形式比如TCustomer對應的資源路徑就是/customers如果你在代碼里寫的是TUserInfo那路徑就是/userInfo大小寫和復數(shù)化規(guī)則要心里有數(shù)否則前端按文檔調不到。3.2 寫一個 TCustomer 實體注解、主鍵與默認命名下面是最小可運行的實體定義以客戶表為例unit uCustomer; interface uses Aurelius.Mapping.Attributes; type [Entity] [Table(Customers)] TCustomer class private [Column(Id, [TColumnProp.Identity])] FId: Integer; [Column(Name, [])] FName: string; [Column(Email, [])] FEmail: string; public property Id: Integer read FId write FId; property Name: string read FName write FName; property Email: string read FEmail write FEmail; end; implementation end.這段代碼里有兩個容易忽略的參數(shù)TColumnProp.Identity表示主鍵自增XData 在生成POST /customers接口時會認為不需要客戶端傳入 Id而PUT /customers(1)則會要求 URL 里的(1)與實體主鍵匹配。如果主鍵屬性沒標Identity創(chuàng)建資源的接口就要求你在 JSON Body 里顯式傳主鍵行為完全不同。3.3 多表和關聯(lián)關系Master-Detail 資源怎么暴露實際業(yè)務很少只有單表XData 對關聯(lián)關系的處理是實體屬性直接引用另一個實體類JSON 序列化時會輸出嵌套對象。舉個例子訂單和客戶的關系type [Entity] [Table(Orders)] TOrder class private [Column(Id, [TColumnProp.Identity])] FId: Integer; [Column(CustomerId, [])] FCustomerId: Integer; [Column(TotalAmount, [])] FTotalAmount: Double; FCustomer: TCustomer; public property Id: Integer read FId write FId; property CustomerId: Integer read FCustomerId write FCustomerId; property TotalAmount: Double read FTotalAmount write FTotalAmount; [Association([TAssociationProp.LazyLoading])] property Customer: TCustomer read FCustomer write FCustomer; end;注意Association注解上的LazyLoading參數(shù)。我一般會默認打開懶加載這樣查詢訂單列表時不會把每個訂單的客戶對象都帶出來JSON 體積會小很多。前端需要客戶詳情時再單獨調/customers(2)取數(shù)據(jù)這也是 REST 接口更合理的使用方式。如果不加懶加載訂單量大時響應會明顯變慢這是新手最容易踩的坑。4. 把 CRUD 暴露成 APIXData 端點、篩選參數(shù)和自定義業(yè)務方法4.1 自動 CRUD 端點GET/POST/PUT/DELETE 映射規(guī)則實體類編譯進去之后XData 會自動注冊一套標準端點映射關系非常直觀HTTP 方法URL 示例行為GET/customers返回客戶列表默認分頁 100 條GET/customers(15)返回單條記錄主鍵為 15POST/customers創(chuàng)建記錄Body 放 JSON 對象PUT/customers(15)整體更新記錄 15DELETE/customers(15)刪除記錄 15這套映射不需要寫一行路由代碼。需要留意的坑是 URL 里主鍵的括號寫法是 OData 風格不是常見的/customers/15前端如果按路徑參數(shù)的習慣拼接就會 404。我一般會在接口文檔里顯式告訴前端這個規(guī)則順手在 WebModule 的響應頭里加上X-XData-EntitySet: customers之類的自定義頭方便排查問題。4.2 分頁、篩選與排序$top/$skip/$filter 的實際用法XData 對列表接口默認支持一組查詢前綴參數(shù)這是它區(qū)別于一個實體一個固定接口的另一個優(yōu)點??匆粋€實際請求GET /tms/xdata/customers?$top20$skip40$filterTotalAmount gt 1000$orderbyName desc參數(shù)含義拆開說$top20只取前 20 條等價于 SQL 里的LIMIT 20。$skip40跳過前 40 條和$top配合做分頁。前端常見的第 3 頁每頁 20 條就是$skip40$top20。$filterTotalAmount gt 1000是服務端過濾gt是大于的意思XData 會把它翻譯成 SQL 的 WHERE 條件不會出現(xiàn)查出全量再在內存里過濾這種災難。$orderbyName desc指定排序字段和方向多個字段用逗號分隔。這套參數(shù)是可以疊加的但要注意$filter的字段名必須和 JSON 序列化后的字段名一致。如果你在實體屬性上用了[Column(full_name)]但屬性名是FName那 URL 里應該寫full_name還是Name以 JSON 鍵名為準這個細節(jié)在聯(lián)調前先確認一次能省半天排查時間。4.3 自定義業(yè)務方法在服務類上開新的路由自動 CRUD 覆蓋的是標準增刪改查但業(yè)務里總有查詢某用戶最近十筆訂單這種聚合邏輯。XData 允許在服務器端注冊服務類把方法暴露成額外端點。我常用的做法是在獨立的單元里定義服務接口不寫進實體類type TCustomerService class public function GetRecentOrders(CustomerId: Integer; Count: Integer 10): TListTOrder; end;服務器啟動時把它注冊進 XData 的服務集合里前端就可以直接請求GET /tms/xdata/Service/GetRecentOrders?CustomerId5Count10這個路徑的規(guī)則是Service固定后面跟著類名和方法名參數(shù)走查詢字符串。好處是業(yè)務邏輯留在 Delphi 端可以繼續(xù)用 Aurelius 的查詢語法、事務和連接池不用為了一個聚合查詢去寫裸 SQL。我建議自定義服務方法統(tǒng)一走這種模式而不是另開一個 HTTP 端口。4.4 客戶端調用用原生 RESTClient 做最小聯(lián)調雖然 TMS 提供了官方的 XData Client 組件但我聯(lián)調時更習慣先用 Delphi 自帶的TNetHTTPClient把接口調通少一層依賴出了問題也好判斷是服務端還是客戶端的事uses System.Net.HttpClient, System.JSON; var HttpClient: TNetHTTPClient; Response: IHTTPResponse; JSONValue: TJSONValue; begin HttpClient : TNetHTTPClient.Create(nil); try Response : HttpClient.Get(http://localhost:8080/tms/xdata/customers?$top10); if Response.StatusCode 200 then begin JSONValue : TJSONObject.ParseJSONValue(Response.ContentAsString(TEncoding.UTF8)); try // JSONValue 里就是客戶列表數(shù)組 WriteLn(JSONValue.ToString); finally JSONValue.Free; end; end; finally HttpClient.Free; end; end;TNetHTTPClient.Get返回的IHTTPResponse里包含狀態(tài)碼、響應頭和 Body狀態(tài)碼 200 是正常401 是鑒權失敗404 多半是資源名和實體名沒對上。這里用TJSONObject.ParseJSONValue解析響應不引入額外的 JSON 庫聯(lián)調階段夠用了。5. 避坑與排查XData 部署中常見的七個翻車現(xiàn)場5.1 安裝后 IDE 里找不到 XData 組件面板現(xiàn)象按步驟編譯了設計期包Install 也提示成功但 Tool Palette 里搜不到TMS XData。原因有兩個一是庫路徑里源碼目錄順序不對IDE 索引到了舊版本的 DCU二是安裝了很多第三方組件組件包分類被折疊了。解決在 Tool Palette 右上角搜索框里直接輸入TXDataServer如果搜不到回到Components Install Packages里確認設計期包名字前打了勾再把庫路徑里舊版路徑刪掉重啟 IDE。5.2 GET 請求返回 500服務端日志卻一片空白現(xiàn)象瀏覽器訪問/customers返回 500 Internal Server ErrorWebModule 的 OnLog 事件什么都沒打。原因通常是實體類的屬性映射問題比如數(shù)據(jù)庫表里沒有Name列但實體里寫了Name這個錯誤在 XData 生成查詢語句時才拋出而異常被框架吞進了默認日志通道。解決先在瀏覽器訪問/tms/xdata/info確認服務正常再直接調用/customers(1)縮小范圍最后檢查實體類映射和數(shù)據(jù)庫字段是否一一對應。5.3 JSON 日期時間格式和前端預期不一致現(xiàn)象前端拿到的是2024-05-12T08:30:00.000Z但頁面顯示時多了 8 小時。原因Delphi 的TDateTime本身沒有時區(qū)概念XData 序列化時按服務器本地時間輸出而前端 JavaScript 的new Date()默認按瀏覽器時區(qū)渲染。解決在服務器端統(tǒng)一定義時間基準我一般會在部署文檔里約定所有接口返回 UTC 時間前端拿到后再轉當?shù)貢r間。如果是內部系統(tǒng)不想改邏輯就在前端解析時忽略時區(qū)字段按純字符串處理。5.4 SQLite 并發(fā)請求時頻繁報database is locked現(xiàn)象請求量稍微上來SQLite 為后端的服務就開始間歇性 500。原因SQLite 默認單連接串行寫XData 的連接池在并發(fā)寫場景下會同時打開多個連接其中一個拿到鎖其他就排隊失敗。解決SQLite 適合接口量小、并發(fā)低的內部工具如果要做多用戶在線服務把數(shù)據(jù)庫切到 MySQL/SQL Server連接字符串改一下應用代碼不用動。這個項目里如果已經踩了 SQLite 的坑別猶豫趁早換庫。5.5 CORS 配置不生效前端跨域被瀏覽器攔截現(xiàn)象前端跑在localhost:4200后端跑在8080瀏覽器控制臺報跨域錯誤但代碼里明明設置了Access-Control-Allow-Origin。原因CORS 的響應頭必須要在預檢請求階段返回也就是OPTIONS請求也要有對應處理很多人只處理了 GET/POST 而漏掉了 OPTIONS。解決在 WebModule 的 BeforeDispatch 事件里統(tǒng)一加三個響應頭Response.SetCustomHeader(Access-Control-Allow-Origin, *); Response.SetCustomHeader(Access-Control-Allow-Methods, GET, POST, PUT, DELETE, OPTIONS); Response.SetCustomHeader(Access-Control-Allow-Headers, Authorization, Content-Type, X-XData-*);Access-Control-Allow-Headers里的Authorization是給 JWT 用的Content-Type是給 JSON 請求體用的。注意*在需要攜帶 Cookie 的場景下不適用那時要寫具體域名。5.6 URL 資源名大小寫導致 404現(xiàn)象實體類叫TUserInfo前端訪問/userinfo是通的但訪問/userInfo就 404。原因XData 的資源名映射保留了實體類名的原始大小寫而且默認復數(shù)化時不改大小寫。解決約定所有資源名統(tǒng)一小寫訪問或者在實體類上用[Entity(userinfo)]顯式指定資源名不要讓前端去猜大小寫規(guī)則。5.7 部署到 Windows 服務后模塊不加載現(xiàn)象開發(fā)環(huán)境一切正常放到服務器上注冊成 Windows 服務一啟動就報模塊不存在或找不到指定的模塊。原因跑 WebBroker 應用需要依賴Delphi 12.3的運行時 DLL如rtl290.bpl等服務器上沒裝對應版本運行時庫。解決發(fā)布時選用Release Static編譯模式并勾選Link with runtime packages取消勾選讓 BPL 依賴打進 EXE。如果你是做綠色發(fā)布記得把XData和Aurelius注冊運行庫包也做好靜默安裝腳本不然換臺機器就翻車。6. 收尾技巧給 XData 服務加日志、鑒權和聯(lián)調腳本6.1 用攔截器記錄每次請求耗時生產環(huán)境里最怕的就是接口偶爾慢一下但說不出具體是哪個請求慢。XData 服務端的WebModule里有兩個事件可以利用BeforeDispatch和AfterDispatch。我在BeforeDispatch里記錄開始時間在AfterDispatch里計算耗時再拼上請求路徑、查詢參數(shù)、狀態(tài)碼一行結構化日志就出來了procedure TXDataWebModule.WebModuleBeforeDispatch(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); begin FStartTick : TStopwatch.GetTimestamp; end; procedure TXDataWebModule.WebModuleAfterDispatch(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); var ElapsedMs: Int64; begin ElapsedMs : (TStopwatch.GetTimestamp - FStartTick) * TStopwatch.TicksPerMillisecond; LogRequest(Request.PathInfo, Request.Query, Response.StatusCode, ElapsedMs); end;這一段代碼簡單直接只要在LogRequest里把信息寫進日志文件或數(shù)據(jù)庫就足夠定位百分之八十的性能問題。慢查詢的特征很統(tǒng)一$filter條件沒走索引、$skip頁數(shù)太大、關聯(lián)對象懶加載被逐條觸發(fā)日志里一看耗時就能確定方向。6.2 配合 FireMonkey 客戶端快速聯(lián)調XData 做服務端Delphi 12.3 的 FireMonkey 做客戶端是同一個技術棧下很舒服的組合。我一般會在客戶端工程里放一個調試窗體放一個TEdit填 URL一個TMemo看返回結果再放幾個按鈕對應 GET、POST、PUT、DELETE。聯(lián)調時不需要斷點直接在這個調試面板里發(fā)請求返回的 JSON 格式化打印出來前端同學要什么字段一眼就能對照。這個方法每次調試都幫我篩掉一類問題到底是接口數(shù)據(jù)不對還是客戶端解析邏輯不對。接口返回的 JSON 在調試面板里先看一遍再讓前端去對接溝通成本低很多。從那以后我每次接手 XData 項目都會強制自己先把/tms/xdata/info、實體映射、調試面板這三件事走一遍確認環(huán)境是健康的再去碰業(yè)務邏輯。希望這篇筆記能幫你少踩幾個坑把 XData 真正用起來。本文還有配套的精品資源點擊獲取