整合營(yíng)銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          前端代碼是怎樣智能生成的?看看大佬怎么說(shuō)

          前端代碼是怎樣智能生成的?看看大佬怎么說(shuō)

          者|萊斯

          出品|阿里巴巴新零售淘系技術(shù)部

          導(dǎo)讀: 作為阿里經(jīng)濟(jì)體前端委員會(huì)四大技術(shù)方向之一,前端智能化項(xiàng)目經(jīng)歷了 2019 雙十一的階段性考驗(yàn),交出了不錯(cuò)的答卷,天貓?zhí)詫氹p十一會(huì)場(chǎng)新增模塊 79.34% 的線上代碼由前端智能化項(xiàng)目自動(dòng)生成。 在 此期間研發(fā)小組經(jīng)歷了許多困難與思考,本次 《前端代碼是怎樣智能生成的》 系列分享,將與大家分享前端智能化項(xiàng)目中技術(shù)與思考的點(diǎn)點(diǎn)滴滴。

          概述

          無(wú)線大促頁(yè)面的前端代碼中,存在大量的業(yè)務(wù)模塊或業(yè)務(wù)組件(下文統(tǒng)稱業(yè)務(wù)模塊),即具有一定業(yè)務(wù)功能的代碼單位。 獲取頁(yè)面中業(yè)務(wù)模塊的信息之后,可以用于復(fù)用代碼、綁定業(yè)務(wù)字段等后續(xù)功能。 因此從視覺(jué)稿識(shí)別出業(yè)務(wù)模塊,在前端智能化領(lǐng)域中成為用途廣泛的功能環(huán)節(jié)。

          與面向中后臺(tái)的基礎(chǔ)組件識(shí)別和表單識(shí)別功能不同,業(yè)務(wù)模塊識(shí)別主要面向無(wú)線端頁(yè)面,并且來(lái)源主要是視覺(jué)稿。 相對(duì)的,業(yè)務(wù)模塊 UI 結(jié)構(gòu)更加復(fù)雜,并且視覺(jué)稿提供的內(nèi)容已經(jīng)有較多可辨別的信息(如文本內(nèi)容、圖片尺寸等),因此我們沒(méi)有直接使用圖片深度學(xué)習(xí)的方案,而是從視覺(jué)稿產(chǎn)出的 DSL 中提取預(yù)定義的特征值,用傳統(tǒng)學(xué)習(xí)多分類的方法來(lái)實(shí)現(xiàn)模塊識(shí)別。 本識(shí)別功能最終返回業(yè)務(wù)模塊的類別、視覺(jué)稿中的位置等信息。

          總體功能如下圖所示。 包括:

          • 樣本構(gòu)造,根據(jù)用戶配置和自定義的數(shù)據(jù)增強(qiáng)規(guī)則對(duì)視覺(jué)稿進(jìn)行 UI 層的增強(qiáng),以得到視覺(jué)多樣化的樣本。 然后在定義好業(yè)務(wù)字段的基礎(chǔ)上,進(jìn)行特征值抽取并存儲(chǔ)。
          • 算法選擇,目前提供的都是傳統(tǒng)機(jī)器學(xué)習(xí)方法中的多分類算法。
          • 模型實(shí)現(xiàn),基于集團(tuán)機(jī)器學(xué)習(xí)平臺(tái)實(shí)現(xiàn)模型搭建及相關(guān)算法工程,做到自動(dòng)化訓(xùn)練與部署。
          • 接口提供,模型對(duì)外提供預(yù)測(cè)識(shí)別服務(wù)以及結(jié)果反饋服務(wù)。

          總體功能

          所在分層

          如下圖所示,我們的業(yè)務(wù)模塊識(shí)別服務(wù)位于物料識(shí)別層,為視覺(jué)稿導(dǎo)出的 DSL 提供進(jìn)一步的業(yè)務(wù)定制化的識(shí)別能力,在后續(xù)代碼生成的過(guò)程中滲透到字段綁定、業(yè)務(wù)邏輯等功能之中。

          D2C 功能分層

          樣本構(gòu)造

          機(jī)器學(xué)習(xí)是基于大量真實(shí)數(shù)據(jù)的訓(xùn)練過(guò)程,一個(gè)好的樣本庫(kù)可以讓你的模型訓(xùn)練事半功倍。 我們的樣本來(lái)源是視覺(jué)稿(Sketch),但同一個(gè)模塊的 Sketch 視覺(jué)稿可能只有寥寥幾張,可獲取的樣本數(shù)量過(guò)少。 因此首先要解決量的問(wèn)題。

          ? 數(shù)據(jù)增強(qiáng)

          為解決樣本數(shù)量問(wèn)題,我們采用了數(shù)據(jù)增強(qiáng)的方法。 數(shù)據(jù)增強(qiáng)有一套默認(rèn)的規(guī)則,同時(shí)也是可配置的。 用戶可自行根據(jù)視覺(jué)稿上各個(gè)元素在真實(shí)場(chǎng)景中可能發(fā)生的變化,如“是否可隱藏”,“文本字?jǐn)?shù)可變范圍”等維度來(lái)調(diào)整屬性,產(chǎn)出自定義的配置項(xiàng)。 因此樣本制作者可以清晰的知道自己所造樣本側(cè)重的差異點(diǎn)在哪里。

          我們根據(jù)這些配置項(xiàng)對(duì)屬性進(jìn)行發(fā)散、組合,生成大量不同的視覺(jué)稿 DSL。 這些 DSL 之間隨機(jī)而有規(guī)律地彼此相異,據(jù)此我們可以獲得大數(shù)量的樣本。

          增強(qiáng)配置的界面如下圖所示,左側(cè)與中部是 DSL 樹(shù)及渲染區(qū)域,右側(cè)就是增強(qiáng)配置的區(qū)域。 配置項(xiàng)由以下 2 部分組成:

          • 增強(qiáng)屬性: 尺寸、位置、隱藏、前景背景色、內(nèi)容
          • 增強(qiáng)方式: 連續(xù)范圍、指定枚舉值

          樣本生成的界面

          ? 數(shù)特征提取

          得到大量增強(qiáng)后的視覺(jué) DSL 后,如何生成樣本呢? 首先明確我們所需的樣本格式應(yīng)該是表格型數(shù)據(jù),以配合傳統(tǒng)機(jī)器學(xué)習(xí)方法的輸入格式: 一條樣本數(shù)據(jù)即一個(gè)特征向量。 因此我們要對(duì) DSL 進(jìn)行特征提取。

          基于此前的模型訓(xùn)練經(jīng)驗(yàn),我們發(fā)現(xiàn)某些視覺(jué)信息對(duì)于模塊的類別判斷尤為重要。 因此我們對(duì) UI 信息進(jìn)行抽象,自定義并提取為特征維度,如 DSL 的寬、高、布局方向、包含圖片數(shù)量、包含文本數(shù)量等。 通過(guò)各種視覺(jué)信息的抽象,我們得到 40 多維的視覺(jué)特征。

          除了視覺(jué)特征維度以外,我們還增加了自定義的業(yè)務(wù)特征。 即根據(jù)一定的“業(yè)務(wù)規(guī)則”,將某些元素塊定義為具有業(yè)務(wù)含義的元素,如“價(jià)格”、“人氣”等,并抽象出 10 個(gè)維度的業(yè)務(wù)特征。 在這一過(guò)程中同樣支持用戶自定義業(yè)務(wù)規(guī)則,可通過(guò)正則匹配等方式實(shí)現(xiàn)。

          視覺(jué)抽象特征加上業(yè)務(wù)特征,組成一個(gè)特征向量。 特征向量加上分類 label,即一個(gè)樣本。

          算法與模型

          首先我們的輸入是 Sketch 設(shè)計(jì)稿提取出的標(biāo)準(zhǔn)化 DSL,目標(biāo)是認(rèn)出該 DSL 是哪個(gè)業(yè)務(wù)模塊,可以歸結(jié)為一個(gè)多分類問(wèn)題。 沿著這一思路,前文我們從大量增強(qiáng)后的 DSL 中提取特征值、生成數(shù)據(jù)集以供訓(xùn)練。 我們使用的多分類模型基于算法平臺(tái)提供的各種組件進(jìn)行搭建。

          ? 隨機(jī)森林

          模 型 搭建

          最初我們選擇隨機(jī)森林模型作為多分類模型,因?yàn)殡S機(jī)森林的執(zhí)行速度快、自動(dòng)化流程順暢,幾乎無(wú)需額外操作就滿足了我們算法工程的需求; 并且對(duì)特征值處理的要求較低,會(huì)自行處理連續(xù)和離散變量,規(guī)則如下表所示。

          隨機(jī)森林變量類型自動(dòng)解析規(guī)則

          因此可以迅速的搭建出十分簡(jiǎn)潔的模型,如下圖所示。

          線上使用的隨機(jī)森林模型

          調(diào)參過(guò)程

          我們發(fā)現(xiàn)隨機(jī)森林對(duì)于樣本庫(kù)內(nèi)的數(shù)據(jù),偶爾會(huì)有不自信的情況發(fā)生,即 positive true 的置信度較低,被置信閾值卡住。 尤其是視覺(jué)非常相似的樣本,如圖所示的兩個(gè)相似模塊就給我們的分類結(jié)果帶來(lái)誤差。

          相似模塊

          為優(yōu)化這種“不自信”的問(wèn)題,我們對(duì)隨機(jī)森林進(jìn)行了調(diào)參,包括單棵樹(shù)隨機(jī)樣本數(shù)、單棵樹(shù)最大深度、ID3/Cart/C4.5 樹(shù)的種類配比等參數(shù),也預(yù)接入特征選擇組件,效果均不理想。 最終在特征值重要性評(píng)估后手動(dòng)反饋到特征選擇并重新訓(xùn)練這一鏈路中取得了較好的結(jié)果,如下圖所示。 但這一過(guò)程無(wú)法融入到自動(dòng)化訓(xùn)練流程中,最終被我們放棄。

          調(diào)參過(guò)程中使用過(guò)的隨機(jī)森林模型

          離散特征問(wèn)題

          隨機(jī)森林雖然可以自動(dòng)處理離散變量,但是如果測(cè)試集中出現(xiàn)了訓(xùn)練集以外的離散值,算法無(wú)法處理這樣的情況。 要解決這一問(wèn)題,需確保每個(gè)離散特征的全部取值都出現(xiàn)在訓(xùn)練集中。 由于有多個(gè)離散特征,也無(wú)法通過(guò)簡(jiǎn)單的分層采樣來(lái)解決。 這也是隨機(jī)森林模型應(yīng)用中的痛點(diǎn)之一。

          綜上是我們?cè)陔S機(jī)森林模型上做的工作,隨機(jī)森林簡(jiǎn)單易上手、快速出結(jié)果,并且在大多數(shù)業(yè)務(wù)場(chǎng)景下都能滿足識(shí)別需求,成為模塊識(shí)別功能的 1.0 版本算法。 但由于其算法缺陷,我們后來(lái)引入了另一種模型 XGBoost。

          ? XGBoost 多分類

          模型搭建

          XGBoost 通過(guò) Boosting 的方法提升樹(shù)的“準(zhǔn)確率”,相較于隨機(jī)森林算法在我們的數(shù)據(jù)集上表現(xiàn)更優(yōu)越。 但是算法平臺(tái)的 XGBoost 模型有許多流程不標(biāo)準(zhǔn)的地方,因此為了實(shí)現(xiàn)自動(dòng)化鏈路,我們搭建了如圖所示模型。

          XGBoost 模型

          預(yù)處理

          XGBoost 模型需要更多的預(yù)處理方法來(lái)實(shí)現(xiàn),包括:

          • Label Encoding: 預(yù)處理過(guò)程。 XGBoost 僅支持從 0 開(kāi)始到(分類數(shù)-1)的 label 數(shù)值。 但為了映射方便,我們存儲(chǔ)的 label 值對(duì)應(yīng)的是平臺(tái)的分類 ID,并不是 0~N 的,甚至可能不是連續(xù)整數(shù)。 因此需要用 Label Encoding 組件編碼到符合 XGBoost 需求的數(shù)值。
          • 存儲(chǔ) Label 映射表: 數(shù)據(jù)轉(zhuǎn)存,因?yàn)轭A(yù)測(cè)接口會(huì)用到這一映射表來(lái)轉(zhuǎn)義平臺(tái)分類,因此要額外保存。
          • 數(shù)據(jù)重整: 預(yù)處理過(guò)程,為防止隨機(jī)拆分算法將訓(xùn)練集的 label 拆分為不完備的數(shù)據(jù)集,把訓(xùn)練集 label 的缺失數(shù)據(jù)撈回來(lái)。 對(duì)模型會(huì)有一定干擾,但是在數(shù)據(jù)極少的極端情況下才會(huì)發(fā)揮作用。

          XGBoost 在測(cè)試數(shù)據(jù)上的表現(xiàn)頗為自信,降低了閾值劃分的困難,預(yù)測(cè)結(jié)果也能夠很好的滿足我們“識(shí)別正確組件”的業(yè)務(wù)需求,并且也可以支持自動(dòng)化流程,因此成為后續(xù)我們主推的傳統(tǒng)訓(xùn)練模型。

          ? 難點(diǎn)問(wèn)題: Out Of Distributio

          值得一提的是,我們無(wú)法對(duì)當(dāng)前模塊庫(kù)以外的所有視覺(jué)樣本進(jìn)行全面的收集,這樣的工程就如同為了做一個(gè)阿里內(nèi)部的面部識(shí)別系統(tǒng),而去收集 70 億人類的面部照片一樣。 樣本庫(kù)以外的數(shù)據(jù)缺失導(dǎo)致我們其實(shí)是少了一個(gè)隱藏的分類——負(fù)樣本分類。 也就引發(fā)了 Out-of-Distribution 問(wèn)題,即樣本庫(kù)以外數(shù)據(jù)帶來(lái)的預(yù)測(cè)失準(zhǔn)問(wèn)題,其本質(zhì)是分類結(jié)果中 false positive 過(guò)多。

          在我們的場(chǎng)景下,這是一個(gè)很難解決的問(wèn)題,因?yàn)槭占控?fù)樣本的困難性。 目前我們是如何應(yīng)對(duì)這一問(wèn)題的呢?

          閾值設(shè)定

          我們將分類模型輸出的置信度 prob 作為確定分類結(jié)果的參考依據(jù),高于某一閾值則認(rèn)為匹配到某個(gè)分類。 這一方法具有經(jīng)驗(yàn)意義,實(shí)踐中有效的屏蔽了大部分 OOD 錯(cuò)誤。

          邏輯控制

          對(duì)于算法模型的部分 OOD 誤判,我們可以通過(guò)邏輯關(guān)系來(lái)辨別。 如我們認(rèn)為 DSL 樹(shù)的同一條路徑上不可能有多個(gè)相同組件(否則形成自嵌套),如果該路徑上識(shí)別出多個(gè)相同組件,那么我們通過(guò)置信度大小來(lái)選擇識(shí)別結(jié)果。 此類邏輯幫我們篩選了大部分誤判。

          負(fù)樣本錄入

          我們提供的反饋服務(wù),允許用戶將識(shí)別錯(cuò)誤的 DSL 上傳,上傳后增強(qiáng)為一定數(shù)量的負(fù)樣本并存儲(chǔ)。 在此基礎(chǔ)上重新訓(xùn)練,可以解決 OOD 問(wèn)題。

          目前 OOD 問(wèn)題還是依賴邏輯和反饋的方法來(lái)規(guī)避,算法層面仍然沒(méi)有解決該問(wèn)題,這是我們下一階段計(jì)劃去做的事。

          ? 模 型部署

          算法平臺(tái)支持將模型部署為線上接口,即預(yù)測(cè)服務(wù),通過(guò) imgcook 平臺(tái)可一鍵調(diào)用部署。 為了實(shí)現(xiàn)自動(dòng)化訓(xùn)練、部署的流程,我們還做了一系列算法工程的工作,在此不作詳述。

          預(yù)測(cè)與反饋

          預(yù)測(cè)服務(wù),輸入為設(shè)計(jì)稿提取的 DSL(JSON),輸出為業(yè)務(wù)模塊信息,包括 ID、在設(shè)計(jì)稿上的位置等。

          在調(diào)用算法平臺(tái)的預(yù)測(cè)接口之前,我們加入了邏輯上的過(guò)濾,包括:

          • 尺寸過(guò)濾: 對(duì)于模塊尺寸偏差較大的,不進(jìn)入預(yù)測(cè)邏輯,直接認(rèn)為不匹配
          • 層級(jí)過(guò)濾: 對(duì)于葉子節(jié)點(diǎn)(即純文本、純圖片),我們不認(rèn)為該節(jié)點(diǎn)具有業(yè)務(wù)含義,因此也過(guò)濾不用。

          結(jié)果反饋鏈路包括自動(dòng)結(jié)果檢測(cè)和用戶手動(dòng)反饋,目前僅提供了預(yù)測(cè)結(jié)果錯(cuò)誤的樣本上傳功能。

          我們的業(yè)務(wù)模塊識(shí)別功能最終在 99 大促中首次在線上使用。 上述的模型、前置邏輯、以及 OOD 規(guī)避等環(huán)節(jié),最終帶來(lái)的效果是: 業(yè)務(wù)場(chǎng)景內(nèi)的識(shí)別準(zhǔn)確率可達(dá) 100%(純模型的實(shí)際準(zhǔn)確率未統(tǒng)計(jì))。

          未來(lái)工作

          ? 算法優(yōu)化

          難點(diǎn)問(wèn)題解決

          如前所述,OOD 問(wèn)題是一個(gè)難點(diǎn),目前仍沒(méi)有很好的解決。 針對(duì)這一問(wèn)題我們有一些解決思路,計(jì)劃在后續(xù)工作中進(jìn)行嘗試。

          基于 DNN 的 loss function 優(yōu)化: 仍基于手動(dòng) UI 特征值搭建 DNN 網(wǎng)絡(luò),通過(guò) loss function 的優(yōu)化,擴(kuò)大不同類別之間的距離、壓縮同類別內(nèi)部的距離,在優(yōu)化后的模型上設(shè)定距離閾值來(lái)鑒別 OOD 數(shù)據(jù)。

          負(fù)樣本自動(dòng)生成的優(yōu)化: 在 XGBoost 算法基礎(chǔ)上,增加一個(gè)前置的二分類模型,用于區(qū)分集合內(nèi)和集合外數(shù)據(jù),并據(jù)此對(duì)負(fù)樣本生成的隨機(jī)范圍進(jìn)行優(yōu)化。 具體方案待調(diào)研。

          深度學(xué)習(xí)

          手動(dòng)特征提取的方法雖然快速有效,但是在泛化能力上無(wú)法與 CNN 之類的深度學(xué)習(xí)方法相比。因此后續(xù)我們會(huì)嘗試基于圖片的算法,使用 CNN 模型提取 UI 特征向量,再通過(guò)向量距離計(jì)算或二分類模型比對(duì)輸入數(shù)據(jù)與各個(gè) UI 組件的相似度。

          在深度學(xué)習(xí)領(lǐng)域還可以有更多嘗試,不限于以上算法設(shè)想。

          ? 樣本平臺(tái)

          目前我們的樣本生成功能存在配置效率低、支持算法類型少等問(wèn)題,因此在后續(xù)工作中,我們計(jì)劃將樣本生成進(jìn)行更豐富的產(chǎn)品化設(shè)計(jì)。 樣本平臺(tái)的功能大致如圖所示。

          樣 本平臺(tái)產(chǎn)品功能

          來(lái)源擴(kuò)展: 目前我們的樣本生成鏈路是從 Sketch 到 ODPS 表格數(shù)據(jù),在后續(xù)的業(yè)務(wù)場(chǎng)景中我們還希望能支持從 HTML、前端代碼生成樣本。 不論何種來(lái)源,在數(shù)據(jù)增強(qiáng)這一層都會(huì)有許多相通之處,我們將抽象出通用的增強(qiáng)算法服務(wù),開(kāi)放調(diào)用。

          算法擴(kuò)展: 最終生成的樣本,可以是特征值表格數(shù)據(jù),用于多分類; 也可以是 PASCAL、COCO 等格式的圖片與標(biāo)注數(shù)據(jù),提供給目標(biāo)檢測(cè)模型使用。

          增強(qiáng)智能化: 目前用戶在使用樣本生成功能時(shí)感到配置復(fù)雜、難上手,甚至常因?yàn)檎`操作而導(dǎo)致樣本不可用。 因此我們期望能通過(guò)數(shù)據(jù)增強(qiáng)的“智能化”,來(lái)盡量減少用戶操作,迅速生成有效樣本。

          綜上,算法優(yōu)化與樣本平臺(tái)產(chǎn)品化是我們下一期的核心工作。

          We are hiring

          淘系技術(shù)部 依托淘系豐富的業(yè)務(wù)形態(tài)和海量的用戶,我們持續(xù)以技術(shù)驅(qū)動(dòng)產(chǎn)品和商業(yè)創(chuàng)新,不斷探索和衍生顛覆型互聯(lián)網(wǎng)新技術(shù),以更加智能、友好、普惠的科技深度重塑產(chǎn)業(yè)和用戶體驗(yàn),打造新商業(yè)。 我們不斷吸引用戶增長(zhǎng)、機(jī)器學(xué)習(xí)、視覺(jué)算法、音視頻通信、數(shù)字媒體、移動(dòng)技術(shù)、端側(cè)智能等領(lǐng)域全球頂尖專業(yè)人才加入,讓科技引領(lǐng)面向未來(lái)的商業(yè)創(chuàng)新和進(jìn)步。



          喜歡小編的可以點(diǎn)個(gè)贊關(guān)注小編哦,小編每天都會(huì)給大家分享文章。

          我自己是一名從事了多年的前端老程序員,小編為大家準(zhǔn)備了新出的前端編程學(xué)習(xí)資料,免費(fèi)分享給大家!

          如果你也想學(xué)習(xí)前端,可以觀看【置頂】文章。也可以私信【1】 領(lǐng)取最新前端練手實(shí)戰(zhàn)項(xiàng)目

          ublime / vscode 快捷生成HTML代碼的實(shí)現(xiàn)

          文章目錄

          1.基本html結(jié)構(gòu)

          2.生成div加類名的快捷鍵

          3.帶類名的div

          4.帶id的div

          5.屬性【】

          6.后代>

          7.兄弟+

          8.上級(jí)^

          9.乘法*

          10.文本{}

          1、基本html結(jié)構(gòu)

          操作: 輸入 !+Enter鍵

          注意:這里輸入“!”+回車鍵前,需要清空原來(lái)生成的基本結(jié)構(gòu)

          實(shí)現(xiàn)效果:

          <!DOCTYPE html>
          <html lang="en">
          <head>
              <meta charset="UTF-8">
              <meta name="viewport" content="width=device-width, initial-scale=1.0">
              <meta http-equiv="X-UA-Compatible" content="ie=edge">
              <title>Document</title>
          </head>
          <body>
               
          </body>
          </html>

          2、生成div加類名的快捷鍵

          操作: 輸入 div.list>div.item_$*6

          實(shí)現(xiàn)效果:

          <div class="list">
              <div class="item_1"></div>
              <div class="item_2"></div>
              <div class="item_3"></div>
              <div class="item_4"></div>
              <div class="item_5"></div>
              <div class="item_6"></div>
          </div>

          3、帶類名的div

          操作: 輸入 div.wrapper

          實(shí)現(xiàn)效果:

          <div class="wrapper"></div>

          4、帶id的div

          操作: div#wrapper

          實(shí)現(xiàn)效果:

          <div id="wrapper"></div>

          5、屬性 []

          操作: span[title=“test”]

          實(shí)現(xiàn)效果:

          <span title="test"></span>

          6、后代 >

          操作: 輸入div>span>a

          實(shí)現(xiàn)效果:

          <span title="test"></span>

          7、兄弟 +

          操作: div+p+span

          實(shí)現(xiàn)效果:

          <div></div>
          <p></p>
          <span></span>

          8、上級(jí) ^

          操作: div>span^i

          實(shí)現(xiàn)效果:

          <div><span></span></div>
          <i></i>

          9、乘法 *

          操作: ul>li*2

          實(shí)現(xiàn)效果:

          <ul>
              <li></li>
              <li></li>
          </ul>

          10、文本 {}

          操作: div>span{這是文本}

          實(shí)現(xiàn)效果:

          <div><span>這是文本</span></div>

          概述

          1.1 介紹

          在項(xiàng)目開(kāi)發(fā)過(guò)程中,有很多業(yè)務(wù)模塊的代碼是具有一定規(guī)律性的,例如controller控制器、service接口、service實(shí)現(xiàn)類、mapper接口、model實(shí)體類等等,這部分代碼可以使用代碼生成器生成,我們就可以將更多的時(shí)間放在業(yè)務(wù)邏輯上。

          傳統(tǒng)的開(kāi)發(fā)步驟:

          創(chuàng)建數(shù)據(jù)庫(kù)和表 根據(jù)表設(shè)計(jì)實(shí)體類 ? 編寫mapper接口 ? 編寫service接口和實(shí)現(xiàn)類 ? 編寫controller控制器 ? 編寫前端頁(yè)面 ? 前后端聯(lián)調(diào)

          基于代碼生成器開(kāi)發(fā)步驟:

          創(chuàng)建數(shù)據(jù)庫(kù)和表 ? 使用代碼生成器生成實(shí)體類、mapper、service、controller、前端頁(yè)面 ? 將生成好的代碼拷貝到項(xiàng)目中并做調(diào)整 ? 前后端聯(lián)調(diào)

          我們只需要知道數(shù)據(jù)庫(kù)和表相關(guān)信息,就可以結(jié)合模版生成各個(gè)模塊的代碼,減少了很多重復(fù)工作,也減少出錯(cuò)概率,提高效率。

          1.2 實(shí)現(xiàn)思路

          (1)需要對(duì)數(shù)據(jù)庫(kù)表解析獲取到元數(shù)據(jù),包含表字段名稱、字段類型等等

          (2)將通用的代碼編寫成模版文件,部分?jǐn)?shù)據(jù)需使用占位符替換

          (3)將元數(shù)據(jù)和模版文件結(jié)合,使用一些模版引擎工具(例如freemarker)即可生成源代碼文件

          2 Freemarker

          2.1 介紹

          FreeMarker 是一款 模板引擎: 即一種基于模板和要改變的數(shù)據(jù), 并用來(lái)生成輸出文本(HTML網(wǎng)頁(yè),電子郵件,配置文件,源代碼等)的通用工具。 它不是面向最終用戶的,而是一個(gè)Java類庫(kù),是一款程序員可以嵌入他們所開(kāi)發(fā)產(chǎn)品的組件。

          模板編寫為FreeMarker Template Language (FTL)。它是簡(jiǎn)單的,專用的語(yǔ)言, 在模板中,你可以專注于如何展現(xiàn)數(shù)據(jù), 而在模板之外可以專注于要展示什么數(shù)據(jù)。

          2.2 應(yīng)用場(chǎng)景

          (1)動(dòng)態(tài)頁(yè)面

          freemarker可以作為springmvc一種視圖格式,像jsp一樣被瀏覽器訪問(wèn)。

          (2)頁(yè)面靜態(tài)化

          對(duì)于一些內(nèi)容比較多,更新頻率很小,訪問(wèn)又很頻繁的頁(yè)面,可以使用freemarker靜態(tài)化,減少DB的壓力,提高頁(yè)面打開(kāi)速度。

          (3)代碼生成器

          根據(jù)配置生成頁(yè)面和代碼,減少重復(fù)工作,提高開(kāi)發(fā)效率。

          2.3 快速入門

          (1)創(chuàng)建freemarker-demo模塊,并導(dǎo)入相關(guān)依賴

          <?xml version="1.0" encoding="UTF-8"?>
          <project xmlns="http://maven.apache.org/POM/4.0.0"
                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
              <modelVersion>4.0.0</modelVersion>
          
              <groupId>com.itheima</groupId>
              <artifactId>freemarker-demo</artifactId>
              <version>1.0-SNAPSHOT</version>
          
              <parent>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-parent</artifactId>
                  <version>2.3.1.RELEASE</version>
              </parent>
          
              <properties>
                  <maven.compiler.source>8</maven.compiler.source>
                  <maven.compiler.target>8</maven.compiler.target>
              </properties>
          
              <dependencies>
                  <dependency>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-starter-web</artifactId>
                  </dependency>
                  <dependency>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-starter-test</artifactId>
                  </dependency>
                  <!-- freemarker -->
                  <dependency>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-starter-freemarker</artifactId>
                  </dependency>
                  <!-- lombok -->
                  <dependency>
                      <groupId>org.projectlombok</groupId>
                      <artifactId>lombok</artifactId>
                  </dependency>
              </dependencies>
          </project>

          (2)application.yml相關(guān)配置

          server:
            port: 8881 #服務(wù)端口
          spring:
            application:
              name: freemarker-demo #指定服務(wù)名
            freemarker:
              cache: false #關(guān)閉模板緩存,方便測(cè)試
              settings:
                template_update_delay: 0 #檢查模板更新延遲時(shí)間,設(shè)置為0表示立即檢查,如果時(shí)間大于0會(huì)有緩存不方便進(jìn)行模板測(cè)試
              suffix: .ftl #指定Freemarker模板文件的后綴名

          (3)創(chuàng)建啟動(dòng)類

          package com.heima.freemarker;
          
          import org.springframework.boot.SpringApplication;
          import org.springframework.boot.autoconfigure.SpringBootApplication;
          
          @SpringBootApplication
          public class FreemarkerDemotApplication {
              public static void main(String[] args) {
                  SpringApplication.run(FreemarkerDemotApplication.class,args);
              }
          }

          (4)創(chuàng)建Student模型類

          package com.itheima.freemarker.entity;
          
          import lombok.AllArgsConstructor;
          import lombok.Data;
          import lombok.NoArgsConstructor;
          
          import java.util.Date;
          
          @Data
          @NoArgsConstructor
          @AllArgsConstructor
          public class Student {
              private Integer id;
              private String name;//姓名
              private Integer age;//年齡
              private Date birthday;//生日
              private Float money;//錢包
          }

          (5)創(chuàng)建StudentController

          package com.itheima.freemarker.controller;
          
          import com.itheima.freemarker.entity.Student;
          import org.springframework.stereotype.Controller;
          import org.springframework.ui.Model;
          import org.springframework.web.bind.annotation.GetMapping;
          import org.springframework.web.bind.annotation.RequestMapping;
          
          import java.util.Date;
          
          @Controller
          @RequestMapping("student")
          public class StudentController {
          
              @GetMapping("index")
              public String index(Model model){
                  //1.純文本形式的參數(shù)
                  model.addAttribute("name", "Freemarker");
          
                  //2.實(shí)體類相關(guān)的參數(shù)
                  Student student=new Student();
                  student.setName("黑馬");
                  student.setAge(18);
                  model.addAttribute("stu", student);
          
                  return "01-index";
              }
          }

          (6)在resources/templates下創(chuàng)建01-index.ftl模版文件

          <!DOCTYPE html>
          <html>
          <head>
              <meta charset="utf-8">
              <title>首頁(yè)</title>
          </head>
          <body>
          <b>普通文本 String 展示:</b><br/>
          Hello ${name} <br>
          
          <hr>
          <b>對(duì)象Student中的數(shù)據(jù)展示:</b><br/>
          姓名:${stu.name}<br/>
          年齡:${stu.age}
          <hr>
          </body>
          </html>

          (7)測(cè)試

          瀏覽器訪問(wèn) http://localhost:8881/student/index

          效果如下

          2.4 模版

          2.4.1 基礎(chǔ)語(yǔ)法種類

          (1)注釋,即<#-- -->,介于其之間的內(nèi)容會(huì)被freemarker忽略

          <#--我是一個(gè)freemarker注釋-->

          (2)插值(Interpolation):即 ${..} 部分,freemarker會(huì)用真實(shí)的值代替${..}

          Hello ${name}

          (3)FTL指令:和HTML標(biāo)記類似,名字前加#予以區(qū)分,F(xiàn)reemarker會(huì)解析標(biāo)簽中的表達(dá)式或邏輯。

          <# >FTL指令</#> 

          (4)文本,僅文本信息,這些不是freemarker的注釋、插值、FTL指令的內(nèi)容會(huì)被freemarker忽略解析,直接輸出內(nèi)容。

          <#--freemarker中的普通文本-->
          我是一個(gè)普通的文本

          2.4.2 if指令

          if 指令即判斷指令,是常用的FTL指令,freemarker在解析時(shí)遇到if會(huì)進(jìn)行判斷,條件為真則輸出if中間的內(nèi)容,否則跳過(guò)內(nèi)容不再輸出。

          格式如下

          <#if condition>
            ....
          <#elseif condition2>
            ...
          <#elseif condition3>   
            ...
          <#else>
            ...
          </#if>

          需求:根據(jù)年齡輸出所處的年齡段

          童年:0歲—6歲(周歲,下同) 少年:7歲—17歲 青年:18歲—40歲 中年:41—65歲 老年:66歲以后

          實(shí)例代碼:

          (1)在01-index.ftl添加如下代碼

          <#if stu.age <=6>
          童年
          <#elseif stu.age <=17>
          少年
          <#elseif stu.age <=40>   
          青年
          <#elseif stu.age <=65>   
          中年
          <#else>
          老年
          </#if>

          (2)測(cè)試

          瀏覽器訪問(wèn)http://localhost:8881/student/index

          效果如下

          2.4.3 list指令

          list指令時(shí)一個(gè)迭代輸出指令,用于迭代輸出數(shù)據(jù)模型中的集合

          格式如下

          <#list items as item>
              ${item_index + 1}------${item}-----<#if item_has_next>,</#if>
          </#list>

          迭代集合對(duì)象時(shí),包括兩個(gè)特殊的循環(huán)變量: (1)item_index:當(dāng)前變量的索引值。 (2)item_has_next:是否存在下一個(gè)對(duì)象

          item_index 和 item_has_nex 中的item為<#list items as item> 中as后面的臨時(shí)變量

          需求:遍歷學(xué)生集合,輸出序號(hào),學(xué)生id,姓名,所處的年齡段,是否最后一條數(shù)據(jù)

          (1)在StudentController中增加方法

          @GetMapping("list")
          public String list(Model model) throws ParseException {
              List<Student> list=new ArrayList<>();
          
              list.add(new Student(1001,"張飛",15, null, 1000.11F));
              list.add(new Student(1002,"劉備",28, null, 5000.3F));
              list.add(new Student(1003,"關(guān)羽",45, null, 9000.63F));
              list.add(new Student(1004,"諸葛亮",62, null, 10000.99F));
              list.add(new Student(1005,"成吉思汗",75, null, 16000.66F));
              model.addAttribute("stus",list);
          
              return "02-list";
          }

          (2)在resources/templates目錄下創(chuàng)建02-list.ftl模版

          <!DOCTYPE html>
          <html>
          <head>
              <meta charset="utf-8">
              <title>列表頁(yè)面</title>
              <style>
                  table{
                      border-spacing: 0;/*把單元格間隙設(shè)置為0*/
                      border-collapse: collapse;/*設(shè)置單元格的邊框合并為1*/
                  }
                  td{
                      border:1px solid #ACBED1;
                      text-align: center;
                  }
              </style>
          </head>
          <body>
          
          <table>
              <tr>
                  <td>序號(hào)</td>
                  <td>id</td>
                  <td>姓名</td>
                  <td>所處的年齡段</td>
                  <td>生日</td>
                  <td>錢包</td>
                  <td>是否最后一條數(shù)據(jù)</td>
              </tr>
              <#list stus as stu >
                  <tr>
                      <td>${stu_index + 1}</td>
                      <td>${stu.id}</td>
                      <td>${stu.name}</td>
                      <td>
                          <#if stu.age <=6>
                              童年
                          <#elseif stu.age <=17>
                              少年
                          <#elseif stu.age <=40>   
                              青年
                          <#elseif stu.age <=65>   
                              中年
                          <#else>
                              老年
                          </#if>
                      </td>
                      <td></td>
                      <td>${stu.money}</td>
                      <td>
                          <#if stu_has_next>
                          否
                          <#else>
                          是
                          </#if>
                      </td>
                  </tr>
              </#list>
          </table>
          
          <hr>
          </body>
          </html>

          (2)測(cè)試

          瀏覽器訪問(wèn)http://localhost:8881/student/list

          效果如下

          2.4.4 include指令

          include指令的作用類似于JSP的包含指令,用于包含指定頁(yè),include指令的語(yǔ)法格式如下

          <#include filename [options]></#include>

          (1)filename:該參數(shù)指定被包含的模板文件 (2)options:該參數(shù)可以省略,指定包含時(shí)的選項(xiàng),包含encoding和parse兩個(gè)選項(xiàng),encoding 指定包含頁(yè)面時(shí)所使用的解碼集,而parse指定被包含是否作為FTL文件來(lái)解析。如果省略了parse選項(xiàng)值,則該選項(xiàng)值默認(rèn)是true

          需求:"早上好,尊敬的 某某 用戶!" 這句話在很多頁(yè)面都有用到,請(qǐng)合理設(shè)計(jì)!

          (1)在resources/templates目錄下創(chuàng)建00-head.ftl模版,內(nèi)容如下

          早上好,尊敬的 ${name} 用戶!

          (2)在resources/templates目錄下創(chuàng)建03-include.ftl模版,使用include引入00-head.ftl模版,內(nèi)容如下

          <!DOCTYPE html>
          <html>
          <head>
              <meta charset="utf-8">
              <title>詳情頁(yè)</title>
          </head>
          <body>
          
          <#include "00-head.ftl" />
          <br>
          歡迎來(lái)到黑馬程序員。
          
          </body>
          </html>

          (3)在StudentController中增加方法

          @GetMapping("include")
          public String include(Model model) throws ParseException {
          model.addAttribute("name", "黑馬");
          return "03-include";
          }

          (4)測(cè)試

          瀏覽器訪問(wèn)http://localhost:8881/student/include

          效果如下

          2.4.5 assign指令

          它用于為該模板頁(yè)面創(chuàng)建或替換一個(gè)頂層變量

          <#assign name="zhangsan" />

          2.4.6 運(yùn)算符

          (1)算數(shù)運(yùn)算符

          FreeMarker表達(dá)式中完全支持算術(shù)運(yùn)算,FreeMarker支持的算術(shù)運(yùn)算符包括:

          • 加法: +
          • 減法: -
          • 乘法: *
          • 除法: /
          • 求模 (求余): %

          (2)比較運(yùn)算符

          • =或者==:判斷兩個(gè)值是否相等.
          • !=:判斷兩個(gè)值是否不等.
          • >或者gt:判斷左邊值是否大于右邊值
          • >=或者gte:判斷左邊值是否大于等于右邊值
          • <或者lt:判斷左邊值是否小于右邊值
          • <=或者lte:判斷左邊值是否小于等于右邊值

          比較運(yùn)算符注意

          • =!=可以用于字符串、數(shù)值和日期來(lái)比較是否相等
          • =!=兩邊必須是相同類型的值,否則會(huì)產(chǎn)生錯(cuò)誤
          • 字符串 "x""x ""X"比較是不等的.因?yàn)镕reeMarker是精確比較
          • 其它的運(yùn)行符可以作用于數(shù)字和日期,但不能作用于字符串
          • 使用gt等字母運(yùn)算符代替>會(huì)有更好的效果,因?yàn)?FreeMarker會(huì)把>解釋成FTL標(biāo)簽的結(jié)束字符
          • 可以使用括號(hào)來(lái)避免這種情況,如:<#if (x>y)>

          (3)邏輯運(yùn)算符

          • 邏輯與:&&
          • 邏輯或:||
          • 邏輯非:!

          邏輯運(yùn)算符只能作用于布爾值,否則將產(chǎn)生錯(cuò)誤 。

          2.4.7 空值處理

          (1)缺失變量默認(rèn)值使用 “!”

          • 使用!要以指定一個(gè)默認(rèn)值,當(dāng)變量為空時(shí)顯示默認(rèn)值
          • 例: ${name!''}表示如果name為空顯示空字符串。
          • 如果是嵌套對(duì)象則建議使用()括起來(lái)
          • 例: ${(stu.bestFriend.name)!''}表示,如果stu或bestFriend或name為空默認(rèn)顯示空字符串。

          (2)判斷某變量是否存在使用 “??”

          用法為:variable??,如果該變量存在,返回true,否則返回false

          例:為防止stus為空?qǐng)?bào)錯(cuò)可以加上判斷如下:

              <#if stus??>
                  <#list stus as stu>
                      ......
                  </#list>
              </#if>

          2.4.8 內(nèi)建函數(shù)

          內(nèi)建函數(shù)語(yǔ)法格式: 變量+?+函數(shù)名稱

          (1)求集合的大小

          ${集合名?size}

          (2)日期格式化

          顯示年月日: ${today?date} 顯示時(shí)分秒:${today?time} 顯示日期+時(shí)間:${today?datetime} 自定義格式化: ${today?string("yyyy年MM月")}

          (3)內(nèi)建函數(shù)c

          model.addAttribute("point", 102920122);

          point是數(shù)字型,使用${point}會(huì)顯示這個(gè)數(shù)字的值,每三位使用逗號(hào)分隔。

          如果不想顯示為每三位分隔的數(shù)字,可以使用c函數(shù)將數(shù)字型轉(zhuǎn)成字符串輸出

          ${point?c}

          (4)將json字符串轉(zhuǎn)成對(duì)象

          一個(gè)例子:

          其中用到了 assign標(biāo)簽,assign的作用是定義一個(gè)變量。

          <#assign text="{'bank':'工商銀行','account':'10101920201920212'}" />
          <#assign data=text?eval />
          開(kāi)戶行:${data.bank}  賬號(hào):${data.account}

          (5)常見(jiàn)內(nèi)建函數(shù)匯總

          ?html:html字符轉(zhuǎn)義
          ?cap_first: 字符串的第一個(gè)字母變?yōu)榇髮懶问?
          ?lower_case :字符串的小寫形式
          ?upper_case :字符串的大寫形式
          ?trim:去掉字符串首尾的空格
          ?substring(from,to):截字符串  from是第一個(gè)字符的開(kāi)始索引,to最后一個(gè)字符之后的位置索引,當(dāng)to為空時(shí),默認(rèn)的是字符串的長(zhǎng)度
          ?lenth: 取長(zhǎng)度
          ?size: 序列中元素的個(gè)數(shù)
          ?int: 數(shù)字的整數(shù)部分(比如 -1.9?int 就是 -1)
          ?replace(param1,param2):字符串替換  param1是匹配的字符串 param2是將匹配的字符替換成指定字符

          內(nèi)建函數(shù)測(cè)試demo1

          (1)在StudentController新增方法:

          @GetMapping("innerFunc")
          public String testInnerFunc(Model model) {
              //1.1 小強(qiáng)對(duì)象模型數(shù)據(jù)
              Student stu1=new Student();
              stu1.setName("小強(qiáng)");
              stu1.setAge(18);
              stu1.setMoney(1000.86f);
              stu1.setBirthday(new Date());
              //1.2 小紅對(duì)象模型數(shù)據(jù)
              Student stu2=new Student();
              stu2.setName("小紅");
              stu2.setMoney(200.1f);
              stu2.setAge(19);
              //1.3 將兩個(gè)對(duì)象模型數(shù)據(jù)存放到List集合中
              List<Student> stus=new ArrayList<>();
              stus.add(stu1);
              stus.add(stu2);
              model.addAttribute("stus", stus);
              // 2.1 添加日期
              Date date=new Date();
              model.addAttribute("today", date);
              // 3.1 添加數(shù)值
              model.addAttribute("point", 102920122);
              return "04-innerFunc";
          }

          (2)在resources/templates目錄下創(chuàng)建04-innerFunc.ftl模版頁(yè)面:

          <!DOCTYPE html>
          <html>
          <head>
              <meta charset="utf-8">
              <title>inner Function</title>
          </head>
          <body>
          
              <b>獲得集合大小</b><br>
          
              集合大小:${stus?size}
              <hr>
          
          
              <b>獲得日期</b><br>
          
              顯示年月日: ${today?date}       <br>
          
              顯示時(shí)分秒:${today?time}<br>
          
              顯示日期+時(shí)間:${today?datetime}<br>
          
              自定義格式化:  ${today?string("yyyy年MM月")}<br>
          
              <hr>
          
              <b>內(nèi)建函數(shù)C</b><br>
              沒(méi)有C函數(shù)顯示的數(shù)值:${point} <br>
          
              有C函數(shù)顯示的數(shù)值:${point?c}
          
              <hr>
          
              <b>聲明變量assign</b><br>
              <#assign text="{'bank':'工商銀行','account':'10101920201920212'}" />
              <#assign data=text?eval />
              開(kāi)戶行:${data.bank}  賬號(hào):${data.account}
          
          <hr>
          </body>
          </html>

          (3)測(cè)試

          瀏覽器訪問(wèn)http://localhost:8881/student/innerFunc

          效果如下

          內(nèi)建函數(shù)測(cè)試demo2

          需求:遍歷學(xué)生集合,顯示集合總條數(shù),id不要逗號(hào)隔開(kāi),顯示學(xué)生的生日(只顯示年月日),錢包顯示整數(shù)并顯示單位,用戶姓名做脫敏處理(如果是兩個(gè)字第二個(gè)字顯示為星號(hào),例如張三顯示為張*,如果大于兩個(gè)字,中間字顯示為星號(hào),例如成吉思汗顯示為成*汗,諸葛亮顯示為諸*亮

          (1)修改StudentController中的list方法,

          @GetMapping("list")
          public String list(Model model) throws ParseException {
              DateFormat dateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
              List<Student> list=new ArrayList<>();
          
              list.add(new Student(1001,"張三",15, dateFormat.parse("2007-10-01 10:00:00"), 1000.11F));
              list.add(new Student(1002,"李四",28, dateFormat.parse("1994-10-01 10:00:00"), 5000.3F));
              list.add(new Student(1003,"王五",45, dateFormat.parse("1977-10-01 10:00:00"), 9000.63F));
              list.add(new Student(1004,"趙六",62, dateFormat.parse("1960-10-01 10:00:00"), 10000.99F));
              list.add(new Student(1005,"孫七",75, dateFormat.parse("1947-10-01 10:00:00"), 16000.66F));
              model.addAttribute("stus",list);
          
              return "02-list";
          }

          (2)修改02-list.ftl模版

          共${stus?size}條數(shù)據(jù):輸出總條數(shù)

          stu.id后面加?c:id不需要逗號(hào)分割

          stu.birthday后面加?date:生日只輸出年月日

          stu.money后面加?int:金額取整

          姓名需要使用replace和substring函數(shù)處理

          完整內(nèi)容如下

          <!DOCTYPE html>
          <html>
          <head>
              <meta charset="utf-8">
              <title>列表頁(yè)面</title>
              <style>
                  table{
                      border-spacing: 0;/*把單元格間隙設(shè)置為0*/
                      border-collapse: collapse;/*設(shè)置單元格的邊框合并為1*/
                  }
                  td{
                      border:1px solid #ACBED1;
                      text-align: center;
                  }
              </style>
          </head>
          <body>
          共${stus?size}條數(shù)據(jù)
          <table>
              <tr>
                  <td>序號(hào)</td>
                  <td>id</td>
                  <td>姓名</td>
                  <td>所處的年齡段</td>
                  <td>生日</td>
                  <td>錢包</td>
                  <td>是否最后一條數(shù)據(jù)</td>
              </tr>
              <#list stus as stu >
                  <tr>
                      <td>${stu_index + 1}</td>
                      <td>${stu.id?c}</td>
                      <td>
                          <#if stu.name?length=2>
                              ${stu.name?replace(stu.name?substring(1), "*")}
                          <#else>
                              ${stu.name?replace(stu.name?substring(1, stu.name?length-1), "*")}
                          </#if>
                      </td>
                      <td>
                          <#if stu.age <=6>
                              童年
                          <#elseif stu.age <=17>
                              少年
                          <#elseif stu.age <=40>   
                              青年
                          <#elseif stu.age <=65>   
                              中年
                          <#else>
                              老年
                          </#if>
                      </td>
                      <td>${stu.birthday?date}</td>
                      <td>${stu.money?int}元</td>
                      <td>
                          <#if stu_has_next>
                          否
                          <#else>
                          是
                          </#if>
                      </td>
                  </tr>
              </#list>
          </table>
          
          <hr>
          </body>
          </html>

          (3)測(cè)試

          瀏覽器訪問(wèn)http://localhost:8881/student/list

          效果如下

          2.4.9 靜態(tài)化

          (1)springboot整合freemarker靜態(tài)化文件用法

          編寫springboot測(cè)試用例

          package com.itheima.test;
          
          import com.itheima.freemarker.FreemarkerDemoApplication;
          import com.itheima.freemarker.entity.Student;
          import freemarker.template.Configuration;
          import freemarker.template.Template;
          import freemarker.template.TemplateException;
          import org.junit.Test;
          import org.junit.runner.RunWith;
          import org.springframework.beans.factory.annotation.Autowired;
          import org.springframework.boot.test.context.SpringBootTest;
          import org.springframework.test.context.junit4.SpringRunner;
          
          import java.io.FileWriter;
          import java.io.IOException;
          import java.util.*;
          
          @SpringBootTest(classes=FreemarkerDemoApplication.class)
          @RunWith(SpringRunner.class)
          public class FreemarkerTest {
          
              //注入freemarker配置類
              @Autowired
              private Configuration configuration;
          
              @Test
              public void test() throws IOException, TemplateException {
                  Template template=configuration.getTemplate("04-innerFunc.ftl");
                  /**
                   * 靜態(tài)化并輸出到文件中   參數(shù)1:數(shù)據(jù)模型     參數(shù)2:文件輸出流
                   */
                  template.process(getData(), new FileWriter("d:/list.html"));
                  /**
                   * 靜態(tài)化并輸出到字節(jié)輸出流中
                   */
                  //StringWriter out=new StringWriter();
                  //template.process(getData(), out);
                  //System.out.println(out.toString());
              }
          
          
              private Map getData(){
          
                  Map<String,Object> map=new HashMap<>();
          
                  Student stu1=new Student();
                  stu1.setName("小強(qiáng)");
                  stu1.setAge(18);
                  stu1.setMoney(1000.86f);
                  stu1.setBirthday(new Date());
          
                  //小紅對(duì)象模型數(shù)據(jù)
                  Student stu2=new Student();
                  stu2.setName("小紅");
                  stu2.setMoney(200.1f);
                  stu2.setAge(19);
          
                  //將兩個(gè)對(duì)象模型數(shù)據(jù)存放到List集合中
                  List<Student> stus=new ArrayList<>();
                  stus.add(stu1);
                  stus.add(stu2);
          
                  //向model中存放List集合數(shù)據(jù)
                  map.put("stus",stus);
          
          
                  //map數(shù)據(jù)
                  Map<String,Student> stuMap=new HashMap<>();
                  stuMap.put("stu1",stu1);
                  stuMap.put("stu2",stu2);
          
                  map.put("stuMap",stuMap);
                  //日期
                  map.put("today",new Date());
          
                  //長(zhǎng)數(shù)值
                  map.put("point",38473897438743L);
          
                  return map;
          
              }
          }

          (2)freemarker原生靜態(tài)化用法

          package com.itheima.freemarker.test;
          
          import com.itheima.freemarker.entity.Student;
          import freemarker.cache.FileTemplateLoader;
          import freemarker.cache.NullCacheStorage;
          import freemarker.template.Configuration;
          import freemarker.template.Template;
          import freemarker.template.TemplateException;
          import freemarker.template.TemplateExceptionHandler;
          
          import java.io.File;
          import java.io.FileWriter;
          import java.io.IOException;
          import java.util.*;
          
          public class FreemarkerTest {
          
              public static void main(String[] args) throws IOException, TemplateException {
                  //創(chuàng)建配置類
                  Configuration CONFIGURATION=new Configuration(Configuration.VERSION_2_3_22);
                  //設(shè)置模版加載路徑
          
                  //ClassTemplateLoader方式:需要將模版放在FreemarkerTest類所在的包,加載模版時(shí)會(huì)從該包下加載
                  //CONFIGURATION.setTemplateLoader(new ClassTemplateLoader(FreemarkerTest.class,""));
          
                  String path=java.net.URLDecoder.decode(FreemarkerTest.class.getClassLoader().getResource("").getPath(),"utf-8");
                  //FileTemplateLoader方式:需要將模版放置在classpath目錄下 目錄有中文也可以
                  CONFIGURATION.setTemplateLoader(new FileTemplateLoader(new File(path)));
          
                  //設(shè)置編碼
                  CONFIGURATION.setDefaultEncoding("UTF-8");
                  //設(shè)置異常處理器
                  CONFIGURATION.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
                  //設(shè)置緩存方式
                  CONFIGURATION.setCacheStorage(NullCacheStorage.INSTANCE);
                  //加載模版
                  Template template=CONFIGURATION.getTemplate("templates/04-innerFunc.ftl");
                  /**
                   * 靜態(tài)化并輸出到文件中   參數(shù)1:數(shù)據(jù)模型     參數(shù)2:文件輸出流
                   */
                  template.process(getModel(), new FileWriter("d:/list.html"));
                  /**
                   * 靜態(tài)化并輸出到字節(jié)輸出流中
                   */
                  //StringWriter out=new StringWriter();
                  //template.process(getData(), out);
                  //System.out.println(out.toString());
              }
          
          
              public static Map getModel(){
                  Map map=new HashMap();
                  //1.1 小強(qiáng)對(duì)象模型數(shù)據(jù)
                  Student stu1=new Student();
                  stu1.setName("小強(qiáng)");
                  stu1.setAge(18);
                  stu1.setMoney(1000.86f);
                  stu1.setBirthday(new Date());
                  //1.2 小紅對(duì)象模型數(shù)據(jù)
                  Student stu2=new Student();
                  stu2.setName("小紅");
                  stu2.setMoney(200.1f);
                  stu2.setAge(19);
                  //1.3 將兩個(gè)對(duì)象模型數(shù)據(jù)存放到List集合中
                  List<Student> stus=new ArrayList<>();
                  stus.add(stu1);
                  stus.add(stu2);
                  map.put("stus", stus);
                  // 2.1 添加日期
                  Date date=new Date();
                  map.put("today", date);
                  // 3.1 添加數(shù)值
                  map.put("point", 102920122);
                  return map;
              }
          }

          3 數(shù)據(jù)庫(kù)元數(shù)據(jù)

          3.1 介紹

          元數(shù)據(jù)(Metadata)是描述數(shù)據(jù)的數(shù)據(jù)。

          數(shù)據(jù)庫(kù)元數(shù)據(jù)(DatabaseMetaData)就是指定義數(shù)據(jù)庫(kù)各類對(duì)象結(jié)構(gòu)的數(shù)據(jù)。

          在mysql中可以通過(guò)show關(guān)鍵字獲取相關(guān)的元數(shù)據(jù)

          show status; 獲取數(shù)據(jù)庫(kù)的狀態(tài)
          show databases; 列出所有數(shù)據(jù)庫(kù)
          show tables; 列出所有表
          show create database [數(shù)據(jù)庫(kù)名]; 獲取數(shù)據(jù)庫(kù)的定義
          show create table [數(shù)據(jù)表名]; 獲取數(shù)據(jù)表的定義
          show columns from <table_name>; 顯示表的結(jié)構(gòu)
          show index from <table_name>; 顯示表中有關(guān)索引和索引列的信息
          show character set; 顯示可用的字符集以及其默認(rèn)整理
          show collation; 顯示每個(gè)字符集的整理
          show variables; 列出數(shù)據(jù)庫(kù)中的參數(shù)定義值

          也可以從 information_schema庫(kù)中獲取元數(shù)據(jù),information_schema數(shù)據(jù)庫(kù)是MySQL自帶的信息數(shù)據(jù)庫(kù),它提供了訪問(wèn)數(shù)據(jù)庫(kù)元數(shù)據(jù)的方式。存著其他數(shù)據(jù)庫(kù)的信息。

          select schema_name from information_schema.schemata; 列出所有的庫(kù)
          select table_name FROM information_schema.tables; 列出所有的表

          在代碼中可以由JDBC的Connection對(duì)象通過(guò)getMetaData方法獲取而來(lái),主要封裝了是對(duì)數(shù)據(jù)庫(kù)本身的一些整體綜合信息,例如數(shù)據(jù)庫(kù)的產(chǎn)品名稱,數(shù)據(jù)庫(kù)的版本號(hào),數(shù)據(jù)庫(kù)的URL,是否支持事務(wù)等等。

          DatabaseMetaData的常用方法:

          getDatabaseProductName:獲取數(shù)據(jù)庫(kù)的產(chǎn)品名稱
          getDatabaseProductName:獲取數(shù)據(jù)庫(kù)的版本號(hào)
          getUserName:獲取數(shù)據(jù)庫(kù)的用戶名
          getURL:獲取數(shù)據(jù)庫(kù)連接的URL
          getDriverName:獲取數(shù)據(jù)庫(kù)的驅(qū)動(dòng)名稱
          driverVersion:獲取數(shù)據(jù)庫(kù)的驅(qū)動(dòng)版本號(hào)
          isReadOnly:查看數(shù)據(jù)庫(kù)是否只允許讀操作
          supportsTransactions:查看數(shù)據(jù)庫(kù)是否支持事務(wù)

          3.2 搭建環(huán)境

          (1)導(dǎo)入mysql依賴

          <dependency>
              <groupId>mysql</groupId>
              <artifactId>mysql-connector-java</artifactId>
              <version>5.1.47</version>
          </dependency>

          (2)創(chuàng)建測(cè)試用例

          package com.itheima.test;
          
          import org.junit.Before;
          import org.junit.Test;
          
          import java.sql.*;
          import java.util.Properties;
          
          public class DataBaseMetaDataTest {
              private Connection conn;
          
              @Before
              public void init() throws Exception {
                  Properties pro=new Properties();
                  pro.setProperty("user", "root");
                  pro.setProperty("password", "123456");
                  pro.put("useInformationSchema", "true");//獲取mysql表注釋
                  //pro.setProperty("remarksReporting","true");//獲取oracle表注釋
                  conn=DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/?useUnicode=true&characterEncoding=UTF8", pro);
              }   
          }

          3.3 綜合信息元數(shù)據(jù)

          (1)獲取數(shù)據(jù)庫(kù)元信息綜合信息

          @Test
          public void testDatabaseMetaData() throws SQLException {
              //獲取數(shù)據(jù)庫(kù)元數(shù)據(jù)
              DatabaseMetaData dbMetaData=conn.getMetaData();
              //獲取數(shù)據(jù)庫(kù)產(chǎn)品名稱
              String productName=dbMetaData.getDatabaseProductName();
              System.out.println(productName);
              //獲取數(shù)據(jù)庫(kù)版本號(hào)
              String productVersion=dbMetaData.getDatabaseProductVersion();
              System.out.println(productVersion);
              //獲取數(shù)據(jù)庫(kù)用戶名
              String userName=dbMetaData.getUserName();
              System.out.println(userName);
              //獲取數(shù)據(jù)庫(kù)連接URL
              String userUrl=dbMetaData.getURL();
              System.out.println(userUrl);
              //獲取數(shù)據(jù)庫(kù)驅(qū)動(dòng)
              String driverName=dbMetaData.getDriverName();
              System.out.println(driverName);
              //獲取數(shù)據(jù)庫(kù)驅(qū)動(dòng)版本號(hào)
              String driverVersion=dbMetaData.getDriverVersion();
              System.out.println(driverVersion);
              //查看數(shù)據(jù)庫(kù)是否允許讀操作
              boolean isReadOnly=dbMetaData.isReadOnly();
              System.out.println(isReadOnly);
              //查看數(shù)據(jù)庫(kù)是否支持事務(wù)操作
              boolean supportsTransactions=dbMetaData.supportsTransactions();
              System.out.println(supportsTransactions);
          }

          (2)獲取數(shù)據(jù)庫(kù)列表

          @Test
          public void testFindAllCatalogs() throws Exception {
              //獲取元數(shù)據(jù)
              DatabaseMetaData metaData=conn.getMetaData();
              //獲取數(shù)據(jù)庫(kù)列表
              ResultSet rs=metaData.getCatalogs();
              //遍歷獲取所有數(shù)據(jù)庫(kù)表
              while (rs.next()) {
                  //打印數(shù)據(jù)庫(kù)名稱
                  System.out.println(rs.getString(1));
              }
              //釋放資源
              rs.close();
              conn.close();
          }

          (3)獲取某數(shù)據(jù)庫(kù)中的所有表信息

          @Test
          public void testFindAllTable() throws Exception {
              //獲取元數(shù)據(jù)
              DatabaseMetaData metaData=conn.getMetaData();
              //獲取所有的數(shù)據(jù)庫(kù)表信息
              ResultSet rs=metaData.getTables("庫(kù)名", "%", "%", new String[]{"TABLE"});
              //拼裝table
              while (rs.next()) {
                  //所屬數(shù)據(jù)庫(kù)
                  System.out.println(rs.getString(1));
                  //所屬schema
                  System.out.println(rs.getString(2));
                  //表名
                  System.out.println(rs.getString(3));
                  //數(shù)據(jù)庫(kù)表類型
                  System.out.println(rs.getString(4));
                  //數(shù)據(jù)庫(kù)表備注
                  System.out.println(rs.getString(5));
                  System.out.println("--------------");
              }
          }

          (4)獲取某張表所有的列信息

          @Test
          public void testFindAllColumns() throws Exception {
              //獲取元數(shù)據(jù)
              DatabaseMetaData metaData=conn.getMetaData();
              //獲取所有的數(shù)據(jù)庫(kù)某張表所有列信息
              ResultSet rs=metaData.getColumns("庫(kù)名", "%", "表名","%");
          
              while(rs.next()) {
                  //表名
                  System.out.println(rs.getString("TABLE_NAME"));
                  //列名
                  System.out.println(rs.getString("COLUMN_NAME"));
                  //類型碼值
                  System.out.println(rs.getString("DATA_TYPE"));
                  //類型名稱
                  System.out.println(rs.getString("TYPE_NAME"));
                  //列的大小
                  System.out.println(rs.getString("COLUMN_SIZE"));
                  //小數(shù)部分位數(shù),不適用的類型會(huì)返回null
                  System.out.println(rs.getString("DECIMAL_DIGITS"));
                  //是否允許使用null
                  System.out.println(rs.getString("NULLABLE"));
                  //列的注釋信息
                  System.out.println(rs.getString("REMARKS"));
                  //默認(rèn)值
                  System.out.println(rs.getString("COLUMN_DEF"));
                  //是否自增
                  System.out.println(rs.getString("IS_AUTOINCREMENT"));
                  //表中的列的索引(從 1 開(kāi)始
                  System.out.println(rs.getString("ORDINAL_POSITION"));
                  System.out.println("--------------");
              }
          }

          3.4 參數(shù)元數(shù)據(jù)

          參數(shù)元數(shù)據(jù)(ParameterMetaData):是由PreparedStatement對(duì)象通過(guò)getParameterMetaData方法獲取而 來(lái),主要是針對(duì)PreparedStatement對(duì)象和其預(yù)編譯的SQL命令語(yǔ)句提供一些信息,ParameterMetaData能提供占位符參數(shù)的個(gè)數(shù),獲取指定位置占位符的SQL類型等等 以下有一些關(guān)于ParameterMetaData的常用方法:

          getParameterCount:獲取預(yù)編譯SQL語(yǔ)句中占位符參數(shù)的個(gè)數(shù)
          @Test
          public void testParameterMetaData() throws Exception {
              String sql="select * from health.t_checkgroup where id=? and code=?";
              PreparedStatement pstmt=conn.prepareStatement(sql);
              pstmt.setString(1, "7");
              pstmt.setString(2, "0003");
              //獲取ParameterMetaData對(duì)象
              ParameterMetaData paramMetaData=pstmt.getParameterMetaData();
              //獲取參數(shù)個(gè)數(shù)
              int paramCount=paramMetaData.getParameterCount();
              System.out.println(paramCount);
          }

          3.5 結(jié)果集元數(shù)據(jù)

          結(jié)果集元數(shù)據(jù)(ResultSetMetaData):是由ResultSet對(duì)象通過(guò)getMetaData方法獲取而來(lái),主要是針對(duì)由數(shù)據(jù)庫(kù)執(zhí)行的SQL腳本命令獲取的結(jié)果集對(duì)象ResultSet中提供的一些信息,比如結(jié)果集中的列數(shù)、指定列的名稱、指 定列的SQL類型等等,可以說(shuō)這個(gè)是對(duì)于框架來(lái)說(shuō)非常重要的一個(gè)對(duì)象。 以下有一些關(guān)于ResultSetMetaData的常用方法:

          getColumnCount:獲取結(jié)果集中列項(xiàng)目的個(gè)數(shù)
          getColumnType:獲取指定列的SQL類型對(duì)應(yīng)于Java中Types類的字段
          getColumnTypeName:獲取指定列的SQL類型
          getClassName:獲取指定列SQL類型對(duì)應(yīng)于Java中的類型(包名加類名
          @Test
          public void testResultSetMetaData() throws Exception {
              String sql="select * from health.t_checkgroup where id=?";
              PreparedStatement pstmt=conn.prepareStatement(sql);
              pstmt.setString(1, "7");
              //執(zhí)行sql語(yǔ)句
              ResultSet rs=pstmt.executeQuery();
              //獲取ResultSetMetaData對(duì)象
              ResultSetMetaData metaData=rs.getMetaData();
              //獲取查詢字段數(shù)量
              int columnCount=metaData.getColumnCount();
              System.out.println("字段總數(shù)量:"+ columnCount);
              for (int i=1; i <=columnCount; i++) {
                  //獲取表名稱
                  System.out.println(metaData.getColumnName(i));
                  //獲取java類型
                  System.out.println(metaData.getColumnClassName(i));
                  //獲取sql類型
                  System.out.println(metaData.getColumnTypeName(i));
                  System.out.println("----------");
              }
          }

          4 代碼生成器環(huán)境搭建

          4.1 創(chuàng)建maven工程

          創(chuàng)建maven工程并導(dǎo)入以下依賴

          <properties>
              <java.version>11</java.version>
              <!-- 項(xiàng)目源碼及編譯輸出的編碼 -->
              <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
              <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
              <!-- 項(xiàng)目編譯JDK版本 -->
              <maven.compiler.source>11</maven.compiler.source>
              <maven.compiler.target>11</maven.compiler.target>
          </properties>
          
          <dependencies>
              <dependency>
                  <groupId>org.freemarker</groupId>
                  <artifactId>freemarker</artifactId>
                  <version>2.3.23</version>
              </dependency>
              <dependency>
                  <groupId>mysql</groupId>
                  <artifactId>mysql-connector-java</artifactId>
                  <version>5.1.47</version>
              </dependency>
              <dependency>
                  <groupId>org.projectlombok</groupId>
                  <artifactId>lombok</artifactId>
                  <version>1.18.8</version>
              </dependency>
              <dependency>
                  <groupId>org.apache.commons</groupId>
                  <artifactId>commons-lang3</artifactId>
                  <version>3.10</version>
              </dependency>
          </dependencies>

          目錄結(jié)構(gòu)如下


          主站蜘蛛池模板: 91精品一区二区三区久久久久| 亚洲AV无码一区二区一二区| 国产成人精品无码一区二区| 国产精品免费一区二区三区| 国产一区二区三区手机在线观看| 天美传媒一区二区三区| 无码人妻啪啪一区二区| 黑人一区二区三区中文字幕| 亚洲第一区精品日韩在线播放| 亚洲熟妇av一区二区三区漫画| 日本不卡免费新一区二区三区| 国产AV一区二区三区传媒| 国产精品成人一区二区| 国产成人综合亚洲一区| 岛国精品一区免费视频在线观看 | 亚洲AV午夜福利精品一区二区| 视频一区二区中文字幕| 精品亚洲一区二区| 黑人大战亚洲人精品一区| 国产精品一区视频| www.亚洲一区| 三上悠亚亚洲一区高清| 亚洲毛片不卡av在线播放一区| 国产麻豆剧果冻传媒一区| 国产在线一区二区三区av| 国产产一区二区三区久久毛片国语| 国内精自品线一区91| 嫩B人妻精品一区二区三区| 中文字幕一区二区三区精华液 | 一区二区视频在线| 成人精品一区二区不卡视频| 怡红院一区二区三区| 痴汉中文字幕视频一区| 亚洲一区精品伊人久久伊人 | 亚洲AV成人一区二区三区观看| 亚洲色一区二区三区四区| 国产成人精品无码一区二区老年人| 一级毛片完整版免费播放一区| 国产成人一区二区三区在线| 色久综合网精品一区二区| 小泽玛丽无码视频一区|