热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

在Silverlight中管理动态内容交付,第1部分

本文示例源代码或素材下载目录Silverlight应用程序的大小动态生成的XAML动态生成的XAP请求内容缓存下载的内容下载工具下载仅含XAML的数据使用XAP程序包处理XAP内容

 本文示例源代码或素材下载

在 Silverlight 中管理动态内容交付,第 1 部分目录

  Silverlight 应用程序的大小

  动态生成的 XAML

  动态生成的 XAP

  请求内容

  缓存下载的内容

  下载工具

  下载仅含 XAML 的数据

  使用 XAP 程序包

  处理 XAP 内容

  总结

  任何使用富 Internet 应用程序 (RIA) 的用户都关注安全性和下载大小问题。Silverlight 应用程序受到完整的 Microsoft .NET Framework 子集的支持,因此可能会对本地用户计算机执行有害操作。为此,Silverlight 团队开发了一个全新的安全模型,可防止应用程序调用核心 CLR(.NET Framework 的 Silverlight 版本)中的任何安全关键类。您可以从文章“使用 CoreCLR 编写 Silverlight”中了解有关 Silverlight 安全模型的更多信息,从“开始在整个网站积累更深入的体验”中了解有关构建 Silverlight 应用程序的详细内容。

  下载 Silverlight 插件并不是问题的核心所在,因为此操作只需几秒钟且只能运行一次。但是所下载应用程序的大小会有什么影响呢?

  在本月的专栏中,我将解决 Silverlight 应用程序下载的问题。首先,我将说明如何动态生成 XAML。然后我将讨论如何严格地根据每个用户请求动态启用应用程序功能。在随后可以单独实现一些使用主代码流难以实现的特定于应用程序的功能,更为重要的是,可以分别下载它们,而且可以轻松地与主用户界面相集成。
Silverlight 应用程序的大小

  用户安装了 Silverlight 插件后,他就拥有了 Silverlight 应用程序可能会需要的所有系统程序集。这意味着下载仅限于包含该应用程序的程序集以及任何引用的自定义程序集。最后,应用程序下载文件的大小通常为数万字节。请注意,此估计值仅适用于 RTM 版本的 Silverlight 2 以及在发布模式下编译的代码。

  当然,该应用程序占用的空间可能会更大,尤其是当其包含长算法、图形和媒体内容或动画时。要处理下载时间过长的大型应用程序,一般可采用以下两种方法。一种是流式传输 Silverlight 内容,如 silverlight.live.com 上所述。另一种是将应用程序拆分成可以按需分别下载的独立片段。

  动态生成的 XAML

  Silverlight 插件本质上设计用于显示 XAML 内容。如果 XAML 附带了一些代码隐藏,则该插件会对代码进行处理以生成用户界面并支持任何编码行为或效果。如果您要下载的是 XAML,那么可以通过 URL 直接指向该 XAML;如果不是,则可以通过 XAP 扩展名来引用 Silverlight 程序包。

  XAP 程序包内含指令清单和一或多个程序集。其中一个程序集包含应用程序的入口点;其他程序集仅仅是引用的程序集。用户界面的 XAML 存储在入口点程序集的资源中。在创建和编译项目时,Silverlight 2 的 Visual Studio 2008 扩展会创建一个 XAP 程序包。

  XAML 和 XAP 流都属于 Silverlight 插件知道如何处理的流。但是要下载此类内容,您不必将该插件指向服务器上的物理 XAML 或 XAP 资源。您可以将该插件指向某个 URL,例如,可以指向返回动态生成的 XAML 或 XAP 内容的 URL。

  图 1 显示了一个示例 ASP.NET HTTP 处理程序,它将返回动态创建的某些 XAML。ProcessRequest 方法在 Response 对象上设置内容类型,然后根据配置数据、参数或运行时条件撰写某些 XAML 内容(如动态合成的 XAML)。通过设置 Response 对象的 Expires 属性,您还可以禁止在客户端上缓存资源。如果您所处理的内容会定期改动并需要刷新,则这可能会有所帮助。

图 1 返回 XAML 的 HTTP 处理程序

