用 Apache 和 Subversion 搭建安全的版本控制環境

用 Apache 和 Subversion 搭建安全的版本控制環境

做爲新一代的開源版本控制工具,Subversion 以其目錄版本化、原子提交、版本化的元數據、更加有效的分支和標籤等優良特性,正逐漸受到開源軟件社區的重視,並有望取代 CVS,成爲開源軟件開發中版本控制的首選系統。在服務端,Subversion 最大的獨特之處,在於它能夠經過一個擴展模塊與 Apache 的HTTP 服務器相結合,實現不少高級的管理功能和安全特性。與 CVS 相比,Subversion 實現了更加先進和安全的用戶認證功能。在Apache 的支持下,用戶能夠經過 HTTP 協議訪問版本庫,管理員能夠對用戶訪問 HTTP 的權限作出具體的設置,同時 Subversion還能夠得到 SSL傳輸加密,用戶數據加密,以及目錄級的訪問控制等特性。java

本文將在服務器端配置工做的角度,結合做者在實際開發工做當中的配置實例,介紹 Subversion 服務器端的基本配置和管理,以及如何將 Subversion 與 Apache 結合,實現一些高級管理功能。mysql

Subversion簡介

在開源軟件的開發過程中,因爲開發方式自由和開發人員分散這些特性,版本控制問題一直是關係到項目成敗的重要問題。沒有版本控制系統的支持,開源軟件的開發過程就是混亂和不可控制的。sql

長期以來,CVS 做爲一種廣泛採用的開源版本控制工具,在不少的開源軟件項目當中充當了重要的角色。在 Eclipse 當中,更是把 CVS做爲一個默認的插件,與 Ant,JUnit 等工具並列在一塊兒,成爲 Eclipse 軟件開發的基本工具。近年來,隨着開源社區的發展,一種功能更增強大的開源版本控制工具逐漸進入了人們的視野,那就是 Subversion,憑藉着更爲優秀的特性,Subversion 正在逐步取代 CVS,成爲新一代的開源版本控制工具。數據庫

相比 CVS,Subversion 中的目錄、文件以及更名等元數據都是被版本化的,例如文件的更名、拷貝等等操做;並且,在 Subversion中,提交操做是不可分割的,修訂版本號是基於每次提交操做而非文件;另外,Subversion 能夠獨立運行,有着輕量級的分支(Branching)與標籤(Tagging)操做,版本庫能夠採用數據庫(BerkeleyDB)或者是使用特定格式的文件進行存儲,對二進制文件進行處理更爲有效;最後,Subversion工具以及相關插件都有着很好的國際化支持,能夠支持包括簡體中文在內的多種語言版本,方便全球各地的開發人員。這些優秀的新特性,使得 Subversion 成爲開源社區目前的最佳選擇。apache

對於普通用戶,即應用程序開發者而言,尤爲是對 Eclipse 的用戶而言,Subversion 的使用十分的簡單。經過官方提供的 Eclipse 插件  Subclipse,用戶能夠在 Eclipse 裏面很方便的使用 Subversion 客戶端的各項基本功能。具體的客戶端設置和使用方法,請參考 Subversion 官方網站Subclipse 官方網站。簡單說來,在 Eclipse 中使用 Subversion 插件的基本功能,如更新、提交、同步、分支等等,基本上同使用 Eclipse 自帶的 CVS 插件如出一轍,這樣,用戶就能夠從 CVS 方便的轉移到 Subversion。瀏覽器

目前,Subversion 已經升級到 1.3.2 版本,相關下載、特性說明和詳細使用手冊能夠在 Subversion 主頁上找到。安全

簡單的版本庫管理

有了簡單易用的客戶端,大部分的用戶均可以輕鬆使用 Subversion 了,不過,做爲服務器端的管理人員,還必須進一步瞭解服務器端的基本配置管理,才能夠充分利用 Subversion 的各項優秀特性。服務器

版本庫建立

Subversion 的版本庫(repository),就是位於服務器端,統一管理和儲存數據的地方。本文中,咱們以 Linux                 爲例,介紹在服務器端配置和管理 Subversion 版本庫的基本方法。網絡

要建立一個版本庫,首先要肯定採用哪一種數據存儲方式。在 Subversion 中,版本庫的數據存儲有兩種方式,一種是在 Berkeley DB數據庫中存放數據;另外一種是使用普通文件,採用自定義的格式來儲存,稱爲 FSFS。dom

