现在的位置: 首页 > 综合 > 正文

MOSS推荐之2- Windows SharePoint Services对象模型

2012年02月15日 ⁄ 综合 ⁄ 共 13534字 ⁄ 字号 评论关闭
From SharePoint Portal Server 2003深入指南

第9章 Windows SharePoint Services对象模型

全文:

作为一个应用原型系统,SharePoint提供了很多强大的应用及管理功能,但是,在实际的应用中,这些原有的功能很难满足用户的需求。因此,SharePoint也提供了一套非常完整的对象模型作为底层接口,以此为基础进行二次开发。
Windows SharePoint Services(以下简称WSS)作为SharePoint Portal Server(以下简称SPS)的基础部分,是在开发过程中使用最多的一套体系。因此,在本书开发部分的开始,使用一章的篇幅来介绍WSS的对象模型体系,以及一些简单的应用。
WSS提供了非常完善的一套对象模型体系,大致整个的Web服务器(SPWebServer类)、小到一个文件的版本信息(SPFileVersion类),以及一些网站的设置,都可以通过WSS对象模型来进行访问或修改。此外,为了弥补.NET类库在该环境中的一些不足,在SharePoint中也提供了一些常用的功能的函数,它们被封装在Microsoft.SharePoint.Utilities命名空间中。
通过使用SharePoint的对象模型,可以完成几乎所有的功能,但是在有些时候,由于SharePoint本身的限制(例如严格的权限控制),有一些功能仅使用对象模型是很难完成甚至无法完成的。所以,在考虑用户需求和设计方案的时候,也应该尽可能地考虑到实现的难易程度。
在WSS对象模型体系中,提供了如下一些命名空间(namespace):
·    Microsoft.HtmlTrans.Interface
提供一个接口,用来将文档库中的某些文件以Html的形式显示到客户端,这样在客户端没有安装相应文件的浏览软件的时候,也可以查看这些文件;
·    Microsoft.SharePoint
该命名空间是最常用的一个命名空间,其中提供了WSS中关于网站内容的基本的对象模型,例如网站、列表、文件和用户等,本章也主要是围绕这一命名空间进行介绍;
·    Microsoft.SharePoint.Administration
该命名空间中主要提供了WSS网站管理和参数设置的一些类;
·    Microsoft.SharePoint.Dsp
该命名空间以及之下的几个命名空间提供了一些WSS数据搜索用的接口;
·    Microsoft.SharePoint.Meetings
该命名空间提供了对会议室网站进行操作的一些类;
·    Microsoft.SharePoint.Security
该命名空间提供了WSS中自定义权限管理相关的类;
·    Microsoft.SharePoint.SoapServer
该命名空间提供了关于WebPart和WebPart页相关的WebService类;
·    Microsoft.SharePoint.Utilities
该命名空间提供了一些网站中常用的功能,主要是扩充.NET类库中的一些功能;
·    Microsoft.SharePoint.WebControls
该命名空间提供了一些WSS中特有的Web控件类;
·    Microsoft.SharePoint.WebPartPages
该命名空间提供了Web部件和Web部件页相关的类,其中Web部件(WebPart)是SharePoint开发中一个重点的环节,我们将在第11章中对其进行详细地叙述;
·    Microsoft.SharePoint.WebPartPages.Communication
该命名空间提供了对Web部件通信的支持。
以上这些命名空间和类库封装在Microsoft.SharePoint.dll中。
由于篇幅的关系,在本章中只介绍最基本的一些对象模型的使用,只涉及到Microsoft.SharePoint这一命名空间。其他对象模型的使用方法请参照SDK。
为了便于实现和观察,也便于避开Web环境中一些复杂的机制和权限控制,本章和下一章的示例代码,均使用命令行(Console)方式完成。程序需要直接运行在装有WSS或SPS的服务器上,并且默认操作者具有网站管理员的权限。此外,本章和第10章着重讲解功能和某些注意事项,因此在介绍对象模型的时候,只介绍最常用的一些属性和方法,并且在每部分的最后给出一个示例说明该对象模型的使用方法。更全面的类库、属性和方法的介绍,以及更加详细的示例程序,请参考SDK中对应的章节。
本书中的代码均使用C#语言编写,在WSS或SPS环境中经过测试通过。

