什么是SCA

服务组件架构(SCA)是一个开发SOA面向服务应用的简单模型。这篇文章通过一个例子描述了SCA的种种优势,并介绍SCA的相关概念。本文中的例子是基于Apache Tuscany开源项目开发的(http://incubator.apache.org/tuscany/)。文中所涉及的实例源码均经Apache License 2.0认证(http://www.apache.org/licenses/LICENSE-2.0),在本文的参考资料部分提供了相关文件的链接。Apache Tuscany 和PHP SCA_SDO ([url]http://pecl.php.net/package/sca_sdo[/url])提供了免费的面向服务的基础架构,在其上可以利用SCA编程模型创建、打包、发布和管理应用。
SCA编程模型本身是由众多厂家和个人共同开发的规范,这套规范被贡献给了Open SOA协作组织(http://www.osoa.org)。

SCA
      面向服务的体系架构(SOA)提供了一条途径来解决紧耦合和应用固化的问题。SOA承诺它将带来诸多好处,例如提高商业敏捷性,提高灵活性,降低成本,使异构和分布式环境中的信息共享变得更加容易。
SOA描绘了一个蓝图,但实现SOA仍然是一个挑战。可供选择的用来实现SOA的技术令技术人员迷惑, 他们需要具有比较全面的技术才能成功建构一个SOA应用。SCA可以解决这一问题——提供了一个用以创建面向服务的端到端企业应用的简单模型。采用了SCA的业务将从以下几个方面受益:
      1) 快速开发,提高生产力: SCA把一个应用看成一系列相互连接的组件。它提供了一个简单的、语言中立的组件模型,组件可以是新创建的,也可以重用已有组件。组件可以用SCA运行环境所支持的任何语言来实现。通过组件的实现和组装细节的分离,SCA实现了真正的松耦合。这种自底向上的开发风格允许开发人员集中精力开发业务相关代码,而不用担心如何使其适用于整个解决方案。
      2) 更高的组织敏捷性和灵活性:SCA同样支持由顶向下的开发方式,通过SCA灵活的服务装配模型创建业务解决方案。SCA组件可以在一个构件组中连接在一起。一个组件可以被有着相同接口的另一个组件所替代。这个构件组可以根据IT架构需求进行调整,例如服务连接、传输协议、事务、安全和消息可靠性。可供选择的传输绑定方式使开发出的解决方案可以适应更广泛的部署需求。
      3) 更高的投资回报: SCA组件模型使得重用已投资的现有应用和服务变得非常容易。其标准方法是封装和抽象接口,通过重新连接服务创建新应用,以此重用服务。SCA本身是技术中立的,也并非要取代现有的技术。其仅仅提供了一个组件组装模型,并描述了如何去装配新的和现有的服务。
