架构蓝图:软件架构4+1视图模型

1. 原文

https://www.cs.ubc.ca/~gregor/teaching/papers/4+1view-architecture.pdf

2. 摘要

本文提出的模型可以描述由多个软件所组成的系统的架构。该模型同时使用多个视图。这些视图可以分别展示系统架构的不同利益相关方(终端用户、开发者、系统工程师、项目经理等)的关注点,以及分别处理功能和非功能需求。文本介绍了全部的五个视图,并使用符号进行表达。视图的设计使用了以架构为中心的、场景驱动的迭代开发过程。

关键字:软件架构、视图、面向对象设计、软件开发过程

3. 介绍

我们见过很多书和文章尝试用一张图来捕捉系统架构的全部要点。仔细看看图里的方框和箭头,显然作者竭力尝试在一张图片上展示超过图片自身表达能力的东西。这些方框是运行中的程序,还是代码片段?是物理计算机还是一组相关联的功能?这些箭头是编译依赖还是控制流或数据流?一个符号可以表示任何东西。架构要遵从单一风格吗?系统设计过早的划分软件模块,或者过度强调某个方面(如数据工程、运行性能、开发策略、团队组织等),往往让软件架构受到损害。架构也常常没有表达出所有“客户”(或利益相关方)的关注点。很多作者,如Garlan和Shaw、CMU的Abowd和Allen、SEI的Clements都注意到了这个问题。作为改进方法,我们建议同时使用多个视图,每个视图处理一组特定的关注点。

4. 架构模型

软件架构处理软件高层次结构的设计和实现。架构是按照精心选择的形式将一定数量的架构元素组合起来的结果。架构需要满足系统的功能和性能需求,以及诸如可靠性、可伸缩性、可移植性和可用性等非功能性需求。Perry和Wolfe提供了一个优美的公式来总结架构,下面是Boehm修改后的版本。

软件架构 =(元素,形式,理由/约束)

软件架构涉及抽象、分解与组合、风格和美学。为了描述软件架构,我们使用由多个视图组成的模型。为了表达大型的复杂架构,我们建议模型由五个主要视图构成。

  • 逻辑视图。即设计中的对象模型(当使用面向对象设计方法时)。
  • 过程视图。描述设计中的并发和同步。
  • 物理视图。描述软件到硬件的映射以及部署信息。
  • 开发视图。描述软件在开发环境中的静态结构。

对架构的描述(即架构决策)可以围绕这四个视图进行组织,并通过一些选定的用例或场景进行说明,即第五个视图。稍后我们会看到,架构实际上是从场景部分演化而来的。

fig1.png

4+1视图模型

我们在每个视图上单独地应用Perry-Wolf公式。对于每个视图,我们定义所需的架构元素(组件、容器、连接器),探索可行的形式和模式以及理由和约束,将架构与需求联系起来。每个视图使用自己特有的符号进行描述。对于每个视图,架构师可以选择特定的架构风格。因此系统中可以存在多种架构风格。

我们依次考察五个视图,给出视图的目的(即表达了哪些关注点)、绘制视图使用的符号,以及描述和管理视图的工具。下面的例子来自于PABX(源于我们在Alcatel Business System的设计)和一个空中交通管制系统,并做了大幅简化。我们的目的是给出视图和符号的示例,而非定义这些系统的架构。

4+1视图模型是包容的,模型里可以使用其他符号或工具,或者应用其他设计方法,特别是对于和逻辑和过程分解的部分。下面展示的是我们曾经使用的符号。

5. 逻辑架构:面向对象分解方法

逻辑架构主要用于支持功能需求,即系统在服务条款中向用户提供的功能。系统被分解为来自问题领域的一组关键抽象概念,以对象或对象类的形式表达。对象和对象类使用抽象、封装和继承原则。这种分解不仅可以进行功能分析,还能用于识别系统各部分的公共机制和公共设计元素。我们使用带有类和类模板的Rational/Booch方法表达逻辑架构。类图展示了一组类及其之间的逻辑关系:关联、使用、组合、继承等。相关的类可以组合成类分组。类模板侧重于单独的类,强调主要的类操作,识别关键对象特性。对于需要定义内部行为的对象,可以使用状态转换图或状态图。公共机制或公共服务在类的效用函数中定义。如果不使用面向对象方法,比如对于数据驱动的应用程序,也可以使用其他形式的逻辑视图,比如E-R图。

