.NET平台下帶許可權控制的TreeView控制項節點產生演算法

來源:互聯網
上載者:User
treeview|控制項|控制|演算法 一、引言

在應用系統開發中,TreeView是一種使用頻率很高的控制項。它的主要特點是能夠比較清晰地實現分類、導航、瀏覽等功能。因而,它的使用方法與編程技巧也一直受到技術人員的關注。隨著應用需求的變化,在很多情況下我們需要實現資料顯示的許可權控制,即使用者看到的資料是經過過濾的,或是連續值,或是一些離散的值。就TreeView而言,原先可能顯示出來的是完整的具有嚴格父子關係得節點集,而經許可權過濾後所要顯示的節點可能會變得離散,不再有完整的繼承關係。本文針對這一問題,通過對已有實現方法進行分析,提出改進演算法。所附樣本程式進一步解釋了演算法設計思想。



二、三種常見產生方式的簡單分析

如文[2,3]所述,TreeView的產生基本上有三種方式:

1. 介面設計時在TreeView設計器或者代碼中直接填充TreeView節點。
這種方式通過拖放控制項的方式產生樹,應用範圍窄,是一種非編程方式;

2. 從XML檔案中建立樹形結構。
這種方式通過XML檔案(串)產生樹,從形式上來說,這種方式是比較直觀的。因為XML本身就是一棵“樹”,在.NET 平台下TreeView的自動產生代碼中,TreeView的實際內容也是由XML表示的。此外,基於XML檔案產生樹對異構環境下的分布式應用具有重要意義。事實上,利用XML作為通用資料傳遞格式已得到普遍認可;

3. 從資料庫中得到資料(在.NET中,我們可以理解為一個資料集),建立樹形結構。
這種方式通過父子關係遞迴產生樹,是最容易理解的一種編程實現方式。一般是自頂向下遞迴產生,得到廣泛應用。

這裡,我們不妨舉一個實際的例子來說明一下,假設我們有這樣的資料集(可以看作是一個公司的部門列表):




TagValue
ContentValue
ParentID

G01
行銷部


G02
顧問部


G03
研發部


G04
測試部


GS01
行銷一部
G01

GS02
行銷二部
G01

GS03
行銷三部
G01

GSL01
行銷一部北京辦
GS01

GSL02
行銷一部上海辦
GS01

GS04
顧問一部
G02

GS05
顧問二部
G02

GS06
研發一部
G03

GS07
研發二部
G03

GS08
測試一部
G04

GS09
測試二部
G04

GSL03
研發一部杭州分部
GS06

GSL04
研發一部西安分部
GS06


表1 樣本資料集

其中,TagValue是節點的實際值,ContentValue是使用者介面上節點顯示的值或者說標籤值,ParentID是節點的父節點的TagValue。若節點為根節點,一般設ParentID為空白或等於本身的TagValue。

預設情況下,我們可以按照下面的演算法把所有的節點裝配成一棵樹,

演算法1:通過父子關係遞迴產生樹基本演算法

l Step 0:資料準備,給定資料集。
一般來說資料集遵循這樣的格式,即(TagValue,ContentValue,ParentID);

l Step 1:給定待增加子節點的節點(初始時一般為根節點),記作CurNode,以及待增加節點的ParentID值(初始時為根節點的ParentID),記作CurParentID;

l Step 2:在資料集中尋找具有指定ParentID值的所有節點,得到節點集objArr[],
if (objArr == null)
return;
else
{
//遍曆節點集
for(int i=0; i<objArr.Length;i++)
{
將objArr[i]添加為CurNode的子節點,同時遞迴(即將objArr[i]作為CurNode,objArr[i]的TagValue作為CurParentID,goto Step 1);
}
}





最終可以得到下圖所示的TreeView:




圖1 TreeView效果圖


這種方法的缺陷在於"由父節點及子節點"的遍曆順序意味著每個子節點的父節點必須存在,否則將搜尋不到,即可能出現斷層現象。在很多實際應用中,我們發現這種實現方式不能完全奏效,最典型的情況就是當需要對眾節點所表徵的實際值(比如機構列表,人員列表,資源清單等)進行許可權控制時,這時往往從資料庫中篩選出來的資料集中節點會出現斷層現象。比如我們假設設定許可權時給定資料如表2,即把第一行“行銷部”去掉(註:許可權過濾操作已超出本文討論的範圍,這裡假定資料集已准好),則運用演算法1產生的TreeView如圖2所示。