图1取自SCA装配模型规范,其展示了SCA的主要组成。(http://www.osoa.org/display/Main/Service+Component+Architecture+Specifications)

      深蓝色方框(ComponentA和ComponentB)表示是组件(Component)。组件是SCA核心,其封装了业务逻辑。组件可以采用运行环境支持的任何编程技术实现。例如, Apache Tuscany 项目目前支持Java、JavaScript、Ruby、Python和C++组件类型,同时为创建新的组件类型提供了扩展API。
      SCA组件可以定义属性(Property,ComponentA和ComponentB上方的黄色方框表示属性)。属性控制组件的行为,在部署其间可以被可改变。例如,一个股票报价应用程序可能会有一个属性用来指定股票报价中使用的货币类型。
SCA组件描述了接口,这些接口可供其它组件调用时使用。在图中接口用组件方框左边的绿色箭头来表示,被称作SCA的“服务”(Service)。组件也描述了被该组件调用的其它组件的接口,在图中这样的接口用组件方框右边的粉红色箭头来表示,称为SCA的“引用”(Reference)。这些服务和引用被连接在一起,组成一个可运行的系统。
       图中的两个组件,A和B,被组装在一个更大“构件组”(Composite)范围内,被称作构件组A。SCA的构件组描述了一个由互相连接的组件所构成的集合。正如你所看到,构件组也声明了服务和引用,它们被暴露到构件组外部.一个构件组内部的组件彼此连接就如同创建一个运行在同一进程中的紧耦合的应用程序。将构件组通过服务和引用连接在一起,则形成了一个更加松耦合的系统;系统中的每一个构件组都可能运行在一个单独的进程或处理器中,在网络中通过各种的协议和传输绑定连接起来。通过这个途径SCA为独立和分布式应用提供了统一的编程模型。

实例场景
      本文将使用一个虚构的MostMortgage公司抵押贷款申批应用来介绍SCA的更多细节。贷款审批应用(Loan Approval)接受抵押申请,包括客户的详细资料和申请的贷款数目。应用首先会检查客户的信用记录(Credit Check),确保信用额度符合最低要求。根据申请的本金数量,贷款条约以及客户的家庭状况,得出利息率(Interest Rate)。然后使用按揭计算器(Mortgage Calculator),用可能的月偿还额度除以客户的月收入额计算出比率。根据这个比率和客户的信用评分进行风险评估(Risk Assessment),然后做最终的决定。(见图2)


用SCA实现抵押贷款审批应用程序
      在下面的部分,将介绍如何用SCA实现贷款审批应用程序(Loan Approval Application),其中会详细讲解每个SCA元素的创建过程。从整体上看,贷款审批应用程序可以被分解成组合装配所需要的若干SCA组件。这个构件组(Mortgage Composite)所包含的组件有:贷款审批(Loan Approval),信用检查(Credit Check),利率计算(Interest Rate),按揭计算器(Mortgage Calculator)和风险评估(Risk Assessment)。整个构件组部署在一个SCA系统中。(见图3)


SCA组件
      SCA组件是开发SOA应用程序的基本构造单元。其具备三个特有的同时也彼此关联的部分:
a)构造组件功能的程序逻辑(简称为组件实现)
b)组件与其它组件交互的定义(简称为组件类型)
c)这一组件和其它组件如何装配以形成一个解决方案的具体描述(简称组件装配或构件组)
本文将在下面的章节具体介绍并举例说明每一部分。

       组件实现: 组件实现可以由任何SCA运行环境所支持的编程语言来完成。组件实现可以自由地选择自己的风格,但其需要与服务、引用和属性绑定,按照组件类型的定义进行组件间的交互。SCA规范描述了程序语言和SCA间的映射。
       组件类型:组件类型通过组件所暴露的服务,依赖的引用以及控制组件行为的属性来描述一个组件的轮廓。组件类型信息可以通过文件定义,通常文件名称是<实现文件名>.componentType,或者通过考察组件实现获取,或者两者并用。
       组件装配或构件组:一旦组件的实现和组件类型已经定义,组件就可以被装配到服务网络中,与其它服务一同提供一个SOA解决方案。装配文件定义了一个SCA构件组。SCA的运行环境使用此文件中的信息来实例化一个SCA应用程序。

      服务组件描述语言(SCDL)是SCA定义的一种可扩展标记语言(XML)格式,被用于定义组件类型文件和装配文件。例如:贷款审批应用中的按揭计算器(Mortgage Calculator)组件有组件类型文件,组件实现文件以及将按揭计算器组件与其它组件连接在一起的装配文件。(见图4)


组件类型(Component Type)
      按揭计算器组件类型文件(MortgageCalculator.componentType)描述了这一组件提供的服务,其并没有引用其它组件,同时也没有提供任何可配置的属性,因此引用<reference>和属性<proprety>元素并没有出现在这个定义中。
<componentType>
    <service name="MortgageCalculatorService">
       <interface.java inter/>
    </service>
</componentType>

组件实现(Component Implementation)
Java类"mortgage.MortgageCalculatorImpl" (MortgageCalculatorImpl.java)包含了这个组件的业务逻辑。
public class MortgageCalculatorImpl implements MortgageCalculator {
    public double getMonthlyPayment(double principal, int years, float interestRate) {
       double monthlyRate = interestRate / 12.0 / 100.0;
       double p = Math.pow(1 + monthlyRate, years * 12);
       double q = p / (p - 1);
       double monthlyPayment = principal * monthlyRate * q;
       return monthlyPayment;
    }
}
在下面的章节中(组件服务),将展示如何使用注释方式将组件类型信息包含在实现文件中,正如Java语言所能做的。本文中大部分的程序片断并非使用组件类型文件,而是通过注释方式提供组件类型信息。

