ASP 組件指南

ASP 組件指南

作者:J.D. Meier
Microsoft Corporation

2000 年 1 月 24 日

如果您符合以下幾種情況,這篇文章正適合您:

  • 從 Active Server Pages (ASP) 代碼調用組件
  • 設計將從 ASP 代碼調用的組件
  • 希望利用 ASP 代碼中的組件

目錄
簡介
爲什麼使用組件?
狀態管理
範圍
分割服務
線程模型
安全性
Server.CreateObject 與 CreateObject
傳遞參數
事件
OnStartPage/OnEndPage 與 ObjectContext
錯誤處理
全局變量
分佈組件
結論

簡介

組件。有人喜歡它們,有人則害怕。害怕組件的人通常都能給您講一個駭人的經歷。讓我們面對它:當開始在 ASP 下使用組件時,並不知道什麼傷害您。如果您摔倒了,那麼站起來,自己拍乾淨,然後接着來。在這篇文章中,我將提供從實踐中獲得的一般指南,幫助您建立更好的基於組件的 ASP 解決方案。

爲什麼使用組件?

在我開始討論組件指南之前,值得考慮將組件添加到 ASP 應用程序的價值。許多對組件不熟悉的開發人員總覺得一切都那麼新鮮。組件可以爲 ASP 應用程序帶來以下種種益處:

  • 封裝功能和隱藏實現細節
  • 可重用性(包括被不同客戶機應用程序重複使用)
  • 知識產權保護
  • 可伸縮性(體現在允許將應用程序分佈到多臺計算機上)
  • 配置和部署靈活性
  • 性能(尤其在早期綁定是重要因素時)
  • 訪問系統,例如 Win32 API 調用或編程語言的任何其他底層功能
  • 鍵入功能較強(“Visual Basic® 腳本編輯器 [VBScript]”的鍵入功能較弱,而且 JScript® 也不太好)
  • 業務邏輯與用戶界面分離,或者 Web 設計人員與 Web 開發人員分離

利益與付出同在。就增加開發過程的複雜性而論,創建組件解決方案可能更加昂貴。部署和疑難解答也可能變得更困難併成爲現實因素。但是,不要讓眼前的困難阻礙了長期的利益。如何知道付出的成本是否值得呢?請考慮下列方面:

  • 現有的代碼庫是如何?
  • 開發隊伍的水平和經驗怎樣?
  • 您對主服務器的控制範圍如何?
  • 爲特定任務選擇了什麼樣的工具和語言?
  • 存在什麼協同問題?
  • 存在性能和可伸縮性的因素嗎?
  • 項目時間框架是什麼?
  • 誰會繼續維護和支持該應用程序?例如,開發小組能介入和接管嗎?

審查以上考慮的問題後,請考察您的假設。原型能迅速從設想中得出實際情況。

現在,對組件可能帶來的益處有了一定的理解,讓我們繼續討論。下面的指南將幫助您獲得最大的益處。這些指南可能成爲指引您順利建立更穩定的、可升級的、性能更優的 ASP 組件應用程序的嚮導。

狀態管理

建議

一般來說,在可能的場合儘量使用無狀態的組件和無狀態的 ASP 頁面。組件不應需要狀態從一個方法調用到下一個狀態。將複雜的狀態存儲在數據庫中。對於簡單的數據、沿用 cookies、QueryString 或在頁面之間傳遞數據的隱藏的表單字段。

爲什麼

服務器資源是有限的。維護組件中的狀態意味着應用程序在資源衝突和併發問題時將消耗寶貴的資源。無狀態組件將幫助您避免這些問題。無狀態組件還提供更多的部署選項,並增強在多個客戶機上共享資源的能力。

常見的陷阱

開發人員常犯的錯誤是設計或使用需要維護狀態的組件。請注意防止這種常用於桌面開發的思想。通常,具有桌面開發背景的開發人員會設計出依賴狀態的組件。

詳細信息

避免使用“ASP 會話”將提高服務器的性能,因爲它簡化了代碼路徑並減少了服務器資源的消耗。如果不使用“ASP 會話”,請通過“Internet 服務管理器” (請參閱“Internet 信息服務 [IIS]”文檔)禁用“會話”狀態。也可以在不需要“會話”的 ASP 頁面中使用下面的標記禁用基於頁的“會話”:

<%@ENABLESESSIONSTATE=False %>

部署靈活性是另一個重要方面,尤其在 Web 區域中運行應用程序時。如果依賴“ASP 會話”, 則給定用戶的請求綁定在指定的 Web 服務器上,因爲“會話”狀態是服務器專用的。在中間層和 Web 服務器中避免狀態,並使用數據庫,將使 ASP 請求可由區域中任何有效的 Web 服務器處理。因此, 您將減少競爭,提供更好的冗餘,並允許更多的分佈選項。

不使用“ASP 會話”而在頁間傳遞數據的其他方法,請參閱下面的“知識庫 (KB)”文章:

Don Box 在 ActiveX® Q&A(英文) 非 MSDN Online 鏈接一文中 還提出有關 MTS 狀態管理的更多見解。

範圍

建議

通常,請在頁面範圍內使用組件。頁面範圍的含義就是在同一頁上創建對象、並使用它和釋放它 — 所有這些均在同一頁上操作。

標記爲“雙重”或“單元”的組件在頁面範圍內都能正常工作。僅在頁面範圍內使用“單元”模型組件,例如 VisualBasic 組件。如果需要在“應用程序”或“會話”中存儲組件,則建議使用“雙重”。可以在“會話”或“應用程序”範圍內存儲標記爲“雙重”的組件,但組件需要保證線程安全。

爲什麼

在頁面範圍內使用組件使服務器資源得以回收。釋放資源將使併發問題減到最小程度,並允許可彙集的資源在客戶機上共享。另外,頁面範圍組件避免了影響“會話” 或“應用程序”範圍的對象的線程問題。在下面的“線程模型分類”中將詳細討論線程問題。

常見的陷阱

最常見的問題之一就是在“應用程序”範圍內存儲 Visual Basic 或其他“單元”模型對象。如果您嘗試在“應用程序”範圍內存儲一個用 Server.CreateObject 創建的“單元”模型對象,可以看見下面的錯誤:

應用程序對象錯誤 'ASP 0197: 80004005'

不允許的對象使用

/VirDir/global.asa, line 7

不能將帶有單元模型行爲的對象添加到應用程序的內部對象。

但是,如果使用 <OBJECT> 標記在“應用程序”範圍內存儲“單元”模型對象,就不會出現運行錯誤。相反,對象將創建在指定的“單線程單元 (STA)”線程上,並且所有調用都彙集到那個線程 — 而且是連續地。原因是沒有複選該組件的線程模型。很遺憾,在運行時出現了問題。

另一個常見問題是在“會話”範圍存儲“單元”模型對象,該“會話”範圍將用戶會話綁定到指定的線程。這個行爲嚴重影響服務器的性能。由於所有調用將連續地彙集到創建該對象的線程,因此從根本上影響了線程緩衝池的目的。

詳細信息

有關詳細內容,請參閱下面的 KB 文章:

分割服務

建議

將表達、業務和數據服務分離。業務組件應該實施業務規則。業務組件不應包含數據訪問技術。那是數據層組件的任務。業務組件不應包含對 ASP 對象的引用。

ASP 提供表達服務。引用 ASP 的對象應該呈現爲 HTML。這些對象能夠依次調用對 MTS/COM+ 註冊的業務對象。

爲什麼

將應用程序分割爲單獨的和截然不同的服務,有以下好處:

  • 更便於組件的重用
  • 支持 Windows DNA model(英文) 非 MSDN Online 鏈接
  • 更好地孤立疑難問題
  • 更靈活的部署選項(去掉服務的耦合允許在多臺計算機上分佈應用程序)

常見的陷阱

有一種我們稱爲“瑞士軍刀”組件的常見問題。 該“瑞士軍”組件將所有服務合成一體 (就像有螺絲錐、牙籤等 17 種工具的小瑞士軍刀)。 把不相關的服務組合到一個組件中, 使該組件很難使用、理解和維護。

容易掉入的另一個陷阱是從業務組件中引用 ASP。使 ASP 和業務邏輯耦合(通過使用 請求響應對象,或在其內部構建 HTML),不僅限制不同的客戶機重用您的組件,而且限制了橫向的可伸縮性。引用 ASP 內置對象的對象應該與 Web 服務器在同一框圍中。理想情況下,由於橫向可伸縮性,業務組件可以分佈在不同的框圍中。可以直接在 ASP 腳本中提供表達服務,也可以建立呈現引用 ASP 內置對象的組件的 HTML,並將這些組件保持在 IIS 框圍中。