5.1. 逻辑视图符号

逻辑视图的符号来源于Booch符号。经过精心简化,只保留具有架构意义的部分。很多辅助符号在这个层级的设计上效果有限。我们使用Rational Rose软件进行逻辑架构设计。

fig2.png

逻辑视图符号

5.2. 逻辑视图样式

我们的逻辑视图采用面向对象风格。主要指导方针是尽量简单的将整个系统中的对象模型联系起来,避免过早的对类以及与站点或处理器相关的机制进行细化设计。

5.3. 逻辑视图例子

图3a显示了Télic PABX架构中的主要类。

fig3.png

逻辑视图例子

PABX在终端之间建立通信。终端可以是电话机、干线(比如到中心局的线路)、联络线(比如专用的PABX到PABX线路)、功能电话线、数据线、ISDN线等。不同线路需要不同的线路接口卡。线路控制器对象的职责是解码和读取线路接口卡上的所有信号,并将不同线路接口卡的信号转换成一个小的统一的事件集合:开始,停止、数字等。控制器受到硬实时约束。这个类有许多子类来满足不同类型的接口。终端对象的职责是维护终端状态、代表线路进行服务协商。例如在选择阶段使用编号计划业务来解释拨号。会话对象表示参与会话的一组终端。会话对象使用转换服务(目录、逻辑到物理地址映射、路由)和连接服务在终端之间建立语音路径。对于更大的、包含几十个具有架构意义的类的系统,图3b显示了空中交通管制系统的顶层类图,包含8个类分组。

6. 过程架构:过程分解方法

过程架构考虑非功能性需求,例如性能和可用性。过程架构展示并发和分布、系统完整性、容错性,以及逻辑视图中的主要抽象概念如何适配过程架构:控制指令序列是对象实际执行的操作。过程架构可以在多个抽象级别上进行描述,每个级别处理不同的关注点。在最高抽象层级,过程架构可以看做一组独立执行的逻辑网络,网络由多个相互通信的程序(称为“过程”)组成,分布在由LAN或WAN连接的硬件资源上。多个逻辑网络可以同时存在,共享相同的物理资源。独立的逻辑网络可以将在线系统与离线系统隔离,以及支持软件同时部署仿真或测试版本。进程是构成可执行单元的一组任务。进程表示过程架构中可以进行战术控制(比如启动、恢复、重新配置、关闭)的层次。此外,增加进程副本以提高系统负载分布,以及提高可用性。软件被划分为一组独立的任务。任务是独立的控制指令序列,可以调度到单独的处理节点上。任务分为主要任务和次要任务。主要任务是需要单独处理的架构元素,次要任务是由于实现原因(循环活动、缓冲、超时等)在局部引入的附加任务。任务可以通过Ada语言中的任务机制,或轻量级线程实现。主要任务通过已定义的跨任务通信机制相互交流,如同步和异步消息通信服务、远程过程调用、事件广播等。次要任务可以通过同步用或共享内存进行通信。主要任务不能假定自己与其他主要任务在同一进程或处理器节点中执行。基于过程视图,我们可以评估消息流和进程负载。也实现一个“空心”过程架构,为进程设定“空白”负载,度量目标系统性能表现。正如Filarey在Eurocontrol实验中所做。

6.1. 过程视图符号

过程视图的符号从Booch为Ada任务给出的符号扩展而来。同样的,所使用的符号关注于具有架构意义的的元素。(图4)

fig4.png

过程视图符号

我们使用了TRW的通用网络架构服务(UNAS)产品来设计并实现过程网络中的过程和任务。UNAS包含支持这种符号的工具——软件架构师生命周期环境(SALE)。SALE允许对过程架构进行图形化表达,包括对跨任务通信路径的规范。从规范可以自动生成相应的Ada或C++源代码。这种规范和实现过程架构的方法,好处在于可以很容易的合并变更,同时不会对程序软件产生太大影响。

6.2. 过程视图样式

有几种风格适用于过程视图。例如根据Garlan-Shaw分类法,可以使用管道和过滤器风格,或客户端/服务器风格(以及多客户端/单服务器和多客户端/多服务器两种变种)。对于更复杂的系统,可以使用类似于K. Birman所描述的ISIS系统的进程组方法符号和工具集。