组件服务(Component Service)
      一个组件如何为其它服件提供服务呢?下面的例子中按揭计算器(Mortgage Calculator)通过使用“@Service”注释声明了一个服务,包含了一个名为“getMonthlyPayment”的方法。因为Java语言运行环境支持注释,通过简单注释Java类,可以把该方法声明为一个服务接口。
@Service(MortgageCalculator.class)
public class MortageCalculatorImpl implements MortageCalculator {

    public double getMonthlyPayment(double principal, float interestRate) {
       ...
    }
}
通过@Service注释标记,SCA运行环境把MortgageCalculatorImpl类实例理解为一个含有MortgageCalcula接口定义的服务。

 

组件引用(Component Reference)
     现在来看一下组件是如何引用其它组件的。贷款审批(Loan Approval)组件引用了其它组件,本文将以此组件作为例子。贷款审批组件由Java语言实现,并使用了注释。@Reference表明该组件依赖风险评估(Risk Assessment)组件、信用检查(Credit Check)组件、利率计算(Interest Rate)组件和按揭计算器(Mortgage Calculator)组件。被引用的组件可以是本地的,也可以是远程的。SCA运行环境将在整个应用的SCDL文件中遍历组件间的关联,并根据遍历的结果正确设置引用。(见列表1)
列表1
@Service(LoanApproval.class) // Service declaration
public class LoanApprovalImpl implements LoanApproval {
    // Reference declarations using a protected or public field
    @Reference
    public RiskAssessment riskAssessment;

    @Reference
    public MortgageCalculator mortgageCalculator;

    @Reference
    protected InterestRateQuote interestRateQuote;

    // Reference declaration using a setter method
    private CreditCheck creditCheck;

    @Reference
    public void setCreditCheck(CreditCheck creditCheck) {
        this.creditCheck = creditCheck;
    }

    public boolean approve(Customer customer, double loanAmount, int years) {
        int score = creditCheck.getCreditScore(customer.getSsn());
        if (score < minimumCreditScore) {
            return false;
        }
        float rate = interestRateQuote.getRate(customer.getState(), loanAmount, years);
        double monthlyPayment = mortgageCalculator.getMonthlyPayment(loanAmount, years, rate);
        double ratio = monthlyPayment/customer.getMonthlyIncome();
        return riskAssessment.assess(score, ratio);
    }
}

组件接口(Component Interfaces)
      在SCA中,无论服务提供的业务功能还是所引用的业务功能都需用接口来描述。接口为服务和引用声明了交互时的约定。Java和WSDL是两类典型的接口定义语言。
@Remotable
public interface CreditCheck {
    int getCreditScore(String ssn);
}
      接口可以是本地接口也可以是远程接口。本地接口提供了一个构件组中组件间优化的本地交互。相比之下,远程接口则定义了松耦合的远程交互。
      一些商业服务有对等的交互关系,因此需要在服务层面有双向依赖。在这些案例中,商业服务既是服务的消费者,也是服务提供者,尤其是在交互是基于异步消息而非远程方法调用交互的情况下。SCA使用双向接口支持双向对等的业务服务关系建模。
     在一些服务中,为了完成一个更高层面的目标,要求调用一系列的操作。这一系列的操作被称作会话(conversation)。如果服务使用了一个双向接口,会话则包含操作和回调。SCA允许接口通过“conversational”标记来使一系列的操作在同一个会话中进行。

组件属性(Component Properties)
      组件属性可用以改变组件运行时行为而无需改变代码。假设贷款审批(Loan Approval)组件有一个名为“minimumCreditScore”组件属性,其可以根据公司的策略被赋予不同的值。例如贷款审批(Loan Approval)组件的代码片断中使用“@Property”定义了一个名为“minimumCreditScore”属性,属性的默认值是650:
private int minimumCreditScore = 650;

// Property declaration using a setter method
@Property(name = "minimumCreditScore", override = "may")
public void setMinimumCreditScore(int minimumCreditScore) {
    this.minimumCreditScore = minimumCreditScore;
}
下边的例子说明通过在SCDL装配文件中设定“minimumCreditScore”属性值为600,这一设置可以覆盖在组件类型中的默认值(650)。(这里使用Java语言的注释来定义组件类型):
<component name="LoanApprovalComponent">
    <implementation.java class="mortgage.LoanApprovalImpl" />
    <property name="minimumCreditScore">600</property>
    ...
