免费视频淫片aa毛片_日韩高清在线亚洲专区vr_日韩大片免费观看视频播放_亚洲欧美国产精品完整版

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開(kāi)通VIP
[轉(zhuǎn)]Eclipse 和 HSQLDB: 將關(guān)系數(shù)據(jù)庫(kù)服務(wù)器嵌入到 Eclipse 中(1) - myali88的專欄
本文介紹如何開(kāi)發(fā)將 HSQLDB 純 Java 關(guān)系數(shù)據(jù)庫(kù)服務(wù)器集成到 Eclipse Workbench 中的插件。盡管不如 DB2 功能強(qiáng)大,也不如 MySQL 流行,但 HSQLDB(超音速 SQL 數(shù)據(jù)庫(kù))可以滿足很大范圍內(nèi) Java 應(yīng)用程序的需要,因?yàn)樗哂锌蓴U(kuò)展性,而且對(duì)內(nèi)存/處理器的要求不高。

超音速 SQL 數(shù)據(jù)庫(kù)后來(lái)正式更名為 HSQLDB,它是一類純 Java 撰寫的嵌入式關(guān)系數(shù)據(jù)庫(kù)服務(wù)器,您可以在單機(jī)模式(使用直接文件訪問(wèn))或客戶機(jī)/服務(wù)器模式中使用它,它支持大量的并發(fā)用戶。盡管不如 DB2 功能強(qiáng)大,也不如 MySQL 流行,但 HSQLDB(超音速SQL數(shù)據(jù)庫(kù))可以滿足很大范圍內(nèi) Java 應(yīng)用程序的需要,因?yàn)樗哂锌蓴U(kuò)展性,而且對(duì)內(nèi)存/處理器的要求不高。

HSQLDB 是一類使用方便的 Java 開(kāi)發(fā)數(shù)據(jù)庫(kù),因?yàn)樗С?Structured Query Language(SQL)的豐富子集,并且 Java 程序員根本不需要在他們的開(kāi)發(fā)工作站上安裝嚴(yán)重消耗處理器、內(nèi)存和磁盤空間的數(shù)據(jù)庫(kù)服務(wù)器。它對(duì)于集成到 Eclipse IDE 中來(lái)說(shuō)是一種很理想的工具,既能為新手也能為經(jīng)驗(yàn)豐富的開(kāi)發(fā)人員提供有用的工具。

本文及同一系列的后續(xù)文章將向您展示如何構(gòu)建一組 Eclipse 插件,以將 HSQLDB 嵌入到 Eclipse Workbench 中。您將看到一個(gè)現(xiàn)實(shí)世界中的例子,目的是說(shuō)明如何在考慮到 API 和用戶接口(UI)的情況下開(kāi)發(fā)這類插件,以及如何評(píng)估可供選擇的方法以給用戶帶來(lái)所需要的功能。本文假定您使用的是 Eclipse SDK 分布,而不是 Platform Runtime-Binary 加上 JDT。但如果只是為了開(kāi)發(fā)常規(guī) Java 應(yīng)用程序,則后者更加適合。

在這個(gè)系列中,我們將根據(jù)“Levels of Integration” 一文中描述的基本原理(請(qǐng)參閱本文后面的 參考資料 中給出的鏈接),使用三個(gè)步驟創(chuàng)建并擴(kuò)展插件組:

  1. 運(yùn)行 Eclipse 中現(xiàn)有的工具, 以便從 Workbench 菜單容易地訪問(wèn)所需要的預(yù)先存在的工具。
  2. 探討如何使用 Eclipse 的其他功能來(lái)向預(yù)先存在的工具集添加值,從而提高 Java 開(kāi)發(fā)人員的生產(chǎn)力。
  3. 使用 SWT 來(lái)重寫工具,以實(shí)現(xiàn)與 Eclipse Workbench 的無(wú)縫集成。

了解 HSQLDB
您可以從 SourceForge(hsqldb.sourceforge.net; 請(qǐng)參閱 參考資料 中給出的鏈接)下載 HSQLDB,其中包括源代碼和文檔。這里注意不要與已經(jīng)被凍結(jié)的原始 SourceForge 項(xiàng)目( hsql.sourceforge.net )相混淆。

二進(jìn)制分布是一個(gè)標(biāo)準(zhǔn)的 ZIP 文件,而您要做的就是把這個(gè) ZIP 文件解壓縮到您硬盤上的某個(gè)地方。所有 HSQLDB 組件 ——數(shù)據(jù)庫(kù)引擎、服務(wù)器進(jìn)程、JDBC 驅(qū)動(dòng)程序、文檔以及一些實(shí)用工具——都放在一個(gè)單獨(dú)的 JAR 包中,這個(gè)包安裝在 lib/hsqldb.jar 中,大小在 260 KB 左右。運(yùn)行數(shù)據(jù)庫(kù)引擎只需 170 KB 的 RAM,這即使是 PDA (如 Sharp 生產(chǎn)的 Zaurus)也能夠滿足,而且包括源文件和文檔在內(nèi)的整個(gè)下載文件小到可以放到一張標(biāo)準(zhǔn)的 1.44 MB 軟盤上。

您可以從命令行啟動(dòng)數(shù)據(jù)庫(kù)服務(wù)器和實(shí)用工具,具體方法是調(diào)用像 org.hsqldb.Server 和 org.hsqldb.util.DatabaseManager 這樣的方便的類,這兩個(gè)類均可以接受為數(shù)不多的一組命令行選項(xiàng),如“-url”(用于遠(yuǎn)程連接)、“-database”(用于直接文件訪問(wèn))和“-user”。還有一種“-?”選項(xiàng)也可以被接受,其作用是提供關(guān)于有效命令行語(yǔ)法的幫助。

造成 HSQLDB 簡(jiǎn)單性的關(guān)鍵因素是SQL語(yǔ)句執(zhí)行的順序化。也就是說(shuō),盡管許多并發(fā)用戶可以連接到數(shù)據(jù)庫(kù)上(當(dāng)數(shù)據(jù)庫(kù)以服務(wù)器模式運(yùn)行時(shí)),但是所有 SQL 語(yǔ)句都被放到一個(gè)隊(duì)列中,然后一次執(zhí)行一條。因此不需要實(shí)現(xiàn)復(fù)雜的鎖定及同步算法。盡管如此,HSQLB 還是實(shí)現(xiàn)了 ACID(Atomicity, Consistency, Isolation, and Durability,即原子性、一致性、隔離性和持久性) 語(yǔ)義。換句話說(shuō),它是一個(gè)事務(wù)性的數(shù)據(jù)庫(kù),但僅僅處于讀未提交級(jí)別,還不具備事務(wù)隔離功能。HSQLDB 實(shí)際上是為嵌入式應(yīng)用程序而不是為共同數(shù)據(jù)中心而創(chuàng)建的。