fig5.png

Télic PABX过程视图(部分)

所有终端都由单一的终端进程处理,该进程由输入队列中的消息驱动。控制器对象在以下组成控制器进程的三个任务之一中执行:低频(200毫秒)任务扫描所有非活动终端,将激活的终端加入高频(10毫秒)任务扫描列表;高频任务检测所有的重要状态变化,并将它们传递给主控制器任务;主控制器任务解释状态变化,并通过消息将其传递给相应的终端。控制器进程内的消息传递是通过共享内存完成的。

7. 开发架构:子系统分解方法

开发架构关注于开发环境中实际的软件模块组织结构。软件被包装成可以由一个或少数开发人员开发的小块部分,如程序库或子系统。子系统按照层级结构或分层结构进行组织,每层子系统为上层系统提供精简的已定义接口。系统的开发架构由模块和子系统图所展示,显示了“导出”和“导入”关系。只有当软件中所有元素都识别完毕时,才能描述出完整的开发架构。在此之前可以列出控制开发架构的规则:划分、聚类、可见性。大多数情况下,开发架构考虑与开发复杂度、软件管理、重用或通用性以及工具集或编程语言限制相关的内部需求。开发视图是需求分配、团队工作分配(或团队组织)、成本评估和计划、项目进度监控,以及思考软件重用、可移植性和安全性等工作的基础。它是建立产品线的基础。

7.1. 开发视图符号

我们同样使用Booch符号的变种,并且只包含具有架构意义的部分。

fig5-2.png

开发视图符号

Rational的Apex开发环境可以用来定义和实现开发架构和前文描述的分层策略,以及实施设计规则。Rational Rose可以在模块和子系统级别上绘制开发视图,基于Ada和C++的源代码中进行正向工程和逆向工程。

7.2. 开发视图样式

我们建议开发视图采用分层样式,定义4到6层的子系统。每层都有已定义的职责。设计规则是,某层中的子系统只能依赖于同层或以更低一层中的子系统,以便最大限度减少模块之间复杂的依赖网络,并支持简单的逐层发布策略。

fig6.png

Hughes空中交通系统(HATS)分层

7.3. 开发架构示例

图6表示了加拿大Hughes公司开发的空中交通管制系统产品线的五层开发组织。这是图3b中逻辑架构相对应的开发架构。第1层和第2层构成了一个同业务领域无关的分布式基础设施,它是产品线中的公共部分,为上层子系统屏蔽了硬件平台、操作系统或第三方软件(如数据库管理系统)的变化。在基础设施中,第3层添加了ATC框架,以构造特定领域软件架构。通过这个框架,第4层构建了一个功能选择面板。第5层依赖于客户和产品,包含大多数用户接口和对外部系统的接口。在5个层中分布着大约72个子系统,每个子系统包含10到50个模块,这些可以在额外的图表中展示。

8. 物理架构:将软件映射到硬件

物理架构主要考虑系统的非功能需求,例如可用性、可靠性(容错)、性能(吞吐量)和可伸缩性。软件运行在由计算机或处理节点(简称节点)组成的网络中。已识别的各种元素(网络、进程、任务和对象)需要映射到不同节点。我们假定系统使用多种不同的物理配置:一些用于开发和测试,另一些用于为不同站点或客户进行部署。因此软件到节点的映射要足够灵活,同时对源代码的影响最小。

8.1. 物理视图符号

物理视图在大型系统中肯能会非常复杂,它可以有多种形式,包含或省略过程视图中信息。

fig7.png

过程视图符号

TRW的UNAS为我们提供了数据驱动方法,将过程架构映射到物理架构上,并允许在不修改源代码的情况下,大量调整类到节点的映射。

fig8.png

PABX系统物理视图

图8显示了大型PABX系统的一种的硬件配置方案,图9和图10显示了如何将过程架构映射到小型PABX系统和大型PABX系统两种不同的物理架构。C、F和K是三种拥有不同容量的计算机,支持三种不同类型的可执行文件。

fig9.png

小型PABX系统物理架构(包含进程分配)

fig10.png

大型PABX系统物理架构(包含进程分配)