在绝大多数的WSS使用场景以及部分SPS的使用场景中,“网站”是使用得最多的一个对象,这是因为在WSS的体系结构中,几乎所有的内容(包括用户、列表、文件系统等)都是依附于网站之上的,这一点在对象模型中也能很明显地体现出来。
在WSS的结构中,网站被划分为Site(网站集)和Web(网站)这两种概念。
 Site和Web的区别与联系
顾名思义,网站集是网站的集合体,在WSS中,Site的主要用处是管理网站,在对象模型中,Site所对应的SPSite除了网站的集合(SPWebCollection)外几乎不包含任何的信息,所有的信息都是储存在Web所对应的SPWeb这一对象模型中的,Web才是真正的内容承载者。
在SharePoint的最初设计理念中,将网站集称为Web,而网站称为Site,这一点从SDK中的SharePoint体系结构图中可以看出来。
但是,在设计对象模型的时候,却又将网站集的对象模型称为SPSite(即SharePoint Site),而网站的对象模型称为SPWeb。因此在SDK的代码示例中经常可以看到例如“SPSite web;”以及“SPWeb site;”这样的变量命名方法,这对于初学者来说是非常容易引起混淆的。
作为开发的参考,为了便于说明,以及和对象模型的统一,本书中所指的Site均为网站集的概念(对应于SPSite),而Web均为网站的概念(对应于SPWeb),请读者注意。
 SPSite的使用
SPSite作为网站集对应的对象模型,其主要的用处是获取某个特定的网站,它最常用的几个功能如下:
        构造函数
SPSite的构造函数有两种,分别以Site的urlGuid作为参数。
在SharePoint对象模型中,大部分对象都拥有一个Guid作为它唯一的标识符,通过这个Guid可以获取该对象的实例,例如Site(SPSite)、Web(SPWeb)、列表(SPList)、视图(SPView)以及SPS中的区域(Area)、门户列表(AreaListing)等。但也有一些是采用整型作为标识符的,例如列表项(SPListItem)、角色(SPRole)和用户(SPUser)等。
        AllWebs属性
该属性是一个SPWebCollection类型的变量,通过这个属性,便可以获取到该网站集中的所有网站,包括一个顶级网站和它下面的所有子网站。然后通过以下三种形式之一,便可以得到某个特定的SPWeb的对象:
·      webs[int index]:根据在该集合中的下标获取一个Web;
·      webs[string url]:根据Web的相对url(相对于包含该网站的Site)获取Web;
·      webs[Guid guid]:根据唯一标识该Web的guid获取。
在WSS对象模型中,集合(Collection)是一个比较重要的概念,它实际上是一种对象的存储方式(即.NET框架中集合的概念)。一般来说SPxxxCollection便是SPxxx这个类所对应的集合类,例如SPWebCollectionSPListCollectionSPFileCollection等。但是应该注意到的是,SPSiteCollection并不是包含在Microsoft.SharePoint命名空间中的类,而是在Microsoft.SharePoint.Administration这一命名空间中的,这也说明了Site的主要意义在于网站本身的管理,而非网站内容的管理。
        RootWeb属性
该属性是一个SPWeb类型的变量,它所对应的对象是该网站集中的顶级网站,通过它可以逐步获得该网站级中的层级结构。(关于顶级网站和子网站的概念,请参考本书中“2.1.2Windows SharePoint 站点架构简介”部分。)
        OpenWeb函数
该函数返回一个SPWeb类型的变量,通过该函数可以获得该网站集中一个特定的网站。它有以下三种使用形式:
·      OpenWeb():在命令行模式中,该方法返回网站级中的顶级网站对象;但是在网络环境中使用该方法会有不同的效果,详见第11章11.3小节“WebPart编程中的对象模型”;
·      OpenWeb(Guid guid):返回该guid所对应的网站对象;
·      OpenWeb(string url):返回该url所对应的网站对象,注意该url为相对地址(相对于服务器根路径或者网站集根路径)。
【示例9-1】 SPSite的基本使用。
在这个示例中,展示了SPSite的基本用法,包括上面所提到的几个重要功能:

using System;
using Microsoft.SharePoint;
 
namespace WSSModelObject
{
    class Example1_1
    {
        [STAThread]
        static void Main(string[] args)
        {
            // 使用构造函数创建网站集对象
            SPSite site = new SPSite("http://localhost");
            // 获取根网站信息
            Console.WriteLine("Root:" + site.RootWeb);
            // 遍历网站集中的所有网站
            Console.WriteLine("All Webs in site:");
            foreach(SPWeb web in site.AllWebs)
            {
                Console.WriteLine(web);
            }
            // 打开某个特定的网站
            try
            {
                SPWeb web = site.OpenWeb("subweb1");
                Console.WriteLine(web);
            }
            catch(Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }
}
在编写程序的过程中,合理地使用try-catch语句是一种良好的编程习惯。SharePoint是一套相对庞大的对象模型体系,在编写程序的过程中,难免会出现预料之外的问题,因此应该尽可能对所有可能出错的语句进行try-catch,如果有必要,捕获每种特定的异常,并提供更加友好的出错信息,使得程序更加人性化。在本书中,为了节省篇幅,除非特殊的需要和说明,在后面的示例程序中一般不进行try-catch处理,请读者注意。
编写SharePoint的程序需要在引用中添加Microsoft.SharePoint.dll,如果是在安装了WSS的机器上,在VS.NET中添加引用界面选择Windows SharePoint Services即可,也可以将dll复制到其他的机器上再引用(但是仍然需要在装有WSS的机器上才能运行),该dll的默认安装路径为:C:\Program Files\Common Files\Microsoft Shared\web server extensions\60\ISAPI。在本书后续章节中除非特殊情况,不再使用完整的代码,而使用代码片段作为示例。
 
SPWeb的使用
在一般的应用场景下,SPWeb是使用最多的一个对象模型,因为几乎所有的WSS的内容都是依附于SPWeb对象的,在编程中也是使用SPWeb来获取其他的对象模型。而获取SPWeb对象的方法在本章9.2.2小节“SPSite的使用”中已经有所提及,就不在这里重复了。
大多数和WSS相关的内容都是通过使用SPWeb的一些成员变量来获取的,这些成员变量主要有:
·      AllUsers网站内的所有成员,以及通过域组(domain group)来浏览网站的用户;
·      Files:网站下的所有文件;
·      Folders:网站下的所有目录;
·      Groups:所有跨网站用户组;
·      Lists:网站下的所有列表;
·      ListTemplates:网站下的所有列表模板;
·      Permissions:网站下的权限;
·      Roles:网站下的所有角色;
·      Users:属于该网站的所有用户;
·      Webs:该网站的所有子网站。
通过使用这些成员变量,就可以获取到相应内容的对象集合(Collection),进而获取需要的内容。
此外,SPWeb的某些成员变量对应于该网站的一些属性,例如:
·      AllowAnonymousAccess:网站是否允许匿名访问(只读);
·      Author:网站的创建者(只读);
·      Created:网站的创建时间(只读);
·      CurrentUser:当前的用户(只读);
·      ID:唯一标识该网站的Guid(只读);
·      IsRootWeb:该网站是否为顶级网站(只读);
·      ParentWeb:该网站的父网站(只读);
·      Site:该网站所属的网站集(只读);
·      Title:该网站的标题;
·      Url:该网站的url(绝对路径)(只读);
·      WebTemplate:该网站的模板(只读)。
SPWeb对象还提供了一些方法,例如:
·      GetFile:通过url获取一个文件的对象(SPFile);
·      GetFolder:通过url获取一个目录的对象(SPFolder);
·      GetListOfType:获取特定类型的列表的集合;
·      GetSubwebsForCurrentUser:获取该网站的子网站(可以指定模板);
·      GetViewFromUrl:通过url获取一个列表的视图(SPView);
·      SaveAsTemplate:将网站保存为模板;
·      SearchDocuments:搜索网站的文档库(需要开启全文检索功能);
·      SearchListItems:搜索列表条目;
·      Update:使网站属性的修改生效。
在SharePoint的对象模型中,一些比较重要的对象都会拥有Update方法(例如SPWebSPListSPFieldSPListItem),在修改这些对象的属性时,只有在调用Update方法之后,这些修改才会真正生效。仅限普通属性,不包括集合类的属性,比如SPWeb.Lists中添加或删除一个列表,不需要调用SPWeb.Update方法。这一点需要特别引起注意 。
【示例9-2】 得到一个网站集中的所有网站,并输出它们的标题、url、父网站及子网站。
SPSite site = new SPSite("http://localhost");
foreach(SPWeb web in site.AllWebs)
{
    Console.WriteLine("Title: " + web.Title);
    Console.WriteLine("Url: " + url);
    Console.WriteLine("ParentWeb: " + web.ParentWeb);
    Console.Write("SubWebs: ");
    foreach(SPWeb subweb in web.Webs)
        Console.Write(subweb.ToString() + " ");
    Console.WriteLine();
}
一个小技巧:在.NET对象模型中,每个类都会有ToString函数(继承自Object类),一般来说每个类也都会对这个方法进行重写。SPWebToString方法一般情况下就是返回了SPWeb.Title这个属性。在大多数字符串操作过程中,一个对象的ToString方法是可以被隐式调用的(例如示例9-1中就使用了这个特性),在不知道该对象是否为空(null)的时候,如果显式调用ToString方法会造成一个异常(NullReferenceException),但是在隐式调用的时候便不会产生这个异常,如果仅是作为输出的需要,隐式的调用要比显式的调用更加方便。
在示例9-2中,输出每个网站的父网站,但是对于顶级网站来说,其父网站为空,如果显示调用的话,便会出现异常,采用隐式调用就可以避开这个问题,只显示一个空的字符串。
示例9-2中使用了三种输出网站标题的方法:SPWeb.Title属性,隐式调用SPWeb.ToString方法,显式调用SPWeb.ToString方法。
列表相关的对象模型
在SharePoint中,列表(List)是一个非常强大的功能。它可以方便地自定义列表的结构,添加和删除各种类型的字段,也可以设置计算字段和引用字段,也可以设置列表条目的审批功能,同时列表还提供了多种可以自定义的视图,对列表的数据进行筛选或者排序。本节主要介绍列表相关的对象,包括列表(SPList)、列表视图(SPListView)、列表字段(SPField)和列表条目(SPListItem)。
SPList的使用
在使用列表的功能之前,通常都需要首先获取该列表对象,获取SPList的主要方法是通过访问SPWeb.Lists这一SPListCollection类型的属性。从该集合中获得一个特定的列表对象有如下三种方法:
·      lists[string name]:通过该列表的名称获取;
·      lists[Guid guid]:通过唯一标识该列表的Guid获取;
·      lists[int index]:通过列表在该列表集合中的下标来获取
在网站上添加列表,也是通过SPListCollection对象来完成的。添加一个列表通过调用SPListCollection.Add方法来完成,创建成功之后,会返回新列表的Guid。该方法提供了如下三种形式:
·      Addstring title, string description, SPListTemplate template
前两个参数分别指定列表的名称和简介,第三个参数是一个SPListTemplate型变量,指定该列表创建时所需要的列表模板对象,该对象可以通过SPWeb.ListTemplates属性获得;
·      Addstring title, string description, SPListTemplateType type
前两个参数指定列表的名称和简介,第三个参数使用SPListTemplateType型枚举指定列表模板,枚举中所包含的都是网站默认拥有的基本类型的列表模板,如果要创建一个自定义的列表,请使用SPListTemplateType.GenericList作为该参数;
·      Addstring title, string description, SPListTemplate template, SPDocTemplate docTemplate
该方法会创建一个文档库(一种特殊的列表),前两个参数分别指定列表的名称和简介,第三个参数制定列表的模板对象,第四个参数指定文档库的模板对象,该参数是一个SPDocTemplate类型的变量,可以通过SPWeb.DocTemplates属性获得。
删除列表时,通过调用SPListCollection.Delete方法来完成,该方法只有一种形式:
·      DeleteGuid guid:通过列表的Guid删除该列表。
SPList类有如下一些常用的属性:
·      BaseTemplate:该列表所对应的列表模板(只读);
·      BaseType:该列表的基础类型(只读);
·      DefaultView:该列表的默认视图对象(只读);
·      EnableAttachments:该列表是否允许添加附件;
·      EnableModeration:该列表是否开启审批功能;
·      Fields:列表字段的集合(将在下文进行详细介绍);
·      Forms:列表表单的集合,主要有浏览、添加和修改几个页面(只读);
·      ID:标识该列表的唯一的Guid(只读);
·      Items:列表条目的集合(将在下文进行详细介绍);
·      ParentWeb:列表所在的网站对象(只读);
·      Title:列表的标题;
·      Views:该列表的视图的集合(将在下文进行详细介绍)(只读)。
这其中值得注意的是BaseTemplateBaseType的区别:BaseTemplate是生成列表的模板,例如“链接”和“联系人”等,每个模板中都默认设置了一些独有的字段,该属性为SPListTemplateType型的枚举类型,默认这些模板的字段设置可以在SDK的“Field Tables for Default Lists”一节中找到;它的具体位置为Microsoft Windows Share Point ServicesŽAppendixŽField Tables for Default Lists,而BaseType则是列表的基础类型,例如“讨论区”、“文档库”等,每种基础类型的列表都提供了一些普通列表所不具备的特殊的功能,该属性为SPBaseType型的枚举类型。
修改列表属性后,通过调用SPList.Update方法使修改生效。
【示例9-3】 在该示例中创建一个联系人列表,并显示该列表添加条目页面的url。
// 首先获取到SPWeb对象web
SPListCollection lists = web.Lists;
SPList newList = null;
try
{
    Guid id = lists.Add("DemoList", "这是一个测试列表",
                            SPListTemplateType.Contacts);
    newList = lists[id];
    SPForm form = newList.Forms[PAGETYPE.PAGE_NEWFORM];
    Console.WriteLine(form.Url);
}
catch
{
    Console.WriteLine("列表添加失败");
}
SPView的使用
视图在SharePoint的列表中是一个比较重要的概念,在SharePoint中,列表中所有的数据都是通过视图呈现给用户的,视图中定义了需要显示的字段,以及显示的方式,某一些特殊的列表默认拥有一些特殊的视图将数据呈现出来,例如“任务”模板列表中的“日历视图”,等等。
除了负责数据的多样化显示,视图的另外一个重要的作用就是对数据的筛选和排序,在SharePoint环境中可以直接对视图的这些内容方便地进行修改,通过使用计算字段,可以更加丰富筛选和排序的功能。
获取一个SPView对象有如下几种方法:
(1)通过List.DefaultView属性得到该列表的默认视图;
(2)通过SPWeb.GetViewFromUrl方法根据url获取到该视图,在实际使用中,可以使用这种方法先获得一个视图,再得到这个视图所在的列表的对象;
(3)通过List.Views得到该列表的所有视图,再通过以下三种方式得到特定的视图:
·      views[string name]:通过视图的名称获取;
·      views[Guid guid]:通过唯一标识该视图的Guid获取;
·      views[int index]:通过视图在该视图集合中的下标来获取。
添加一个视图通过调用SPViewCollection.Add方法来完成,该方法有两种形式:
·      Add(string name, StringCollection fields, string query,
         uint rowLimit, bool paged, bool makeDefault)
name参数指定了视图的名称,fields参数指定了需要显示的字段名的集合,query参数指定了对数据的筛选条件,rowLimit参数指定了视图中一次所返回的列表条目的最大个数,paged参数指定了该视图是否支持翻页,makeDefault参数指定了是否需要将该视图作为默认视图;
·      Add(string name, StringCollection fields, string query,
         uint rowLimit, bool paged, bool makeDefault
·      SPViewCollection.SPViewType type, bool personalView)
前面的参数含义与上文相同,type参数指定了该视图的基本类型,personalView参数指定了该视图是否为个人视图(否则为公共视图)。
需要注意的是,与列表的添加方法不同,视图的添加方法返回的对象是SPView的对象,而不是一个Guid。
删除一个视图同样使用SPViewCollection.Delete方法,用视图的Guid作为参数。
有一点需要注意的是,在使用默认页面浏览一个视图的时候,视图的翻页功能只有“下一页”而没有“上一页”,如果需要实现向前翻页的功能,需要自己编写程序来完成。
SPView中常用的一些属性如下:
·      ID:标识该视图的唯一的Guid;
·      ParentList:包含该视图的列表对象(SPList);
·      PropertiesXml:该视图的基本信息,包括url、名称和类型等(只读);
·      Query:视图中对数据进行筛选的查询xml字符串;
·      SchemaXml:该视图的信息,包括基本信息和例如筛选、分组等的设置(只读);
·      Title:该视图的名称;
·      ViewFields:该视图需要显示的字段的集合;
·      Url:显示该视图所对应的url(只读);
修改视图属性后,使用SPView.Update方法使修改生效。
示例9-4】 该示例中创建了一个视图,并指定了该视图的一些设置:
// 首先获取到SPWeb对象web
SPList list = web.Lists["ListName"];
SPViewCollection views = list.Views;
 
string viewName = "View_Name";
 
StringCollection viewFields = new StringCollection();
 
viewFields.Add("Field1_Name");
viewFields.Add("Field2_Name");
viewFields.Add("Field3_Name");
 
string query = "<Where><Eq><FieldRef Name=\"Field3_Name\"/>" +
    "<Value Type=\"Text\">Text</Value></Eq></Where>";
 
views.Add(viewName, viewFields, query, 100, true, false);
程序中的StringCollection类是在System.Collection.Specialized命名空间中。
SPField的使用
SPField对应于列表对象中的字段(Field)对象,和传统的数据表一样,SharePoint的列表也是由若干个字段所组成的,每个字段可以设置为不同的数据类型,也可以拥有不同的属性设置(例如默认值、是否隐藏、是否可以为空等)。而SharePoint列表的最大一个特点,就是字段的可定制性,不需要进行任何的编程,也不用深入数据库进行复杂的调整,仅在使用界面上就可以方便地对列表的字段进行添加、删除和修改,这种方便的特性也正是很多用户喜爱SharePoint的一个重要原因。
获得一个SPField对象的方法主要是通过SPList.Fields属性得到所有字段,再通过以下两种方法得到特定字段的对象。
·      fields[string dispName]:根据该字段的显示名称获得
·      fields[int index]:根据该字段在集合中的下标获得。
为列表添加一个字段有三种方法:
·      Add(string dispName, SPFieldType type, bool bRequired)
其中第一个参数指定该字段的显示名称,第二个参数指定该字段的类型,第三个参数指定在填写数据的时候,该字段是否为必填内容。
·      AddLookup(string dispName, Guid lookupListID, bool bRequired)
该方法为列表添加一个特殊的字段——“查阅项”字段,通过第二个参数指定目标列表的Guid,其余两个参数的含义和使用方法与Add方法相同。
·      AddFieldAsXml(string strXml)
通过一段xml的描述添加一个字段,该描述中需要包括字段等信息,实际上,这段xml描述对应于SPField.SchemaXml属性。
需要特别注意的是,以上三种方法所返回的虽然都是string型的变量,但是它们的含义各不相同,Add方法返回的是显示名称,AddLookup方法所返回的是内部名称(这两者的区别稍候会进行介绍),而AddFieldAsXml方法所返回的是一段包含字段名称的xml。
注意
在添加字段时,bRequired参数(对应与SPField.Required属性)指定的是在用户界面中新建、修改列表项的时候,该字段是否为必填内容。但实际上,如果我们通过对象模型来创建或修改一个列表条目的时候,该字段并不会起到任何限制作用。
SPField拥有如下一些比较常用的属性。
·      DefaultFormula:计算字段的公式;
·      DefaultValue:字段的默认值,即该字段在未进行设置时的初始值;
·      Description:该字段的说明描述信息;
·      Filterable:该字段是否可以被筛选(只读);
·      Hidden:该字段是否在列表中显示;
·      InernalName:该字段的内部名称;
·      ParentList:该字段所在的列表的对象;
·      ReadOnlyField:该字段是否被只读;
·      Required:在填写数据的时候,该字段是否必填;
·      SchemaXml:该字段设置的信息;
·      Sortable:该字段是否可以被排序;
·      Title:该字段的字段名,即显示名称;
·      Type:该字段的类型,为SPFieldType类型的枚举。
在SharePoint中,每一个字段都有两个名称,即显示名称(DisplayName)和内部名称(InternalName),它们在对象模型中对应于SPField.TitleSPField.InternalName。其中显示名称是在用户使用界面上看到的名称,虽然该名称在网站使用界面的操作中是不可重复的,但是,在使用对象模型添加字段的时候,或者内部管理的时候,可能会有一些重名的字段(其中典型的代表就是每个列表的“标题”字段);而内部名称是SharePoint列表用于内部管理的名称,它在列表中是唯一的,而且其中没有中文和空格(在保存为内部名称时,对于这些字符会进行编码)。
在编写程序的时候,

抱歉!评论已关闭.