TagValue
ContentValue
ParentID

G02
顧問部


G03
研發部


G04
測試部


GS01
行銷一部
G01

GS02
行銷二部
G01

GS03
行銷三部
G01

GSL01
行銷一部北京辦
GS01

GSL02
行銷一部上海辦
GS01

GS04
顧問一部
G02

GS05
顧問二部
G02

GS06
研發一部
G03

GS07
研發二部
G03

GS08
測試一部
G04

GS09
測試二部
G04

GSL03
研發一部杭州分部
GS06

GSL04
研發一部西安分部
GS06


表2 給定資料集






圖2 TreeView效果圖

可以看到,這裡產生了節點遺漏現象。一般來說,我們可以從兩方面入手去解決問題,一方面可以修正資料集,另一方面則可以修改產生樹演算法。顯然直接修正資料集是很複雜的,且會帶來效率問題。而單方面修改產生樹演算法也是不是很好(即把遺漏的節點直接插到根節點下),因為這時會出現父輩和晚輩同級的現象。

三、通過深度編號遞迴產生樹演算法

回顧到已有的一些方法(文[1~5]),其中基於節點深度產生樹的方法給我們一些啟發,我們在構造資料集時可以增加深度欄位,但這裡的深度不是簡單的層級號,是一個擴充了的概念,具體地說其實是一個深度編號,它與父輩編號存在一定的對應關係。比如表1所示的資料集可以作如下編號:

TagValue
ContentValue
ParentID
DepthID

G01
行銷部

a001

G02
顧問部

a002

G03
研發部

a003

G04
測試部

a004

GS01
行銷一部
G01
a001001

GS02
行銷二部
G01
a001002

GS03
行銷三部
G01
a001003

GSL01
行銷一部北京辦
GS01
a001001001

GSL02
行銷一部上海辦
GS01
a001001002

GS04
顧問一部
G02
a002001

GS05
顧問二部
G02
a002002

GS06
研發一部
G03
a003001

GS07
研發二部
G03
a003002

GS08
測試一部
G04
a004001

GS09
測試二部
G04
a004002

GSL03
研發一部杭州分部
GS06
a003001001

GSL04
研發一部西安分部
GS06
a003001002


表3 帶深度編號的資料集

其中,DepthID即是節點的深度編號。產生深度編號的過程其實也不複雜,首先我們可以制定編號的規則,比如層級編號的首碼、編碼長度、起始值等。當給某個節點編號時,只要找到所在層級的最大編號,然後增1。具體實現過程這裡不再細述。

於是,我們很自然地想到借鑒演算法1的思想設計基於深度編號的產生樹程式。這時,我們可以根據當前節點的深度編號尋找其後代節點集,但要給出一個最大跨度(可以理解為最進階與最低級間的間隔級數),因為不可能無限制地找下去。這種方法可以部分程度上彌補"由父節點及子節點"的遍曆的缺陷,因為當出現斷層時會沿著編號繼續往後找。但是還是會可能漏掉,比如我們給定資料集(把“研發一部”過濾掉):

TagValue
ContentValue
ParentID
DepthID

G01
行銷部

a001

G02
顧問部

a002

G03
研發部

a003

G04
測試部

a004

GS01
行銷一部
G01
a001001

GS02
行銷二部
G01
a001002

GS03
行銷三部
G01
a001003

GSL01
行銷一部北京辦
GS01
a001001001

GSL02
行銷一部上海辦
GS01
a001001002

GS04
顧問一部
G02
a002001

GS05
顧問二部
G02
a002002

GS07
研發二部
G03
a003002

GS08
測試一部
G04
a004001

GS09
測試二部
G04
a004002

GSL03
研發一部杭州分部
GS06
a003001001

GSL04
研發一部西安分部
GS06
a003001002


表4 給定資料集

在產生樹過程中,當從“研發部”(a003)往下找子節點時,找到的應該是“研發二部”(a003002),因為它是最近的節點。而下面的順序就是沿著“研發二部”再往下找,顯然不可能找到“研發一部杭州分部”和“研發一部西安分部”,因為編號規則不一樣,這樣產生的樹同樣會漏掉節點。