9. 场景:连接四个视图

我们将展示:四个视图中的元素可以通过一组重要场景(用例的具体实例)紧密的协同工作。我们像Rubin和Goldberg那样描述场景脚本(各对象之间、各过程之间的交互序列)。在某种意义上,场景是对最重要需求的抽象。它的设计使用对象场景图和对象交互图来表达。这个视图存在与其他视图重复的部分(所以叫“+1”),它有两个主要目的:

  1. 在架构设计中驱动架构元素识别工作。后续我们将描述这一过程。
  2. 在架构设计完成后,承担验证和说明的职责,并作为测试架构原型的出发点。

9.1. 场景符号

这些符号类似于逻辑视图中的组件符号(图2),同时使用过程视图中的链接器符号描述对象交互(图4)。注意,对象实例用实线表示。至于逻辑视图,我们使用Rational Rose软件识别并管理对象场景图。

9.2. 场景示例

图11显示了小型PABX系统的场景片段。对应的脚本如下:

  1. Joe手机中的控制器检测并验证挂机到脱机的状态转换,发送消息唤醒对应的终端对象。
  2. 终端分配资源,通知控制器发出拨号音。
  3. 控制器接收数字并将其发送到终端。
  4. 终端使用编号方案分析数字流。
  5. 在输入有效的数字流后,终端打开对话。

fig11.png

本地呼叫选择阶段场景简图

10. 视图之间的联系

各个视图不是完全正交或不相关的。一个视图中元素,按照一定的设计规则和引申,与其他视图中的元素关联。

10.1. 从逻辑视图到过程视图

我们发现逻辑架构中的类有几个重要特征:

  • 自主性:对象是主动的、被动的还是受保护的?
    • 主动对象主动调用自己或其他对象的操作,并且在其他对象调用自己的操作时,拥有完整的控制权。
    • 被动对象不会主动调用任何操作,在其他对象调用自己的操作时,也不掌握控制权。
    • 受保护对象不会主动调用任何操作,但在其操作被调用时执行一些判断。
  • 持久性:对象是临时的还是永久的?它表示过程或处理器故障吗?
  • 从属关系:对象的存在或生命周期是否依赖于其他对象?
  • 分发:对象的状态或操作是否可以从物理架构中的多个节点访问,或者从过程架构中的多个过程访问?

在架构的逻辑视图中,我们把每个对象都看做是主动的,并且是“并发的”,即与其他对象“并行”地操作,我们不会过度关注实现逻辑架构所需要的并发级别。因为逻辑架构只考虑需求的功能部分。在定义过程架构时,以当前的技术水平,以独立的控制指令序列(如Unix进程或Ada任务)实现每一个对象是不可行的,这会带来巨大的性能开销。如果对象是并发的,必须以某种形式协调对象操作调用。另一方面,出于以下原因,需要使用多个控制指令序列:

  • 可以快速响应外部信号,包括对处理时间有要求的事件。
  • 可以充分发挥多CPU或者分布式系统中的多节点的优势。
  • 可以在某些控制指令序列挂起(比如等待外部设备或其他主动对象)时将CPU分配给其他控制指令序列,提高CPU使用率。
  • 可以为任务分配优先级(并提升响应速度)。
  • 可以为系统提供伸缩性支持(使用多个过程分担负载)。
  • 可以将软件不同部分的关注点分离。
  • 可以实现更高的系统可用性级别(使用备份过程)。

我们使用两种策略来决定合适的并发数量,并定义需要的过程集合。考虑到目标平台的物理架构,我们可以采用:

  • 由内到外方式。从逻辑架构开始:定义代理任务,任务可以在某个类的多个主动对象上复用单个控制指令序列;生命周期从属于主动对象的其他对象,也在同一个代理任务中执行;一些类可能需要以互斥方式执行,或者要求只有少量类可以同时共享代理。持续这样的聚类工作,直到我们将过程集合缩减到足够小的规模,并且可以部署和使用物理资源。
  • 由外到内方式。从物理架构开始:识别外部信号(请求),定义处理信号的客户过程和只提供服务的服务过程;使用数据完整性和序列化约束来定义服务集合,并为客户代理和服务代理分配对象;识别需要部署的对象。