詳細信息

成功的設計模型可用作處理公共業務問題的模型。例如,處理“創建讀更新刪除(CRUD)” 操作的模型,可幫助您將應用程序分爲幾個截然不同的邏輯服務,即表達、業務規則和數據訪問。

請參閱下文以獲得更多的設計模型具體示例,可以在您自己的應用程序中模仿它:

線程模型

建議

選擇組件的範圍還是選擇組件的線程模型,哪種方法優先?兩種方法都要考慮線程分支,除非決定在頁面範圍內使用“單元”或“雙重”模型組件。(如果 Visual Basic 程序員不知道組件是哪種線程模型,則總是“單元”。)

如果需要在“應用程序”或“會話”中存儲對象,則需要使用標記爲“雙重”的組件並聚集“自由線程編組程序 (FTM)”。

不要使用“單線程”組件並避免使用來自 ASP 的“自由線程”組件。

注意: 如果不小心, Visual Basic 可產生“單線程”組件。請確保在項目屬性頁的常規選項卡上將線程模型設置爲單元線程。還要注意在相同選項卡上選定無人值守執行保留在內存中選項。

爲什麼

如果您使用的是 Visual Basic,它是一種“傻瓜”開發環境。 Visual Basic 僅限於使用“單元”模型。假如 Visual Basic“單元”模型對象執行得非常良好,我不想對頁面範圍上的限制考慮太多。 Fitch 和 Mathers Stock 2000 破壞了對性能的任何預先想法。另外,由 ASP、SQL 和 Visual Basic 構建的許多現有網站,無時不刻都在證明頁面範圍的“單元”模型組件是可伸縮和執行的。

如果在標記爲“雙重”的組件上聚集 FTM,則可以不用任何編組或線程切換,便能在線程之間調用。如果標記爲“雙重”的組件沒有聚集 FTM,ASP 將其視爲“單元”線程對象 — 就像 Visual Basic 組件一樣。請記住,如果計劃利用“COM+ 對象池”,則不要聚集 FTM。 有關“對象池”的規則,請參閱“平臺 SDK”文檔。

“單線程”和“自由線程”組件運行在“系統”安全環境下。更糟的是,“單線程”組件會導致死鎖。

常見的陷阱

也許最常見的陷阱就是使用了沒有被設計爲在 ASP 下運行的組件,如“單線程”組件。大多數開發人員陷入其中,是因爲將桌面應用程序移向 ASP,或者使用了第三方的控件時。如果您不能確定組件的線程模型,可以檢查組件的註冊表項(但不能總依賴它)。

詳細信息

有關線程模型及其對 ASP 的影響,請參閱下面的文章:

另外,下面的 KB 文章提供了有關線程問題的詳細內容:

安全性

建議

組件不應對它運行的用戶環境做任何假設。不要訪問用戶專用信息,如 HKEY_CURRENT_USER,或桌面計算機的專用資源,因爲這些對組件來講是不可用的。應用程序也不要使用 SendKeys 或調用依賴用戶界面的組件,執行通常需要桌面交互的操作,如打開對話框。

爲什麼

組件將運行在不同安全性的桌面上。首先,這表示應用程序不能打開對話框,並不能與其他 GUI 實用程序交互(例如,使用 SendKeys)。默認情況下,不允許 Inetinfo.exe 與桌面交互。不同的用戶環境也會限制組件訪問某些資源 — 主要是註冊表的 HKEY_CURRENT_USER 部分。

常見的陷阱

常見的失誤是引用 HKEY_CURRENT_USER 下的表項。例如,Visual Basic 的 GetSettingSaveSetting 函數不能在 ASP 下使用,因爲它們引用了 HKEY_CURRENT_USER 配置單元下的表項。下面的 KB 將討論這個問題:

當從 ASP 而不是從桌面客戶機調用組件時,打印機、MAPI 信息和網絡共享通常“失效”。

有關詳細內容,請參閱下面的 KB 文章:

詳細信息