</component>

构件组 - 合成组件(Composites-Composing Components)
      至此本文主要介绍了如何开发单个组件、创建服务以及定义其对其它组件的依赖。如何将若干组件装配在一起以提供一个商业解决方案呢?SCA使用构件组(Composite)来实现这一目标。这是一个逻辑概念,其包含一个或多个组件。(见图5)

      通过MortgageComposite的装配定义文件(default.scdl),就能看到SCA如何将组件组装在一起。
SCA运行环境使用SCDL文件中的信息来实例化、装配和配置组件。正如本文例子中描述的,每一个组件在装配文件中都用一个<component>元素标识,每一个组件也可以引用其它组件。在这个例子中,贷款审批组件定义了四个引用<reference>元素,这四个引用分别指向装配文件中的其它四个组件。在图中,连接(wiring)是用箭头表示的。每个连接两端所定义的接口必须是兼容的。
在SCA组装模型中,一个装配好的构件组能够被当作组件重用,尽管本文并没没有这样的例子。

本地服务(Local Services)
      上例中的装配定义文件展示了组件的引用是如何连接到其它组件的,但是没有描述采用何种技术在组件间传递消息。在这种情况下,SCA假定组件均为本地,也就是说这些组件将在同一个进程地址空间内初始化和运行。由于这个例子使用Java语言,组件实例将运行在同一个Java VM中,在这种情形下,SCA会自动地选择最高效的机制将消息从一个组件传递到另一个组件,既组件到组件的直接交互,几乎很少或没有中介的参与。
      从表面上看,组件间使用本地连接显得并非十分有用。为何不用常规的Java类来编写这些组件,组件间用常规调用的方式进行交互呢?因为在这种粗粒度组件情形下,SCA会有许多优势:
      组件可以容易地被重用或重新配置到其它构件组中;

      组件可以现在是本地的,但是将来变成远程的;
      由不同编程语言所实现的组件可以方便地被组装。

远程服务(Remote Services)
      远程服务是指运行在同一个或不同物理机器上的另一个进程。本文通过贷款审核例子展示如何与远程服务交互。首先CreditCheck会被包装成一个服务,然后对外提供CreditCheck Web服务。

包装组件为远程服务
     一个扩展的用例场景是,假设MostMortgage公司有一个业务伙伴,它对于CreditCheck功能也感兴趣。为了让LoanApproval组件和MostMortgage的业务伙伴都能使用这个服务,CreditCheck可以被包装成一个Web服务。如果使用SCA,这个任务就变得非常的简单。先将CreditCheck组件剥离到一个新的构件组中,然后在这一构件组中添加一个Web服务元素,然后将这一服务元素和CreditCheck组件绑定起来,这样就可以实现预期的目标了。(参见图6)

<composite xmlns="http://www.osoa.org/xmlns/sca/1.0"
xmlns:wsdli="http://www.w3.org/2006/01/wsdl-instance" name="CreditComposite">

    <service name="CreditCheckWebService">
       <interface.wsdl inter wsdl:wsdlLocation="http://credit wsdl/credit.wsdl" />
       <binding.ws endpoint="http://credit#wsdl.endpoint(CreditCheckService/CreditCheckSoapPort)"
                           conformanceURIs=http://ws-i.org/profiles/basic/1.1 location="wsdl/credit.wsdl" />
       <reference>CreditCheckServiceComponent</reference>
    </service>

    <component name="CreditCheckServiceComponent">
       <implementation.java class="credit.CreditCheckImpl"/>
    </component>

</composite>
注意:新的服务元素采用一个WSDL文件,该文件既描述了服务的接口,也描述了绑定。

用远程服务取代本地服务
      在前面的章节里CreditCheck被做成了一个远程组件。如何让MortgageComposite引用这个远程Web服务呢?所需要做的是修改MortgageComposite的SCDL文件:增加一个名CreditCheckReference引用元素用以声明远程CreditCheck Web服务,然后将LoanApprovalComponent的CreditCheck引用绑定到新的CreditCheckReference上。