其结果是将类(以及实例对象)映射到过程架构中的任务和进程上。通常每个主动类会分配一个代理任务。有时稍有不同:一个类会分配多个代理以增加吞吐量。或者将几个类映射到一个代理上,如果这些类的操作很少被调用,或者为了保证类按顺序执行。值得注意的是,并不存在线性的、确定性的,可以直接得到最优过程架构的方法。通常需要多次迭代才能得到一个可行的方案。此外还有其他的一些方法,比如Birman或Witt展示的例子。用于构造映射的具体方法不在本文的讨论范围内,但是我们可以用一个简单的例子做一些说明。

图12显示了一个假想的空中交通管制系统中,类是如何映射到进程的。

航班类被映射到一组飞行代理上。由于有许多航班要处理,外部信号频率很高,响应时间至关重要,负载必须分发到多个CPU上。此外,航班处理的持久化和分发工作由航班服务器延迟处理。出于可用性考虑,航班服务器存在副本。飞行剖面或出发许可是从属于航班的,虽然它们是比较复杂的类,它们和航班类共用进程。航班对象分发到其他几个进程中,尤其是用于展示以及外部接口使用。航段类建立了空域的分区,以便划分管制员对航班的管辖范围。由于存在完整性约束,航段类只能由单个代理处理。它可以与航班类共用服务器进程,航段类的更新不频繁。地点、空域和其他静态航空信息是受保护对象,在多个类之间共享,很少更新。它们被映射到自己的服务器,并分发到其他进程。

fig12.png

逻辑视图到过程视图的映射

10.2. 从逻辑视图到开发视图

类通常作为模块实现,例如Ada包中的可见类型。巨大的类被分解到多个包中。紧密关联的几个类——类科目——被分组到子系统中。在定义子系统时还要考虑额外的约束,比如团队组织、期望的代码量(子系统通常有5千到2万行代码)、期望的重用和通用级别、严格的分层原则(可见性)、发布策略和配置管理。因此我们最终得到的开发视图通常不会是来自逻辑视图的一一映射。逻辑视图和开发视图很像,但表达的是不同的关注点。我们发现项目越大,两个视图的差别越大。过程视图和物理视图也有同样的现象。例如,如果我们比较图3b和图6就会发现类科目到层之间没有一一映射。以“外部接口-网关”科目为例,它的实现分布在几个层中:通信协议在第1层或更低的子系统中,通用网关机制在第2层子系统中,而实际的网关在第5层子系统中。

10.3. 从过程视图到物理视图

进程和进程组按照不同配置映射到可用的物理硬件上,以便测试或部署。Birman在Isis项目中展示了精巧的映射模式。场景主要与逻辑视图(即类的视图),以及在对象间交互涉及多个控制指令序列时和过程视图有关。

11. 裁剪模型

并非所有的软件架构都需要完整的“4+1”视图。不需要的视图可以从架构中省略。例如在单一处理器架构中不需要物理视图。如果只有一个进程或程序,过程视图也不需要。小系统的逻辑视图和开发视图可能非常相似,以至于不需要分别绘制。而场景视图则在所有情况下都是有用的。

12. 迭代过程

Witt等人指出了架构设计的4个阶段:绘制草图、组织、细化和优化,并进一步细分为12个步骤。这表明设计架构的过程往往需要进行回溯。这种方法对一个大型的新项目来说过于“简略”。在4个阶段结束时,我们对架构的了解还不足以验证架构。我们建议进行更多的迭代设计,设计时需要建立原型、测试、测量、分析,然后在下一次迭代中细化。除了允许降低与架构关联的风险之外,迭代方法对项目还有额外好处:团队建设、培训、加深对架构的理解,加强对工具的运用、建立设计过程和工具等等(这里谈论的是不断演进的、逐渐的进化为系统的原型,而不是可以随意抛弃的、试验性的原型)。迭代方法还可以细化和完善需求,以及加强对需求的理解。

12.1. 场景驱动方法

系统最关键的功能以场景(或用例)的形式捕获。“关键”的意思是:最重要的功能(它们是开发系统的主要原因)、使用频率最高的功能,以及存在重大技术风险的、需要控制开发风险的功能。

