WebService技术(C#+IIS+Android)

12 篇文章 0 订阅
4 篇文章 0 订阅

一、1、WebService定义

WebService是一种跨编程语言和跨操作系统平台的远程调用技术。

所谓跨编程语言和跨操作平台,就是说服务端程序采用.Net编写,客户端程序则可以采用其他编程语言编写,反之亦然!跨操作系统平台则是指服务端程序和客户端程序可以在不同的操作系统上运行。

所谓远程调用,就是一台计算机A上的一个程序可以调用到另外一台计算机B上的一个对象的方法,譬如,银联提供给商场的pos刷卡系统,商场的POS机转账调用的转账方法的代码其实是跑在银行服务器上。再比如,amazon,天气预报系统,淘宝网,校内网,百度等把自己的系统服务以webservice服务的形式暴露出来,让第三方网站和程序可以调用这些服务功能,这样扩展了自己系统的市场占有率,更大的概念即所谓的SOA应用。

其实可以从多个角度来理解WebService,从表面上看,WebService就是一个应用程序向外界暴露出一个能通过Web进行调用的API,也就是说能用编程的方法通过Web来调用这个应用程序。我们把调用这个WebService的应用程序叫做客户端,而把提供这个WebService的应用程序叫做服务端。从深层次看,WebService是建立可互操作的分布式应用程序的新平台,是一个平台,是一套标准。它定义了应用程序如何在Web上实现互操作性,你可以用任何你喜欢的语言,在任何你喜欢的平台上写WebService,只要我们可以通过Web service标准对这些服务进行查询和访问。

WebService平台需要一套协议来实现分布式应用程序的创建。任何平台都有它的数据表示方法和类型系统。要实现互操作性,WebService平台必须提供一套标准的类型系统,用于沟通不同平台、编程语言和组件模型中的不同类型系统。WebService平台必须提供一种标准来描述Web service,让客户可以得到足够的信息来调用这个WebService。最后,我们还必须有一种方法来对这个Web service进行远程调用,这种方法实际是一种远程过程调用协议(RPC)。为了达到互操作性,这种RPC协议还必须与平台和编程语言无关。

2、WebService平台技术

XML+XSD,SOAP和WSDL是构成WebService平台的三大技术。

(1)XML+XSD

WebService采用HTTP协议传输数据,采用XML格式封装数据(即XML中说明调用远程服务对象的哪个方法,传递的参数是什么,以及服务对象的返回结果是什么)。XML是WebService平台中表示数据的格式。除了易于建立和易于分析外,XML主要的优点在于它既是平台无关的,又是厂商无关的。无关性是比技术优越性更重要的:软件厂商是不会选择一个由竞争对手所发明的技术的。

XML解决了数据表示的问题,但它没有定义一套标准的数据类型,更没有说怎么去扩展这套数据类型。例如,整形数到底代表什么?16位,32位,64位?这些细节对实现互操作性很重要。XML Schema(XSD)就是专门解决这个问题的一套标准。它定义了一套标准的数据类型,并给出了一种语言来扩展这套数据类型。WebService平台就是用XSD来作为其数据类型系统的。当你用某种语言(如VB.NET或C#)来构造一个Web service时,为了符合WebService标准,所有你使用的数据类型都必须被转换为XSD类型。你用的工具可能已经自动帮你完成了这个转换,但你很可能会根据你的需要修改一下转换过程。

(2)SOAP:

WebService通过HTTP协议发送请求和接收结果时,发送的请求内容和结果内容都采用XML格式封装,并增加了一些特定的HTTP消息头,以说明HTTP消息的内容格式,这些特定的HTTP消息头和XML内容格式就是SOAP协议。SOAP(Simple Object Access Protocol)提供了标准的RPC方法来调用Web Service。

SOAP协议 = HTTP协议 + XML数据格式

SOAP协议定义了SOAP消息的格式,SOAP协议是基于HTTP协议的,SOAP也是基于XML和XSD的,XML是SOAP的数据编码方式。打个比喻:HTTP就是普通公路,XML就是中间的绿色隔离带和两边的防护栏,SOAP就是普通公路经过加隔离带和防护栏改造过的高速公路。

(3)WSDL:

好比我们去商店买东西,首先要知道商店里有什么东西可买,然后再来购买,商家的做法就是张贴广告海报。 WebService也一样,WebService客户端要调用一个WebService服务,首先要有知道这个服务的地址在哪,以及这个服务里有什么方法可以调用,所以,WebService务器端首先要通过一个WSDL文件来说明自己家里有啥服务可以对外调用,服务是什么(服务中有哪些方法,方法接受的参数是什么,返回值是什么),服务的网络地址用哪个url地址表示,服务通过什么方式来调用。

WSDL(Web Services Description Language)就是这样一个基于XML的语言,用于描述Web Service及其函数、参数和返回值。它是WebService客户端和服务器端都能理解的标准格式。因为是基于XML的,所以WSDL既是机器可阅读的,又是人可阅读的,这将是一个很大的好处。一些最新的开发工具既能根据你的Web service生成WSDL文档,又能导入WSDL文档,生成调用相应WebService的代理类代码。

WSDL文件保存在Web服务器上,通过一个url地址就可以访问到它。客户端要调用一个WebService服务之前,要知道该服务的WSDL文件的地址。WebService服务提供商可以通过两种方式来暴露它的WSDL文件地址:1.注册到UDDI服务器,以便被人查找;2.直接告诉给客户端调用者。

3、WebService开发

WebService开发可以分为服务器端开发和客户端开发两个方面:

(1)服务端开发:

把公司内部系统的业务方法发布成WebService服务,供远程合作单位和个人调用。(借助一些WebService框架可以很轻松地把自己的业务对象发布成WebService服务,Java方面的典型WebService框架包括:axis,xfire,cxf等,java ee服务器通常也支持发布WebService服务,例如JBoss。)

(2)客户端开发:

调用别人发布的WebService服务,大多数人从事的开发都属于这个方面,例如,调用天气预报WebService服务。(使用厂商的WSDL2Java之类的工具生成静态调用的代理类代码;使用厂商提供的客户端编程API类。

WebService的工作调用原理:对客户端而言,我们给这各类WebService客户端API传递wsdl文件的url地址,这些API就会创建出底层的代理类,我调用这些代理,就可以访问到webservice服务。代理类把客户端的方法调用变成soap格式的请求数据再通过HTTP协议发出去,并把接收到的soap数据变成返回值返回。对服务端而言,各类WebService框架的本质就是一个大大的Servlet,当远程调用客户端给它通过http协议发送过来soap格式的请求数据时,它分析这个数据,就知道要调用哪个类的哪个方法,于是去查找或创建这个对象,并调用其方法,再把方法返回的结果包装成soap格式的数据,通过http响应消息回给客户端。

4、适用场合

(1)跨防火墙通信:

如果应用程序有成千上万的用户,而且分布在世界各地,那么客户端和服务器之间的通信将是一个棘手的问题。因为客户端和服务器之间通常会有防火墙或者代理服务器。在这种情况下,使用DCOM就不是那么简单,通常也不便于把客户端程序发布到数量如此庞大的每一个用户手中。传统的做法是,选择用浏览器作为客户端,写下一大堆ASP页面,把应用程序的中间层暴露给最终用户。这样做的结果是开发难度大,程序很难维护。如果中间层组件换成WebService的话,就可以从用户界面直接调用中间层组件。从大多数人的经验来看,在一个用户界面和中间层有较多交互的应用程序中,使用WebService这种结构,可以节省花在用户界面编程上20%的开发时间。

(2)应用程序集成:

企业级的应用程序开发者都知道,企业里经常都要把用不同语言写成的、在不同平台上运行的各种程序集成起来,而这种集成将花费很大的开发力量。应用程序经常需要从运行在IBM主机上的程序中获取数据;或者把数据发送到主机或UNIX应用程序中去。即使在同一个平台上,不同软件厂商生产的各种软件也常常需要集成起来。通过WebService,可以很容易的集成不同结构的应用程序。

(3)B2B集成:

用WebService集成应用程序,可以使公司内部的商务处理更加自动化。但当交易跨越供应商和客户、突破公司的界限时会怎么样呢?跨公司的商务交易集成通常叫做B2B集成。WebService是B2B集成成功的关键。通过WebService,公司可以把关键的商务应用“暴露”给指定的供应商和客户。例如,把电子下单系统和电子发票系统“暴露”出来,客户就可以以电子的方式发送订单,供应商则可以以电子的方式发送原料采购发票。当然,这并不是一个新的概念,EDI(电子文档交换)早就是这样了。但是,WebService的实现要比EDI简单得多,而且WebService运行在Internet上,在世界任何地方都可轻易实现,其运行成本就相对较低。不过,WebService并不像EDI那样,是文档交换或B2B集成的完整解决方案。WebService只是B2B集成的一个关键部分,还需要许多其它的部分才能实现集成。

用WebService来实现B2B集成的最大好处在于可以轻易实现互操作性。只要把商务逻辑“暴露”出来,成为WebService,就可以让任何指定的合作伙伴调用这些商务逻辑,而不管他们的系统在什么平台上运行,使用什么开发语言。这样就大大减少了花在B2B集成上的时间和成本,让许多原本无法承受EDI的中小企业也能实现B2B集成。

(4)软件和数据重用: 

软件重用是一个很大的主题,重用的形式很多,重用的程度有大有小。最基本的形式是源代码模块或者类一级的重用,一种形式是二进制形式的组件重用。采用WebService应用程序可以用标准的方法把功能和数据“暴露”出来,供其它应用程序使用,达到业务级重用。

5、不适用的场合

(1)单机应用程序:

   目前,企业和个人还使用着很多桌面应用程序。其中一些只需要与本机上的其它程序通信。在这种情况下,最好就不要用WebService,只要用本地的API就可以了。COM非常适合于在这种情况下工作,因为它既小又快。运行在同一台服务器上的服务器软件也是这样。最好直接用COM或其它本地的API来进行应用程序间的调用。当然WebService也能用在这些场合,但那样不仅消耗太大,而且不会带来任何好处。

(2)局域网的同构应用程序:

   在许多应用中,所有的程序都是用VB或VC开发的,都在Windows平台下使用COM,都运行在同一个局域网上。例如,有两个服务器应用程序需要相互通信,或者有一个Win32或WinForm的客户程序要连接局域网上另一个服务器的程序。在这些程序里,使用DCOM会比SOAP/HTTP有效得多。与此相类似,如果一个.NET程序要连接到局域网上的另一个.NET程序,应该使用.NETremoting。有趣的是,在.NETremoting中,也可以指定使用SOAP/HTTP来进行WebService调用。不过最好还是直接通过TCP进行RPC调用,那样会有效得多。

二、WebService具体实现

1、服务端搭建:

Microsoft Visual Studio中新建网站,选择ASP.NET Web服务搭建服务端,生成的解决方案的工程中展开App_Code,打开Service.cs可写入服务端待调用方法,运行之后ASP.NET会打开一个服务端端口,并显示网页,展示网址,命名空间,方法等内容。

2、客户端搭建:

(1)网站客户端:

Microsoft Visual Studio中新建网站,选择ASP.NET网站,工程名右键选择添加Web引用,将服务端运行时的网页网址输入,点击前往,在引入的文件Service.wsdl中可查看服务端网址,命名空间和可调用方法(包括参数和返回值类型等)。前往时设置的的引用名.服务名的实例化对象可调用方法。

(2)桌面应用客户端:

Microsoft Visual Studio工程添加服务引用,前往时可设置命名空间,命名空间名.客户端可取的公共类(ServiceSoapClient)可调用服务端方法。

(3)Android客户端:

<1>导包:

Google为Android平台开发Web Service客户端提供了ksoap2-android项目,http://simpligility.github.io/ksoap2-android/getting-started.html可下载导入包,工程目录project模式下libs中粘贴并在导入的包名右键Add as Library

<2>允许网络服务

AndroidManifest.Xml文件中需要允许网络服务,写入:

<uses-permission android:name="android.permission.INTERNET"></uses-permission>

<3>操作准备:

final HttpTransportSE ht = new HttpTransportSE(SERVICE_URL);//网络寻址

ht.debug = true;//设置启用HTTP的Debug模式

//得到命名空间和方法名(命名空间,方法可在服务端发布网址中查看)

SoapObject soapObject = new SoapObject(SERVICE_NS,MethodName);

//如果待调用的服务端方法有参数,参数名称可与服务端不同,但顺序应相同

soapObject.addProperty("参数名称","参数值");

SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER12);

//设置是否调用的是DotNet开发的WebService,即是否支持.net 的WebService

envelope.dotNet = true;

//SoapEnvelope类中,设置发送的数据和接收数据,分别使用方法:bodyOut,bodyIn

envelope.bodyOut = soapObject;  

<4>调用方式:

//方法的SoapAction可在服务端发布网址中查看

//SOAP_ACTION =SERVICE_NS + MethodName

try{ht.call(SOAP_ACTION, envelope);

//envelope.getResponse()得到方法返回值   

   } catch (IOException e) {

   e.printStackTrace();

   } catch (XmlPullParserException e) {

   e.printStackTrace();

}

<5>主线程中网络操作:

Android3.0之后不允许主线程进行网络服务操作,强制允许Activity的onCreate方法中写入代码:

StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();

StrictMode.setThreadPolicy(policy);

<6>子线程中网络操作:

new Thread(){@Override

           public void run() {                 

           }}.start();

<7>同步调用和异步调用:

同步调用:一个同步操作会阻塞整个当前的进程,直到这个操作完成才能执行下一段代码。

异步调用:不会阻塞启动操作的调用线程,调用程序必须通过轮流检测,或者等待完成信号来发现调用的完成。

如果调用WebService的用户很多,至使服务端响应迟缓;或服务端的IP根本就不对,那么在这些情况下,用户界面的组件会像“死”了一样无法响应用户的其他动作。当然,发生这种情况的可能性是有的,尤其是在复杂的网络环境中发生的可能性是很大的,一但发生这种事情,就会使整个软件系统在用户体验上变得非常糟糕。

用户和开发人员都希望改善这种糟糕的情况。最理想的状态是调用WebService方法时,即使由于某种原因,WebService方法并未立即返回,界面上的组件仍然会处于活动状态,也就是说,用户仍然可以使用当前界面中的其他组件。

<8>AsyncTask类实现异步:

一般使用new Thread(this).start()来创建和开始一个线程。但也可不使用Thread来实现异步,而是通过AsyncTask类使要执行的任务 (调用WebService)在后台执行。

AsyncTask,是Android提供的轻量级的异步类,可以直接继承AsyncTask,在类中实现异步操作,并提供接口反馈当前异步执行的程度(可以通过接口实现UI进度更新),最后反馈执行的结果给UI主线程.

使用的优点:

简单,快捷,过程可控。

使用的缺点:

在使用多个异步操作和并需要进行Ui变更时,就变得复杂起来;Handler异步实现的原理和适用的优缺点。

网络操作放在继承AsyncTask类的Activity的内部类(如此类名为WSAsyncTask)中进行,更新UI可使用控件的post方法。在主线程中调用继承类的execute方法即可(如new WSAsyncTask().execute(); )。

4、关于从服务器获取最新消息:

(1)App从服务器获取最新消息有两种基本方式(原理):

主动获取方式(Pull):客户端隔固定时间主动向服务器获取信息,看是否有更新的信息;若有更新信息,则发送到客户端。

被动接受方式(Push):当服务器有更新信息时主动发送到客户端。

对比:Push方式比Pull方式更优越。因为采用Pull方式时客户端需要不停地去监测服务器的变化,更费客户端的资源(CPU资源、网络流量、系统电量)。

(2)具体推送方式:

轮询(Pull)方式:通过客户端不断向服务器询问是否有消息。缺点是十分耗电,而且会消耗大量网络资源。

SMS(Push)方式:通过拦截SMS消息(即短信)来解析服务器信息。缺点是必须要给运营商支付高额费用。

持久连接(Push)方式:通过服务器与客户端建立持久连接来接受实时的服务器消息。目前最可行的方案。

(3)关于持久连接方式的实现方案:

<1>C2DM云端推送:

在Android手机上,Google提供了C2DM(Cloudto Device Messaging)服务,一个用来帮助开发者从服务器向Android应用程序发送数据的服务。该服务提供了一个简单的、轻量级的机制,允许服务器可以通知移动应用程序直接与服务器进行通信,以便于从服务器获取应用程序更新和用户数据。C2DM服务负责处理诸如消息排队等事务并向运行于目标设备上的应用程序分发这些消息。但是由于国内的网络环境,是没法使用该服务器的,因此所有国内软件都没有采用这种方式。

<2>国内推送平台:

如小米、个推、极光、百度、腾讯信鸽等推送都做的很成熟了,而且只需要简单的集成推送平台的SDK就快速的实现推送功能,而且有些服务也是免费的。

<3>MQTT协议推送:

MQTT是IBM开发的一个即时通讯协议,该协议支持所有平台。不仅如此使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合;对负载内容屏蔽的消息传输;使用TCP/IP 提供网络连接;有三种消息发布服务质量:“至多一次”,消息发布完全依赖底层TCP/IP 网络。会发生消息丢失或重复。这一级别可用于如下情况,环境传感器数据,丢失一次读记录无所谓,因为不久后还会有第二次发送。“至少一次”,确保消息到达,但消息重复可能会发生。“只有一次”,确保消息到达一次。这一级别可用于如下情况,在计费系统中,消息重复或丢失会导致不正确的结果。小型传输,开销很小(固定长度的头部是2字节),协议交换最小化,以降低网络流量。

<4>RSMB(Really Small Message Broker)推送:

一个简单的MQTT代理,同样由IBM提供。

<5>XMPP协议推送:

由于XMPP是开源的,我们能通过自己项目的具体功能,进行高度的定制化,Google使用的C2DM推送服务也是基于该协议实现的,而且现在国外著名的推送平台也支持这种推送方式,比如facebook、msn、Google等等,而国内像QQ也使用类似的协议,由于其源代码并没有开源,我们不知道腾讯使用的是什么即时通讯协议,但是如果国内所有通讯软件都支持XMPP协议,那么陌陌和QQ用户就可以进行聊天了,就像Google talk 能和Facebook用户进行通讯一样。但是这是不可能的,因为腾讯现在在这个行业基本上出于弄断地位。但不管怎么样XMPP协议现在是用在项目中最为合适的及时通讯协议。

5、WebService局域网发布

(1)安装IIS:

控制面板中,打开程序功能,选择打开或关闭Windows功能,勾选IIS内组件安装。

(2)本地发布:

ASP.NET Web服务工程右键点击生成网站,生成成功后右键点击发布网站并确定。

(3)局域网发布方式一(网址无虚拟目录名方式):

<1>添加网站:

控制面板的管理工具中打开安装的IIS,点击添加网站,物理地址是本地发布网站时的路径,网站名称自定,应用程序池选择默认程序池,IP地址为局域网内本机地址,端口选择避免与其他应用正使用端口冲突,网址名中带有IP则不写主机名。

<2>默认程序池修改:

默认程序池高级设置中标识选择NetworkService。

<3>编辑权限:

网站操作中点击编辑权限,安全中点编辑,然后点添加对象名称输入Everyone,点击确定,然后勾选允许修改的权限。

<4>目录浏览:

网站功能视图中点击目录浏览,然后点击启用。

(4)局域网发布方式二(网址有虚拟目录名方式):

<1>添加网站:

控制面板的管理工具中打开安装的IIS,点击添加网站,物理地址自定,网站名称自定,应用程序池选择默认程序池,IP地址为局域网内本机地址,端口选择避免与其他应用正使用端口冲突,网址名中带有IP则不写主机名。

<2>添加虚拟目录:

新添加的网站右键添加虚拟目录,别名将出现在网址的IP后,物理地址是本地发布网站时的路径。

<3>ASP修改:

虚拟目录的功能视图中点击ASP,启用父路径选择True。

<4>编辑权限:

网站操作中点击编辑权限,安全中点编辑,然后点添加对象名称输入Everyone,点击确定,然后勾选允许修改的权限。

<5>目录浏览:

网站功能视图中点击目录浏览,然后点击启用。

(5)浏览网站:

网站操作中点击启动,然后点击浏览。

(6)关于身份验证:

网站和虚拟目录的功能视图中,点击身份验证,应确保匿名身份验证启用,及其他验证方式禁用。

(7)关于Windows防火墙:

关闭Windows防火墙(推荐),或者在Windows防火墙高级设置中,点击入站规则,新建规则,允许外部主机使用发布网站的端口。

(8)关于WebService服务代码修改:

WebService服务代码修改后重新生成网站后,发布网站时提示目标位置文件删除,点击确定后在浏览网站前需重新启用目录浏览。

(9)关于局域网IP:

浏览网站时注意本机重新启动时在局域网中的IP可能改变,需在网站操作的绑定中修改IP。

6、WebService服务端方法调用:

<1>WebService服务端继承System.Web.Services.WebService的子类(即服务类)不能作为自己中的方法的返回值,无法序列化。

如:Service为服务类

[WebMethod]
publicService getService()
{
    Service service =newService();
    returnservice;
}

网站浏览时提示服务类无法序列化

<2>服务类中的属性使用需将get和set标记为[WebMethod]。

如:

publicStringName
{
    [WebMethod]
    get
    {
        returnname;
    }
    [WebMethod]
    set
    {
        name = value;
    }
}

<3>服务类中两个方法操作同一个变量结果是独立的。

如:服务端的变量值为1,Add1()将此变量值加1返回,Add2()将此变量值加2返回。

ServiceReference5.ServiceSoapClient serviceSoapClient =newWindowsFormsApplication1.ServiceReference5.ServiceSoapClient();
label1.Text = serviceSoapClient.Add1();
label2.Text =serviceSoapClient.Add2();

label1.Text为2,label2.Text为3。

7、详解Ksaop2的返回结果:

(1)关于SoapObject类型:

SoapSerializationEnvelope的实例化对象调用的方法getResponse()、getProperty(int index)、getProperty(String name)得到的返回值都是Object类型,必须转为SoapObject类型才能调用getProperty(int index)、getProperty(String name)方法。

finalSoapObject result = (SoapObject)envelope.getResponse();

String property1 = result.getProperty(1).toString();

String property2 = result.getProperty("Name").toString();

SoapObjectpro = (SoapObject)result.getProperty(1);

String prop = result.getProperty("Sch").toString();

(2)关于SoapObject[]类型:

SoapSerializationEnvelope的实例化对象调用的方法getResponse()、getProperty(int index)、getProperty(String name)得到的返回值无法转为SoapObject[]类型。

finalSoapObject[] result1 = (SoapObject[])envelope.getResponse();

finalSoapObject[] result2 = (SoapObject[])result1.getProperty();

以上两种使用方式都是错误的。

(3)关于getProperty(int index)方法:

<1>服务端方法是Object[]类型:

SoapSerializationEnvelope的实例化对象对应的服务端方法返回值,如果是Object[]类型,SoapSerializationEnvelope的实例化对象调用getResponse()得到的返回值,转为SoapObject类型后,调用getProperty(int index)得到的是数组的元素。

<2>服务端方法是类类型:

SoapSerializationEnvelope的实例化对象对应的服务端方法返回值,如果是类类型,SoapSerializationEnvelope的实例化对象调用getResponse()得到的返回值,转为SoapObject类型后,调用getProperty(int index)得到的是类的属性。

SoapObjectvlaue= (SoapObject)result.getProperty(1);

如果result是数组,value是数组中第二个元素;

如果result是类,value是类中第二个声明的属性。

(4)关于getProperty(String name)方法:

name为类的公有属性名或者公有变量名。

finalStringName =result.getProperty("Name").toString();

finalStringname =result.getProperty("name").toString();

(5)关于getPropertyCount()方法:

<1>服务端方法是Object[]类型:

SoapSerializationEnvelope的实例化对象对应的服务端方法返回值,如果是Object[]类型,SoapSerializationEnvelope的实例化对象调用getResponse()得到的返回值,转为SoapObject类型后,调用getPropertyCount()得到的是数组的大小。

<2>服务端方法是类类型:

SoapSerializationEnvelope的实例化对象对应的服务端方法返回值,如果是类类型,SoapSerializationEnvelope的实例化对象调用getResponse()得到的返回值,转为SoapObject类型后,调用getPropertyCount()得到的是类的属性个数。

如:

finalSoapObject result = (SoapObject)envelope.getResponse();
final intcount = result.getPropertyCount();

如果envelope.getResponse()是Length为2的String数组,count为2;

如果envelope.getResponse()是有3个属性的类,count为3。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

风铃峰顶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值