<%&#64; WebHandler Language&#61;"C#" Class&#61;"XamlGenHandler" %>
using System;
using System.Web;
public class XamlGenHandler : IHttpHandler
{
  public void ProcessRequest (HttpContext context)
  {
    // Prevent caching of the response
    context.Response.Expires &#61; -1;
    // Set the type of data we&#39;re returning
    context.Response.ContentType &#61; "text/xaml";
    // Create some XAML and return it down the wire
    context.Response.Write("      &#39;http://schemas.microsoft.com/client/2007&#39; " &#43;
      "xmlns:x&#61;&#39;http://schemas.microsoft.com/winfx/2006/xaml&#39;>" &#43;
      "
       XAML content" &#43;
      "[generated " &#43; DateTime.Now.ToLongTimeString() &#43; "]" &#43;
      "
");
  }
  public bool IsReusable
  {
    get {return true;}
  }
}

动态生成的 XAP

  返回动态生成的 XAP 程序包与返回原始 XAML 文本二者的差别并不大&#xff0c;只不过 XAP 程序包不是纯文本文件罢了。XAP 程序包是一个 ZIP 文件&#xff0c;它包含 XML 指令清单和一或多个程序集。通过使用程序包格式&#xff0c;团队可以最大程度地减少下载 Silverlight 应用程序请求的全部内容所需的往返次数。图 2 显示了一个 ASP.NET HTTP 处理程序&#xff0c;它将 XAP 文件的内容写入到 HTTP 响应流中。

在 Silverlight 中管理动态内容交付&#xff0c;第 1 部分图 2 返回 XAP 程序包的 HTTP 处理程序

<%&#64; WebHandler Language&#61;"C#" Class&#61;"XapGenHandler" %>
using System;
using System.Web;
public class XapGenHandler : IHttpHandler
{
  public void ProcessRequest (HttpContext context)
  {
    // XAP file to return
    string xapFile &#61; "...";
    // Set the type of data we&#39;re returning
    context.Response.ContentType &#61; "application/octet-stream";
    // Create some XAML and return it down the wire
    content.Response.WriteFile(xapFile);
  }
  public bool IsReusable
  {
    get {return true;}
  }
}

  示例代码从现有文件中读取 XAP 数据。显然&#xff0c;如果在项目中嵌入一个 ZIP 库&#xff0c;则可以轻松地动态组合程序包&#xff08;首先将不同的 DLL 组合在一起&#xff0c;然后创建适当的 XML 指令清单文件&#xff09;。

  如果想返回 XAP 内容&#xff0c;可将响应的内容类型设置为 application/octet-stream&#xff0c;这是一种 MIME 类型&#xff0c;通常用于标识通用的二进制内容。

  要将该插件与 HTTP 处理程序或您选择的任何其他端点相关联&#xff0c;可使用常见的 Silverlight 编程技术。例如&#xff0c;您可以在 ASP.NET 页面中使用 Silverlight 服务器控件&#xff1a;

  Source&#61;"~/xap.ashx"
  MinimumVersion&#61;"2.0.30523"
  Width&#61;"100%"
  Height&#61;"100%" />

  在这两个示例中&#xff0c;Silverlight 应用程序的工厂均位于 Web 服务器上。如果宿主页面需要动态指示要下载的内容&#xff0c;则这不失为一种不错的方法。

  但是&#xff0c;这只是一种可能的情况。还有另一种情况可能更常见&#xff0c;即需要下载当前 Silverlight 应用程序的可选组件。在这种情况下&#xff0c;选择和下载外部内容的逻辑全部位于客户端上运行的 Silverlight 插件中。

  请求内容

  Silverlight 2 为按需下载代码和/或 XAML 提供了一个丰富而又强大的 API&#xff0c;可用来下载内容并将其插入到现有的 XAML 文档对象模型中。

  XAML 树的所有可视元素都有一个名为 Children 的属性&#xff0c;可用来以编程方式添加或删除任意大小的子元素。例如&#xff0c;您可以附加从同一服务器甚至是从信任的可选远程服务器下载的整个用户控件。示例如下行所示&#xff1a;

StackPanel1.Children.Add(downloadedContent);

  由参数表示的用户控件被添加到 StackPanel XAML 元素的 Children 集合中。呈现是即时性的&#xff0c;而用户界面是实时更新的。