兩種存放方式各有優缺點,讀者能夠參考 http://svnbook.org/上面的文檔來了解二者詳細的比較和區別,這裏,咱們僅引用上述文檔當中的簡單對照表,給出一個簡明的比較。

表 1. 兩種版本庫數據存儲對照表
特性 Berkeley DB FSFS
對操做中斷的敏感 很敏感;系統崩潰或者權限問題會致使數據庫「塞住」,須要按期進行恢復。 不敏感
可只讀加載 不能 能夠
存儲平臺無關 不能 能夠
可從網絡文件系統訪問 不能 能夠
版本庫大小 稍大 稍小
擴展性:修訂版本樹數量 無限制 某些本地文件系統在處理單一目錄包含上千個條目時會出現問題。
擴展性:文件較多的目錄 較慢 較慢
檢出最新代碼的速度 較快 能夠
大量提交的速度 較慢,但時間被分配在整個提交操做中 較快,但最後較長的延時可能會致使客戶端操做超時
組訪問權處理 對於用戶的 umask                             設置十分敏感,最好只由一個用戶訪問。 對 umask 設置不敏感
功能成熟時間 2001 年 2004 年

肯定了具體的數據存儲類型,只要在命令行當中執行 svnadmin 命令就能夠建立一個 Subversion                 版本庫,命令以下

# 建立文件夾 $ mkdir /etc/svn/ $ mkdir /etc/svn/repos # 運行建立版本庫的命令,指定數據存儲爲 FSFS,若是要指定爲 Berkeley DB,則將 fsfs 替換爲 bdb $ svnadmin create --fs-type fsfs /etc/svn/repos

若是一切正常,命令執行後不會有任何反饋信息而迅速返回,這樣,一個新的版本庫就被建立出來了。咱們來查看一下生成的版本庫結構:

$ ls -l /etc/svn/repos 總用量 56 drwxrwxr-x  2 robert robert 4096  8月 27 17:27 conf drwxrwxr-x  2 robert robert 4096  8月 27 17:27 dav drwxrwsr-x  5 robert robert 4096  8月 27 17:27 db -r--r--r--  1 robert robert    2  8月 27 17:27 format drwxrwxr-x  2 robert robert 4096  8月 27 17:27 hooks drwxrwxr-x  2 robert robert 4096  8月 27 17:27 locks -rw-rw-r--  1 robert robert  229  8月 27 17:27 README.txt

其中,conf 目錄下存放了版本庫的配置文件,包括用戶訪問控制和權限控制等內容,文件自己的註釋說明十分詳細,讀者能夠根據註釋自行配置;dav 目錄是提供給 Apache 相關模塊的目錄,目前爲空;db 目錄下存放着 Subversion所要管理的全部受版本控制的數據,不一樣的存儲方式(Berkeley DB 或者FSFS)下有着不一樣的目錄結構,不過咱們通常不用直接修改和查看這個目錄下的內容,Subversion 的命令能夠安全的操做這個目錄;另外,hooks目錄存放着鉤子腳本及其模版(一種版本庫事件觸發程序),locks 目錄存放着 Subversion 版本庫鎖定數據,format文件記錄了版本庫的佈局版本號。

項目添加

有了新建的版本庫, 就能夠往裏面添加項目了。不過,管理員必須考慮的問題是,應該將每個項目分別放在不一樣的版本庫裏面,仍是應該將它們放在統一的版本庫裏面。統一的版本庫 可讓管理員更加容易的升級和備份,不過,因爲訪問權限控制是針對整個版本庫的,因此,統一的版本庫也爲不一樣項目配置不一樣的訪問權限帶來了麻煩。因此,管 理員應該根據實際狀況權衡考慮。

咱們以統一的版本庫爲例,添加兩個項目 project_luni 和 project_test。要作到這個,最好的辦法就是用                 svn import 命令導入已有的目錄樹。

首先,咱們在一個臨時目錄下,根據 Subversion 版本控制的通常佈局結構,建立出兩個項目的主要目錄樹,以下:

/etc/svn/tmp | ――project_luni | | | ――branches | | | ――tags | | | ――trunk | ――project_test | ――branches | ――tags | ――trunk

而後,用 svn import 命令來進行項目的導入:

$ svn import /etc/svn/tmp/ file:///etc/svn/repos/ --message "init" 新增           /etc/svn/tmp/project_test 新增           /etc/svn/tmp/project_test/trunk 新增           /etc/svn/tmp/project_test/branches 新增           /etc/svn/tmp/project_test/tags 新增           /etc/svn/tmp/project_luni 新增           /etc/svn/tmp/project_luni/trunk 新增           /etc/svn/tmp/project_luni/branches 新增           /etc/svn/tmp/project_luni/tags 提交後的修訂版爲 1。

版本庫查看

做爲版本庫管理員,咱們常常須要查看 Subversion 版本庫的情況,這就須要一些「只讀」的查看工具。

在上述項目導入完成之後,咱們能夠用 svn list 確認導入的項目是否正確:

$ svn list --verbose file:///etc/svn/repos/ 1 robert                 8月 27 18:24 project_luni/ 1 robert                 8月 27 18:24 project_test/

另外,若是要查看最新修訂版本的信息,可使用 svnlook info 命令:

$ svnlook info /etc/svn/repos/ robert 2006-08-27 18:24:27 +0800 (日, 27  8月 2006) 0

命令輸出了這個版本庫的最新修訂版本信息,包括做者、時間、日誌字數和日誌內容等。除開最新修訂版之外,咱們還能夠在命令後面接上 「--revision 版本號」 來指定某一個修訂版的顯示。

另外,咱們還能夠用以下命令來顯示版本庫的具體樹形結構,後面的 「--show-ids」選項指定顯示每個顯示元素的修改版本 ID。

$ svnlook tree /etc/svn/repos/ --show-ids / <0.0.r1/1007> project_test/ <1.0.r1/333> trunk/ <2.0.r1/0> branches/ <3.0.r1/74> tags/ <4.0.r1/152> project_luni/ <5.0.r1/793> trunk/ <6.0.r1/454> branches/ <7.0.r1/530> tags/ <8.0.r1/609>

其餘

這裏有一個須要新手,尤爲是習慣了 Subversion 客戶端命令 「svn」  的用戶注意的問題,那就是,「svnadmin」 「svnlook」都被認爲是服務器端的管理工具,只被用於版本庫所在的機器,用來檢查和維護版本庫,不能經過網絡來執行任務。因此,試圖將 URL 甚至本地 file路徑傳遞給這兩個程序,都是錯誤的。

Subversion 還有不少管理工具可供管理員應用,須要瞭解這項工具的使用方法,讀者們能夠用 svn help,                 svnadmin help, svnlook help 等等命令查看幫助信息,另外,Subversion 參考手冊提供了更爲全面和詳細的使用介紹。

基本的服務器配置

Subversion 設計了一個抽象的網絡層,版本庫創建完畢以後,能夠經過各類服務器向外公佈。svnserve 是 Subversion                 自帶的一個小型的服務器,它使用獨立的協議與客戶端。咱們能夠經過

svnserve –i

做爲 inetd 啓動或者

svnserve –d

做爲守護進程啓動一個服務。服務器啓動後,客戶端便可以經過絕對路徑訪問。如上例能夠訪問 svn://服務器IP/etc/svn/repos。同時能夠指定一些選項,經常使用的如 -r,用來指定版本庫的根路徑,例如假設版本庫位於 /etc/svn/repos:

svnserve –d -r /etc/svn

則客戶端能夠經過以下 URL 訪問服務器:svn://服務器IP/repos, 這樣能夠有效的避免暴露本地路徑。另外如                 --listen-port--listen-host 能夠指定綁定的地址和端口,-R 能夠強制設定爲 Read-Only 模式。若是在 Windows                 操做系統下,能夠將版本庫設定在 C 分區,如 c:\svnroot\repos 能夠經過svn://服務器IP/svnroot/repos訪問,若是在其餘分區,就必需要經過 -r 選項來指定 root 位置。

svnserve 能夠經過配置 svnserve.conf 來進行一些簡單的訪問權限控制。你能夠在版本庫的 conf子文件夾下發現這個文件。文件的初始內容大體以下:

[general] # anon-access = read # auth-access = write password-db = passwd # authz-db = authz # realm = My First Repository