有關安全性的幾點考慮:

  • 啓用哪種 IIS 身份驗證方法?
  • 您的 Web 應用程序是進程內的還是進程外的?
  • 如果組件以 MTS 或 COM+ 註冊,它是在“服務器”上還是在庫軟件包中?
  • 您正在調用本地 DLL、遠程 DLL、本地 EXE、遠程 EXE 嗎?

有關安全性的詳細說明超出了本文的範圍。但是,由於這個主題的複雜性,下面的文章對從 ASP 組件角度理解問題有很大幫助:

下文很好地概述了 IIS 如何處理安全性:

Server.CreateObject 與 CreateObject

建議

使用 Server.CreateObject。如果正在使用 MTS/COM+ 庫軟件包,請使用 Server.CreateObject 來避免線程阻塞。

爲什麼

CreateObject 相當於通過腳本引擎調用 CoCreateInstance。如果使用 CreateObject 而不是 Server.CreateObject,將發生下面情況:

  • ASP 不能識別該對象。
  • OnStartPage/OnEndPage 頁面方法沒有調用。
  • ASP 不知道對象的線程模型。

Server.CreateObject 相當於 GetObjectContext.CreateInstance。這表示 ASP 清楚該對象並知道它的線程模型。另外,如果 ASP 頁面是事務性的,則通過調用 Server.CreateObject 可使組件與 ASP 頁面在同一事務中。(請注意,事務性的頁面可能意味着可避免的業務規則與表達層的耦合。)

常見的陷阱

如果對象處於防火牆後面,可能需要調用 CreateObject。請參閱 Q193230 PRB: Server.CreateObject Fails when Object is Behind Firewall(英文) 非 MSDN Online 鏈接 以獲得詳細信息。

詳細信息

雖然在 IIS 4.0 下面 CreateObjectServer.CreateObject 快,但在 IIS 5.0 下性能是相同的。同樣,如果正在使用 MTS/COM+ 庫軟件包/應用程序, Server.CreateObject 可防止線程阻塞。

傳遞參數

建議

聲明 Out 參數爲 Variant。在 Visual Basic 術語中,這表示按引用 參數應該爲 Variant按值傳遞的參數(In 參數)不限於 Variant,但必須與 Variant 兼容。

爲什麼

腳本客戶機使用 Variant。 COM 服務器可使用指定的數據類型。當您將指定的數據類型按值傳遞給 COM 服務器時, COM 服務器可以毫無問題地接收。但除 Variant 外,其他按引用參數無法“回送”給 ASP 腳本。

常見的陷阱

最常見的錯誤之一是“類型不匹配”。這通常是因爲按引用 傳遞到 COM 對象的變量不是 Variant。通常的解決方法是按值傳遞參數或者將參數變爲 Variant

詳細信息

如果要在多臺計算機上分佈組件或在進程外運行它們,可能看到按值 傳遞參數獲得的顯著性能。按引用傳遞將在進程或計算機間造成更多的編組開銷,因爲數據必須往復發送。如果實際上並不需要按引用傳遞參數時, 按值傳遞參數的正確性和有效性也是一個問題。注意,在默認情況下 Visual Basic 按引用傳遞參數。

下面的 KB 文章討論將參數從 ASP 傳遞到 COM 對象:

事件

建議

避免調用等待其他組件返回事件的組件。

組件方法應儘快返回對 ASP 的執行。請考慮使用“MSMQ”或“COM+ 排隊組件” 來提供異步調用 — 或當要做的工作正長時間運行並且不必聯機運行時。

請異步地分派工作項目,而不要讓 ASP 等待長時間運行的進程結束。然後您將從 ASP 給客戶機返回一個響應。一旦工作項目完成,您可以用電子郵件或其他方法通知客戶機(請參閱下面內容)。

爲什麼

ASP 並不是爲處理事件設計的。爲了優化服務器性能,請儘快返回對 HTTP 請求的響應。

常見的陷阱

循環檢查服務器上的狀態標識並不是一種提供瀏覽器通知的“服務器友好”方法。

詳細信息

通常開發人員關注事件的原因是爲了給瀏覽器提供關於在服務器上處理的工作的通知。雖然可以開發出精緻的瀏覽器通知系統,如通過套接字在服務器上打開另一個端口,但許多開發人員通過下面的技術實現他們的需要:

  • 使用電子郵件通知
  • 在頁面中添加 Meta-Refresh 標記以輪詢服務器。
  • 發送到瀏覽器的連接,並讓客戶機手動檢查未決請求的狀態。