我們提出一種新的演算法,即打破傳統的遍曆順序,採用由底向上的遍曆順序。形象地說,傳統的方法是通過一個既有根節點或父節點來不斷衍生新的子節點(如圖3(a)所示),而新的演算法是通過不斷聚集節點,形成子樹集,最後匯成一棵樹(如圖3(b)所示)。






圖3 TreeView節點產生流程示意圖



演算法2:由底向上按層次(深度)遍曆法產生樹演算法

l Step 0:資料準備,給定資料集(TagValue,ContentValue,DepthID), TagValue是節點的實際值,ContentValue是節點顯示的值或者說標籤值,DepthID是節點的深度編號。若節點為根節點,一般其DepthID長度為最短。給定最大深度iMaxDepLen和最小深度iMinDepLen。給定用於儲存當前子樹的Hashtable;

l Step 1:給定當前遍曆的層級長度iCurDepLen,初始設為iMaxDepLen;

l Step 2:在資料集中根據給定iCurDepLen尋找滿足條件的層級,得到該層級的節點集objArr[],
if (objArr == null)
return;
else
{
//遍曆節點集
for(int i=0; i<objArr.Length;i++)
{
Step 2.1 尋找objArr[i]的父節點,若無父節點,直接加入,goto Step 2.2;若有父節點,先尋找父節點是否已在Hashtable中。若有,將其從Hashtable中移出並記為tempParNode;否則產生新節點tempParNode;goto Step 2.3;
Step 2.2 若當前節點objArr[i]不在Hashtable中,在Hashtable中添加objArr[i];continue;
Step 2.3 若當前節點objArr[i]不在Hashtable中,根據objArr[i]產生節點tempNode;否則,將其從Hashtable中移出,並記為tempNode;將tempNode插到tempParNode中,並將存入Hashtable。
}
}

l Step 3:若當前層級iCurDepLen大於最小層級iMinDepLen,則繼續回溯,將iCurDepLen減1並作為當前iCurDepLen,goto Step 2;否則goto Step 4.

l Step 4:在得到用Hashtable儲存的節點表後(實際上是一子樹表),遍曆Hashtable,將各棵子樹插入TreeView.





在該演算法中,我們一開始便計算好資料集中節點深度編號的最小長度和最大長度,目的是為了不盲目搜尋。但如果資料集中每一層級的深度編號是固定長的,則可以更簡化搜尋過程。存放臨時子樹的Hashtable的索引值是當前子樹根節點的Tag值,這樣的好處是尋找相當方便,不需要在TreeView中遍曆一個個節點。所以,每次處理上一層級的節點,只需看其父節點在不在Hashtable中,若在將其插入子樹,否則增加Hashtable項。

附錄樣本程式實現了這一演算法,這裡介紹一下關鍵的幾個函數。

函數形式及其參數解釋
功能

PopulateCompleteTree(ref System.Windows.Forms.TreeView objTreeView,DataSet dsSource,string strTreeCaption,int iTagIndex,int iContentIndex,int iDepthIndex)

1. objTreeView是最終要產生的TreeView;

2. dsSource是給定資料集;

3. strTreeCaption指定TreeView根節點的名稱;

4. iTagIndex是資料集中TagValue欄位的列號;

5. iContentIndex是資料集中ContentValue欄位的列號;

6. iDepthIndex是資料集中DepthID欄位的列號;
1. 採用層次(深度)遍曆法產生樹主調函數;

2. 調用CollectNodes(DataSet dsSource,int iTagIndex,int iContentIndex,int iDepthIndex,int iCurDepLen,int iMinDepLen,ref Hashtable objArrNode)

CollectNodes(DataSet dsSource,int iTagIndex,int iContentIndex,int iDepthIndex,int iCurDepLen,int iMinDepLen,ref Hashtable objArrNode)

1. dsSource,iTagIndex,iContentIndex,iDepthIndex同上;

2. iCurDepLen指當前層級深度編號長度;

3. iMinDepLen指最小深度即最頂層深度編號長度;

4. objArrNode指用於存放中間子樹的Hashtable
1. 從底往上聚集節點;

2. 調用 LookupParentNode(DataSet dsSource,int iDepthIndex,string strSubDepth,int iTagIndex,int iContentIndex)

LookupParentNode(DataSet dsSource,int iDepthIndex,string strSubDepth,int iTagIndex,int iContentIndex)