如果您想要使用觸發(fā)器、聚合函數(shù)、外部聯(lián)接、視圖以及其他 SQL 功能,HSQLB 都可以滿足您的需要(大部分輕量級(jí)關(guān)系數(shù)據(jù)庫(kù)無(wú)法做到這一點(diǎn))。通過(guò)把您的 Java 類添加到 HSQLB 的類路徑中,您可以實(shí)現(xiàn)存儲(chǔ)過(guò)程。然后您發(fā)出一條 CREATE FUNCTION 語(yǔ)句即可大功告成。事實(shí)上,像 SQRT 和 ABS 之類的許多標(biāo)準(zhǔn) SQL 函數(shù)都被實(shí)現(xiàn)為到標(biāo)準(zhǔn) Java 類(比如 java.lang.Math)的直接映射 。

HSQLDB 的運(yùn)行模式
HSQLDB 引擎可以以多種模式運(yùn)行,以適應(yīng)不同的應(yīng)用場(chǎng)合:

駐留內(nèi)存模式
所有數(shù)據(jù)庫(kù)表和索引都放在內(nèi)存中,而且永遠(yuǎn)不會(huì)保存到磁盤上。在您發(fā)出為什么有人想要使用在應(yīng)用程序終止時(shí)就會(huì)丟失的數(shù)據(jù)庫(kù)這樣的疑問(wèn)之前,請(qǐng)先考慮為您可以使用標(biāo)準(zhǔn) SQL 語(yǔ)句進(jìn)行查詢、排序、分組和更新的數(shù)據(jù)庫(kù)數(shù)據(jù)擁有一塊本地高速緩存。

單機(jī)模式
應(yīng)用程序使用 JDBC 創(chuàng)建一個(gè)數(shù)據(jù)庫(kù)連接,并且 HSQLDB 引擎運(yùn)行在該應(yīng)用程序中,這時(shí)允許直接訪問(wèn)數(shù)據(jù)庫(kù)文件。不能存在并發(fā)用戶(應(yīng)用程序獨(dú)占地訪問(wèn)數(shù)據(jù)庫(kù)文件),但因此也沒(méi)有額外的線程和 TCP 連接開(kāi)銷。單機(jī)模式是許多嵌入式應(yīng)用程序的首選模式。

服務(wù)器模式
這是類似于其他關(guān)系數(shù)據(jù)庫(kù)的標(biāo)準(zhǔn)客戶機(jī)/服務(wù)器數(shù)據(jù)庫(kù)配置,允許出現(xiàn)使用 TCP 套接字的并發(fā)連接。大部分開(kāi)發(fā)人員喜歡這種模式,因?yàn)樗试S任何 JDBC 客戶機(jī)在主應(yīng)用程序仍在運(yùn)行的情況下連接并查詢/更新表。

Web服務(wù)器模式
HSQLDB 可以用作 Web 服務(wù)器,可以通過(guò) HTTP 接受 SQL 查詢;也能作為任何標(biāo)準(zhǔn) Web 容器中的 servlet 來(lái)運(yùn)行,可以穿過(guò)防火墻或者安裝在 Web 宿主服務(wù)上,而不用涉及到提供者支持小組(和昂貴的數(shù)據(jù)庫(kù)宿主選項(xiàng))。由于 HTTP 是無(wú)狀態(tài)的,所以本模式中不存在事務(wù)。

HSQLDB 數(shù)據(jù)庫(kù)文件結(jié)構(gòu)
HSQLDB 將所有表和索引數(shù)據(jù)放在內(nèi)存中, 將所有發(fā)出的 SQL 語(yǔ)句保存到一個(gè)名為 database.script 的文件中,該文件同時(shí)也充當(dāng)著事務(wù)日志的角色。初始化引擎之后,該文件被讀取,然后其中所有的 SQL 語(yǔ)句都被運(yùn)行,從而完成整個(gè)數(shù)據(jù)庫(kù)的重建。停機(jī)期間,HSQLDB 引擎將生成一個(gè)新的 database.script 文件,其中只包含最少的語(yǔ)句,目的是讓數(shù)據(jù)庫(kù)可以快速啟動(dòng)。

除了默認(rèn)放在內(nèi)存中的表之外,HSQLDB 還支持“緩存”表和“文本”表。所有緩存表的數(shù)據(jù)放在一個(gè)名為 database.data 的文件中,而文本表的數(shù)據(jù)則放在由 set table source 非標(biāo)準(zhǔn) SQL 語(yǔ)句命名的任意分隔文本文件(像 CSV 文件)中。緩存表支持比可用 RAM 大的數(shù)據(jù)集,而文本表則可以作為一種導(dǎo)入導(dǎo)出數(shù)據(jù)的方便手段。

除了 database.script 和 database.data 文件之外,任何 HSQLDB 數(shù)據(jù)庫(kù)還可能包含一個(gè) database.properties 文件,管理員可以在該文件中設(shè)置許多影響到 ANSI SQL 兼容性的參數(shù)。所有數(shù)據(jù)庫(kù)文件(文本表數(shù)據(jù)文件除外)必須放在同一個(gè)目錄中。

不存在創(chuàng)建 HSQLDB 數(shù)據(jù)庫(kù)的顯式方法。如果您要求引擎打開(kāi)一個(gè)目前不存在的數(shù)據(jù)庫(kù)文件(使用服務(wù)器模式的 -database 選項(xiàng)或單機(jī)模式的 JDBC URL),就會(huì)創(chuàng)建該文件及其所在目錄。所以,如果您肯定那個(gè)空數(shù)據(jù)庫(kù)中存在數(shù)據(jù),請(qǐng)檢查是否有錄入錯(cuò)誤。

現(xiàn)在讓我們開(kāi)始開(kāi)發(fā)插件!

