Web Service描述语言 WSDL 详解 (2)

WSDL的Types栏和Messages栏中的XML Schema

  WSDL数据类型是基于"XML Schema: Datatypes"(XSD)的,现在已经被W3C推荐。这一文档共有三个版本(1999,2000/10,2001),因此必须在namespace属性的<definitions>元素中指明所使用的是哪一个版本。

  

xmlns:xsd="http://www.w3.org/2001/XMLSchema"


  在本文中,我将只考虑2001版本。WSDL标准的推荐者强烈建议使用2001版。

  在本栏和以后各部分,需使用以下简缩或前缀

前缀代表的Namespace描述
Soapenchttp://schemas.xmlsoap.org/soap/encodingSOAP 1.1 encoding
Wsdlhttp://schemas.xmlsoap.org/wsdl/soapWSDL 1.1
Xsd http://www.w3.org/2001/XMLSchemaXML Schema


  XSD基类型

  下表是直接从MSTK2文档中取出的,列举了MSTK2所支持的所有XSD基类型。它也告诉在客户端或服务器端的WSDL读取程序如何把XSD类型映射到在VB、C++和IDL中相应的类型。

XSD (Soap)类型变量类型 VBC++IDLComments
anyURIVT_BSTRStringBSTRBSTR 
base64Binary VT_ARRAY | VT_UI1Byte()SAFEARRAYSAFEARRAY(unsigned char) 
BooleanVT_BOOL Boolean VARIANT_BOOLVARIANT_BOOL 
ByteVT_I2Integershortshort转换时验证范围有效性
DateVT_DATEDateDATEDATE时间设为 oo:oo:oo
DateTimeVT_DATEDateDATEDATE 
DoubleVT_R8Doubledoubledouble 
DurationVT_BSTRStringBSTRBSTR不转换和生效
ENTITIESVT_BSTRStringBSTRBSTR不转换和生效
ENTITYVT_BSTRStringBSTRBSTR不转换和生效
FloatVT_R4Singlefloatfloat 
GDayVT_BSTRStringBSTRBSTR不转换和生效
GMonthVT_BSTRStringBSTRBSTR不转换和生效
GMonthDayVT_BSTRStringBSTRBSTR不转换和生效
GYearVT_BSTRStringBSTRBSTR不转换和生效
GYearMonthVT_BSTRStringBSTRBSTR不转换和生效
IDVT_BSTRStringBSTRBSTR不转换和生效
IDREFVT_BSTRStringBSTRBSTR不转换和生效
IDREFSVT_BSTRStringBSTRBSTR不转换和生效
IntVT_I4Longlonglong 
IntegerVT_DECIMALVariantDECIMALDECIMAL转换时范围生效
LanguageVT_BSTRStringBSTRBSTR不转换和生效
LongVT_DECIMALVariantDECIMALDECIMAL转换时范围生效
NameVT_BSTRStringBSTRBSTR不转换和生效
NCNameVT_BSTRStringBSTRBSTR不转换和生效
negativeIntegerVT_DECIMALVariantDECIMALDECIMAL转换时范围生效
NMTOKENVT_BSTRStringBSTRBSTR不转换和生效
NMTOKENSVT_BSTRStringBSTRBSTR不转换和生效
nonNegativeIntegeVT_DECIMALVariantDECIMALDECIMAL转换时范围生效
nonPositiveIntegerVT_DECIMALVariantDECIMADECIMAL转换时范围生效
normalizedStringVT_BSTRStringBSTRBSTR 
NOTATIONVT_BSTRStringBSTRBSTR不转换和生效
NumberVT_DECIMALVariantDECIMALDECIMAL 
positiveIntegerVT_DECIMALVariantDECIMALDECIMAL转换时范围生效
QnameVT_BSTRStringBSTRBSTR不转换和生效
ShortVT_I2Integershortshort 
StringVT_BSTR StringBSTRBSTR 
TimeVT_DATEDateDATEDATE日设为1899年12月30日
TokenVT_BSTRStringBSTRBSTR不转换和生效
unsignedByteVT_UI1Byteunsigned charunsigned char 
UnsignedIntVT_DECIMALVariantDECIMALDECIMAL转换时范围生效
unsignedLongVT_DECIMALVariantDECIMALDECIMAL转换时范围生效
unsignedShortVT_UI4LongLongLong转换时范围生效


  XSD定义了两套内建的数据类型:原始的和派生的。在下文中查阅内建数据类型的层次十分有益:

http://www.w3.org/TR/2001/PR-xmlschema-2-20010330complex类型

  XML schema允许complex类型的定义,就像C里是struct。例如,为了定义类似如下的C的struct类型:

typedef struct {
 string firstName;
 string lastName;
 long ageInYears;
 float weightInLbs;
 float heightInInches;
} PERSON;

  我们可以写XML schema:

<xsd:complexType name="PERSON">
<xsd:sequence>
 <xsd:element name="firstName" type="xsd:string"/>
 <xsd:element name="lastName" type="xsd:string"/>
 <xsd:element name="ageInYears" type="xsd:int"/>
 <xsd:element name="weightInLbs" type="xsd:float"/>
 <xsd:element name="heightInInches" type="xsd:float"/>
</xsd:sequence>
</xsd:complexType>

  不过,complex类型可以表达比struct更多的信息。除了<sequence>以外,它还可以有其他的子元素,比如<all>

<xsd:complexType name="PERSON">
<xsd:all>
 <xsd:element name="firstName" type="xsd:string"/>
 <xsd:element name="lastName" type="xsd:string"/>
 <xsd:element name="ageInYears" type="xsd:int"/>
 <xsd:element name="weightInLbs" type="xsd:float"/>
 <xsd:element name="heightInInches" type="xsd:float"/>
</xsd:all>
</xsd:complexType>

  这意味着<element>的成员变量可以以任何顺序排列,每一个都是可选的。这和C中的struct类型不太一样。

  注意内建数据类型string, int, float。C的string也是XML的string,float也类似。但C中的long类型在XML中是int(上表中)。

  在WSDL文件中,像上面的complex类型可以在Types栏声明。例如,我可以用以下方式声明PERSON类型并用在Messages栏。

<?xml version="1.0" encoding="UTF-8" ?>
<definitions … >
<types>
<schema targetNamespace="someNamespace"
xmlns:typens="someNamespace" >
<xsd:complexType name="PERSON">
<xsd:sequence>
 <xsd:element name="firstName" type="xsd:string"/>
 <xsd:element name="lastName" type="xsd:string"/>
 <xsd:element name="ageInYears" type="xsd:int"/>
 <xsd:element name="weightInLbs" type="xsd:float"/>
 <xsd:element name="heightInInches" type="xsd:float"/>
</xsd:sequence>
</xsd:complexType>
</schema>
</types>

<message name="addPerson">
 <part name="person" type="typens:PERSON"/>
</message>

<message name="addPersonResponse">
 <part name="result" type="xsd:int"/>
</message>

</definitions>

  上例中第一个消息由"adperson",并且有一个<part>,其类型为"PERSON"。PERSON类型是在Types栏声明的。

  如果我们使用完整的WSDL文件包含以上的部分,并以之初始化MSTK2 SoapClient,它将成功的解析该文件。当然,它不会去调用<addPerson>。这是因为SoapClient本身并不知道如何处理complex类型,它需要定制类型映射来处理complex类型。MSTK2文档中有包含定制类型映射的示例。

  还有另一种方法可以把<part>元素联系到类型声明。这就是使用元素。下例中我将Types栏中声明两个元素("Person"和"Gendr"),然后我将在"addPerson"<message>中使用元素属性来引用它们。

<?xml version="1.0" encoding="UTF-8" ?>
<definitions … >
<types>
<schema targetNamespace="someNamespace"
 xmlns:typens="someNamespace" >
<element name="Person">
<xsd:complexType>
 <xsd:sequence>
  <xsd:element name="firstName" type="xsd:string"/>
  <xsd:element name="lastName" type="xsd:string"/>
  <xsd:element name="ageInYears" type="xsd:int"/>
  <xsd:element name="weightInLbs" type="xsd:float"/>
  <xsd:element name="heightInInches" type="xsd:float"/>
 </xsd:sequence>
</xsd:complexType>
</element>
<element name="Gender">
<xsd:simpleType>
 <xsd:restriction base="xsd:string">
  <xsd:enumeration value="Male" />
  <xsd:enumeration value="Female" />
 </xsd:restriction>
</xsd:simpleType>
</element>
</schema>
</types>

<message name="addPerson">
 <part name="who" element="typens:Person"/>
 <part name="sex" element="typens:Gender"/>
</message>

<message name="addPersonResponse">
 <part name="result" type="xsd:int"/>
</message>
</definitions>

  Types栏中的Gender<element>里嵌入了枚举类型,其枚举值为"Male""Female"。然后我又在"addPerson"<message>中通过元素属性而不是类型属性来引用它。

  "元素属性"和"类型属性"在把某特定类型关联到<part>时有什么不同呢?使用元素属性,我们可以描述一个部分,它可以假定几个类型(就像变量一样),而是用类型属性我们就无法这样做。下例说明了这一点。

<?xml version="1.0" encoding="UTF-8" ?>
<definitions … >
<types>
<schema targetNamespace="someNamespace"
xmlns:typens="someNamespace">
<xsd:complexType name="PERSON">
 <xsd:sequence>
  <xsd:element name="firstName" type="xsd:string"/>
  <xsd:element name="lastName" type="xsd:string"/>
  <xsd:element name="ageInYears" type="xsd:int"/>
  <xsd:element name="weightInLbs" type="xsd:float"/>
  <xsd:element name="heightInInches" type="xsd:float"/>
 </xsd:sequence>
</xsd:complexType>
<xsd:complexType name="femalePerson">
<xsd:complexContent>
 <xsd:extension base="typens:PERSON" >
 <xsd:element name="favoriteLipstick" type="xsd:string" />
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:complexType name="malePerson">
<xsd:complexContent>
<xsd:extension base="typens:PERSON" >
<xsd:element name="favoriteShavingLotion" type="xsd:string" />
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:complexType name="maleOrFemalePerson">
<xsd:choice>
 <xsd:element name="fArg" type="typens:femalePerson" >
 <xsd:element name="mArg" type="typens:malePerson" />
</xsd:choice>
</xsd:complexType>
</schema>
</types>

<message name="addPerson">
 <part name="person" type="typens:maleOrFemalePerson"/>
</message>

<message name="addPersonResponse">
 <part name="result" type="xsd:int"/>
</message>

</definitions>

  上例也告诉我们extension的派生。"femailPerson"和"malePerson"都是从"PERSON"派生出来的。它们各有一些额外的元素:"femalePerson"有"favoriteLipstick"元素,"malePerson"有"favoriteShavingLotion"元素。两派生类型都归入一个complex类型"maleOrFemalePerson",使用的是<choice>构造。最后,在"adperson"<message>中,新类型有"person"<part>引用。这样,参数或<part>就可以是"femalePerson"或"malePerson"了。
数组

  XSD提供<list>结构来声明一个数组,元素之间有空格界定。不过SOAP不是使用XSD来编码数组的,它定义了自己的数组类型--"SOAP-ENC: Array"。下列的例子揭示了从这一类型派生出一位整数数组的方法:

<xsd:complexType name="ArrayOfInt">
<xsd:complexContent>
 <xsd:restriction base="soapenc:Array">
  <attribute ref="soapenc:arrayType" wsdl:arrayType="xsd:int[]"/>
 </xsd:restriction>
</xsd:complexContent>
</xsd:complexType>

  新的complex类型从soapenc:array限制派生。然后又声明了complex类型的一个属性。引用"soapenc:arrayType"实际上是这样完成的:

<xsd:attribute name="arrayType" type="xsd:string"/>

  wsdl:arrayType属性值决定了数组每个成员的类型。数组的成员也可以是Complex类型。:

<xsd:complexType name="ArrayOfPERSON">
<xsd:complexContent>
<xsd:restriction base="soapenc:Array">
<attribute ref="soapenc:arrayType"
wsdl:arrayType="typens:PERSON[]"/>
</xsd:restriction>
</xsd:complexContent>
</xsd:complexType>

  WSDL要求数组的类型由"ArrayOf"和每个数组元素的类型串联而成。很显然,顾名思义,"ArrayOfPERSON"是PERSON结构的数组。下面我将使用ArrayOfPERSON来声明一个<message>,并加入不止一个PERSON:

<?xml version="1.0" encoding="UTF-8" ?>
<definitions … >
<types>
<schema targetNamespace="someNamespace"
xmlns:typens="someNamespace" >
<xsd:complexType name="PERSON">
 <xsd:sequence>
  <xsd:element name="firstName" type="xsd:string"/>
  <xsd:element name="lastName" type="xsd:string"/>
  <xsd:element name="ageInYears" type="xsd:int"/>
  <xsd:element name="weightInLbs" type="xsd:float"/>
  <xsd:element name="heightInInches" type="xsd:float"/>
 </xsd:sequence>
</xsd:complexType>
<xsd:complexType name="ArrayOfPERSON">
<xsd:complexContent>
<xsd:restriction base="soapenc:Array">
<attribute ref="soapenc:arrayType"
wsdl:arrayType="typens:PERSON[]"/>
</xsd:restriction>
</xsd:complexContent>
</xsd:complexType>
</schema>
</types>

<message name="addPersons">
 <part name="person" type="typens:ArrayOfPERSON"/>
</message>

<message name="addPersonResponse">
 <part name="result" type="xsd:int"/>
</message>

</definitions>
<portType>和<operation>元素

  PortType定义了一些抽象的操作。PortType中的operation元素定义了调用PortType中所有方法的语法,每一个operation元素声明了方法的名称、参数(使用<message>元素)和各自的类型(<part>元素要在所有<message>中声明)。

  在一篇WSDL文档中可以有几个<PortType>元素,每一个都和一些相关操作放在一起,就和COM和一组操作的接口相似。

  在<operation>元素中,可能会有至多一个<input>元素,一个<output>元素,以及一个<fault>元素。三个元素各有一个名字和一个消息属性。

  <input>, <output>, <fault>元素属性的名字有何含义呢?它们可以用来区别两个同名操作(重载)。例如,看下面两个C函数:

void foo(int arg);
void foo(string arg);

  这种重载在WSDL中可以这样表示:

<?xml version="1.0" encoding="UTF-8" ?>
<definitions name="fooDescription"
 targetNamespace="http://tempuri.org/wsdl/"
 xmlns:wsdlns="http://tempuri.org/wsdl/"
 xmlns:typens="http://tempuri.org/xsd"
 xmlns:xsd="http://www.w3.org/2001/XMLSchema"
 xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
 xmlns:stk="http://schemas.microsoft.com/soap-toolkit/wsdl-
 extension"
 xmlns="http://schemas.xmlsoap.org/wsdl/">
<types>
<schema targetNamespace="http://tempuri.org/xsd"
 xmlns="http://www.w3.org/2001/XMLSchema"
 xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
 xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
 elementFormDefault="qualified" >
</schema>
</types>

<message name="foo1">
 <part name="arg" type="xsd:int"/>
</message>

<message name="foo2">
 <part name="arg" type="xsd:string"/>
</message>

<portType name="fooSamplePortType">
<operation name="foo" parameterOrder="arg " >
 <input name="foo1" message="wsdlns:foo1"/>
</operation>
<operation name="foo" parameterOrder="arg " >
 <input name="foo2" message="wsdlns:foo2"/>
</operation>
</portType>

<binding name="fooSampleBinding" type="wsdlns:fooSamplePortType">
<stk:binding preferredEncoding="UTF-8" />
<soap:binding style="rpc"
transport="http://schemas.xmlsoap.org/soap/http"/>
<operation name="foo">
<soap:operation soapAction="http://tempuri.org/action/foo1"/>
 <input name="foo1">
  <soap:body use="encoded" namespace="http://tempuri.org/message/"
    encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" />
 </input>
</operation>
<operation name="foo">
<soap:operation soapAction="http://tempuri.org/action/foo2"/>
<input name="foo2">
<soap:body use="encoded"
   namespace="http://tempuri.org/message/"
   encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
/>
</input>
</operation>
</binding>

<service name="FOOService">
<port name="fooSamplePort" binding="fooSampleBinding">
<soap:address
  location="http://carlos:8080/fooService/foo.asp"/>
</port>
</service>
</definitions>

  到目前为止,还没有一种SOAP的实现支持重载。这对基于JAVA的客户端十分重要,因为JAVA服务器使用的接口用到JAVA的重载特性。而对基于COM的客户端,就不那么重要,因为COM是不支持重载的。

<binding>和<operation>元素

  Binding栏是完整描述协议、序列化和编码的地方,Types, Messages和PortType栏处理抽象的数据内容,而Binding栏是处理数据传输的物理实现。Binding栏把前三部分的抽象定义具体化。

  把相关的数据制定和消息声明分开,这意味着同一类型服务的提供者可以把一系列的操作标准化。每个提供者可以提供定制的binding来互相区分。WSDL也有一个重要的结构,使抽象定义可以放在分离的文件中,而不是和Bindings和Services在一起,这样可在不同的服务提供者之间提供标准化的抽象定义,这很有帮助。例如,银行可以用WSDL文档来标准化一些银行的操作。每个银行仍然可以自由的订制下层的协议、串行优化,及编码。

  下面是重载的WSDL示例 的Binding栏,重复在此以便讨论:

<binding name="fooSampleBinding" type="wsdlns:fooSamplePortType">
<stk:binding preferredEncoding="UTF-8" />
<soap:binding style="rpc"
 transport="http://schemas.xmlsoap.org/soap/http"/>
<operation name="foo">
 <soap:operation soapAction="http://tempuri.org/action/foo1"/>
  <input name="foo1">
   <soap:body use="encoded" namespace="http://tempuri.org/message/"
         encodingStyle=
         "http://schemas.xmlsoap.org/soap/encoding/" />
  </input>
 </operation>
<operation name="foo">
<soap:operation soapAction="http://tempuri.org/action/foo2"/>
 <input name="foo2">
  <soap:body use="encoded"
          namespace="http://tempuri.org/message/"
        encodingStyle=
          "http://schemas.xmlsoap.org/soap/encoding/" />
 </input>
</operation>
</binding>

  <binding>元素已经取了一个名字(本例中"fooSampleBinding"),这样就可以被Services栏的<port>元素引用了。它有一个"type"的属性引用<portType>,本例中就是"wsdlns:fooSamplePortType"。第二行是MSTK2的扩展元素<stk:binding>,它指定了preferredEncoding属性为"UTF-8"。

  <soap:binding>元素指定了所使用的风格("rpc")和传输方式。Transport属性应用了一个namespace,正是这个namespace指明使用HTTP SOAP协议。

  有两个同以"foo"命名的<operation>元素。唯一不同的是它们各自的<input>名字,分别为"foo1"和"foo2"。两个<operation>元素中的<soap:operation>元素有同样的"soapAction"属性,是URI。soapAction属性是SOAP特定的URI,它只是简单的使用于SOAP消息。所产生的SOAP消息有一个SOAPAction头,而URI也仅在<soap:operation>元素里才起作用。soapAction属性在HTTP的binding中是必需的,但在其他非HTTP binding中却不要提供。目前它的使用并不清楚,但它似乎有助于本例中的两个"foo"操作。SOAP 1.1指明soapAction用来确定消息的"意图"。似乎服务器可以在不解析整个消息的情况下就能使用这一属性来发送消息。实际上,它的使用多种多样。<soap:operation>元素也可以包含另一属性,即"style"属性,在有必要冲突<soap:binding>元素指定的风格时可以使用。

  <operation>属性可以包含<input>, <output> 和<fault>的元素,它们都对应于PortType栏中的相同元素。只有<input>元素在上例中提供。这三个元素中的每一个可有一个可选的"name"属性,在本例中,我们用这种方法来区分同名操作。在本例的<input>元素中有一个<soap:body>元素,它指定了哪些信息被写进SOAP消息的信息体中。该元素有以下属性:

  Use

   用于制定数据是"encoded"还是"literal"。"Literal"指结果SOAP消息包含以抽象定义(Types, Messages, 和PortTypes)指定格式存在的数据。"Encoded"指"encodingStyle"属性决定了编码方式。

  Namespace

   每个SOAP消息体可以有其自己的namespace来防止命名冲突。这一属性制定的URI在结果SOAP消息中逐字使用。

  EncodingStyle

   对SOAP编码,它应该有以下URI值:

"http://schemas.xmlsoap.org/soap/encoding"

文档风格实现

  在前几栏中,<soap:binding>元素有一个类型属性,设为"rpc"。此属性设为"document"时会改变传输时消息的串行化。不同于函数签名,现在的消息是文档传输的。在这类binding中,<message>元素定义文档格式,而不是函数签名。作为例子,考虑以下WSDL片段:

<definitions
xmlns:stns="(SchemaTNS)"
xmlns:wtns="(WsdlTNS)"
targetNamespace="(WsdlTNS)">

<schema targetNamespace="(SchemaTNS)"
elementFormDefault="qualified">
<element name="SimpleElement" type="xsd:int"/>
<element name="CompositElement" type="stns:CompositeType"/>
<complexType name="CompositeType">
<all>
 <element name='a' type="xsd:int"/>
 <element name='b' type="xsd:string"/>
</all>
</complexType>
</schema>

<message...>
 <part name='p1' type="stns:CompositeType"/>
 <part name='p2' type="xsd:int"/>
 <part name='p3' element="stns:SimpleElement"/>
 <part name='p4' element="stns:CompositeElement"/>
</message>

</definitions>


  schema有两个元素:SimpleElement和CompositeElement,还有一个类型声明(CompositeType)。唯一声明的<message>元素有四个部分:p1:Composite型;p2:int型;p3:SimpleElement型;p4:CompositeElement型。以下有一个表,对四种类型的use/type决定的binding作一比较:rpc/literal, document/literal, rpc/encoded, 以及document/encoded。表指明了每种binding的表现。

  <service>和<port>元素

  service是一套<port>元素。在一一对应形式下,每个<port>元素都和一个location关联。如果同一个<binding>有多个<port>元素与之关联,可以使用额外的URL地址作为替换。
一个WSDL文档中可以有多个<service>元素,而且多个<service>元素十分有用,其中之一就是可以根据目标URL来组织端口。这样,我就可以方便的使用另一个<service>来重定向我的股市查询申请。我的客户端程序仍然工作,因为这种根据协议归类的服务不随服务而变化。多个<service>元素的另一个作用是根据特定的协议划分端口。例如,我可以把所有的HTTP端口放在同一个<service>中,所有的SMTP端口放在另一个<service>里。我的客户可以搜索与它可以处理的协议相匹配的<service>。

<service name="FOOService">
<port name="fooSamplePort" binding="fooSampleBinding">
 <soap:address
   location="http://carlos:8080/fooService/foo.asp"/>
</port>
</service>


  在一个WSDL文档中,<service>的name属性用来区分不同的service。因为同一个service中可以有多个端口,它们也有"name"属性。

  总结

  本文中我描述了WSDL文档关于SOAP方面的最显著的特点。不过应该说明的是WSDL并不仅限于HTTP上的SOAP。WSDL用来描述HTTP-POST、HTTP-GET、SMTP及其他协议时非常清晰。使用了WSDL,SOAP更加容易处理了,无论是开发者还是使用者。我相信WSDL和SOAP一起将会开创网络应用程序世界的新时代。

  WSDL的namespace里有一系列的XML元素。下表概述了那些元素、它们的属性和内容。

元素属性内容(子元素)
<definitions>name
targetNamespace
xmlns (other namespaces)
<types>
<message>
<portType>
<binding>
<service>
<types>(none)<xsd:schema>
<message>Name<part>
<portType>Name <operation>
<binding>name
type
<operation>
<service>name <port>
<part> name
type
(empty)
<operation>name
parameterOrder
<input>
<output>
<fault>
<input> name
message
(empty)
<output>name
message
(empty)
<fault> name
message
(empty)
<port>name
binding
<soap:address>

    

  资源:

  1. WSDL 1.1

  2. SOAP 1.1

  3. XML Schema Primer

  4. MS SOAP Toolkit Download Site

  5. A tool for translating IDL to WSDL

  6. Free Web Services resources including a WSDL to VB proxy generator

  7. PocketSOAP: SOAP related components, tools & source code

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值