其中 anon-access 表示匿名用戶的權限,auth-access 表示認證用戶的權限設置,password-db 指向保存用戶賬號密碼的文件的位置,可使用相對路徑。svnserve 只能對全局提供簡單的訪問控制,若是想要更加靈活的方式,可使用 Apache Http Server 做爲向外公佈版本庫的方式。

與 Apache Http Server                 的結合

經過 Http 協議訪問版本庫是 Subversion 的亮點之一。使用 Http 協議意味着只須要打開瀏覽器,輸入 URL 便可輕鬆的瀏覽整個版本庫。靈活一般帶來複雜性,Http 方式相對於 svnserve 方式來講須要更多的配置。

因爲 Subversion 須要版本化的控制,所以標準的 Http 協議不能知足需求。要讓 Apache 與 Subversion 協同工做,須要使用 WebDAV(Web 分佈式創做和版本控制)。WebDAV 是 HTTP 1.1 的擴展,關於 WebDAV 的規範和工做原理,能夠參考 IETF RFC 2518

爲了使 Subversion 與 dav 模塊通訊,須要安裝 mod_dav_svn 插件,能夠在 Subversion 的安裝目錄中找到。將其拷貝到  Apache 安裝目錄的 modules 文件夾下。接下來就是配置 Apache 的 httpd.conf 文件,讓 Apache 在啓動的時候加載上述模塊。

須要添加的內容以下:

LoadModule dav_module modules/mod_dav.so LoadModule dav_svn_module modules/mod_dav_svn.so <Location /repos>   DAV svn   SVNPath /etc/svn/repos </Location>

首先須要啓用 dav_module,而後加載 dav_svn_module。Location 標籤指出訪問的 URL 以及在服務器上的實際位置。配置完畢後從新啓動 Apache,打開瀏覽器,輸入 http://服務器IP/repos將會看到以下畫面:

圖1. 一個初始版本庫

初始版本庫

這表示 Apache 的 dav_svn 模塊已經能夠正常工做了。用戶可使用任何一種 Subversion 的客戶端經過 Http                 協議訪問你的版本庫。

若是想要指定多個版本庫,能夠用多個 Location 標籤,也可使用 SVNParentPath 代替 SVNPath,例如在                 /etc/svn 下有多個版本庫 repos1,repos2 等等,用以下方式指定:

<Location /repos>   DAV svn   SVNParentPath /etc/svn </Location>

"SVNParentPath /etc/svn" 表示 /etc/svn 下的每一個子目錄都是一個版本庫。能夠經過                 http://服務器IP/repos/repos1http://服務器IP/repos/repos2                 來訪問。

如今你的版本庫任何人均可以訪問,而且有徹底的寫操做權限。也就是說任何人均可以匿名讀取,修改,提交,以及刪除版本庫中的內容。顯然大部分場合這是不符合需求的。那麼如何進行權限設置呢,Apache 提供了基本的權限設置:

首先須要建立一個用戶文件。Apache 提供了一個工具 htpasswd,用於生成用戶文件,能夠在 Apache 的安裝目錄下找到。具體使用方法以下:

htpasswd etc/svn/passwordfile username

若是 passwordfile 不存在,能夠加上 -c 選項讓 htpasswd新建一個。建立好的文件內容是用戶名加上密碼的 MD5 密文。

接下來修改 httpd.conf,在 Location 標籤中加入以下內容:

AuthType Basic AuthName "svn repos" AuthUserFile /etc/svn/passwordfile Require valid-user

從新啓動 Apache, 打開瀏覽器訪問版本庫。Apache 會提示你輸入用戶名和密碼來認證登錄了,如今只有 passwordfile       文件中設定的用戶才能夠訪問版本庫。也能夠配置只有特定用戶能夠訪問,替換上述 "Require valid-user" 爲 "Require user tony robert" 將只有 tony 和 robert 能夠訪問該版本庫。

有的時候也許不須要這樣嚴格的訪問控制,例如大多數開源項目容許匿名的讀取操做,而只有認證用戶才容許寫操做。爲了實現更爲細緻的權限認證,可使用  Limit 和 LimitExcept 標籤。例如:

<LimitExcept GET PROPFIND OPTIONS REPORT> require valid-user </LimitExcept>

如上配置將使匿名用戶有讀取權限,而限制只有 passwordfile 中配置的用戶可使用寫操做。若是這還不能知足你的要求,可使用 Apache 的                 mod_authz_svn 模塊對每一個目錄進行認證操做。

