• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

ASP.NET 2.0 中改进的缓存功能(转)

原作者: [db:作者] 来自: [db:来源] 收藏 邀请
摘要:本文中,Stephen Walther 将重点介绍 ASP.NET 2.0 中新增的缓存功能,以及如何使用这些新功能改进 ASP.NET 应用程序的性能和可扩展性。(本文包含一些指向英文站点的链接。)



本页内容
更轻松的数据缓存
使用 SQL Cache Invalidation
使用 Post-Cache Substitution
结论


对于由数据库驱动Web  应用程序来说,要改进其性能,最好的方法就是使用缓存。从数据库中检索数据可能是您在 Web 站点上执行的最慢的操作之一。如果能够将数据库中的数据缓存到内存中,就无需在请求每个页面时都访问数据库,从而可以大大提高应用程序的性能。


缓存有一个且只有一个缺点,那就是数据过期的问题。如果将数据库表的内容缓存到内存中,当基础数据库表中的记录发生更改时,您的 Web 应用程序将显示过期的、不准确的数据。对于某些类型的数据,即便显示的数据稍微有些过期,影响也不会太大;但对于诸如股票价格和竞拍出价之类的数据,即使显示的数据稍微有些过期也是不可接受的。


Microsoft ASP.NET 1.0 Framework 没有针对此问题提供一个完善的解决方案。使用 ASP.NET 1.0 Framework 时,您不得不在性能和数据过期之间作出权衡。幸运的是,Microsoft ASP.NET 2.0 Framework 提供了一项新功能,称为 SQL Cache Invalidation,可以解决这一棘手的问题。


在本文中,您将进一步了解 ASP.NET 2.0 Framework 中许多新的缓存改进功能。首先,您将了解到如何在新增的 DataSource 控件中集成缓存支持。然后,您将了解到如何配置和使用 SQL Cache Invalidation。最后,您将了解到随 ASP.NET 2.0 Framework 引入的一个新控件:Substitution 控件,使用该控件可以向已缓存的页面中插入动态内容。


更轻松的数据缓存
在 ASP.NET 2.0 Framework 中,最大的变化之一就是在 ASP.NET 页面上访问XML 文件。


在 ASP.NET 1.0 Framework 中,是通过将控件绑定到 DataSet 或 DataReader,使用控件来显示数据库数据的。而在 ASP.NET 2.0 Framework 中,通常是将控件绑定到 DataSource 控件。通过 DataSource 控件,您可以创建显示数据库数据的 ASP.NET 页面,而不用为访问数据库编写任何代码。


在处理数据库数据时,通常使用下列三个 DataSource 控件中的一个控件:


• SqlDataSource — 表示 SQL 数据源,例如 Microsoft SQL Server 或 Oracle 数据库。

AccessDataSource — 一个专用的 SqlDataSource 控件,用于 Microsoft Access 数据库。

• ObjectDataSource — 表示充当数据源的自定义业务对象。


例如,假设您要在 DropDownList 控件中显示从数据库中检索到的书目列表(参见图 1)。列表 1 中的页面说明了如何将 DropDownList 控件绑定到 SqlDataSource 控件。



图 1:使用 SqlDataSource 控件检索数据



列表 1:DisplayTitles.aspx


<html>
<head runat="server">
<title>Display Titles</title>
</head>
<body>
<form >


<asp:DropDownList
ID="DropDownList1"
DataSourceId="SqlDataSource1"
DataTextField="Title"
Runat="server" />

<asp:SqlDataSource
ID="SqlDataSource1"
ConnectionString="Server=localhost;database=Pubs"
SelectCommand="SELECT Title FROM Titles"
Runat="server" />

</form>
</body>
</html>


请注意,列表 1 中的 SqlDataSource 控件用于提供连接字符串,SQL SELECT 命令用于从数据库中检索记录。DropDownList 控件通过其 DataSourceID 属性绑定到 SqlDataSource 控件。


使用 DataSource 控件缓存数据
使用 DataSource 控件,不仅可以更轻松地连接数据库,还使缓存数据库数据变得更容易。只需在 SqlDataSource 控件上设置一两个属性,就可以自动在
内存中缓存由 DataSource 控件表示的数据。