創(chuàng)建 HSQLDB Eclipse 插件組
把現(xiàn)有的應(yīng)用程序放到 Eclipse 這樣功能強(qiáng)大的工具中去并不是一件容易的事情。值得慶幸的是,HSQLDB 和 Eclipse 均降低了上述任務(wù)的難度,因?yàn)?HSQLDB 本身可以嵌入到其他應(yīng)用程序中,而 Eclipse 提供了清晰而且易于理解的插件基礎(chǔ)設(shè)施以及用于創(chuàng)建新插件的健壯的開(kāi)發(fā)環(huán)境 PDE。即使您以前從未接觸過(guò) Eclipse 插件開(kāi)發(fā),PDE 也可以讓您很容易上手。請(qǐng)參閱本文后面 參考資料 部分中講述 Eclipse 基礎(chǔ)知識(shí)的文章。

這些指導(dǎo)性內(nèi)容假定您使用的是 Eclipse SDK 分布,而不是 Platform Runtime-Binary 加 JDT。但如果您只是要開(kāi)發(fā)常規(guī) Java 應(yīng)用程序,則后者更加適合。您可以使用您最喜歡的操作系統(tǒng),因?yàn)槲覀儗⒅皇褂?Java 代碼,而根本不會(huì)用到本機(jī)代碼。

為了創(chuàng)建有用的插件組并盡可能地少書寫代碼,我們將從最不費(fèi)力的工作開(kāi)始。稍后,在這個(gè)系列的下一部分內(nèi)容中,我們將看到如何利用 Eclipse 功能為 HSQLDB 提供增值。

在本文中,我們將集中講述我們代碼的下列功能:

  1. 以服務(wù)器模式啟動(dòng) HSQLDB 引擎,這樣用戶應(yīng)用程序和 SQL 控制臺(tái)(像 HSQLDB 自帶的 DatabaseManager 實(shí)用工具)都可以運(yùn)行 SQL 語(yǔ)句。
  2. 完全停止 HSQLDB 服務(wù)器。
  3. 調(diào)用 DatabaseManager 實(shí)用工具,這樣開(kāi)發(fā)人員可以從 Workbench 交互式地輸入 SQL 語(yǔ)句。
  4. 使用 HSQLDB ScriptTool 實(shí)用工具運(yùn)行 SQL 腳本文件。這些年來(lái),為了創(chuàng)建數(shù)據(jù)庫(kù)表和插入測(cè)試數(shù)據(jù),我已經(jīng)在我的項(xiàng)目文件夾中放入了大量 *.sql 文件,而且長(zhǎng)久以來(lái),我一直希望能夠有一種容易的方式來(lái)運(yùn)行它們。
  5. 配置 HSQLDB 連接屬性,比如 TCP 端口和管理員密碼。

如何才能使得這些函數(shù)可以為 Workbench 所用呢?完成前面三步最容易的方式是提供一個(gè) actionSet,它被添加到新的頂級(jí)菜單中并且可從它自己的工具欄訪問(wèn)。運(yùn)行 SQL 腳本文件的操作必須只被綁定到擴(kuò)展名為“*.sql” 的文件上,所以這將是一個(gè) objectContribution, 它被 Workbench 添加到顯示這些文件的任意視圖上的彈出式菜單中。最后,連接參數(shù)要能很好地符合插件的參數(shù)選擇頁(yè)面,從 Workbench Window 菜單中可以訪問(wèn)這個(gè)頁(yè)面。

圖 1 顯示了新的菜單和工具欄,而圖 2 顯示了 Navigator 視圖的彈出式菜單中的新項(xiàng),圖 3 則顯示了屬性頁(yè)面,這樣您就可以看到我們的插件組的第一個(gè)版本是什么樣子。

圖 1.HSQLDB 菜單和相關(guān)工具欄

圖 2. 添加到 *.sql 文件的彈出式菜單項(xiàng)

圖 3. HSQLDB 連接屬性

把 HSQLDB 變成一個(gè) Eclipse 插件
構(gòu)建我們的插件組的第一步是把 HSQLDB 本身包裝成一個(gè) Eclipse 插件。這個(gè)插件將只包含 hsqldb.jar 和 Workbench 要求的必要的plugin.xml 文件。如果您已經(jīng)瀏覽了標(biāo)準(zhǔn) Eclipse 插件目錄,那么您可能已經(jīng)發(fā)現(xiàn),JUnit, Xerces, Tomcat,以及其他常見(jiàn)的 Java 包被隔離在它們各自的插件中,未曾改變,而且所有的 Eclipse 細(xì)節(jié)都被封裝在其他插件中。這種劃分方式使得這些第三方工具易于更新,而不一定要求改變 Eclipse 本身。另外一個(gè)好處就是與許多插件共享這些常見(jiàn)的庫(kù)很容易。

打開(kāi)您的 Eclipse SDK 安裝,并創(chuàng)建一個(gè)新的插件項(xiàng)目;將其命名為 hsqldb.core(我知道推薦使用的名稱是 org.hsqldb.core,但是我不愿意假裝使用了 HSQLDB 名稱空間?;蛟S HSQLDB 開(kāi)發(fā)人員閱讀至此會(huì)贊同這個(gè)想法,并推薦它為“正式的”Eclipse 集成插件;這樣的話該名稱極有可能被改掉)。確保選中的是“Empty Plugin” 選項(xiàng), 而不是任何插件模板;否則,您將得到一個(gè)毫無(wú)用處的頂級(jí)插件類,如果您創(chuàng)建了該類之后希望刪掉它,那么它可以被安全地刪除。把 hsqldb.jar 從您的 HSQLDB 安裝拷貝到項(xiàng)目目錄中,并將其添加到項(xiàng)目的 Runtime。您可以使用 PDE Plugin Manifest Editor或者簡(jiǎn)單地拷貝清單 1 中的內(nèi)容來(lái)完成這項(xiàng)工作。

清單 1. 用于 hsqldb.core 插件的 plugin.xml 清單文件
<?xml version="1.0" encoding="UTF-8"?><plugin   id="hsqldb.core"   name="Hsqldb Core Plug-in"   version="0.0.1"   provider-name="Fernando Lozano (www.lozano.eti.br)">   <runtime>      <library name="hsqldb.jar">         <export name="*"/>      </library>   </runtime></plugin>