  您可以做的不仅仅是下载内容并将其附加到现有文档对象模型中。您可以执行更多其他操作&#xff0c;例如&#xff0c;您可以在本地将其缓存到应用程序的本地存储中并检查自己的缓存是否存在请求内容&#xff0c;在确认完毕后再向服务器发出新请求。

此方法使您能够永久存储下载的内容。但在某些情况下&#xff0c;这可能有些多余。另一种简单的方法不需要任何额外的工作&#xff1a;让浏览器为您缓存 XAP 资源。

  缓存下载的内容

  从 Web 服务器获得的 XAP 程序包对浏览器而言没有任何特殊含义。因此&#xff0c;浏览器在缓存从 Web 服务器获得的任何其他内容的同时也会缓存它&#xff0c;但这一切都遵守在宿主 HTML 页面中由请求或类似 meta 标记中的 cache-control 和 "expires" HTTP 标头所确定的请求缓存策略。

  请注意&#xff0c;当遇到需要下载到浏览器中的 XAP 资源时&#xff0c;可以在通常使用 meta 标记或 ASP.NET 指令属性插入的页面中通过进行一些设置来控制缓存。如果 XAP 资源将要通过 HTTP 处理程序下载&#xff08;如之前的示例所示&#xff09;&#xff0c;则您可以控制特定请求的缓存。

  值得注意的另外一点是&#xff0c;这里被缓存的是原始 XAP 内容&#xff08;包括程序集和 XAML&#xff09;。因此&#xff0c;正在运行的应用程序可以通过编程方式修改原始 XAML。但是&#xff0c;这种更改不会被自动缓存&#xff0c;同样&#xff0c;您从 XAP 程序包中提取的任何资源&#xff08;媒体、图像等&#xff09;也不会被单独缓存。这样一来&#xff0c;每次用户访问该页面时&#xff0c;都不会重新下载 XAP 程序包&#xff08;除非已过期&#xff09;&#xff0c;但会重新提取所有资源。此外&#xff0c;您在先前的会话中对这些资源所做的所有更改也将丢失。要保留对 XAML 文档对象模型所做的更改&#xff0c;必须安排您自己的定制缓存。&#xff08;这是一种很酷的方法&#xff0c;我将在本主题的第 2 部分对此加以介绍。&#xff09;

  最后还要注意&#xff0c;保存在浏览器缓存中的 XAP 程序包的存留完全取决于用户。如果用户决定在某个时间清除缓存&#xff0c;则其中的所有内容都将丢失&#xff08;包括 XAP 程序包&#xff09;。要永久存储 Silverlight XAP 程序包&#xff0c;必须求助于独立存储&#xff08;此主题也安排在第 2 部分进行介绍&#xff09;。

  下载工具
在编写 Silverlight 应用程序时&#xff0c;要记住届时需要用到但却未打包到应用程序的 XAP 中的所有资源都必须从服务器显式下载。WebClient 类是可用来安排附加资源下载的主要 Silverlight 工具。它提供了一些异步方法&#xff0c;可用于将数据发送到 Web 资源以及从 Web 资源接收数据。下面介绍它的工作原理&#xff1a;

WebClient wc &#61; new WebClient();
wc.DownloadStringCompleted &#43;&#61;
   new DownloadStringCompletedEventHandler(callback);
wc.DownloadStringAsync(address);