例如,如果要将 Titles 数据库表在内存中缓存至少 10 分钟,可以按照以下方式声明 SqlDataSource 控件。


<asp:SqlDataSource
ID="SqlDataSource1"
EnableCaching="true"
CacheDuration="600"
ConnectionString="Server=localhost;database=Pubs"
SelectCommand="SELECT Title FROM Titles"
Runat="server" />


如果 EnableCaching 属性的值为 true,SqlDataSource 将自动缓存通过 SelectCommand 检索到的数据。使用 CacheDuration 属性,可以指定从数据库中刷新数据之前缓存数据的时间(以秒为单位)。


默认情况下,SqlDataSource 使用绝对过期策略来缓存数据,即每隔指定的秒数就从数据库中刷新一次。此外,您还可以选择使用可变过期策略。如果将 SqlDataSource 配置为使用可变过期策略,那么只要持续访问数据,数据就不会过期。如果需要缓存大量项目,使用可变过期策略将非常有用,因为这种过期策略将只在内存中保留访问最频繁的项目。


例如,下面的 SqlDataSourceControl 被配置为使用可变过期策略,过期时间为 10 分钟。


<asp:SqlDataSource
ID="SqlDataSource1"
EnableCaching="true"
CacheExpirationPolicy="Sliding"
CacheDuration="600"
ConnectionString="Server=localhost;database=Pubs"
SelectCommand="SELECT Title FROM Titles"
Runat="server" />


由于 CacheExpirationPolicy 属性的值被设置为 Sliding,CacheDuration 属性的值被设置为 600,因此,只要在 10 分钟内持续访问,此 SqlDataSource 表示的数据就会一直保留在内存中。


返回页首
使用 SQL Cache Invalidation
SQL Cache Invalidation 是 ASP.NET 2.0 Framework 最值得期待的新增功能之一。使用 SQL Cache Invalidation 可以获得缓存的全部性能优势,而不用担心数据过期的问题。SQL Cache Invalidation 使您可以在基础数据库中的数据发生更改时自动更新缓存中的数据。


SQL Cache Invalidation 通过在后台不断轮询数据库来检查数据更改。每隔一定的时间(毫秒),ASP.NET Framework 就会检查数据库中是否存在更新。如果 ASP.NET Framework 检测到任何更改,将从缓存中删除从数据库中添加的、依赖于数据库的任何项目(即,这些项目将过期)。


注意:Microsoft SQL Server 2005 支持一种截然不同的 SQL Cache Invalidation 方法。您可以配置 SQL Server 2005,使其在数据库、数据库表或数据库行发生变化时通知 ASP.NET 应用程序。这样,ASP.NET Framework 就不需要通过不断轮询 SQL Server 2005 数据库来检查数据更改了。


需要注意的是,SQL Cache Invalidation 只能用于 Microsoft SQL Server 7 及更高版本,不能用于其他数据库,例如 Microsoft Access 或 Oracle。


在缓存整个页面的输出、使用 DataSource控件或直接使用 Cache 对象时,都可以使用 SQL Cache Invalidation。下面将分别介绍这三种情况。


配置 SQL Cache Invalidation
在 Web 应用程序中使用 SQL Cache Invalidation 之前,首先必须执行一些配置步骤。必须将 Microsoft SQL Server 配置为支持 SQL Cache Invalidation,还必须在应用程序的 Web 配置文件中添加必要的配置信息。


可以按照以下两种方法配置 SQL Server:使用 aspnet_regsql 命令行工具,或者使用 SqlCacheDependencyAdmin 类。


使用 ASPNET_REQSQL 启用 SQL Cache Invalidation
使用 aspnet_regsql 工具,您可以通过命令行来配置 SQL Cache Invalidation。aspnet_regsql 工具位于 Windows\Microsoft.NET\Framework\[版本] 文件夹中。要使用此工具,必须打开命令提示符窗口并浏览到此文件夹。


要在使用 Pubs 数据库时支持 SQL Cache Invalidation,需要执行以下命令。


aspnet_regsql -E -d Pubs -ed


-E 选项使 aspnet_regsql 工具在连接到数据库服务器时使用集成的安全设置。-d 选项用于选择 Pubs 数据库。最后,-ed 选项用于为数据库启用 SQL Cache Invalidation。