添加 Workbench 擴(kuò)展
接下來(lái),創(chuàng)建名為 hsqldb.ui 的第二個(gè)插件項(xiàng)目。這個(gè)項(xiàng)目將為插件組的第一修訂本包含所有的 Eclipse 擴(kuò)展:包含三個(gè)操作的一個(gè)操作集、與 *.sql 文件相關(guān)的對(duì)象作用,以及一個(gè)屬性頁(yè)面。將其主類(Plugin 類)命名為 PluginUi,并接受包的默認(rèn)名稱 hsqldb.ui

使用 Plugin Manifest Editor 打開(kāi) plugin.xml 文件,并選擇 Extensions 選項(xiàng)卡。將所分配菜單更名為 HSQLDB 并改變示范操作,使其顯示標(biāo)簽“Runs HSQLDB Database Manager”。向帶有標(biāo)簽“Stops HSQLDB database server” 和“Starts HSQLDB database server”的同一個(gè) actionSet 添加另外兩個(gè)操作,并為每個(gè)操作提供惟一的操作 ID 和實(shí)現(xiàn)類。請(qǐng)注意,上述操作將以與創(chuàng)建時(shí)相反的順序(即與在插件清單文件中出現(xiàn)的順序相反)顯示在菜單和工具欄中。

單擊 Add 按鈕,從而使用 Extension 模板、彈出式菜單和屬性頁(yè)面添加兩個(gè)新的擴(kuò)展。彈出式菜單應(yīng)該與 *.sql 文件模式相關(guān)聯(lián),但是屬性頁(yè)面字段應(yīng)該通過(guò)編程進(jìn)行設(shè)置。

圖 4 顯示了 Plugin Manifest 編輯器的最終外觀,并顯示了所有的插件擴(kuò)展;圖 5 顯示了對(duì)象分配的屬性(注意針對(duì) *.sql 文件的過(guò)濾器),而圖 6 顯示了文件資源彈出式菜單中的“Run SQL Script”操作的屬性。 圖標(biāo)在本文的源代碼中給出(請(qǐng)參閱 參考資料),但是我敢肯定您認(rèn)識(shí)有比這畫得更好的圖形專家!

圖 4. 清單編輯器上的 HSQLDB 操作

圖 5. 清單編輯器上的 HSQLDB 對(duì)象分配

圖 6. 清單編輯器上的 Run SQL Script 操作

在能夠給我們的操作添加代碼之前,我們需要將這種 UI 插件與相應(yīng)的核心插件區(qū)別開(kāi)來(lái),否則它將不能訪問(wèn) HSQLDB 類。轉(zhuǎn)到 Plugin Manifest Editor 上的“Dependencies” 頁(yè)面,并添加一個(gè)插件依賴性,如圖 7 所示。某些依賴性,像“eclipse.ui”,由 PDE 自動(dòng)進(jìn)行配置,而其他依賴性,像 “org.eclipse.debug.core”,則在對(duì)操作進(jìn)行編碼時(shí)添加。如果您情愿直接編輯 XML 代碼,清單 2 顯示了 hsqldb.ui 插件的完整 plugin.xml 文件。

要完成插件的安裝工作,請(qǐng)?zhí)砑右粋€(gè)新類到 hsqldb.ui 包中,然后將其命名為 HsqldbUtil。這個(gè)類將包含所有直接處理 HSQLDB 的代碼,并使分配的擴(kuò)展代碼保持簡(jiǎn)單。

圖 7. hsqldb.ui 插件依賴性

清單 2. hsqldb.ui 插件的 plugin.xml 清單文件
<?xml version="1.0" encoding="UTF-8"?><plugin   id="hsqldb.ui"   name="Hsqldb Ui Plug-in"   version="0.0.1"   provider-name="Fernando Lozano (www.lozano.eti.br)"   class="hsqldb.ui.PluginUi">   <runtime>      <library name="ui.jar"/>   </runtime>   <requires>      <import plugin="org.eclipse.core.resources"/>      <import plugin="org.eclipse.ui"/>      <import plugin="hsqldb.core"/>      <import plugin="org.eclipse.debug.core"/>      <import plugin="org.eclipse.jdt.launching"/>      <import plugin="org.eclipse.debug.ui"/>   </requires>   <extension         point="org.eclipse.ui.actionSets">      <actionSet            label="Hsqldb"            visible="true"            id="hsqldb.ui.actionSet">         <menu               label="Hsql&db"               id="hsqldbMenu">            <separator                  name="dbServerGroup">            </separator>         </menu>         <action               label="Run Hsql &Database Manager"               icon="icons/dbman.gif"               tooltip="Runs the Hsql database manager"               class="hsqldb.ui.actions.HsqldbDatabaseManagerAction"               menubarPath="hsqldbMenu/dbServerGroup"               toolbarPath="dbServerGroup"               id="hsqldb.ui.actions.HsqldbDatabaseManagerAction">            <enablement>               <pluginState                     value="activated"                     id="hsqldb.ui">               </pluginState>            </enablement>         </action>         <action               label="S&top Hsqldb"               icon="icons/stop.gif"               tooltip="Stops the Hsql database server"               class="hsqldb.ui.actions.HsqldbStopAction"               menubarPath="hsqldbMenu/dbServerGroup"               toolbarPath="dbServerGroup"               id="hsqldb.ui.actions.HsqldbStopAction">            <enablement>               <pluginState                     value="activated"                     id="hsqldb.ui">               </pluginState>            </enablement>         </action>         <action               label="&Start Hsqldb"               icon="icons/start.gif"               tooltip="Starts the Hsql database server"               class="hsqldb.ui.actions.HsqldbStartAction"               menubarPath="hsqldbMenu/dbServerGroup"               toolbarPath="dbServerGroup"               id="hsqldb.ui.actions.HsqldbStartAction">            <enablement>               <pluginState                     value="installed"                     id="hsqldb.ui">               </pluginState>            </enablement>         </action>      </actionSet>   </extension>   <extension         point="org.eclipse.ui.perspectiveExtensions">      <perspectiveExtension            targetID="org.eclipse.ui.resourcePerspective">         <actionSet               id="hsqldb.ui.actionSet">         </actionSet>      </perspectiveExtension>   </extension>   <extension         point="org.eclipse.ui.popupMenus">      <objectContribution            objectClass="org.eclipse.core.resources.IFile"            nameFilter="*.sql"            id="hsqldb.ui.SQLScriptFiles">         <action               label="Run SQL Script"               class="hsqldb.ui.popup.actions.HsqldbRunScript"               menubarPath="additions"               enablesFor="1"               id="hsqldb.ui.HsqldbRunScript">            <enablement>               <pluginState                     value="activated"                     id="hsqldb.ui">               </pluginState>            </enablement>         </action>      </objectContribution>   </extension>   <extension         id="hsqldb.ui.preferences"         point="org.eclipse.ui.preferencePages">      <page            name="HSQLDB Server"            class="hsqldb.ui.preferences.HSQLDBPreferencePage"            id="hsqldb.ui.preferences.HSQLDBPreferencePage">      </page>   </extension></plugin>

啟動(dòng) HSQLDB
使用類 org.hsqldb.Server 以服務(wù)器模式啟動(dòng) HSQLDB 引擎相當(dāng)容易。以HSQLDB 數(shù)據(jù)庫(kù)名稱(路徑+數(shù)據(jù)庫(kù)文件的基本名稱)、用于監(jiān)聽(tīng)連接請(qǐng)求的TCP 端口以及一個(gè)用于判別停機(jī)時(shí)它是否應(yīng)該調(diào)用 System.exit() 的標(biāo)志作為命令行參數(shù)。下面的命令行是運(yùn)行 HSQLDB 時(shí)的典型情況

java -cp /opt/hsqldb/hsqldb.jar org.hsqldb.Server -database /tmp/bd -port 9001 -system_exit=true

上面這一行命令創(chuàng)建了 /tmp/db.script、 /tmp/db.properties 和 /tmp/db.data 三個(gè)數(shù)據(jù)庫(kù)文件。

我們可以使用 Server 類主方法并傳遞一個(gè)字符串?dāng)?shù)組作為其參數(shù)。但是我們必須在一個(gè)新線程中做這項(xiàng)工作;否則,我們將鎖定整個(gè) Workbench。惟一的插件類維持一個(gè)到這個(gè)線程的引用,這樣它就能夠在連接到某臺(tái)服務(wù)器之前檢查該服務(wù)器是否已經(jīng)啟動(dòng),并且還可以檢查它是否正在運(yùn)行,因?yàn)槿魏慰蛻魴C(jī)均可連接到這臺(tái)服務(wù)器上,然后提交 SHUTDOWN 語(yǔ)句終止它的運(yùn)行。

清單 3 中的代碼顯示了 hsqldb.ui.actions.HsqldbStartAction 的 run 方法,而清單 4 顯示了 hsqldb.ui.HsqldbUtil startHsqldb 的代碼,這些代碼實(shí)際上啟動(dòng)了服務(wù)器。稍后我們將討論管理用戶反饋的技術(shù)。

上述代碼最有趣的部分是我們?nèi)绾握业揭粋€(gè)位置來(lái)放置數(shù)據(jù)庫(kù)文件。一個(gè)名為 .hsqldb 的項(xiàng)目如果不存在就會(huì)被創(chuàng)建,而在該項(xiàng)目中引擎將查找 database.script 和其他數(shù)據(jù)庫(kù)文件。

Eclipse 在一個(gè)可以保存任何配置文件的標(biāo)準(zhǔn)目錄中提供了所有插件??梢酝ㄟ^(guò)調(diào)用 plugin.getStateLocation 來(lái)得到這樣一個(gè)目錄,但是我并不覺(jué)得數(shù)據(jù)庫(kù)文件實(shí)際上是插件配置文件。它們看起來(lái)更像是用戶數(shù)據(jù)文件,而且照此說(shuō)來(lái),它們應(yīng)該位于開(kāi)發(fā)人員工作區(qū)中的項(xiàng)目?jī)?nèi)。