开始:

  • 根据风险级别和重要程度选择少量场景进行迭代。可以对场景进行概括,以提取用户需求的共同点。
  • 设定架构草稿。编写场景脚本以识别主要抽象(类、机制、过程、子系统),并分解成二元组(对象,操作)序列。
  • 识别架构元素,布置到逻辑、过程、开发、物理这4个视图中。
  • 实现(原型)、测试、测量架构。在分析过程中可能会发现缺陷或潜在的优化措施。
  • 吸取经验教训。

循环: 下一次迭代可以这样开始:

  • 重新评估风险。
  • 扩展场景选项。
  • 选择可以降低风险或扩大功能覆盖范围的附加场景。

然后:

  • 尝试架构草稿中为新场景编写脚本。
  • 识别新架构元素,识别为了适应新场景需要进行的架构变更。
  • 更新逻辑、过程、开发和物理视图。
  • 根据架构变更修改原有场景。
  • 更新(原型)实现,支持新的场景集合。
  • 测试。如果可能,在真实环境和负载下测量。
  • 审查全部五个视图,寻找可以简化、重用或通用的地方。
  • 更新设计准则和理由。
  • 吸取经验教训。

结束循环

最初的架构原型演进为真正的系统。通常2到3次迭代之后架构进入稳定状态:没有遗漏主要抽象概念,没有遗漏子系统或过程,没遗漏接口。剩下的事就属于软件设计范畴了。顺便说一下,软件设计可以采用类似的方法和过程。不同项目的迭代周期的有很大差异。主要考虑的因素有:项目规模、项目参与人数、人员对业务领域和架构设计方法的熟悉程度、开发团队对系统的熟悉程度等。对于小型项目(比如1万行代码),迭代周期可能是2到3周,对于大型项目(比如70万行代码)则可能是6到9个月。

13. 架构文档

在架构设计过程中产生的设计记录在两个文档中:

  • 软件架构文档。它的结构按照“4+1”视图(参见图13中的大纲)组织。
  • 软件设计指南。记录了维护系统的架构完整性必须遵守的重要设计决策。

fig13.png

软件架构文档大纲

Listing 1: 软件架构文档大纲(翻译)

标题页
修改记录
目录
图表目录
1. 范围
2. 参考文献
3. 软件架构
4. 架构目标与约束
5. 逻辑架构
6. 过程架构
7. 开发架构
8. 物理架构
9. 场景
10. 尺寸和性能
11. 质量
附录
A. 缩略语
B. 定义
C. 设计原则

14. 结论

“4+1”视图模型已经成功地应用于多个大型项目。在其中一些项目中,模型进行了本地化改造和术语调整。模型可以让利益相关方找到他们关注的架构信息。系统工程师会首先查看物理视图,然后是过程视图。终端用户、客户、数据科学家会查看逻辑视图。项目经理、软件配置员则会查看部署视图。在Rational公司内部和其他地方,架构师经常提出和讨论不同类型的视图,比如Meszaros(BNR)、Hofmeister、Nord和Soni(Siemens)、Emery和Hilliard(Mitre)。我们发现这些视图通常可以合并到上述4种视图之中。例如成本和进度视图可以合并到开发视图中,数据视图可以合并到逻辑视图中,执行视图可以分拆合并到过程视图和物理视图中。

Table 1: 4+1视图模型总结
视图逻辑视图过程视图部署视图物理视图场景
组件任务模块节点步骤
子系统场景脚本
连接关联同步编译通信媒介
继承消息依赖局域网
组合广播伴随广域网
远程调用包含通信总线
容器类科目进程子系统/库物理子系统万维网
利益相关方最终用户系统设计师开发者系统设计师最终用户
系统整合人员管理人员开发者
关注点功能性能组织可伸缩性可理解性
可用性重用性能
软件容错可移植性可用性
完整性产品线
工具RoseUNAS/SALEApexUNASRose
DADSSoDAOpenview
DADS

14.1. 致谢

“4+1”视图模型的存在要归功于Rational、加拿大Hughes Aircraft、Alcatel和其他公司的许多同事。我特别感谢他们的贡献:Ch. Thompson、A. Bell、M. Devlin、G. Booch、W. Royce、J. Marasco、R. Reitman、V. Ohnjec和E. Schonberg。

Date: 2023年07月29日

Author: 作者:Philippe Kruchten

Created: 2023-08-22 周二 21:36

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值