用 mod_authz_svn進行目錄訪問控制

首先須要讓 Apache 將 mod_authz_svn 模塊加載進來。在 Subversion 的安裝目錄中找到 mod_auth_svn                 模塊,將其拷貝到 Apache 安裝目錄的 modules 子目錄下。修改 httpd.conf 文件,找到

LoadModule dav_svn_module modules/mod_dav_svn.so

在其後面加上

LoadModule authz_svn_module modules/mod_authz_svn.so

如今能夠在 Location 標籤中使用 authz 的功能了。一個基本的 authz 配置以下:

<Location /repos>   DAV svn    SVNPath /etc/svn/repos    AuthType Basic    AuthName "svn repos"    AuthUserFile /etc/svn/passwd    AuthzSVNAccessFile /etc/svn/accesspolicy   Satisfy Any    Require valid-user  </Location>

AuthzSVNAccessFile 指向的是 authz 的策略文件,詳細的權限控制能夠在這個策略文件中指定,如:

#兩個分組:committers,developers [groups]  committers = paulex richard developers = jimmy michel spark sean \             steven tony robert #在根目錄下指定全部的用戶有讀權限 [/]  * = r  #追加 committers 組用戶有讀寫權限 @committers = rw  #在 branches/dev 目錄下指定 developers 組的用戶有讀寫權限 [/branches/dev]  @developers = rw  #在 /tags 組下給予用戶 tony 讀寫權限 [/tags]  tony = rw  #禁止全部用戶訪問 /private 目錄 [/private]  * =  #給 committers 組用戶讀權限 @committers= r

使用 SVNParentPath 代替 SVNPath 來指定多個版本庫的父目錄時,其中全部的版本庫都將按照這個策略文件配置。例如上例中 tony 將對全部版本庫裏的 /tags 目錄具備讀寫權限。若是要對具體每一個版本庫配置,用以下的語法:

[groups]  project1_committers = paulex richard project2_committers = jimmy michel spark sean \             steven tony robert [repos1:/]  * = r  @ project1_committer = rw  [repos2:/]  * = r  @ project2_committer = rw

這樣項目1的 committer 組只能對 repos1 版本庫下的文件具備寫權限而不能修改版本庫 repos2,一樣項目2的 commiter                 也不能修改 repos1 版本庫的文件。

用 MySQL 代替文件形式存放密碼

到目前爲止咱們的用戶名密碼文件仍是以文本文件形式存放在文件系統中的,出於安全性的須要或者單點登錄等可擴展性的考慮,文本文件的管理方式都不能知足需求了。經過 Apache 的 module_auth_mysql 模塊,咱們能夠用 MySQL 來保存用戶信息。該模塊的主頁在 http://modauthmysql.sourceforge.net/,你也能夠在 http://modules.apache.org/                 找到它的發行版本。安裝方法同上述 Apache 的模塊同樣,拷貝至 modules 目錄並在 httpd.conf 文件中添加以下語句:

LoadModule mysql_auth_module modules/mod_auth_mysql.so

相應的 Location 區域改寫爲:

<Location /repos>    AuthName "MySQL auth"    AuthType Basic    AuthMySQLHost localhost    AuthMySQLCryptedPasswords Off   AuthMySQLUser root   AuthMySQLDB svn    AuthMySQLUserTable users   require valid-user  </Location>

而後在 mysql 中添加名爲 svn 的數據庫,並創建 users 數據表:

create database svn; use svn; CREATE TABLE users ( user_name CHAR(30) NOT NULL, user_passwd CHAR(20) NOT NULL, user_group CHAR(10), PRIMARY KEY (user_name) );

在 users 表中插入用戶信息

insert into users values('username','password','group');

從新啓動 Apache,在訪問版本庫的時候 Apache 就會用 mysql 數據表中的用戶信息來驗證了。

用 SSL 實現安全的網絡傳輸

經過 Apache 的網絡連接,版本庫中的代碼和數據能夠在互聯網上傳輸,爲了不數據的明文傳輸,實現安全的版本控制,須要對數據的傳輸進行加密。Apache 提供了基於SSL 的數據傳輸加密模塊 mod_ssl,有了它,用戶就能夠用 https 協議訪問版本庫,從而實現數據的加密傳輸了。SSL 協議及其實現方式,是一個很是複雜的話題,本文只是介紹 Apache 提供的最基本的SSL配置方法,更加詳細的介紹內容,請參考 http://httpd.apache.org/docs-2.0/ssl/ 上的文檔。