  DownloadStringAsync 方法操控 HTTP GET 并捕获 URL 响应作为一个字符串。此字符串由相关联的回调接收&#xff0c;如下所示&#xff1a;

void callback(object sender, DownloadStringCompletedEventArgs e)
{
 if (e.Error !&#61; null)
   return;
 string response &#61; e.Result;
...
}

  稍后您会看到&#xff0c;此方法非常适合于下载没有附加任何代码隐藏的纯 XAML。要以编程方式下载二进制数据&#xff08;如 XAP 程序包&#xff09;&#xff0c;您需要用到流并需要使用一种略微不同的方法。这时 WebClient 类仍很有帮助&#xff0c;因为它在 OpenReadAsync 中提供了一种非常适合的方法&#xff1a;

WebClient wc &#61; new WebClient();
wc.OpenReadCompleted &#43;&#61;
   new OpenReadCompletedEventHandler(callback);
wc.OpenReadAsync(address);

  关联回调的结构与上一示例中的完全相同。最后&#xff0c;使用 DownloadStringAsync 方法获取一个简单的字符串&#xff1b;使用 OpenReadAsync 方法获取任意数据的流。无论您决定下载字符串还是流&#xff0c;都属于个人喜好问题&#xff0c;在本质上取决于您打算如何使用接收到的数据。

  另外还要注意&#xff0c;WebClient 提供了一对可以向远程 URL 执行写入操作的方法&#xff1a;UploadStringAsync&#xff08;用于发布字符串&#xff09;和 OpenWriteAsync&#xff08;使用流将任意数据上载到 URL&#xff09;。
 您可以使用 Headers 属性来指定附加标头&#xff0c;因为默认情况下该类并不指定任何标头。但应注意&#xff0c;您设置的某些标头会被 Framework 剥离而改为进行内部管理。这些标头包括 Referer、Connection 和 User-Agent。Content-Type 标头&#xff08;如果已设置&#xff09;会被保留。

  在下载资源时&#xff0c;WebClient 类会在尝试连接之前透明地使用浏览器缓存。如果 XAML 或 XAP 资源不在缓存中&#xff0c;则该类将继续执行下载。之所以要从 Silverlight 应用程序下载内容&#xff0c;是 Silverlight 运行时和主机浏览器提供给插件的内部 API 这二者的共同作用而决定的。这意味着在 WebClient 的掩盖下&#xff0c;Silverlight 运行时会与浏览器沙箱进行沟通&#xff0c;以查看请求的资源是否已存在于缓存中。如果不在&#xff0c;Silverlight 会依照自己的安全策略继续授权请求。当数据最终从端点返回时&#xff0c;Silverlight 运行时会通过浏览器的服务将其缓存在本地&#xff0c;并完全遵循当前的缓存策略。

  Silverlight System.Net 命名空间中的 WebClient 类和其他 HTTP 类具有许多安全限制。特别是 WebClient 类仅支持 HTTP 和 HTTPS 方案&#xff08;当通过流进行下载时&#xff09;以及 FILE 方案&#xff08;当下载纯 XAML 时&#xff09;。跨方案访问被严格禁止&#xff0c;因此&#xff0c;如果宿主页面是通过 HTTP 下载的&#xff0c;则无法将 WebClient 指向 HTTPS 资源&#xff08;反之亦然&#xff09;。WebClient 请求通常可以进入不同浏览器区域内的某个 URL&#xff0c;但无法从一个 Internet 区域移至具有更严格限制的另一个区域。Silverlight 目前仅在 Windows 操作系统中支持区域。

  最后&#xff0c;仅当远程站点通过在其根目录下托管适当的 XML 文件来加入时&#xff0c;才支持跨域访问。另外还要注意&#xff0c;跨域访问对 HTTPS-to-HTTPS 方案不起作用。

  同一个 WebClient 对象无法同时处理多个请求。应检查 IsBusy 属性&#xff08;这是一个布尔值&#xff09;以确定您的代码在通过同一个 WebClient 实例发出新请求时是否安全。如果使用多个 WebClient 对象&#xff08;可能在不同的线程上&#xff09;&#xff0c;则可以同时启动多个下载。

  下载仅含 XAML 的数据

  让我们看一看如何使用 WebClient 下载 XAML 数据并将其集成到可视树中。在 Silverlight 2 应用程序中&#xff0c;动态下载纯 XAML 数据不一定会为您带来您需要的编程功能。XAML 字符串必须是纯 XAML&#xff0c;没有需要在运行时解析的任何引用&#xff0c;例如绑定或对样式、资源和事件的引用。

  XAML 字符串下载完毕后&#xff0c;可使用 XamlReader 类将其转换为 UI 元素&#xff0c;然后即可添加到现有文档对象模型中。下面的代码显示了如何以编程方式从某个 URL 下载 XAML 字符串。请注意&#xff0c;您需要提供 URL 作为 Uri 对象&#xff1a;

WebClient client &#61; new WebClient();
client.DownloadStringCompleted &#43;&#61;
  new DownloadStringCompletedEventHandler(OnDownloadCompleted);
Uri uri &#61; new Uri("xaml.ashx", UriKind.Relative);
client.DownloadStringAsync(uri);

  该 URL 可以指向纯 XAML 资源或指向返回 text/xaml 响应的端点。下面的代码将处理下载的 XAML 并将其附加到可视树中的一个占位符上&#xff1a;

void OnDownloadCompleted(object sender, DownloadStringCompletedEventArgs e)
{
  // Parse XAML to a UI element
  string xaml &#61; e.Result;
  UIElement dom &#61; XamlReader.Load(xaml) as UIElement;
  // Append to the DOM
  Placeholder.Children.Clear();
  Placeholder.Children.Add(dom);
}

  如前所述&#xff0c;占位符可以是插件中当前呈现的文档对象模型中的任何元素。请注意&#xff0c;UI 元素的子元素会形成一个集合&#xff0c;它们将呈现为一个序列。这意味着&#xff0c;为避免元素之间产生不必要的重叠&#xff0c;在更新时应首先删除这些元素然后再重新添加。

  XAML 序列化&#xff08;通过 XamlReader 和 XamlWriter 类执行&#xff09;所基于的原则是扩展引用被取消引用&#xff0c;而且运行时值通过设计时设置进行保存。如果希望下载 XAML 内容并在显示前对其进行自定义&#xff08;例如&#xff0c;通过动态数据绑定&#xff09;&#xff0c;这时该怎么办&#xff1f;您无法将绑定嵌入到 XAML 源中&#xff0c;但可以在下载的 XAML 中定义占位符、通过数据解析检索它们并以编程方式将其设置为您希望的任何值。但在 Silverlight 2 中&#xff0c;下载 XAP 程序包无疑是更合理的解决方案。

  使用 XAP 程序包

  XAP 程序包内含整个 Silverlight 应用程序&#xff0c;其用户界面主要由一个用户控件构成&#xff0c;此控件实际上只是 XAML 标记和代码的容器。

  如前所述&#xff0c;XAP 程序包内含指令清单文件所跟踪的一或多个程序集。除下载外&#xff0c;处理 XAP 程序包还会导致两个额外的步骤。您需要提取主程序集&#xff0c;然后实例化用于启动已下载应用程序的入口点类。毫无疑问&#xff0c;此时您可以在 XAML 中使用绑定、样式、事件以及其他任何需要的内容。使用 XAP 程序包时&#xff0c;是由 Silverlight 运行时&#xff08;而不是序列化 API&#xff09;来处理 XAML 并在随后解析引用。这将对编程功能产生巨大影响。

  下载和处理 XAP 程序包还需要许多其他工作&#xff0c;而不仅仅是通过字符串来构建对象模型。其中的一些工作&#xff08;通常包括内容下载和程序集提取&#xff09;可以转给可重复使用的 downloader 类&#xff08;请参见图 3&#xff09;。

在 Silverlight 中管理动态内容交付&#xff0c;第 1 部分图 3 动态下载 XAP 程序包

public partial class Page : UserControl
{
  private UIElement content &#61; null;
  private TabItem item &#61; null;
  public Page()
  {
    InitializeComponent();
  }
  private void chkNewContent_Click(object sender, RoutedEventArgs e)
  {
    bool shouldDisplay &#61; (sender as CheckBox).IsChecked.Value; 
    if (shouldDisplay)
    {
      if (!IsContentAvailable())
        DownloadContent();
      else
        ShowContent();
    }
    else
    {
      HideContent();
    }
  }
  private bool IsContentAvailable()
  {
    return (content !&#61; null);
  }
  private void DownloadContent()
  {
    Downloader dl &#61; new Downloader();
    dl.XapDownloaded &#43;&#61;
      new EventHandler(OnPackageDownload);
    dl.LoadPackage("more.xap", "more.dll", "More.ExtraTab");
  }
  void OnPackageDownload(object sender, XapEventArgs e)
  {
    content &#61; e.DownloadedContent;
    ShowContent();
  }
  private void HideContent()
  {
    this.TabList.Items.Remove(item);
  }
  private void ShowContent()
  {
    item &#61; new TabItem();
    item.Header &#61; "Extra tab";
    item.Content &#61; content;
    this.TabList.Items.Add(item);
  }
}

  图 3 中的代码显示了一个基于选项卡的示例应用程序&#xff0c;它会在用户首次单击复选框时加载一个新选项卡。在本例中&#xff0c;会下载一个新的 XAP 程序包&#xff0c;而且其用户界面中包含的用户控件将被插入到新建的 TabItem 中。隐藏新下载的程序包中的内容是一个简单的客户端操作。由于以下两个合理原因&#xff0c;重新显示该内容并不需要第二次往返动作&#xff1a;该内容被缓存在内存中&#xff1b;从中构建该内容的程序包被缓存在浏览器缓存中。



推荐阅读
  • 本文探讨了使用Python实现监控信息收集的方法,涵盖从基础的日志记录到复杂的系统运维解决方案,旨在帮助开发者和运维人员提升工作效率。 ... [详细]
  • 本文详细介绍了PHP中的几种超全局变量,包括$GLOBAL、$_SERVER、$_POST、$_GET等,并探讨了AJAX的工作原理及其优缺点。通过具体示例,帮助读者更好地理解和应用这些技术。 ... [详细]
  • 本文详细介绍了Socket在Linux内核中的实现机制,包括基本的Socket结构、协议操作集以及不同协议下的具体实现。通过这些内容,读者可以更好地理解Socket的工作原理。 ... [详细]
  • 本文探讨了在AspNetForums平台中实施基于角色的权限控制系统的方法,旨在为不同级别的用户提供合适的访问权限,确保系统的安全性和可用性。 ... [详细]
  • WebBenchmark:强大的Web API性能测试工具
    本文介绍了一款名为WebBenchmark的Web API性能测试工具,该工具不仅支持HTTP和HTTPS服务的测试,还提供了丰富的功能来帮助开发者进行高效的性能评估。 ... [详细]
  • 本文探讨了在不同场景下如何高效且安全地存储Token,包括使用定时器刷新、数据库存储等方法,并针对个人开发者与第三方服务平台的不同需求提供了具体建议。 ... [详细]
  • 本文探讨了一个Web工程项目的需求,即允许用户随时添加定时任务,并通过Quartz框架实现这些任务的自动化调度。文章将介绍如何设计任务表以存储任务信息和执行周期,以及如何通过一个定期扫描机制自动识别并加载新任务到调度系统中。 ... [详细]
  • 本文详细介绍了在PHP中如何获取和处理HTTP头部信息,包括通过cURL获取请求头信息、使用header函数发送响应头以及获取客户端HTTP头部的方法。同时,还探讨了PHP中$_SERVER变量的使用,以获取客户端和服务器的相关信息。 ... [详细]
  • This article explores the process of integrating Promises into Ext Ajax calls for a more functional programming approach, along with detailed steps on testing these asynchronous operations. ... [详细]
  • 使用 ModelAttribute 实现页面数据自动填充
    本文介绍了如何利用 Spring MVC 中的 ModelAttribute 注解,在页面跳转后自动填充表单数据。主要探讨了两种实现方法及其背后的原理。 ... [详细]
  • 探索CNN的可视化技术
    神经网络的可视化在理论学习与实践应用中扮演着至关重要的角色。本文深入探讨了三种有效的CNN(卷积神经网络)可视化方法,旨在帮助读者更好地理解和优化模型。 ... [详细]
  • 如何高效学习鸿蒙操作系统:开发者指南
    本文探讨了开发者如何更有效地学习鸿蒙操作系统,提供了来自行业专家的建议,包括系统化学习方法、职业规划建议以及具体的开发技巧。 ... [详细]
  • 使用jQuery与百度地图API实现地址转经纬度功能
    本文详细介绍了如何利用jQuery和百度地图API将地址转换为经纬度,包括申请API密钥、页面构建及核心代码实现。 ... [详细]
  • 本文详细介绍了如何使用Linux下的mysqlshow命令来查询MySQL数据库的相关信息,包括数据库、表以及字段的详情。通过本文的学习,读者可以掌握mysqlshow命令的基本语法及其常用选项。 ... [详细]
  • 本文详细探讨了如何根据不同的应用场景选择合适的PHP版本,包括多版本切换技巧、稳定性分析及针对WordPress等特定平台的版本建议。 ... [详细]
author-avatar
子晴一-夏
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有