<composite xmlns="http://www.osoa.org/xmlns/sca/1.0" name="MortgageComposite">
    <component name="LoanApprovalComponent">
       ...
       <reference name="creditCheck">CreditCheckReference</reference>
       ...
    </component>

    <reference name="CreditCheckReference">
       <interface.java inter />
       <binding.ws endpoint="http://credit#wsdl.endpoint(CreditCheckService/CreditCheckSoapPort)"
                            location="wsdl/credit.wsdl" />
    </reference>
    ...
</composite>
注意:在SCDL中该引用被指定一个叫做“binding.ws”的绑定,这一绑定信息表明了用于传送消息的协议。在这种情况下,系统可以很灵活地配置MortgageComposite和CreditComposite之间保障安全通信的绑定方式。参见图7。


      CreditCheck Web服务的实现并非必须是SCA组件。假设第三方已经提供了一个CreditCheck Web服务,则可以使用同样的方式调用这个Web服务。唯一的不同是在WSDL文件中要指定这个Web服务的URL。在图8中,CreditCheck Web服务是由一个部署在Apache Tomcat服务器上的Axis2服务提供的。
<composite xmlns="http://www.osoa.org/xmlns/sca/1.0" name="MortgageComposite">
    <component name="LoanApprovalComponent">
       ...
       <reference name="creditCheck">CreditCheckReference1</reference>
       ...
    </component>
    <reference name="CreditCheckReference1">
       <interface.java inter />
       <binding.ws endpoint="http://credit#wsdl.endpoint(CreditCheckService/CreditCheckSoapPort1)"
                             location="wsdl/credit.wsdl" />
    </reference>
    ...
</composite>

多语言扩展
      到目前为止,本文仅讨论了Java实现的组件和本地及远程的服务,还基本上没有涉及其它的选择。SCA通过SCDL描述组件及其组装的方法,并没有规定组件如何实现,或者消息是如何在组件之间传输的。各种运行环境可以自由的添加新的组件实现扩展。Apache Tuscany项目已经提供了扩展API,以尽可能地使支持新的实现类型变得简单。目前Apache Tuscany Java SCA运行环境支持如下几种组件实现类型:implemetation.java、implemetation..javascript和implementation.ruby。Tuscany组织目前正在致力于使用这种扩展机制提供Spring和BPEL的实现类型。
Apache Tuscany C++ SCA运行环境目前支持如下几种组件实现类型:implementation.cpp、implementation.python和implementation.ruby。
目前PHP SCA运行环境仅支持用PHP实现SCA组件。
在图8中,已经将Apache Tuscany Java SCA运行环境中MortgageCalculator组件的实现由Java变成了JavaScript。
MortgageCalculator.getMonthlyPayment()的JavaScript实现如下所示:


function getMonthlyPayment(principal, years, interestRate) {
    var monthlyRate = interestRate / 12.0 / 100.0;
    var p = Math.pow(1 + monthlyRate, years * 12);
    var q = p / (p - 1);
    var monthlyPayment = principal * monthlyRate * q;
    return monthlyPayment;
}


组件的定义文件(无需修改):
<componentType xmlns="http://www.osoa.org/xmlns/sca/1.0"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <service name="MortgageCalculatorService">
       <interface.java inter/>
    </service>
</componentType>

现在可以修改Mortgage构件组定义,使其引用JavaScript的MortgageCalculator 组件:
<component name="MortgageCalculatorJSComponent"
xmlns:js="http://incubator.apache.org/tuscany/xmlns/container/js/1.0-incubator-M2">
    <js:implementation.js script="MortgageCalculator.js" />
</component>

绑定及其扩展
      组件间传递消息的协议也是可扩展的。比如,Apache Tuscany运行环境提供了显式的扩展API来帮助建立新的绑定方式。目前Apache Tuscany Java SCA运行环境支持如下绑定方式:binding.ws,binding.celtix,binding.jsonrpc和binding.rmi。
SCA定义的一个组装模型以最大限度的利用现有技术,可以使用Java和JavaScript语言与外部Web服务通信。这就意味着SCA可以被逐步地被引入到企业中而无需重写现有应用。Apache Tuscany支持的组件实现方法和绑定形式的列表代表了现今正在普遍使用和不断发展的技术。进一步而言,可以通过使用扩展API创建目前没有被支持的新的组件实现和绑定。