開始配置前,咱們須要一個實現 Apache 中 SSL 模塊的動態程序庫,一般名爲 mod_ssl.so,及其配置文件,一般名爲  ssl.conf。這個實現是跟 Apache 的版本相關的,不匹配的版本是不能用的;並且,並非每個 Apache 的版本都自帶了相關實現文件,不少狀況下,咱們須要本身去搜尋相關文件。另外,咱們還須要 OpenSSL 軟件及其配置文件,來生成加密密鑰和數字證書。這裏,咱們可使用一些免費網站,如 http://hunter.campbus.com/ 上提供的集成版本的 Apache。

有了相關的工具和文件,咱們就能夠開始生成 SSL 的證書和密鑰了。首先,咱們須要找到 openssl 程序及其配置文件                 openssl.cnf,運行以下命令來生成 128 位的 RSA 私有密鑰文件

my-server.key: openssl genrsa -des3 -out my-server.key 1024 Loading 'screen' into random state - done Generating RSA private key, 1024 bit long modulus .....++++++ ........++++++ e is 65537 (0x10001) Enter pass phrase for server.key:******** Verifying - Enter pass phrase for server.key:********

命令運行期間須要用戶輸入並確認本身的密碼。

如今,咱們須要 SSL 的認證證書,證書是由 CA(certificate authority) 發放而且認證的。爲此,咱們能夠用以下命令生成一個 CSR(Certificate Signing Request) 文件發給 CA,從而獲得 CA 的認證:

openssl req -new -key my-server.key -out my-s erver.csr -config openssl.cnf

固然,通常狀況下,若是 Subversion 的用戶不是太多,安全狀況不是很複雜,咱們也能夠生成一個自簽名的認證證書,從而省去了向 CA  申請認證的麻煩。以下命令:

openssl req -new -key my-server.key -x509 -out my-server.crt -config openssl.cnf

以上兩個命令都須要用戶輸入那個 key 文件的密碼,以及一些網絡設置信息,如域名,郵箱等等,這裏輸入的服務器域名應該與 Apache  配置文件當中的一致。如今,咱們能夠在 Apache 的 conf 目錄下新建一個 ssl 目錄,將 my-server.key 和          my-server.crt 文件都移動到 ssl 目錄裏面。而後修改 ssl.conf 文件,將 SSLCertificateKeyFile 和                 SSLCertificateFile 項指向這兩個文件。

若是 Apache 的 module 目錄裏面沒有 mod_ssl.so 文件,能夠將事先準備好的文件拷貝過去。而後,咱們能夠設置 Apache  的配置文件 httpd.conf,將 ssl 模塊加入其中:

LoadModule ssl_module modules/mod_ssl.so

而後,在配置文件的最後,加上以下 SSL 相關配置項:

SSLMutex default SSLRandomSeed startup builtin SSLSessionCache none ErrorLog logs/SSL.log LogLevel info <VirtualHost svntest.ut.cn.ibm.com:443>   SSLEngine On   SSLCertificateFile conf/ssl/my-server.crt   SSLCertificateKeyFile conf/ssl/my-server.key </VirtualHost>

這樣,基本的設置工做就完成了。從新啓動 Apache 服務器,如今能夠用 https 協議代替 http 協議來訪問版本庫了。若是要限定版本庫只能用 https 訪問,咱們能夠在 Apache 配置文件當中 Subversion 部分加上 「SSLRequireSSL」。以下:

<Location /repos>   DAV svn    SVNPath /etc/svn/repos   ………….#other items   SSLRequireSSL  </Location>

總結

Subversion 以其優良的版本控制功能,靈活的網絡訪問模型,以及與 Apache  服務器聯合配置所帶來的更強大的管理控制功能,逐漸在開源軟件開發的實踐當中獲得普遍的應用。本文重點介紹了 Subversion 服務器端的配置以及與                 Apache 服務器聯合配置的基本步驟和簡單應用,實現了簡單的實例應用。讀者若是想要進一步瞭解相關信息,請參考文章後面列出的相關資料。

相關文章
相關標籤/搜索