清單 3. 以服務(wù)器模式啟動(dòng) HSQLDB 的操作,來(lái)自 hsqldb.ui.actions.HsqldbStartAction
    public void run(IAction action) {        // check a database was really started by the plug-in        PluginUi plugin = PluginUi.getDefault();        if (plugin.getHsqldbServer() != null) {            ((ApplicationWindow)window).setStatus(                "HSQLDB Server already running.");        }        else {            Cursor waitCursor = new Cursor(window.getShell().getDisplay(),                SWT.CURSOR_WAIT);            window.getShell().setCursor(waitCursor);            try {                HsqldbUtil.startHsqldb();                ((ApplicationWindow)window).setStatus("HSQLDB Server started.");            }            catch (CoreException e) {                MessageDialog.openError(window.getShell(),                    "Hsqldb Plugin",                    "Could not create HSQLDB database project.");                e.printStackTrace(System.err);            }            finally {                window.getShell().setCursor(null);                waitCursor.dispose();            }        }    }            


清單 4. 真正以服務(wù)器模式啟動(dòng) HSQLDB 的代碼,來(lái)自 hsqldb.ui.HsqldbUtil
    public static void startHsqldb() throws CoreException {        PluginUi plugin = PluginUi.getDefault();        // finds project local path for database files        IWorkspaceRoot root = PluginUi.getWorkspace().getRoot();        IProject hsqldbProject = root.getProject(".hsqldb");        if (!hsqldbProject.exists()) {            hsqldbProject.create(null);        }        hsqldbProject.open(null);        IPath dbPath = hsqldbProject.getLocation();        final String database = dbPath.toString() + "/database";        // starts a new thread to run the database server        final HsqldbParams params = getConnectionParams();        Thread server = new Thread() {            public void run() {                String[] args = { "-database", database,                    "-port", String.valueOf(params.port),                    "-no_system_exit", "true" };                Server.main(args);            }        };        plugin.setHsqldbServer(server);        server.start();    }            

停止 HSQLDB
要停止 HSQLDB 服務(wù)器,所需要的只是一個(gè)作為 SQL 語(yǔ)句的 SHUTDOWN 命令。如清單 5 所示,來(lái)自 hsqldb.ui.HsqldbUtil 的 stopHsqldb 方法完成了這個(gè)任務(wù)。相應(yīng)操作對(duì)應(yīng)的代碼和啟動(dòng)服務(wù)器時(shí)描述的代碼幾乎完全相同,所以這里沒(méi)有列出。清單 5 中拋出了 ClassNotFoundException (來(lái)自 Class.forName)和 SQLException 異常,所以操作代碼能夠提供足夠的反饋給用戶。

清單 5. 用于停止 HSQLDB 服務(wù)器的代碼
    public static void stopHsqldb() throws ClassNotFoundException,                SQLException {        PluginUi plugin = PluginUi.getDefault();        HsqldbParams params = getConnectionParams();        // submits the SHUTDOWN statement        Class.forName("org.hsqldb.jdbcDriver");        String url = "jdbc:hsqldb:hsql://127.0.0.1:" + params.port;        Connection con = DriverManager.getConnection(url, params.user,            params.passwd);        String sql = "SHUTDOWN";        Statement stmt = con.createStatement();        stmt.executeUpdate(sql);        stmt.close();        // no need to close a dead connection!        plugin.setHsqldbServer(null);    }            

運(yùn)行 HSQL Database Manager
運(yùn)行 HSQLDB 中的 Database Manager 實(shí)用工具比運(yùn)行服務(wù)器本身還要棘手一點(diǎn)。因?yàn)榉?wù)器被創(chuàng)建為可以嵌入到其他應(yīng)用程序中,而上述實(shí)用工具則計(jì)劃作為單機(jī)應(yīng)用程序或 Java applet 來(lái)運(yùn)行。盡管兩種模式均接受命令行參數(shù)(或 applet 參數(shù)),我們還是不希望僅僅為了得到一個(gè)用戶可以在其中輸入 SQL 語(yǔ)句的窗口,就需要額外增加啟動(dòng)一個(gè)新的 Java VM 的開(kāi)銷。直接調(diào)用類 static void main(String[] args) 不會(huì)如預(yù)期一樣工作,因?yàn)樗鼘⒄{(diào)用 System.exit() 終止 Workbench。

瀏覽 org.hsqldb.util.DatabaseManager 源代碼之后,我們發(fā)現(xiàn)實(shí)例化和初始化一個(gè) JDBC 連接很容易,但是所必需的方法不是公共的。所以我們可以在 HSQLDB 源樹(shù)中創(chuàng)建一個(gè)名為 org.hsqldb.util.PatchedDatabaseManager 的類,然后使用所提供的 Ant 構(gòu)建腳本生成一個(gè)新的 hsqldb.jar,其中包含我們打過(guò)補(bǔ)丁的 Database Manager。只有兩個(gè)方法的可見(jiàn)性需要改為 public:void main() 和 void connect(Connection con)。清單 6 顯示了插件如何使用這兩個(gè)方法運(yùn)行補(bǔ)丁類。

Database Manager 是一個(gè) AWT 應(yīng)用程序 (參見(jiàn)圖 8), 它將創(chuàng)建自己的事件線程(獨(dú)立于 Eclipse SWT 線程),并且在調(diào)用 System.exit() 關(guān)閉 Workbench 之后就會(huì)終止。可以運(yùn)行該實(shí)用程序的多個(gè)實(shí)例而不會(huì)導(dǎo)致問(wèn)題,除非沒(méi)有 Workbench 窗口可以依靠。對(duì)于我們的 HSQLDB 插件的第一修訂本來(lái)說(shuō),這很好,但是在這個(gè)系列結(jié)束之前,我們必須將其改為一個(gè) SWT 應(yīng)用程序,就像 Eclipse 視圖那樣嵌入其中。

圖 8. HSQLDB Database Manager

清單 6. 啟動(dòng)打過(guò)補(bǔ)丁的 Database Manager 實(shí)用程序
    public static void runDatabaseManager() throws ClassNotFoundException,                SQLException {        PluginUi plugin = PluginUi.getDefault();        HsqldbParams params = getConnectionParams();        // creates a connection to the internal database         String url = "jdbc:hsqldb:hsql://127.0.0.1:" + params.port;        Class.forName("org.hsqldb.jdbcDriver");        Connection con = DriverManager.getConnection(url, params.user,            params.passwd);        if (con != null) {            // needed to patch DatabaseManager so it could            // be initialized and use the supplied connection             PatchedDatabaseManager dm = new PatchedDatabaseManager();            dm.main();            dm.connect(con);        }            }            

運(yùn)行 SQL 腳本
HSQLDB Script Tool 讀取純文本文件,并依靠一個(gè)給定的 JDBC URL 執(zhí)行文件中包含的 SQL 語(yǔ)句。SQL 語(yǔ)句批處理由 go 命令進(jìn)行分隔, 而且腳本也可以使用 print 命令在腳本中書寫消息。

熟悉其他數(shù)據(jù)庫(kù)系統(tǒng)的開(kāi)發(fā)人員創(chuàng)建的腳本可能僅僅包含由分號(hào)(;)分隔開(kāi)的 SQL 語(yǔ)句,但是這使得 HSQLDB 可以在單個(gè)批處理中運(yùn)行所有腳本,而只返回最后一條語(yǔ)句的結(jié)果。您需要在 SQL 語(yǔ)句之間包含 go 命令,以接受每一條語(yǔ)句的結(jié)果。

讓用戶瀏覽腳本結(jié)果最容易的方法是把它們放入一個(gè)控制臺(tái)視圖中,就像從 Workbench 調(diào)用的 Java 應(yīng)用程序和 Ant 構(gòu)建腳本一樣。這要求創(chuàng)建一個(gè) Java Launch 配置,而該配置又會(huì)創(chuàng)建另一個(gè) Java VM。因?yàn)?SQL 腳本存在時(shí)間較短,所以我們可以認(rèn)為其開(kāi)銷是可以接受的,而且如果您認(rèn)為 Database Manager 也應(yīng)該在它自己的 VM 中被調(diào)用,您可以使用清單 7中給出的 runScriptTool 方法作為一個(gè)例子。

清單 7 是本文(這個(gè)系列的第一篇)中最長(zhǎng)的清單,其中大部分內(nèi)容是關(guān)于建立一個(gè)包含正確的 JRE 自舉類(必須被顯式設(shè)定)和 hsqldb.core 插件中的 hsqldb.jar 的類路徑。查閱本文末尾的 參考資料 部分可以獲得更多關(guān)于運(yùn)行由 Eclipse 提供的框架以及由 JDT 提供的擴(kuò)展的信息。

對(duì)于熟悉其他 GUI 工具包的開(kāi)發(fā)人員來(lái)說(shuō),如何獲取被選中 SQL 腳本文件的路徑并不是一件顯而易見(jiàn)的事情。IObjectActionDelegate 接口由擴(kuò)展資源彈出式菜單的對(duì)象分配實(shí)現(xiàn),調(diào)用它的 run 方法后收到的只是一個(gè)到操作本身的引用,就像一個(gè)頂級(jí)菜單操作一樣,沒(méi)有包含與被選中的資源或始發(fā)控件有關(guān)的信息。資源引用實(shí)際上被提供給 selectionChanged 方法,而且必須被保存為一個(gè)實(shí)例變量,以備稍后使用——請(qǐng)參見(jiàn)清單 8。

清單 7. 運(yùn)行 HSQLDB Script Tool
    public static void runScriptTool(IFile currentScript) throws CoreException {        PluginUi plugin = PluginUi.getDefault();        // destroys any preexisting configuration and create a new one        ILaunchManager manager = DebugPlugin.getDefault().getLaunchManager();        ILaunchConfigurationType type = manager.getLaunchConfigurationType(            IJavaLaunchConfigurationConstants.ID_JAVA_APPLICATION);        ILaunchConfiguration[] configurations = manager.getLaunchConfigurations(            type);        for (int i = 0; i > configurations.length; i++) {            ILaunchConfiguration config = configurations[i];            if (config.getName().equals("SQL Script")) {                config.delete();            }        }        ILaunchConfigurationWorkingCopy wc = type.newInstance(null,            "SQL Script");        // constructs a classpath from the default JRE...        IPath systemLibs = new Path(JavaRuntime.JRE_CONTAINER);        IRuntimeClasspathEntry systemLibsEntry =            JavaRuntime.newRuntimeContainerClasspathEntry(                systemLibs, IRuntimeClasspathEntry.STANDARD_CLASSES);        systemLibsEntry.setClasspathProperty(            IRuntimeClasspathEntry.BOOTSTRAP_CLASSES);        //... plus hsqldb.core plugin        IPluginRegistry registry = Platform.getPluginRegistry();        IPluginDescriptor hsqldbCore = registry.getPluginDescriptor(            "hsqldb.core");        ILibrary[] libs = hsqldbCore.getRuntimeLibraries();        String installDir = hsqldbCore.getInstallURL().toExternalForm();        URL hsqldbJar = null;        try {            hsqldbJar = Platform.asLocalURL(new URL(installDir +                libs[0].getPath()));        }        catch(Exception e) {            // ignore URL exceptions        }        IRuntimeClasspathEntry hsqldbEntry =            JavaRuntime.newArchiveRuntimeClasspathEntry(new Path(                hsqldbJar.getPath()));        hsqldbEntry.setClasspathProperty(IRuntimeClasspathEntry.USER_CLASSES);        // sets the launch configuration classpath        List classpath = new ArrayList();        classpath.add(systemLibsEntry.getMemento());        classpath.add(hsqldbEntry.getMemento());        wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_CLASSPATH,            classpath);        wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_DEFAULT_CLASSPATH,            false);        // current directory should be the script container        IPath dir = currentScript.getParent().getLocation();        wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_WORKING_DIRECTORY,            dir.toString());        // gets the path for the selected SQL script file        wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_MAIN_TYPE_NAME,            "org.hsqldb.util.ScriptTool");        // builds ScriptTool command line        HsqldbParams params = getConnectionParams();        String args = "-driver org.hsqldb.jdbcDriver " +            "-url jdbc:hsqldb:hsql: " +            "-database //127.0.0.1:" + params.port + " " +            "-user " + params.user + " " +            "-script " + currentScript.getName();        if (params.passwd.length() > 0)            args += "-password " + params.passwd + " ";        wc.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROGRAM_ARGUMENTS,            args);        // saves the new config and launches it        ILaunchConfiguration config = wc.doSave();        DebugUITools.launch(config, ILaunchManager.RUN_MODE);    }            


清單 8. 如何得到要執(zhí)行的 SQL Script,來(lái)自 hsqldb.ui.popup.actions.HsqldbRunScript
    public void selectionChanged(IAction action, ISelection selection) {        currentScript = null;        if (selection != null) {            if (selection instanceof IStructuredSelection) {                IStructuredSelection ss = (IStructuredSelection)selection;                // as this action is enabled only for a single selection,                // it‘s enough to get the first element                Object obj = ss.getFirstElement();                if (obj instanceof IFile) {                    currentScript = (IFile)obj;                }            }        }    }            

HSQLDB 屬性
現(xiàn)在我們的插件的大部分功能都已經(jīng)就位,僅僅缺少配置服務(wù)器連接參數(shù)的方法:它監(jiān)聽(tīng)的 TCP 端口、管理員用戶名稱以及管理員用戶密碼。TCP 端口可能必須要改變,從而避免與安裝在開(kāi)發(fā)人員機(jī)器上的其他應(yīng)用程序發(fā)生沖突;用戶及密碼可以從任意客戶機(jī)上使用 SQL 語(yǔ)句來(lái)改變。這些參數(shù)可以被放入一個(gè)類似 C 結(jié)構(gòu)(或類似 pascal 記錄)的名為 HsqldbParams 的類中, 如清單 9 所示。它也聲明了由 Plugin Preferences Store 和 Property Page 使用的常量來(lái)取得并保存參數(shù)(引用)值。

清單 9. 用于 HSQLDB 服務(wù)器連接參數(shù)的參數(shù)選擇結(jié)構(gòu)
package hsqldb.ui;public class HsqldbParams {    // preference names for the plugin    public static final String P_PORT = "serverPort";    public static final String P_USER = "serverUser";    public static final String P_PASSWD = "serverPasswd";        public int port = 9001;    public String user = "sa";    public String passwd = "";}            

不幸的是,由 PDE New Extension Wizard 生成的參數(shù)選擇頁(yè)面類起了一點(diǎn)誤導(dǎo)作用,它包括一個(gè)方法,用于設(shè)置不會(huì)被插件參數(shù)選擇存儲(chǔ)使用的默認(rèn)參數(shù)選擇值。初始化默認(rèn)值的正確位置應(yīng)該是主要的 Plugin 類本身。否則,所有使用默認(rèn)值的參數(shù)選擇將被參數(shù)選擇存儲(chǔ)返回為 0 或 null。

因此,參數(shù)選擇頁(yè)面類變得簡(jiǎn)單多了,如清單 10 所示,并且方法 initializeDefaultPreferences 被添加到 PluginUi 類中,如清單 11 所示。注意,每個(gè)參數(shù)選擇的默認(rèn)值均由參數(shù)選擇結(jié)構(gòu)定義,而不是由插件類或參數(shù)選擇頁(yè)面類來(lái)定義。

清單 10. HSQLDB 插件的參數(shù)選擇頁(yè)面
public class HSQLDBPreferencePage    extends FieldEditorPreferencePage    implements IWorkbenchPreferencePage {    public HSQLDBPreferencePage() {        super(GRID);        setPreferenceStore(PluginUi.getDefault().getPreferenceStore());        setDescription("Connection parameters for the embedded HSQLDB server");    }        public void createFieldEditors() {        addField(new IntegerFieldEditor(HsqldbParams.P_PORT,                 "&TCP Port:",                getFieldEditorParent()));        addField(new StringFieldEditor(HsqldbParams.P_USER,                "Administrator &User:",                getFieldEditorParent()));        addField(new StringFieldEditor(HsqldbParams.P_PASSWD,                "Administrator &Password:",                getFieldEditorParent()));    }        public void init(IWorkbench Workbench) {    }}            


清單 11. 來(lái)自生成的 Plugin 類的已更改方法
    public void shutdown() throws CoreException {        // shuts down the server if running        Thread server = getHsqldbServer();        if (server != null && server.isAlive()) {        		 try {                HsqldbUtil.stopHsqldb();            }            catch (Exception e) {                e.printStackTrace();            }        }        super.shutdown();    }    protected void initializeDefaultPreferences(IPreferenceStore store) {        super.initializeDefaultPreferences(store);        HsqldbParams params = new HsqldbParams();        store.setDefault(HsqldbParams.P_PORT, params.port);        store.setDefault(HsqldbParams.P_USER, params.user);        store.setDefault(HsqldbParams.P_PASSWD, params.passwd);    }               

提供反饋給用戶
由于插件組既沒(méi)有提供視圖也沒(méi)有提供編輯器給 Workbench,所以只有幾種有限的方法可以為用戶提供操作方面的反饋。我們可以使用 Workbench 窗口的狀態(tài)欄以及一個(gè) SWT MessageDialog ,這樣用戶就不會(huì)錯(cuò)過(guò)重要的事件(通常是錯(cuò)誤)。

Workbench 操作的可見(jiàn)性及實(shí)現(xiàn)模型主要集中在工作區(qū)資源選擇方面,但是大部分插件操作(啟動(dòng)及停止 HSQLDB 服務(wù)器,還包括啟動(dòng)Database Manager)并不依賴于資源選擇,而是依賴于服務(wù)器是否正在運(yùn)行——這個(gè)條件必須由每個(gè)操作的 run 方法進(jìn)行顯式檢查。

警告: 插件類與操作類直到絕對(duì)需要降低 Workbench 對(duì)內(nèi)存的需求時(shí)才會(huì)被初始化。清單文件內(nèi)容被用于表示菜單選擇和啟用/禁用它們, 但是由于 HSQLDB 服務(wù)器不屬于工作區(qū)資源,所以它不能啟用操作。正如清單 2 中的清單代碼所描述的那樣,您可以(請(qǐng)參見(jiàn) <enablement> 元素)在激活插件的基礎(chǔ)之上啟用/禁用一項(xiàng)操作,所以在啟動(dòng)時(shí)只有“Start HSQLDB server” 操作可以被啟用,但在這之后如果服務(wù)器已經(jīng)處于運(yùn)行狀態(tài),則不存在容易的方法來(lái)禁用這項(xiàng)操作以及當(dāng)其停止后重新啟用它。

注意,用于在 Workbench 窗口上放置一個(gè)沙漏狀光標(biāo)的代碼,以及用于設(shè)置狀態(tài)欄消息的代碼不易在 PDE 文檔或 Eclipse.org 文章中找到。

最后一步
插件類重寫了 shutdown 方法,所以當(dāng) Workbench 被關(guān)閉時(shí)它能夠干凈利落地關(guān)閉服務(wù)器,這時(shí)我們就結(jié)束了整個(gè)過(guò)程。當(dāng)然,要得到一個(gè)完整的插件組還需要完成這里沒(méi)有講到的額外任務(wù),比如:

  • 把兩個(gè)插件包裝成一個(gè)功能,這樣它們就可以作為一個(gè)單元進(jìn)行安裝、移除、啟用和禁用。
  • 為插件本身提供幫助文檔,并使用一種適合于 Workbench 幫助管理器的格式將 HSQLDB 文檔包含在其內(nèi)。

結(jié)束語(yǔ)
本文介紹如何創(chuàng)建可將 HSQLDB 數(shù)據(jù)庫(kù)引入到 Eclipse Workbench 中的一組插件,以增添啟動(dòng)和停止服務(wù)器以及運(yùn)行 SQL 語(yǔ)句和腳本的能力。我們已經(jīng)了解到 PDE 是如何使這類插件的創(chuàng)建工作變得容易的,即對(duì)大多數(shù)任務(wù)使用向?qū)Ш途庉嬈鳎艚o開(kāi)發(fā)人員的任務(wù)不過(guò)是創(chuàng)建真正實(shí)現(xiàn)所需功能的代碼。

在這個(gè)系列的下一個(gè)部分中,您將了解到如何利用 Workbench 的功能向 HSQLDB 開(kāi)發(fā)添加值,而不僅僅是簡(jiǎn)單地運(yùn)行預(yù)先存在的工具。

參考資料


本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)。
打開(kāi)APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
org.eclipse.ui.plugin.AbstractUIPlugin 插件
開(kāi)發(fā) Eclipse 插件
CSDN技術(shù)中心 教您如何創(chuàng)建、調(diào)試和安裝Eclipse插件
eclipse入門:開(kāi)發(fā)eclipse插件
Eclipse 體系結(jié)構(gòu)綜述(二)
Rich Ajax Platform,第 1 部分: 簡(jiǎn)介
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服