服务质量(QoS)和策略(Policy)
      SCA明确地将组件的实现和组件间消息交互机制分离开来,其允许消息传送策略的声明独立于组件实现。例如,如果要求可靠的消息传送,或者要求在同一个事务的上下文中,或者要求加密传送,那么可以在SCDL文件中对策略(policy)添加相应的声明。关于策略声明的规范目前尚未完成,但是可以在OSOA网站上查到草案。(http://www.osoa.org/display/Main/Service+Component+Architecture+Specifications)

非SCA客户端访问SCA服务
     在MostMortgate的例子中,已经讲了基于SCA的系统既可以提供服务接口给外界调用,也可以调用外界提供的服务接口。
      只要使用SCA运行环境支持的协议能够访问的服务,SCA可以调用外部任意一个服务。在前面的例子里已经讲了CreditCheck Web服务可以由第三方提供,并没有要求必须用SCA实现。其中依赖于被访问服务的WSDL描述,并使用Web服务(SOAP/HTTP)绑定与其进行交互。
      那么如何用非SCA客户端访问SCA服务呢?在本例中,SCA使外部的应用可以通过Web服务访问CreditCheck组件,那么任何可以访问Web服务的客户端都可以访问CreditCheck组件,不管客户端是使用何种技术实现的。而且SCA也提供了相应的API使非SCA客户端调用一个本地的SCA服务。
public class MortgageClient {
    public static void main(String[] args) throws Exception {

       // Locate the service using SCA APIs
       CompositeContext context = CurrentCompositeContext.getContext();
       LoanApproval loanApplication = context.locateService(LoanApproval.class,
       "LoanApplicationComponent");
       ...
       // Invoke the service
       boolean result = loanApplication.approve(customer, 200000d, 30);
    }
}
注意SCA的API类CurrentCompositeContext可根据装配文件中组件的名字直接获取到服务实例或者是实现服务接口的代理(Proxy)。当客户端的代码调用某一个方法时(在本例中是approve()),SCA运行环境会根据SCDL文件中的定义将操作参数传递给相应的方法。

SDO和DAS
       本文主要叙述了SCA如何描述组件以及如何装配组件。SCA有一个姊妹技术-服务数据对象(Service Data Object,SDO):其定义了一组访问数据的统一API。这对于SCA是很重要的,因为其使得Apache Tuscany和PHP SCA运行环境可以采用统一的API来处理消息,包括组件发送和接收到的消息。SCA和SDO可以一起协同工作,当然,也能被独立使用。关于SDO的更多信息和相关规范,请参见OSOA网站。(http://www.osoa.org/display/Main/Service+Data+Objects+Home

总结
      SCA为开发SOA应用和解决敏捷IT环境中的战略需求提供了一个简洁而灵活的模型。SCA编程模型关注于描述组件及其装配方法。其主要目标是使现有的各种技术能够协调运作,以补充现有的异构环境。本文旨在对SCA进行一个宏观的介绍,有兴趣的读者可以在[url]www.osoa.org上找到有关SCA更深入的资料,而且可以使用三个免费的开源项目做相关的试验,这三个项目分别是:

        Apache Tuscany(http://incubator.apache.org/tuscany/), 
        SCA的Java和C++运行环境和SCA的PHP运行环境(http://www.osoa.org/display/PHP/SOA+PHP+Homepage)。

相关资源:
        OSOA网站――www.osoa.org/display/Main/Home;
        Apache Tuscany项目――http://incubator.apache.org/tuscany/;
        PHP PECL SOA项目――http://pecl.php.net/package/sca_sdo;

                             http://www.osoa.org/display/PHP/SOA+PHP+Homepage[/url];
l         贷款审核例子―?Dhttp://svn.apache.org/repos/asf/incubator/tuscany/java/sca/demos/mortgage-loanapproval/


术语表:
英文 中文
Binding 绑定
Component 组件
Composite 构件组
Data Access Service (DAS) 数据访问服务
Interface 接口
Local Service 本地服务
Reference 引用
Remote Service 远程服务
Policy 策略
Property 属性
Quality of Service (QoS) 服务质量
Service Component Architecture (SCA) 服务组件架构
Service Data Object (SDO) 服务数据对象
Service Oriented Architecture (SOA) 面向服务架构


9月4日 12:48:55
于 2007年09月04日 15:43:36

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值