據(jù)字典離線網(wǎng)頁(yè)版制作詳解:從數(shù)據(jù)庫(kù)到零依賴靜態(tài)頁(yè)面)
簡(jiǎn)介面向用友NC Cloud 2105用戶的離線數(shù)據(jù)字典以網(wǎng)頁(yè)形式收錄了系統(tǒng)核心數(shù)據(jù)表、字段、索引、視圖及業(yè)務(wù)對(duì)象關(guān)聯(lián)信息適合實(shí)施顧問(wèn)、開(kāi)發(fā)人員、數(shù)據(jù)庫(kù)管理員和業(yè)務(wù)分析師查閱也可作為企業(yè)數(shù)字化轉(zhuǎn)型中理解數(shù)據(jù)模型的基礎(chǔ)資料。這一版本經(jīng)過(guò)細(xì)致校對(duì)修正消除了原始文檔中的常見(jiàn)不一致問(wèn)題內(nèi)容準(zhǔn)確性有保障同時(shí)無(wú)需聯(lián)網(wǎng)即可在瀏覽器中快速檢索十分適合無(wú)網(wǎng)絡(luò)或弱網(wǎng)環(huán)境。資源共11203個(gè)文件其中11191個(gè)html頁(yè)面承載數(shù)據(jù)字典正文另配少量js、css、gif用于頁(yè)面交互與樣式呈現(xiàn)壓縮包整體僅2.98MB輕量易部署。目前已有706人學(xué)習(xí)瀏覽。借助這份資料讀者可以按模塊梳理客戶、供應(yīng)商、庫(kù)存、訂單等業(yè)務(wù)實(shí)體及數(shù)據(jù)表結(jié)構(gòu)理解權(quán)限與角色劃分、接口集成規(guī)范并參考其中關(guān)于查詢報(bào)表與數(shù)據(jù)庫(kù)調(diào)優(yōu)的說(shuō)明更高效地支撐NCC2105的實(shí)施和日常運(yùn)維。1. 為什么要把NCC2105數(shù)據(jù)字典做成離線網(wǎng)頁(yè)版1.1 原始需求從哪來(lái)做過(guò)NCC2105二次開(kāi)發(fā)的朋友應(yīng)該都有過(guò)這種經(jīng)歷剛接手一個(gè)項(xiàng)目還沒(méi)開(kāi)始寫代碼先被一摞表結(jié)構(gòu)文檔勸退了。NCC2105作為成熟的ERP產(chǎn)品后臺(tái)表數(shù)量輕輕松松上千張字段更是上萬(wàn)起步業(yè)務(wù)表、中間表、配置表、日志表混在一起如果不依賴數(shù)據(jù)字典連“這個(gè)字段到底存的是什么”都搞不清楚。最原始的做法是直接連數(shù)據(jù)庫(kù)查。開(kāi)發(fā)環(huán)境有權(quán)限還好說(shuō)生產(chǎn)環(huán)境給你只讀賬號(hào)都算客氣很多時(shí)候只能找DBA要一份導(dǎo)出。就算拿到了視圖翻起來(lái)也極不順手字段注釋、枚舉值、主外鍵關(guān)系全擠在一起。更麻煩的是項(xiàng)目組里不同角色的人都在頻繁翻閱同一份字典前端要看狀態(tài)位含義后端要核對(duì)字段類型測(cè)試要確認(rèn)邊界值一份好用的字典幾乎是全組剛需。1.2 三個(gè)核心痛點(diǎn)缺一不可做這個(gè)離線網(wǎng)頁(yè)版之前我先后試過(guò)幾種形態(tài)最終確定了三個(gè)必須滿足的條件。第一必須離線可用。項(xiàng)目現(xiàn)場(chǎng)經(jīng)常是內(nèi)網(wǎng)環(huán)境甚至客戶機(jī)房都不讓帶外部設(shè)備進(jìn)去線上文檔、云端筆記全部失效。把字典做成一個(gè)本地網(wǎng)頁(yè)文件雙擊就能打開(kāi)不依賴任何服務(wù)器和網(wǎng)絡(luò)環(huán)境這才叫真正的隨時(shí)可查。第二必須帶全局搜索。NCC2105的表名是NX開(kāi)頭加數(shù)字不熟悉的人根本記不住靠肉眼在一千多張表里找目標(biāo)那不是在查字典是在練眼力。支持按表名、按表注釋、按字段名、按字段注釋模糊搜索這才算達(dá)到“字典”的及格線。第三必須有層級(jí)導(dǎo)航。NCC2105的表有清晰的模塊歸屬比如基礎(chǔ)檔案、供應(yīng)鏈、財(cái)務(wù)、人力資源等這些信息藏在表名前綴或元數(shù)據(jù)分類里。一個(gè)好的字典頁(yè)面應(yīng)該能先按模塊縮小范圍再精確定位到具體表最后查看字段明細(xì)。層級(jí)導(dǎo)航加搜索兩條路徑互補(bǔ)才是完整的檢索體驗(yàn)。2. 方案選型我為什么放棄PDF最終選了純靜態(tài)網(wǎng)頁(yè)2.1 PDF方案的致命缺陷很多人第一反應(yīng)是導(dǎo)成PDF我最早也這么干過(guò)。工具也好找數(shù)據(jù)庫(kù)客戶端基本都自帶導(dǎo)出功能選好表就能生成一份幾十頁(yè)甚至上百頁(yè)的PDF。真正用起來(lái)才發(fā)現(xiàn)問(wèn)題一堆。PDF是靜態(tài)排版內(nèi)容不會(huì)變但NCC2105的表結(jié)構(gòu)是動(dòng)態(tài)的二次開(kāi)發(fā)過(guò)程中經(jīng)常會(huì)加字段、改注釋、調(diào)整長(zhǎng)度。PDF只要導(dǎo)出一版這張表就“過(guò)期”了想更新必須重新導(dǎo)出整份文檔然后重復(fù)發(fā)給所有人。字典本該是隨時(shí)查閱的參考工具而不是一份需要反復(fù)替換的存檔文件。還有個(gè)很實(shí)際的問(wèn)題PDF的搜索體驗(yàn)非常差。Adobe Reader的CtrlF只能逐頁(yè)跳轉(zhuǎn)對(duì)上千張表來(lái)說(shuō)基本形同虛設(shè)。手機(jī)上打開(kāi)更是災(zāi)難頁(yè)面縮放、排版錯(cuò)亂字小到要拿放大鏡看。字段描述和枚舉值在PDF里往往擠在一個(gè)大單元格里閱讀體驗(yàn)遠(yuǎn)談不上友好。2.2 離線網(wǎng)頁(yè)版的兩個(gè)路線對(duì)比確定要做網(wǎng)頁(yè)版之后我評(píng)估了兩條實(shí)現(xiàn)路線。第一條是搭建Web服務(wù)方案典型做法是用Python的Flask或Django寫一個(gè)后臺(tái)數(shù)據(jù)放SQLite通過(guò)瀏覽器訪問(wèn)。好處是查詢能力強(qiáng)支持復(fù)雜篩選缺點(diǎn)是必須啟動(dòng)服務(wù)現(xiàn)場(chǎng)機(jī)器可能沒(méi)裝Python環(huán)境即便裝好了進(jìn)程掛了又得有人去重啟。第二條就是最終采用的純靜態(tài)方案把所有表結(jié)構(gòu)數(shù)據(jù)預(yù)生成成一個(gè)JSON文件配合一個(gè)HTML頁(yè)面用瀏覽器直接打開(kāi)file://協(xié)議訪問(wèn)。沒(méi)有任何服務(wù)端進(jìn)程沒(méi)有依賴安裝一個(gè)文件夾拷到哪都能用。搜索、導(dǎo)航、字段明細(xì)全部在前端完成。兩條路線的取舍本質(zhì)是你更在乎查詢能力的上限還是部署的零門檻。對(duì)于NCC2105數(shù)據(jù)字典這種“低頻高可靠性”工具零門檻部署的優(yōu)先級(jí)遠(yuǎn)高于復(fù)雜查詢能力。JSON文件雖然需要全量加載但幾千張表、幾萬(wàn)個(gè)字段的結(jié)構(gòu)化數(shù)據(jù)壓縮后通常只有幾MB現(xiàn)代瀏覽器解析起來(lái)完全沒(méi)有壓力。2.3 “完美修正版本”到底修正了什么標(biāo)題里提到“完美修正版本”是因?yàn)樵缦任易鲞^(guò)一個(gè)初版用起來(lái)有幾個(gè)明顯缺陷這次一并處理掉了。第一個(gè)缺陷是搜索邏輯太“笨”。初版用簡(jiǎn)單的includes匹配搜“供應(yīng)商”會(huì)把所有注釋里帶“供應(yīng)商”三個(gè)字的表全部撈出來(lái)結(jié)果幾百條等于沒(méi)搜。修正版改成了分詞匹配加權(quán)重排序完全匹配的表名排最前注釋包含關(guān)鍵詞的表名次之字段命中再次之。這樣搜索“供應(yīng)商”不再是海撈而是真正給你一條有優(yōu)先級(jí)的檢索列表。第二個(gè)缺陷是字段枚舉值缺失。NCC2105很多字段是字符型存數(shù)字編碼比如單據(jù)狀態(tài)存0、1、2如果不看枚舉文檔根本不知道0代表什么。初版漏掉了這部分修正版把字段的enum取值說(shuō)明也納入生成邏輯在字段詳情中一并展示查字典的時(shí)候不用再另開(kāi)一張枚舉對(duì)照表。第三個(gè)缺陷是移動(dòng)端適配太差?,F(xiàn)場(chǎng)調(diào)試、去車間看問(wèn)題經(jīng)常是拿手機(jī)臨時(shí)查一下。初版沒(méi)有做響應(yīng)式布局手機(jī)上頁(yè)面縮放錯(cuò)位表格擠成一團(tuán)。修正版對(duì)卡片式布局做了全面適配PC端左右分欄手機(jī)端上下堆疊滿足了現(xiàn)場(chǎng)隨時(shí)查的需求。這三個(gè)修正點(diǎn)看起來(lái)不大但每一項(xiàng)都直接影響日常使用體驗(yàn)也是我在實(shí)際項(xiàng)目中反復(fù)碰壁后才意識(shí)到的。3. 核心實(shí)現(xiàn)細(xì)節(jié)從NCC2105數(shù)據(jù)庫(kù)到離線頁(yè)面的全鏈路3.1 第一步從元數(shù)據(jù)抽取表結(jié)構(gòu)NCC2105的數(shù)據(jù)庫(kù)基于Oracle或PostgreSQL表結(jié)構(gòu)的元數(shù)據(jù)存儲(chǔ)在系統(tǒng)表中。以O(shè)racle為例核心信息從ALL_TAB_COLUMNS、ALL_COL_COMMENTS、ALL_TAB_COMMENTS這三張視圖取。用一條SQL就能獲得表名、表注釋、字段名、字段類型、字段長(zhǎng)度、字段注釋等信息SELECT c.table_name, tc.comments AS table_comment, c.column_name, c.data_type, c.data_length, cc.comments AS column_comment FROM all_tab_columns c LEFT JOIN all_tab_comments tc ON c.table_name tc.table_name LEFT JOIN all_col_comments cc ON c.table_name cc.table_name AND c.column_name cc.column_name WHERE c.owner NCC_USER ORDER BY c.table_name, c.column_id;這里有個(gè)容易踩的坑如果owner不寫會(huì)把系統(tǒng)表、臨時(shí)表全部掃出來(lái)數(shù)據(jù)量爆炸且沒(méi)有任何參考價(jià)值。NCC2105的業(yè)務(wù)表統(tǒng)一在特定schema下寫SQL時(shí)務(wù)必帶上owner條件。提取完字段信息還需要補(bǔ)一張“表級(jí)維度”的清單每張表屬于哪個(gè)業(yè)務(wù)模塊、是主表還是子表、核心邏輯主鍵是什么。這些信息不在系統(tǒng)表里需要結(jié)合NCC2105的建模規(guī)范來(lái)判斷。我根據(jù)表名前綴和NCC的元數(shù)據(jù)分類做了映射比如以bd開(kāi)頭的表屬于基礎(chǔ)數(shù)據(jù)以po開(kāi)頭的是采購(gòu)訂單模塊以so開(kāi)頭的是銷售模塊。把模塊信息拼進(jìn)表清單導(dǎo)航才能按“模塊分組”來(lái)組織。3.2 第二步生成結(jié)構(gòu)化JSON數(shù)據(jù)原始SQL查詢結(jié)果是二維表結(jié)構(gòu)不適合前端頁(yè)面直接使用。我寫了一個(gè)Python腳本把查詢結(jié)果轉(zhuǎn)換成嵌套JSON結(jié)構(gòu)大致是{ modules: [ { name: 采購(gòu)管理, tables: [ { tableName: po_order, comment: 采購(gòu)訂單主表, columns: [ { name: pk_order, type: varchar2(20), comment: 訂單主鍵, enumValue: }, { name: billstatus, type: int, comment: 單據(jù)狀態(tài), enumValue: 0:自由, 1:審批中, 2:已生效, 3:關(guān)閉 } ] } ] } ] }關(guān)鍵點(diǎn)在于枚舉值的整合。NCC2105的枚舉信息通常散落在代碼里、配置表里或者干脆只有老員工口口相傳。我的做法是在生成腳本里維護(hù)一份“字段枚舉值映射表”定期從開(kāi)發(fā)環(huán)境中核對(duì)補(bǔ)齊。對(duì)于沒(méi)有枚舉信息的字段enumValue字段留空字符串前端就不顯示枚舉區(qū)塊保持頁(yè)面干凈。數(shù)據(jù)量方面NCC2105完整庫(kù)大概有1500張表1.8萬(wàn)個(gè)字段生成后的JSON大約4MB左右不壓縮也能接受。但如果未來(lái)要擴(kuò)展到更多項(xiàng)目建議對(duì)JSON做一次Gzip體積能壓縮到1MB以內(nèi)。3.3 第三步前端頁(yè)面實(shí)現(xiàn)與檢索邏輯前端使用純?cè)鶫TMLCSSJavaScript不引入任何框架理由很簡(jiǎn)單框架需要構(gòu)建、需要CDN、需要npm install這些在離線環(huán)境全是障礙。原生三件套寫完之后整個(gè)字典就是一個(gè)文件夾放U盤里甚至可以直接拷給同事。頁(yè)面布局采用左右兩欄左側(cè)是模塊樹(shù)和表名列表右側(cè)展示選中表的字段明細(xì)。頂部放一個(gè)全局搜索框輸入關(guān)鍵詞后左側(cè)列表實(shí)時(shí)刷新為搜索結(jié)果。搜索邏輯是這套頁(yè)面的靈魂。我實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的加權(quán)評(píng)分函數(shù)表名完全等于關(guān)鍵詞權(quán)重100表名以關(guān)鍵詞開(kāi)頭權(quán)重80表名包含關(guān)鍵詞權(quán)重60表注釋包含關(guān)鍵詞權(quán)重40字段名包含關(guān)鍵詞權(quán)重20字段注釋包含關(guān)鍵詞權(quán)重10每個(gè)結(jié)果取最高權(quán)重作為排序依據(jù)同時(shí)顯示命中的字段信息。這個(gè)設(shè)計(jì)看似簡(jiǎn)單實(shí)際使用效果遠(yuǎn)超初版的“無(wú)腦includes”方案。搜索“客戶”時(shí)客戶主表排在前面而客戶名稱字段命中的結(jié)果排在后面用戶一眼就能找到最核心的表。3.4 性能優(yōu)化幾萬(wàn)字段的搜索如何做到秒開(kāi)有人說(shuō)才4MB的數(shù)據(jù)不至于談性能吧。但最開(kāi)始我確實(shí)踩過(guò)性能坑。初版搜索是遍歷所有表的字段做循環(huán)匹配每次輸入一個(gè)字符就全量跑一遍在低配辦公本上明顯卡頓。后來(lái)做了三處優(yōu)化整個(gè)體驗(yàn)就順了。第一處是輸入防抖。用戶停止輸入300毫秒后才觸發(fā)搜索而不是每個(gè)字符都觸發(fā)。第二處是數(shù)據(jù)預(yù)索引。頁(yè)面加載時(shí)把所有字段的“表名字段名注釋”拼接成一個(gè)長(zhǎng)字符串?dāng)?shù)組搜索時(shí)只需遍歷這個(gè)預(yù)先打平的索引不用反復(fù)嵌套訪問(wèn)對(duì)象。第三處是結(jié)果數(shù)量限制。搜索列表最多渲染前100條結(jié)果避免DOM一次性插入過(guò)多節(jié)點(diǎn)導(dǎo)致頁(yè)面無(wú)響應(yīng)。這三處優(yōu)化沒(méi)有用到任何高深技術(shù)但實(shí)實(shí)在在地把搜索響應(yīng)時(shí)間從幾百毫秒降到了幾乎無(wú)感知。性能優(yōu)化這件事很多時(shí)候不是靠框架而是靠“減少無(wú)用功”。4. 實(shí)測(cè)記錄與問(wèn)題排查4.1 常見(jiàn)問(wèn)題速查表版本做出來(lái)之后我讓項(xiàng)目組幾位同事各用了兩周收集到一批真實(shí)反饋整理成表格。問(wèn)題現(xiàn)象原因分析解決方法雙擊html文件后頁(yè)面空白瀏覽器禁止本地文件讀取外部JSON將JSON文件改為內(nèi)聯(lián)到HTML中打包成一個(gè)單文件搜索中文關(guān)鍵詞無(wú)結(jié)果JSON編碼不是UTF-8中文亂碼生成腳本中強(qiáng)制指定encodingutf-8Oracle的CLOB字段顯示為[CLOB]查詢結(jié)果未做類型轉(zhuǎn)換SQL中用DBMS_LOB.SUBSTR轉(zhuǎn)換為字符串部分表注釋為空開(kāi)發(fā)階段未維護(hù)注釋生成腳本跳過(guò)空注釋并在前端顯示“無(wú)注釋”表名點(diǎn)擊后字段明細(xì)加載慢每次點(diǎn)擊都重建表格DOM改為預(yù)渲染所有表詳情CSS控制顯隱4.2 幾個(gè)值得說(shuō)的坑與教訓(xùn)第一個(gè)坑是瀏覽器安全策略。HTML用file://協(xié)議打開(kāi)時(shí)瀏覽器出于安全考慮會(huì)攔截本地JSON文件的異步請(qǐng)求控制臺(tái)報(bào)CORS錯(cuò)誤。這個(gè)問(wèn)題我排查了大半天一度以為是代碼寫錯(cuò)了。后來(lái)發(fā)現(xiàn)解決方案無(wú)非兩種要么把JSON轉(zhuǎn)成JS文件通過(guò)script標(biāo)簽引用要么啟動(dòng)一個(gè)本地靜態(tài)服務(wù)器但這就違背了“零部署”的初衷。我最終選擇將JSON內(nèi)容直接內(nèi)聯(lián)進(jìn)HTML雖然文件變大了一些但徹底規(guī)避了跨域問(wèn)題單文件拷貝非常方便。第二個(gè)坑是Oracle大小寫敏感。NCC2105數(shù)據(jù)庫(kù)里表名既有大寫又有小寫如果不加處理前端按字母排序時(shí)會(huì)混亂。我在生成腳本中對(duì)表名統(tǒng)一做了大寫處理同時(shí)保留原始表名用于實(shí)際SQL查詢時(shí)復(fù)制使用。這個(gè)細(xì)節(jié)看似微不足道但確實(shí)影響日常使用的觀感。第三個(gè)坑是枚舉值數(shù)據(jù)的準(zhǔn)確性。一次更新時(shí)我把某個(gè)狀態(tài)字段的枚舉值寫錯(cuò)了導(dǎo)致組里同事按錯(cuò)誤值去排查數(shù)據(jù)浪費(fèi)了半天時(shí)間。從那以后我養(yǎng)成了一個(gè)習(xí)慣任何枚舉值變更必須在生成腳本的映射表里同步修改并且導(dǎo)出前自動(dòng)打印一份變更日志人工確認(rèn)無(wú)誤后再生成HTML。數(shù)據(jù)字典這種工具內(nèi)容出錯(cuò)比沒(méi)有更可怕。5. 幾個(gè)可以繼續(xù)擴(kuò)展的方向離線網(wǎng)頁(yè)版做到這個(gè)程度核心需求已經(jīng)全部滿足了但用久了之后我自己的體會(huì)是它還有幾個(gè)值得繼續(xù)深挖的方向。一個(gè)方向是支持增量更新?,F(xiàn)在的流程是數(shù)據(jù)庫(kù)結(jié)構(gòu)變化后必須重新跑一次完整腳本再打包。對(duì)于頻繁迭代的開(kāi)發(fā)項(xiàng)目來(lái)說(shuō)這個(gè)操作頻率其實(shí)挺高的。如果能在頁(yè)面里內(nèi)置一個(gè)“數(shù)據(jù)更新”入口允許導(dǎo)入一份增量JSON就能省去重新打包的步驟對(duì)多人協(xié)作場(chǎng)景會(huì)友好很多。另一個(gè)方向是加入表間關(guān)系可視化。NCC2105的主外鍵關(guān)系比較隱蔽依賴字段命名規(guī)范和ER圖才能看清。如果能從數(shù)據(jù)庫(kù)約束或數(shù)據(jù)流中解析出表間關(guān)聯(lián)在前端以簡(jiǎn)單的父子關(guān)系列表形式展示排查問(wèn)題時(shí)能省不少事。不需要畫復(fù)雜的關(guān)系圖列出來(lái)就夠了。還有一個(gè)小方向是導(dǎo)出能力收口。現(xiàn)在字典只能看如果要引文檔到項(xiàng)目周報(bào)或交付物里還得手動(dòng)復(fù)制粘貼。如果給每張表加一個(gè)“導(dǎo)出Markdown”按鈕一鍵生成當(dāng)前表的字典片段對(duì)交付文檔的整理會(huì)非常方便。這些方向我目前都只是在腦子里過(guò)了一遍還沒(méi)有全部落地。但數(shù)據(jù)字典這種工具本質(zhì)上是越用越順手、越迭代越貼合團(tuán)隊(duì)習(xí)慣的東西每次小改動(dòng)都能帶來(lái)實(shí)打?qū)嵉男侍嵘?。本文還有配套的精品資源點(diǎn)擊獲取