下面的 KB 文章討論這些問題:

OnStartPage/OnEndPage 與 ObjectContext

建議

在 IIS 4.0 及更高版本中使用 ObjectContext 訪問 ASP 內置對象(如響應、請求、服務器等等)。無論何時請儘量避免使用 ScriptingContext 對象、 OnStartPageOnEndPage

爲什麼

OnStartPageOnEndPageScriptingContext 對象是用於遺留支持的。

常見的陷阱

如果插入 ASP 對象,ATL 嚮導將使用 OnStartPageOnEndPage

詳細信息

用“雙重”或“單元”模型組件獲取 ObjectContext 而無須以 MTS/COM+ 進行註冊。對於本地的 ActiveX EXE 不能使用 ObjectContext,所以需要使用 OnStartPage/OnEndPage。若要使用“自由線程”和“單線程”組件環境,需要以 MTS/COM+ 註冊這些組件。否則需要使用 OnStartPage

錯誤處理

建議

錯誤處理器將期待着意外情況。捕獲應用程序每一部分中的錯誤,並儘可能完整地記錄下來。好的日誌對於跟蹤、隔離和疑難解答有重大意義。這些日誌可以實現爲文本文件或寫入 NT 事件日誌。在多數情況下,邊添加信息邊“冒出”錯誤,是通知調用者已經出錯的有效途徑。冒出錯誤使調用者可自由地與處理具體問題的具體方法交互。

當記錄錯誤時,提供儘可能多的有用信息至關重要。考慮包括以下幾點:

  • 當前用戶環境(調用 Win32 API — GetUserName
  • 當前線程 ID(調用 Win32 API — GetCurrentThreadId 或 Visual Basic 中的 App.ThreadId
  • 當前時間(使用 Win32 GetTickCount,得到的是毫秒數據)
  • 傳遞至方法的參數
  • 錯誤源,包括方法名

爲什麼

根據我們的經驗,好的錯誤處理和記錄是隔離和診斷運行時問題的最有效途徑。

常見的陷阱

還記得 ASP 0115 錯誤嗎? 但願您不用和它苦苦鬥爭了。 如果還在爲其苦惱, 建議您參閱 Troubleshooting with the IIS Exception Monitor(英文)

ASP 0115 錯誤不是總出現在開發人員的控制下 — 但多數時候是這樣,錯誤處理可能已經避免了很多這種情況的發生,還可能在其發生時幫助解決了它們。

總之,最大的問題爲跳過錯誤處理或沒有包含有用的診斷信息。

在 COM 中,罕有跨越組件的界限傳播異常的情況。捕獲異常 — 但返回 HResults,以向調用者傳送失敗信息。

詳細信息

下面的文章提供了有關有效錯誤處理的應用示例:

全局變量

建議

避免在組件中使用全局變量。在 Visual Basic 術語中,這表示在標準的 .BAS 模塊中沒有 Public 或 Global 變量。

爲什麼

Global 變量並不是真正意義上的全局。每個線程都有自己的副本。如果幾種方法恰好在同一線程中執行,它們將看到相同的變量;否則它們訪問的是這些變量的不同副本。這意味着您可能給一個全局變量賦了值(在線程 A 中),但其另一個用戶(在線程 B 中執行)看不到新值。

其原因是 Visual Basic 內部使用“線程本地存儲 (TLS)”來引用全局變量。這意味着每個線程都有自己的 Public 變量的副本,並且因爲它存在多個副本,全局數據並不是真正“全局的”。也就是說,恰好在同一線程中運行的用戶纔會訪問到同一個變量,不論他們是否期望如此。

常見的陷阱

如果在標準 .BAS 模塊中使用 Public 變量,當不同線程向還想使用同一個數據的不同用戶請求提供服務時,這個數據可能已被破壞了。

詳細信息

Visual Basic Programmer's Journal(英文)1999 年 6 月版中由 Matt Curland 所著的下列文章非 MS 鏈接 是必讀的:

  • Black Belt Programming - Create Worker Threads in DLLs
  • COMponent Builder - Create Efficient Multithreaded Apps

另外,下面 Daniel Appleman 所著的文章 很好地概述了 Visual Basic 中多線程的工作原理: A Thread to Visual Basic(英文) 非 MS 鏈接

分佈組件

建議

組件的分佈涉及性能、可伸縮性和安全性問題。相同組件的不同分佈可能產生更高性能、更易伸縮和更易管理的配置。

下面的指南有助於提高在多臺計算機上分佈組件時的性能和可伸縮性:

  • 在 IIS 的同一框圍中運行引用 ASP 內置對象的組件。
  • 在應用程序服務器上運行數據庫組件。
  • 在哪一臺計算機上運行業務組件很重要。倘若您去掉業務組件與任何 ASP 的耦合,您就可以根據您的應用程序設計、計算機的可用性和測試,來自由選擇。

當然還有例外。但這些是指南的好的開始。

爲什麼

跨計算機分佈組件使應用程序可以滿足伸縮性要求。其次,上面提到的指南有助於實現應用程序的性能和可伸縮性目標。

對象引用 ASP 內置對象,會與您的 Web 服務器進行大量通訊,並且由於它們是表達層的一部分,因此它們就在那裏。

數據庫或對數據極爲敏感的邏輯可能在數據庫的存儲過程中。將數據訪問組件置於應用程序服務器而非數據庫上,避免了組件之間的昂貴調用。相反,數據訪問組件則利用 SQL Server 通信(如 TCP/IP)與數據庫更有效地通信。

常見的陷阱

您應當嘗試避免下列問題:

  • 當橫向可伸縮性較爲合適之後,繼續追求從您的計算機開始的縱向可伸縮性。
  • 忽視了防火牆的考慮(幫自己一個忙。如果計算機間的產品環境有防火牆,則在測試方案中添加防火牆。)
  • 將引用 ASP 內置對象的組件置於與 IIS 服務器分離的計算機上(回調和編組 ASP 內置對象的成本很高。)
  • 使用組件內部的後期綁定(這產生對 GetIdsOfNames 的額外調用,這在分佈式應用程序中可能很昂貴。儘量使用早期綁定。)
  • 按引用傳遞參數(這產生更多的編組開銷。儘可能“按值”傳遞參數。)

成功地從 IIS 調用遠程 MTS 組件也可能很棘手。一個簡單有效、既提高性能又簡化安全性問題的解決方案,是調用中間的 MTS/COM+ 軟件包/應用程序。早期綁定可減少網絡路程段,提高性能。如果您使用“服務器”軟件包/應用程序,則可以設置軟件包/應用程序的運行標識。這個技術將在 KB 文章 159311 Instantiating Remote Components in Microsoft Transaction Server and Internet Information Server 中討論。

詳細信息

如果已經解耦了服務,特別是已使 ASP 在業務組件之外,則分佈將相當靈活。您就可以更多地考慮框圍,並根據需要分散組件以解決隨之而來的可伸縮性和性能問題。如何知道?進行測試。如何測試?請看下面的基本指南:

  • 若要測試 Web 站點的可靠性,請剖析計算機並檢查錯誤。
  • 若要測試性能,請查看每秒可處理多少 ASP 請求。
  • 若要測試可伸縮性,請設置每秒需要處理多少 ASP 請求的閥值。用重要的工具考驗應用程序——添加用戶直到性能壞到不能接受爲止。
  • 加強對應用程序的測試非常重要,因爲需要暴露運轉條件和單瀏覽器測試中不會出現的其他問題。

有關對應用程序加強測試的詳細內容, 請參閱 I Can't Stress It Enough -- Load Test Your ASP Application(英文)

結論

正如所見,有一些事情在整個開發中需要時刻注意。在此,應用程序指南所涉及的諸多因素已全部闡明,因爲它們有助於徹底避免嚴重失誤。在整個開發週期中遵循本文中略述的幾個指南,不僅可以避免一些額外的工作,而且能夠提交可收縮的、可靠的、高性能的基於 ASP 組件的解決方案。

 


J.D. Meier 出生併成長於美國東海岸。聽從 Horace Greeley 的建議,他成爲一名“開發人員支持”工程師,主要致力於包括 MTS 和 ASP 技術在內的服務器端組件以及 Windows DNA 應用程序。


歸檔的 Servin' It Up 列

12 月 27 日  ASP 指南

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章