执行此命令时,将在数据库中添加一个名为 AspNet_SqlCacheTablesForChangeNotification 的新数据库表。此表包含启用了 SQL Cache Invalidation 的所有数据库表的列表。此命令还将在数据库中添加一组存储过程。


为数据库启用 SQL Cache Invalidation 后,必须从数据库中选择要启用 SQL Cache Invalidation 的特定表。以下命令将为 Titles 数据库表启用 SQL Cache Invalidation。


aspnet_regsql -E -d Pubs -t Titles -et


-t 选项用于选择数据库表。-et 选项为数据库表启用 SQL Cache Invalidation。当然,您可以通过对每个数据库表重复执行此命令,为多个表启用 SQL Cache Invalidation。


执行此命令时,将在数据库表中添加一个触发器。只要您对表进行了修改,此触发器就将触发并更新 AspNet_SqlCacheTablesForChangeNotification 表。


最后,要获取某个特定数据库中当前启用了 SQL Cache Invalidation 的表的列表,可以使用以下命令。


aspnet_regsql -E -d Pubs -lt


此方法将从 AspNet_SqlCacheTablesForChangeNotification 中选择表的列表。此外,您也可以通过直接在该数据库表中执行查询来检索此信息。


使用 SqlCacheDependencyAdmin 类
aspnet_regsql 工具在后台使用 SqlCacheDependencyAdmin 类的方法来配置 Microsoft SQL Server。如果您愿意,可以直接从 ASP.NET 页面中使用此类的方法。


SqlCacheDependencyAdmin 类具有五个重要的方法:


• DisableNotifications — 为特定数据库禁用 SQL Cache Invalidation。

• DisableTableForNotifications — 为数据库中的特定表禁用 SQL Cache Invalidation。

• EnableNotifications — 为特定数据库启用 SQL Cache Invalidation。

• EnableTableForNotifications — 为数据库中的特定表启用 SQL Cache Invalidation。

• GetTablesEnabledForNotifications — 返回启用了 SQL Cache Invalidation 的所有表的列表。


例如,使用列表 2 中的 ASP.NET 页面,您可以为 Pubs 数据库中的任何表配置 SQL Cache Invalidation(参见图 2)。



图 2:从 ASP.NET 页面中启用 SQL Cache Invalidation