1. dsSource,iTagIndex,iContentIndex,iDepthIndex同上;

2. strSubDepth指當前節點的深度編號(因為是遞迴尋找)
1. 尋找最近的上控層級,因為有可能父節點層級不存在。







此時若給定資料集(我們把“研發部”和“行銷一部”過濾掉),

TagValue
ContentValue
ParentID
DepthID

G01
行銷部

a001

G02
顧問部

a002

G04
測試部

a004

GS02
行銷二部
G01
a001002

GS03
行銷三部
G01
a001003

GSL01
行銷一部北京辦
GS01
a001001001

GSL02
行銷一部上海辦
GS01
a001001002

GS04
顧問一部
G02
a002001

GS05
顧問二部
G02
a002002

GS07
研發二部
G03
a003002

GS08
測試一部
G04
a004001

GS09
測試二部
G04
a004002

GSL03
研發一部杭州分部
GS06
a003001001

GSL04
研發一部西安分部
GS06
a003001002


表5 給定資料集

則產生樹如下圖所示,




圖4 TreeView效果圖

這正是我們需要的結果。

當然,有時為了結構的需要,我們還會採取所謂“中立”的方法。比如對於本文所提的TreeView控制項節點產生問題,如果不想再寫演算法去產生深度編號,那麼我們還可以通過給資料集增加標誌位的方法,即用標誌位來標識資料是否已被篩選。在運用傳統演算法產生樹後,再檢查一下是否有未被篩選的資料,若有則尋找其祖輩節點,將其插入祖輩節點。不過這裡的“尋找祖輩節點”是在TreeView上進行的,當節點很多時其效率肯定沒有直接在資料集上搜尋高。

另外,深度編號的引入不僅會給產生樹帶來方便,還可以讓使用權限設定更靈活。具體到我們的樣本來說,一般如果我們要把某些部門過濾掉,那麼會把這些部門一個一個挑出來,我們稱之為“離散值設定方式”。而當系統結構龐大時,我們更希望挑選一個區間,比如把一個部門及其下控的n級過濾掉,這是一個“連續值設定方式”,這時包含層級概念的深度編號可以很好地解決這個問題。實際的系統開發中,我們也發現採用這種方式是切實可行的。



四、其他TreeView產生方式

前面提到TreeView還可以通過XML檔案(串)產生。這種方式實現的關鍵是構造出一個類似於TreeView的XML文檔或字串出來。其基本思想應該與前面討論的演算法是相似的,只是在程式實現上稍微複雜一些(其中,XML節點的索引可以基於文件物件模型(DOM)來做)。另外還要注意的是,有很多的第三方TreeView控制項,他們所支援的XML文檔的格式是不盡相同的。限於篇幅,本文不詳細討論具體實現過程。

五、小結

本文主要討論了.NET平台下TreeView控制項節點產生程式設計,結合已有方法和實際需求,對設計方法進行了研究,給出了比較完整的解決方案。

在樹的具體應用中,除了產生樹之外,節點的增、刪、改、查甚至節點的升級和降級都是很常見的。本質上說,這些操作所涉及的是與業務相關的資料庫操作,所以在採用“由底向上按層次(深度)遍曆法”產生的TreeView中,這些操作的實現與傳統方法是一致的,額外的操作無非是添加或修改深度編號。當然,實際需求是變化多端的,相應演算法的設計與分析也是無止境的。



參考文獻(Reference):

[1] Zane Thomas. DataViewTree for Windows Forms,http://www.abderaware.com/WhitePapers/ datatreeview.htm

[2] 李洪根. 樹形結構在開發中的應用, http://www.microsoft.com/china/community/Column/ 21.mspx

[3] 李洪根. .NET平台下Web樹形結構程式設計, http://www.microsoft.com/china/community/ Column/30.mspx

[4] Don Schlichting. Populating the TreeView Control from a Database, http://www.15seconds. com/issue/030827.htm

[5] HOW TO: Populate a TreeView Control from a Dataset in Visual Basic .NET, http://support. microsoft.com/?kbid=320755

[6] Scott Mitchell. Displaying XML Data in the Internet Explorer TreeView Control,http://aspnet. 4guysfromrolla.com/articles/051403-1.aspx




-------------
source code:

using System;
using System.Data;
using System.Windows.Forms;
using System.Collections;


namespace PopTreeView
{
/// <summary>
/// TreeOperator 的摘要說明。
/// </summary>
public class TreeOperator
{
public TreeOperator()
{
//
// TODO: 在此處添加建構函式邏輯
//
}


/// <summary>
/// 採用層次(深度)遍曆法產生樹
/// </summary>
/// <param name="objTreeView">目標樹</param>
/// <param name="dsSource">資料集</param>
/// <param name="strTreeCaption">樹顯示名</param>
/// <param name="iTagIndex">值索引</param>
/// <param name="iContentIndex">內容索引</param>
/// <param name="iDepthIndex">層次索引</param>
public static void PopulateCompleteTree(ref System.Windows.Forms.TreeView objTreeView,DataSet dsSource,string strTreeCaption,int iTagIndex,int iContentIndex,int iDepthIndex)
{
//從底層開始遍曆,開闢一個HashTable(以Tag值為關鍵字),存放當前計算的節點
objTreeView.Nodes.Clear();
int iMaxLen = GetMaxDepthLen(dsSource,iDepthIndex);
int iMinLen = GetTopDepthLen(dsSource,iDepthIndex);
Hashtable objArrNode = new Hashtable();
CollectNodes(dsSource,iTagIndex,iContentIndex,iDepthIndex,iMaxLen,iMinLen,ref objArrNode);

TreeNode objRootNode = new TreeNode(strTreeCaption);

//在得到節點表後,插入樹
foreach(object objNode in objArrNode.Values)
{
TreeNode objNewNode = new TreeNode();
objNewNode = (TreeNode)objNode;
objRootNode.Nodes.Add(objNewNode);
}

objTreeView.Nodes.Add(objRootNode);
}


/// <summary>
/// 從底往上聚集節點
/// </summary>
/// <param name="dsSource"></param>
/// <param name="iTagIndex"></param>
/// <param name="iContentIndex"></param>
/// <param name="iDepthIndex"></param>
/// <param name="iCurDepLen"></param>
/// <param name="iMinDepLen"></param>
/// <param name="objArrNode"></param>
private static void CollectNodes(DataSet dsSource,int iTagIndex,int iContentIndex,int iDepthIndex,int iCurDepLen,int iMinDepLen,ref Hashtable objArrNode)
{
//收集節點
System.Data.DataView dv;
System.Windows.Forms.TreeNode tempNode;

//尋找給定層節點
int i=iCurDepLen;
do
{
dv = new DataView(dsSource.Tables[0]);
string strExpr = "LEN(TRIM("+dsSource.Tables[0].Columns[iDepthIndex].ColumnName+"))="+Convert.ToString(i);
dv.RowFilter = strExpr;
i--;
}while(i>=iMinDepLen && dv.Count<=0);
iCurDepLen = i+1;

#region 逐層回溯,收集節點
foreach(System.Data.DataRowView drow in dv)
{
//尋找父節點
string[] strArrParentInfo = LookupParentNode(dsSource,iDepthIndex,drow[iDepthIndex].ToString().Trim(),iTagIndex,iContentIndex);
string strTagValue = drow[iTagIndex].ToString().Trim();
string strContentValue = drow[iContentIndex].ToString();

//若無父節點,直接加入
if (strArrParentInfo == null)
{
//當前節點不在Hashtable中
if (objArrNode[strTagValue]==null)
{
//添加當前節點
tempNode = new TreeNode(strContentValue);
tempNode.Tag = strTagValue;
objArrNode.Add(strTagValue,tempNode);
}
}
else //有父節點,此時先尋找父節點是否已在Hashtable中
{
string strParTagValue = strArrParentInfo[0].Trim();
string strParContentValue = strArrParentInfo[1].Trim();

//父節點已在Hashtable中
if (objArrNode[strParTagValue]!= null)
{
//當前節點不在Hashtable中
if (objArrNode[strTagValue]==null)
{
tempNode = new TreeNode(strContentValue);
tempNode.Tag = strTagValue;
}
else
{
//取出並移除該節點,然後插入父節點
tempNode = new TreeNode();
tempNode =(TreeNode)objArrNode[strTagValue];
objArrNode.Remove(strTagValue);
}

//插入到父節點中
TreeNode tempParNode = new TreeNode();
tempParNode = (TreeNode)objArrNode[strParTagValue];
tempParNode.Nodes.Add(tempNode);
objArrNode[strParTagValue] = tempParNode;
}
else //父節點不在Hashtable中
{
//當前節點不在Hashtable中
if (objArrNode[strTagValue]==null)
{
tempNode = new TreeNode(strContentValue);
tempNode.Tag = strTagValue;
}
else
{
//取出並移除該節點,然後插入父節點
tempNode = new TreeNode();
tempNode = (TreeNode)objArrNode[strTagValue];
objArrNode.Remove(strTagValue);
}

//建立父節點並將當前節點插入到父節點中
TreeNode tempParNode = new TreeNode(strParContentValue);
tempParNode.Tag = strParTagValue;
tempParNode.Nodes.Add(tempNode);
objArrNode.Add(strParTagValue,tempParNode);
}
}
}
#endregion

//還有未遍曆的層
if (iCurDepLen>iMinDepLen)
{
CollectNodes(dsSource,iTagIndex,iContentIndex,iDepthIndex,iCurDepLen-1,iMinDepLen,ref objArrNode);
}

}


/// <summary>
/// 尋找父親節點
/// </summary>
/// <param name="dsSource"></param>
/// <param name="iDepthIndex"></param>
/// <param name="strSubDepth"></param>
/// <param name="iTagIndex"></param>
/// <param name="iContentIndex"></param>
/// <returns>找到返回由Tag值,內容值和深度值組成的字串數組,否則返回null</returns>
private static string[] LookupParentNode(DataSet dsSource,int iDepthIndex,string strSubDepth,int iTagIndex,int iContentIndex)
{
System.Data.DataView dv;
int iSubLen = strSubDepth.Length;

if (iSubLen<=1)
{
return null;
}

int i=1;
do
{
dv = new DataView(dsSource.Tables[0]);
string strExpr ="TRIM("+dsSource.Tables[0].Columns[iDepthIndex].ColumnName+") = '"+strSubDepth.Substring(0,iSubLen-i)+"'";
dv.RowFilter = strExpr;
i++;
}while(i<iSubLen && dv.Count<=0);

if (dv.Count<=0)
{
return null;
}
else
{
string[] strArr = {dv[0][iTagIndex].ToString(),dv[0][iContentIndex].ToString(),dv[0][iDepthIndex].ToString()};
return strArr;
}
}


/// <summary>
/// 得到最大深度值(深度的長度)
/// </summary>
/// <param name="dsSource">資料集</param>
/// <param name="iDepthIndex">深度索引(列號)</param>
/// <returns>最大深度值</returns>
private static int GetMaxDepthLen(DataSet dsSource,int iDepthIndex)
{
DataRowCollection objRowCol = dsSource.Tables[0].Rows;
int iMax = objRowCol[0][iDepthIndex].ToString().Trim().Length;

foreach(DataRow objRow in objRowCol)
{
int iCurlen = objRow[iDepthIndex].ToString().Trim().Length;
if (iMax<iCurlen)
{
iMax = iCurlen;
}
}

return iMax;
}


/// <summary>
/// 得到最小深度值(深度的長度)
/// </summary>
/// <param name="dsSource">資料集</param>
/// <param name="iDepthIndex">深度索引(列號)</param>
/// <returns>最小深度值</returns>
private static int GetTopDepthLen(DataSet dsSource,int iDepthIndex)
{
DataRowCollection objRowCol = dsSource.Tables[0].Rows;
int iMin = objRowCol[0][iDepthIndex].ToString().Trim().Length;

foreach(DataRow objRow in objRowCol)
{
int iCurlen = objRow[iDepthIndex].ToString().Trim().Length;
if (iMin>iCurlen)
{
iMin = iCurlen;
}
}

return iMin;
}


}
}


-----------
memo: 本文最初我想在刊物上發表的,篇幅很長,這裡有所刪節。歡迎指正、交流!





相關文章

E-Commerce Solutions

Leverage the same tools powering the Alibaba Ecosystem

Learn more >

Apsara Conference 2019

The Rise of Data Intelligence, September 25th - 27th, Hangzhou, China

Learn more >

Alibaba Cloud Free Trial

Learn and experience the power of Alibaba Cloud with a free trial worth $300-1200 USD

Learn more >

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。