列表 2:EnableSCI.aspx (C#)


<%@ Page Language="c#" %>
<%@ Import Namespace="System.Web.Caching" %>
<script runat="server">


const string connectionString = "Server=localhost;Database=Pubs";


void Page_Load()
{
if (!IsPostBack)
{
SqlCacheDependencyAdmin.EnableNotifications(
connectionString);
SqlDataSource1.SelectParameters.Add("connectionString",
connectionString);
}
}


void EnableTable(Object s, EventArgs e)
{
try
{
SqlCacheDependencyAdmin.EnableTableForNotifications(
connectionString, txtTableName.Text);
}
catch (Exception ex)
{
lblErrorMessage.Text = ex.Message;
}
txtTableName.Text = "";
}


</script>


<html>
<head runat="server">
<title>Enable SQL Cache Invalidation</title>
</head>
<body>
<form >

<h1>SQL Cache Invalidation</h1>


以下表格已启用 SQL Cache Invalidation:


<p>
<asp:GridView />
</p>


<asp:TextBox ID="txtTableName" Runat="Server" />
<asp:Button Text="Enable Table" OnClick="EnableTable"
Runat="Server" />

</form>
</body>
</html>


列表 2:EnableSCI.aspx (Visual Basic .NET)


<%@ Page Language="VB" %>
<%@ Import Namespace="System.Web.Caching" %>
<script runat="server">


Const connectionString As String = "Server=localhost;Database=Pubs"


Sub Page_Load()

If Not IsPostBack Then
SqlCacheDependencyAdmin.EnableNotifications( _
connectionString)
SqlDataSource1.SelectParameters.Add("connectionString", _
connectionString)
End If
End Sub


Sub EnableTable(ByVal s As Object, ByVal e As EventArgs)

Try

SqlCacheDependencyAdmin.EnableTableForNotifications( _
connectionString, txtTableName.Text)
Catch ex As Exception
lblErrorMessage.Text = ex.Message
End Try
txtTableName.Text = ""
End Sub


</script>


<html>
<head >

<h1>SQL Cache Invalidation</h1>


以下表格已启用 SQL Cache Invalidation:


<p>
<asp:GridView />
</p>


<asp:TextBox ID="txtTableName" Runat="Server" />
<asp:Button ID="Button1" Text="Enable Table"
OnClick="EnableTable" Runat="Server" />

</form>
</body>
</html>


在列表 2 中,connectionString 常量用于选择启用了 SQL Cache Invalidation 的数据库(如果要为 Pubs 数据库以外的数据库启用 SQL Cache Invalidation,可以更改此常量的值)。在 Page_Load 方法中,调用 SqlCacheDependencyAdmin 类上的 EnableNotifications 方法,为由 connectionString 常量指定的数据库启用 SQL Cache Invalidation。


列表 2 中的 GridView 显示了当前启用了 SQL Cache Invalidation 的所有数据库表。GridView 被绑定到 ObjectDataSource 控件上,该控件为其 SelectMethod 调用 GetTablesneabledForNotifications 方法。


最后,您可以使用列表 2 中的页面为其他表启用 SQL Cache Invalidation。在文本框中输入表的名称并单击“Enable Table”按钮时,将调用 EnableTableForNotifications 方法。


SQL Cache Invalidation 的 Web 配置设置
在 ASP.NET 应用程序中使用 SQL Cache Invalidation 之前,下一步要做的是更新您的 Web 配置文件。您需要配置 ASP.NET Framework,以便轮询启用了 SQL Cache Invalidation 的数据库。


列表 3 中的 Web 配置文件包含轮询 Pubs 数据库所必需的配置信息。


列表 3:Web.Config


<configuration>

<connectionStrings>
<add name="MySQLServer"
connectionString="Server=localhost;Database=Pubs" />
</connectionStrings>

<system.web>


<caching>
<sqlCacheDependency enabled="true">
<databases>
<add
name="Pubs"
connectionStringName="mySqlServer"
pollTime="60000" />
</databases>
</sqlCacheDependency>
</caching>
</system.web>
</configuration>


列表 3 中的 Web 配置文件包含两部分。<connectionStrings> 部分用于创建数据库连接字符串,以连接到名为 mySqlServer 的 Pubs 数据库。


caching 部分用于配置 SQL Cache Invalidation 轮询。在 <databases> 子部分中,您可以列出要对其进行轮询以检查数据更改的一个或多个数据库。在列表 3 中,mySqlServer 表示的数据库每分钟(每 60000 毫秒)轮询一次。


您可以为不同的数据库指定不同的轮询间隔。每次轮询数据库以检查数据更改时,服务器都必须执行一些操作。如果您认为数据库中的数据不会频繁地更改,可以增加轮询间隔。


在页面输出缓存中使用 SQL Cache Invalidation
现在,我们已经完成了 SQL Cache Invalidation 的所有配置步骤,可以在 ASP.NET 页面中使用它了。一种方法是在页面输出缓存中使用 SQL Cache Invalidation。页面输出缓存允许您在内存中缓存页面所显示的所有内容。通过使用 SQL Cache Invalidation,您可以在(且只在)数据库表发生更改时自动更新缓存的页面。


例如,列表 4 中的页面在 GridView 控件中显示了 Titles 数据库表的内容。在该页面的顶部,OutputCache 指令用于在内存中缓存页面内容。如果 Titles 数据库表发生更改,SqlDependency 属性将使页面更新。


列表 4:OutputCacheTitles.aspx


<%@ OutputCache SqlDependency="Pubs:Titles"
Duration="6000" VaryByParam="none" %>
<html>
<head runat="server">
<title>Output Cache Titles</title>
</head>
<body>
<form >

<%= DateTime.Now %>


<asp:GridView
ID="grdTitles"
DataSourceID="SqlDataSource1"
Runat="Server" />

<asp:SqlDataSource
ID="SqlDataSource1"
SelectCommand="Select * FROM Titles"
ConnectionString="<%$ ConnectionStrings:mySqlServer %>"
Runat="Server" />

</form>
</body>
</html>


请注意,SqlDependency 属性引用了 Web 配置文件中定义的数据库的名称。由于我们指定了每分钟轮询一次 Pubs 数据库以检查数据更改,因此如果对该数据库进行了更改,列表 4 中的页面将在一分钟之内进行更新。


您可以为 SqlDependency 属性值列出多个数据库和/或多个数据库表。要创建多个依赖关系,只需用分号分隔每个依赖关系即可。


在 DataSource 控件中使用 SQL Cache Invalidation
除了在页面输出缓存中使用 SQL Cache Invalidation 之外,还可以直接在 DataSource 控件中使用 SQL Cache Invalidation。如果要在多个页面中使用相同的数据库数据,请考虑在 DataSource 控件中使用 SQL Cache Invalidation。SqlDataSource、AccessDataSource 和 ObjectDataSource 控件都支持 SqlCacheDependency 属性。


例如,列表 5 中的页面在 SqlDataSource 控件中使用了 SQL Cache Invalidation。


列表 5:SqlDataSourceCaching.aspx


<html>
<head >


<%= DateTime.Now %>


<asp:GridView
ID="grdTitles"
DataSourceId="SqlDataSource1"
Runat="server" />

<asp:SqlDataSource
ID="SqlDataSource1"
EnableCaching="true"
SqlCacheDependency="Pubs:Titles"
SelectCommand="select * from titles"
ConnectionString="<%$ ConnectionStrings:mySqlServer %>"
Runat="server" />

</form>
</body>
</html>


在列表 5 中,SqlDataSource 控件是使用 EnableCaching 和 SqlCacheDependency 这两个属性声明的。SqlCacheDependency 属性使用的语法与 OutputCache 指令的 SqlDependency 属性相同。您需要列出数据库的名称,后跟数据库表的名称。


在 Cache 对象中使用 SQL Cache Invalidation
最后,您还可以在 Cache 对象中使用 SQL Cache Invalidation。此选项使您可以最大程度地对 SQL Cache Invalidation 进行编程控制。


要在 Cache 对象中使用 SQL Cache Invalidation,您需要创建一个 SqlCacheDependency 对象实例。使用 Insert 方法在 Cache 中插入新对象时,可以使用 SqlCacheDependency 对象。


例如,列表 6 中的页面显示了 Titles 数据库表中的记录数。计数是基于对基础数据库表的依赖关系进行缓存的。


列表 6:DisplayTitleCount.aspx (C#)


<%@ Page Language="c#" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<script runat="server">


void Page_Load()
{
int count = 0;


if (Cache["TitleCount"] != null)
{
count = (int)Cache["TitleCount"];
}
else
{
string connectionString =
ConfigurationSettings.ConnectionStrings[
"mySqlServer"].ConnectionString;
SqlConnection con = new SqlConnection(connectionString);
SqlCommand cmd = new
SqlCommand("SELECT Count(*) FROM Titles", con);
con.Open();
count = (int)cmd.ExecuteScalar();
con.Close();
Cache.Insert("TitleCount", count,
new SqlCacheDependency("Pubs", "Titles"));
}
lblTitleCount.Text = count.ToString();
}


</script>


<html>
<head runat="server">
<title>Display Title Count</title>
</head>
<body>
<form >


<asp:Label ID="lblTitleCount" Runat="Server" />

</form>
</body>
</html>


列表 6:DisplayTitleCount.aspx (Visual Basic .NET)


<%@ Page Language="vb" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<script runat="server">


Sub Page_Load()

Dim count As Integer = 0


If Not Cache("TitleCount") Is Nothing Then
count = Convert.ToInt32(Cache("TitleCount"))
Else
Dim connectionString As String = _
ConfigurationSettings.ConnectionStrings( _
"mySqlServer").ConnectionString
Dim con As New SqlConnection(connectionString)
Dim cmd As New _
SqlCommand("SELECT Count(*) FROM Titles", con)
con.Open()
count = Convert.ToInt32(cmd.ExecuteScalar())
con.Close()
Cache.Insert("TitleCount", count, _
new SqlCacheDependency("Pubs", "Titles"))
End If

lblTitleCount.Text = count.ToString()
End Sub


</script>


<html>
<head >


<asp:Label ID="lblTitleCount" Runat="Server" />

</form>
</body>
</html>


返回页首
使用 Post-Cache Substitution
在许多情况下,您需要缓存页面的一部分,而不是整个页面。例如,在您的 Web 站点主页上,您可能希望同时显示随机的标题广告和数据库表中的记录。如果缓存整个页面,每个用户都将在每次请求的页面上看到同一个标题广告。


要处理这种同时混有动态内容和缓存内容的问题,一种方法是使用 Web 用户控件。因为可以为 Web 用户控件添加 OutputCache 指令,所以即使不缓存包含页面的内容,也可以缓存 Web 用户控件的内容。


但有时候可能会事与愿违。虽然您可以使用 Web 用户控件在动态页面上添加缓存的内容,但很多情况下,您实际上是想在缓存的页面中添加动态内容。例如,假设您要缓存整个页面的内容,只留一小块区域用于显示当前用户的用户名。这种情况下最好使用 Post-Cache Substitution。


ASP.NET 2.0 Framework 引入了一种新控件,称为 Substitution 控件。您可以使用 Substitution 控件在缓存的页面中插入动态内容。列表 7 中的页面使用 Substitution 控件将用户名插入到缓存的内容中(参见图 3)。



图 3:使用 Substitution 控件显示用户名



列表 7:PostCacheSubstitution.aspx (C#)


<%@ Page Language="C#" %>
<%@ OutputCache Duration="6000" VaryByParam="none" %>


<script runat="server">


static string DisplayUsername(HttpContext context)
{
if (!context.Request.IsAuthenticated)
return "Anonymous";
else
return context.User.Identity.Name;
}


</script>


<html>
<head runat="server">
<title>Post Cache Substitution</title>
</head>
<body>
<form ) %> 并无改变。
</p>

</form>
</body>
</html>


列表 7:PostCacheSubstitution.aspx (Visual Basic .NET)


<%@ Page Language="vb" %>
<%@ OutputCache Duration="6000" VaryByParam="none" %>


<script runat="server">


Shared Function DisplayUsername(ByVal context As HttpContext) _
As String
If Not context.Request.IsAuthenticated Then
Return "Anonymous"
Else
Return context.User.Identity.Name
End If
End Function


</script>


<html>
<head ) %> 并无改变。
</p>

</form>
</body>
</html>


Substitution 控件有一个非常重要的属性:即 MethodName 属性。MethodName 属性用于表示为返回动态内容而调用的方法。由 Substitution 控件调用的方法必须是静态方法(在 Visual Basic .NET 中共享的方法)。此外,该方法还必须具有一个表示当前 HttpContext 的参数。


在 ASP.NET 2.0 Framework 中,已对 AdRotator 控件进行了修改以支持 Post-Cache Substitution。如果在使用 OutputCache 指令的页面中添加 AdRotator 控件,AdRotator 控件将自动从其包含页面的缓存策略中排除出去。


返回页首
结论
缓存对由数据库驱动的 Web 应用程序的性能具有非常大的影响。幸运的是,ASP.NET 2.0 Framework 提供了许多新的重要增强功能,使您可以在应用程序中更轻松地使用缓存功能。


新增的 DataSource 控件中包含的属性使得在内存中缓存数据库数据变得非常容易。通过使用 DataSource 控件,您可以检索并缓存数据库数据,而无需编写任何代码。


新增的 SQL Cache Invalidation 支持使您可以在基础数据库中的数据发生更改时自动在缓存中重新加载数据库数据。此功能为您提供了缓存的所有性能优势,而不用担心数据过期的问题。


最后,使用新增的 Substitution 控件,您可以更轻松地在缓存的页面中混合动态内容。Substitution 控件为您在缓存的页面中插入动态内容提供了一个独立的空间。


参考资料


• ASP.NET Unleashed

• A First Look at ASP.NET V. 2.0

• ASP.NET 2.0 Revealed


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
ASP.NET MVC Preview 2 - 流程分析 (1)发布时间:2022-07-10
下一篇:
Asp.Net分页和AspNetPager控件的使用发布时间:2022-07-10
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap