《流程的永恒之道》(二):控制模式之单选分裂与单选汇聚模式

1. 单选分裂模式(排他选择模式)

\

原型实例(故事片段)

\

4fa401cef025f580e408def6109c6537.png

\

图3.13 房改购房审批流程中的排他选择故事片段

\

如图3.13所示,“初审”环节之后,需要根据业务情况,选择“公告”或“复审”两个活动中的一个活动进行转出。例如,如果房改房的面积大于70平米就进行“公告”,否则直接提交给“复审”。

\
  • 上下文(描述、动机)\

描述:当前活动(初审)分裂为两个或多个后续分支,当前活动执行完毕后只能选择触发一个后续分支执行,即多选一。

\

动机:在现实生活或生产中,很多时候需要做出选择,就像你走到一个岔路口时,必须选择其中一条路走,排他选择模式就是提供了可以进行多选一的机制和功能的模式。还有一个更容易理解的例子,就是我们考试试卷中的单选题,只有一个答案是正确的,而在高级模式中的“多选模式”则对应试卷中的多选题。

\
  • 问题的本质\

排他选择的本质就是在多个可选的选择中,根据每个选择的执行条件输入当前的境况,并进行特定的匹配,当某个选择的执行条件与当前的境况一致时,就执行这个特定的选择。很多事情都有其执行的前提,就像我们出门,如果下雨天则带伞,如果晴天则不带。所以,是否下雨就是排他选择的判断标准。

\
  • 解决方案及技术实现\

解决方案。排他选择同样有两种解决方案:一种是在XORSplit节点上定义条件(如图3.14所示),另一种是在转移线上定义条件(图3.15所示)。

\

5bb0c5c3b6c1d960f05c7c49778a9f06.png

\

技术实现

\

(1) 定义期:在设计器中,为两个不同的方案提供了不同的定义界面。提供可求值的条件表达式(包括表达式中的变量)的输入及持久化存储机制。例如在本模式的故事中,首先定义一个工作流变量int area,然后再定义个求值表达式:area\u0026gt;70。这样在运行期,area的实际值(如房改房面积为80平米),与area\u0026gt;70这个表达式进行匹配,得出匹配结果为true,所以需要进行“公告”。

\

(2) 运行期:在本模式的本质中我们讲到,排他选择的本质就是将当前境况与已经定义的条件进行匹配,根据匹配结果进行选择。因此,按照匹配的方式,本模式的实现可以分为人工匹配、基于数据的自动匹配两种场景。方案一和方案二只是在进行条件定义及匹配的位置上有所不同,本质的技术实现并没有不同,因此我们将只给出方案一的技术实现。

\

1. 人工匹配的技术实现:

\

顾名思义,人工匹配就是由人(活动A的执行人)在转出任务时,手动地选择“活动B”或“活动C”。因此,必须直接将活动B和活动C同时返回给活动A的办理人,由活动A的办理人选择是执行活动B还是活动C;然后根据活动A的办理人所选择的活动,进行求值表达式的赋值。如果活动A的办理人选择了活动B,则将“XORSplit转到活动B”这个转移线上的condition赋值为1,即set condition=1;否则将condition赋为任意不等于1的值,如set condition=2。

\

需要说明的是,人工匹配本质上也是基于数据的匹配,因为人工选择之后还是要将选择结果所对应的数据赋值给求值表达式。在BPMN 2.0及XPDL 2.1规范中,排他选择模式的类型就是两种:基于数据的排他选择(Data-based Exclusive)和基于事件的排他选择(Event-based Exclusive)。在BPMN 2.0规范中,排他选择的类型默认为基于数据的。

\

2. 基于数据自动匹配的技术实现:

\

在本模式的故事片段中,房改房的面积在每次审批时都是不同的,对于受理人员来讲,他应该只负责填写房改房的面积等数据,至于提交之后是否要公示应该由流程自动判断,而不是由受理人员判断。此时,就需要引擎根据面积进行自动匹配。

\

1) 方案一的动态脚本语言的技术实现:

\

如果采用方案一,在定义期,在XOR split节点上,编写可进行求值的表达式,并持久保存。在运行期,调用动态脚本语言(BeanShell、Groovy等)对此表达式进行求值,在本故事中,condition为area\u0026gt;70(也可以为area\u0026lt;=70),客户端传入的值为80,则调用BeanShell提供的eval求值方法对condition进行求值,详见2.4.5节中规则引擎的内容。

\

2) 方案一的规则引擎的技术实现:

\

在XOR split节点上定义规则库,由业务客户端传入事实库,然后由规则引擎进行规则匹配,详见2.4.5节中规则引擎的内容。

\
  • 约束及可能存在的问题\

问题:当采用工作流变量与表达式的技术实现本模式时,工作流变量如果定义为流程实例级别的,那么在驳回时就会遇到变量值是否要清空的问题。例如在上述流程中,如果“复审”不同意,将此审批流程驳回给了“受理”,此时,“受理”活动的受理人就可能要求购房申请人重新修改购房面积(例如改为70平米),这样工作流引擎在执行到XORSplit活动时继续取得工作流变量area的值。由于在第一次受理时,area已经被赋值为80,因此再次排他选择时,引擎依旧选择了“公共”活动。这与实际的需求就不一致了。

\

解决方案:

\

(1) 在驳回时或者XORSplit活动执行完毕后,清空工作流变量area的值(例如赋值为-1;如果是Integer类型的,赋值为null);

\

(2) 不要采用流程级别的实例变量,而要采用活动级别的变量。这样对于area变量,在活动被实例化时,就会生成一个不同的实例。

\
  • 规范中的实现\

XPDL 2.1中的实现

\
\\u0026lt;WorkflowProcess Id=\"3ef6aa14-5eb0-487d-9255-74ade5596129\" Name=\"Process 1\"\u0026gt;\  \u0026lt;Activities\u0026gt;\    \u0026lt;Activity Id=\"e13ce2f2-9038-4015-9e0d-ba35a6de3d4d\" Name=\"活动A\"\u0026gt;\      \u0026lt;Description /\u0026gt;\      \u0026lt;Implementation\u0026gt;\        \u0026lt;Task /\u0026gt;\      \u0026lt;/Implementation\u0026gt;\    \u0026lt;/Activity\u0026gt;\    \u0026lt;Activity Id=\"07eac136-9d2d-449a-80a6-812f03e08d83\" Name=\"排他选择分裂网关\"\u0026gt;\      \u0026lt;Description /\u0026gt;\      \u0026lt;Route MarkerVisible=\"true\" /\u0026gt;\    \u0026lt;/Activity\u0026gt;\    \u0026lt;Activity Id=\"700c6485-e12e-4f1d-8bbf-505893f8934a\" Name=\"活动B\"\u0026gt;\      \u0026lt;Description /\u0026gt;\      \u0026lt;Implementation\u0026gt;\        \u0026lt;Task /\u0026gt;\      \u0026lt;/Implementation\u0026gt;\    \u0026lt;/Activity\u0026gt;\    \u0026lt;Activity Id=\"240f5cad-7550-4e3a-8d39-2e52a8985d9a\" Name=\"活动C\"\u0026gt;\      \u0026lt;Description /\u0026gt;\      \u0026lt;Implementation\u0026gt;\        \u0026lt;Task /\u0026gt;\      \u0026lt;/Implementation\u0026gt;\    \u0026lt;/Activity\u0026gt;\  \u0026lt;/Activities\u0026gt;\  \u0026lt;Transitions\u0026gt;\    \u0026lt;Transition Id=\"2a56817b-d40c-4a24-9acd-0111875083d7\" From=\"e13ce2f2-9038-4015-9e0d-ba35a6de3d4d\" To=\"07eac136-9d2d-449a-80a6-812f03e08d83\" Name=\"\"\u0026gt;\      \u0026lt;Condition /\u0026gt;\    \u0026lt;/Transition\u0026gt;\    \u0026lt;Transition Id=\"21a54465-3563-4f8f-bf87-a81dfb700b45\" From=\"07eac136-9d2d-449a-80a6-812f03e08d83\" To=\"700c6485-e12e-4f1d-8bbf-505893f8934a\" Name=\"\"\u0026gt;\      \u0026lt;Condition Type=\"CONDITION\"\u0026gt;\        \u0026lt;Expression\u0026gt;condition==1\u0026lt;/Expression\u0026gt;\      \u0026lt;/Condition\u0026gt;    \\u0026lt;/Transition\u0026gt;\    \u0026lt;Transition Id=\"9822c8a4-b273-4451-b725-7ccd68755ad5\" 
\

afe887976e779dc5d452b6678c0045ea.png

\

如上所示,XPDL 2.1规范中,采用的是方案二(参见图3.15)实现的排他选择模式,即在转移线上设置触发条件。

\

BPEL 2.0中的实现

\

排他选择模式与简单合并模式一般都是成对出现的,其在BPEL规范中的流程图如图3.16所示。

\

e464b990e8d4bea8b271538677ad3a2d.png

\\

图3.16 BPEL规范中的排他选择模式与简单合并模式流程图

\

可以看出,在BPEL2.0规范中,对于排他选择模式,提供基于\u0026lt;if\u0026gt;…\u0026lt;elseif\u0026gt;…\u0026lt;else\u0026gt;的实现,其逻辑结构如下:

\
\\u0026lt;if standard-attributes\u0026gt; \   standard-elements \   \u0026lt;condition expressionLanguage=\"anyURI\"?\u0026gt;bool-expr\u0026lt;/condition\u0026gt; \   activity \   \u0026lt;elseif\u0026gt; \      \u0026lt;condition expressionLanguage=\"anyURI\"?\u0026gt;bool-expr\u0026lt;/condition\u0026gt; \      activity \   \u0026lt;/elseif\u0026gt; \   \u0026lt;else\u0026gt;  \      activity \   \u0026lt;/else\u0026gt; \\u0026lt;/if\u0026gt;
\

基于以上结构,排他选择模式的具体WSBPEL 2.0定义实现如下:

\
\\u0026lt;bpel:process name=\"exclusivePattern\"\         targetNamespace=\"http://sample.bpel.org/bpel/sample\"\         suppressJoinFailure=\"yes\"\         xmlns:tns=\"http://sample.bpel.org/bpel/sample\"\         xmlns:bpel=\"http://docs.oasis-open.org/wsbpel/2.0/process/executable\"\         \u0026gt;\    \u0026lt;bpel:sequence name=\"main\"\u0026gt;\        \u0026lt;bpel:receive name=\"receiveInput\" partnerLink=\"client\" portType=\"tns:switch\" operation=\"initiate\" variable=\"input\" createInstance=\"yes\"/\u0026gt;\        \u0026lt;bpel:empty name=\"Empty\"\u0026gt;\u0026lt;/bpel:empty\u0026gt;\        \u0026lt;bpel:if name=\"days\"\u0026gt;\            \u0026lt;bpel:condition expressionLanguage=\"urn:oasis:names:tc:wsbpel:2.0:sublang:xpath1.0\"\u0026gt;\u0026lt;![CDATA[days\u0026lt;=3]]\u0026gt;\u0026lt;/bpel:condition\u0026gt;\            \u0026lt;bpel:invoke name=\"Invoke1\"\u0026gt;\u0026lt;/bpel:invoke\u0026gt;\            \u0026lt;bpel:else\u0026gt;\                \u0026lt;bpel:invoke name=\"Invoke2\"\u0026gt;\u0026lt;/bpel:invoke\u0026gt;\            \u0026lt;/bpel:else\u0026gt;\        \u0026lt;/bpel:if\u0026gt;\        \u0026lt;bpel:invoke name=\"Invoke\"\u0026gt;\u0026lt;/bpel:invoke\u0026gt;\        \u0026lt;bpel:reply name=\"Reply\"\u0026gt;\u0026lt;/bpel:reply\u0026gt;\    \u0026lt;/bpel:sequence\u0026gt;\\u0026lt;/bpel:process\u0026gt;
\

我们注意到,在WSBPEL 2.0规范中,并没有显式地用来区分“分裂”与“汇聚”的网关,而是按照程序的逻辑默认执行,这也再次印证了WSBPEL 2.0是一种半结构化的语言。

\

BPMN 2.0中的实现

\
\\u0026lt;process id=\"sid-35e21b29-6800-47c6-b881-fee1c14917f5\" isExecutable=\"false\"\u0026gt;\    \u0026lt;task completionQuantity=\"1\" id=\"sid-E5764C9A-E643-4752-AF7A-711A1C8A4542\" isForCompensation=\"false\" name=\"活动A\" startQuantity=\"1\"\u0026gt;\       \u0026lt;outgoing\u0026gt;sid-700B2452-17F0-4779-84F2-F1B43E7D2A46\u0026lt;/outgoing\u0026gt;\    \u0026lt;/task\u0026gt;\    \u0026lt;exclusiveGateway gatewayDirection=\"Diverging\" id=\"sid-CAB5BFB7-5767-41B0-86EB-9812E8881562\" name=\"\"\u0026gt;\       \u0026lt;incoming\u0026gt;sid-700B2452-17F0-4779-84F2-F1B43E7D2A46\u0026lt;/incoming\u0026gt;\       \u0026lt;outgoing\u0026gt;sid-44AC65E4-E786-47EA-A528-057C593776DB\u0026lt;/outgoing\u0026gt;\       \u0026lt;outgoing\u0026gt;sid-42231692-6651-4919-B8CD-62851B8F8E96\u0026lt;/outgoing\u0026gt;\    \u0026lt;/exclusiveGateway\u0026gt;\    \u0026lt;task completionQuantity=\"1\" id=\"sid-1607CBCF-0DA9-45D8-9AE2-EE15C1A35218\" isForCompensation=\"false\" name=\"活动B\" startQuantity=\"1\"\u0026gt;\       \u0026lt;incoming\u0026gt;sid-44AC65E4-E786-47EA-A528-057C593776DB\u0026lt;/incoming\u0026gt;\    \u0026lt;/task\u0026gt;\    \u0026lt;task completionQuantity=\"1\" id=\"sid-D34AEF8E-3FC3-4361-B8BD-3B1F7114C744\" isForCompensation=\"false\" name=\"活动C\" startQuantity=\"1\"\u0026gt;\       \u0026lt;incoming\u0026gt;sid-42231692-6651-4919-B8CD-62851B8F8E96\u0026lt;/incoming\u0026gt;\    \u0026lt;/task\u0026gt;\    \u0026lt;sequenceFlow id=\"sid-700B2452-17F0-4779-84F2-F1B43E7D2A46\" name=\"\" sourceRef=\"sid-E5764C9A-E643-4752-AF7A-711A1C8A4542\" targetRef=\"sid-CAB5BFB7-5767-41B0-86EB-9812E8881562\"/\u0026gt;\    \u0026lt;sequenceFlow id=\"sid-44AC65E4-E786-47EA-A528-057C593776DB\" name=\"\" 
\

bdead9974298c1ab5f21edcd94951ffc.png

\

如上所示,在BPMN 2.0规范中采用的也是方案二(参见图3.15)实现排他选择模式。

\
  • 与其他模式的关系\

(1) 与简单合并模式配对使用。

\

(2) 与基于事件的排他选择模式的区别:基于事件的排他选择模式是通过外部事件直接触发对路由选择的计算,而本模式是由人为触发对路由选择的计算的。

\

2. 单选汇聚模式(简单合并模式)

\
  • 原型实例(故事片段)\

cdeab53b7dd2dbadb17db9c581977c54.png

\\

图3.17 房改购房审批流程中的简单合并故事片段

\

图3.17绘出了某个申请人的房改购房审批流程,在“初审”之后有可能需要经过“公告”再进入“复审”,也有可能直接进入“复审”。

\
  • 上下文(描述、动机)\

描述:在定义期,排他选择模式分裂出的两个或多个分支合并为一个后续分支。在实例期,对于同一个流程实例,只会有一个分支到达简单合并网关。这一点是通过排他选择模式的保证的。

\

动机:为排他选择模式提供一种简单的合并模式。

\
  • 问题的本质\

单选汇聚(简单合并)模式的本质,就是一次只允许一个分支通过。

\
  • 解决方案及技术实现\

解决方案。由于在此模式中不需要做任何的条件判断,因此解决方案同样也有两种:显式实现(如图3.18所示)及隐式实现(如图3.19所示)。

\

63b8fc55f16009209b9013637ef67c3a.png

\

技术实现:

\

(1) 定义期:对于显式方案,直接在流程设计器中提供“简单合并网关”。对于隐式方案,则需要在“活动D”上设置简单合并标志(或称之为“或汇聚”标志),并持久存储。

\

(2) 运行期:由于此模式与排他选择模式配对使用,而排他选择模式在运行期已经保证了incoming分支的唯一性,因此,这一模式在运行期不需要任何特定的动作。

\
  • 约束及可能存在的问题\

约束:该模式的约束是“简单合并网关”或者“活动D”的进入分支(incoming Transition)有且只有一个会执行。其实,该约束已经被排他选择模式所保证了。如果出现了多个分支,就变成了另一个模式:多选汇聚模式。

\
  • 规范中的实现\

XPDL 2.1中的实现

\
\\u0026lt;WorkflowProcess Id=\"87a71a3c-2ce6-4309-a596-9f9b48bfa641\" Name=\"Process 1\"\u0026gt;\  \u0026lt;Activities\u0026gt;\    \u0026lt;Activity Id=\"a2f55f3d-b539-456c-a5da-1211ae3aebd3\" Name=\"活动C\"\u0026gt;\      \u0026lt;Description /\u0026gt;\      \u0026lt;Implementation\u0026gt;\        \u0026lt;Task /\u0026gt;\      \u0026lt;/Implementation\u0026gt;\    \u0026lt;/Activity\u0026gt;\    \u0026lt;Activity Id=\"84633c75-7fdc-4069-ac64-351d1ddd2984\" Name=\"活动B\"\u0026gt;\      \u0026lt;Description /\u0026gt;\      \u0026lt;Implementation\u0026gt;\        \u0026lt;Task /\u0026gt;\      \u0026lt;/Implementation\u0026gt;\    \u0026lt;/Activity\u0026gt;\    \u0026lt;Activity Id=\"9e2abbc2-c366-49c1-925b-a0f13270c88f\" Name=\"简单合并网关\"\u0026gt;\      \u0026lt;Description /\u0026gt;\      \u0026lt;Route MarkerVisible=\"true\" /\u0026gt;\    \u0026lt;/Activity\u0026gt;\    \u0026lt;Activity Id=\"0a7d70c3-d92a-4271-87c1-fd7061783bd6\" Name=\"活动D\"\u0026gt;\      \u0026lt;Description /\u0026gt;\      \u0026lt;Implementation\u0026gt;\        \u0026lt;Task /\u0026gt;\      \u0026lt;/Implementation\u0026gt;\    \u0026lt;/Activity\u0026gt;\  \u0026lt;/Activities\u0026gt;\  \u0026lt;Transitions\u0026gt;\    \u0026lt;Transition Id=\"13fe4bcb-f081-4243-87d5-16becc28d918\" From=\"84633c75-7fdc-4069-ac64-351d1ddd2984\" To=\"9e2abbc2-c366-49c1-925b-a0f13270c88f\"\u0026gt;\      \u0026lt;Condition /\u0026gt;\    \u0026lt;/Transition\u0026gt;\    \u0026lt;Transition Id=\"5b212da5-0b0b-493b-a472-9b3d018c6d20\" From=\"a2f55f3d-b539-456c-a5da-1211ae3aebd3\" To=\"9e2abbc2-c366-49c1-925b-a0f13270c88f\"\u0026gt;\      \u0026lt;Condition /\u0026gt;\    \u0026lt;/Transition\u0026gt;\    \u0026lt;Transition Id=\"f867061f-54ca-4499-9ca9-9125e322592c\" From=\"9e2abbc2-c366-49c1-925b-a0f13270c88f\" To=\"0a7d70c3-d92a-4271-87c1-fd7061783bd6\"\u0026gt;\      \u0026lt;Condition Type=\"CONDITION\"\u0026gt;\        \u0026lt;Expression /\u0026gt;\      \u0026lt;/Condition\u0026gt;\    \u0026lt;/Transition\u0026gt;\  \u0026lt;/Transitions\u0026gt;\  \u0026lt;ExtendedAttributes /\u0026gt;\\u0026lt;/WorkflowProcess\u0026gt;
\

在XPDL 2.1规范中,采用的是显式方案来实现简单合并模式。

\

BPEL 2.0中的实现

\

在单选分裂模式中,我们讲到,WSBPEL 2.0规范中不存在显式的“分裂”与“汇聚”网关,并且它是采用了程序语言的结构化实现方式(即直接用\u0026lt;if\u0026gt;…\u0026lt;else if\u0026gt;…\u0026lt;else\u0026gt;来实现分支的执行),因此,也就无须采用一个“简单合并网关”来实现汇聚功能了。

\

BPMN 2.0中实现

\
\\u0026lt;process id=\"sid-16c272d4-2a98-48db-ae7b-4b6f54be6492\" isExecutable=\"false\"\u0026gt;\    \u0026lt;task completionQuantity=\"1\" id=\"sid-A0DF4916-F0C0-4FF4-A767-E4F512E833D7\" isForCompensation=\"false\" name=\"活动B\" startQuantity=\"1\"\u0026gt;\       \u0026lt;outgoing\u0026gt;sid-21F6C678-AA5F-4543-A5E2-5EDF2AADCC78\u0026lt;/outgoing\u0026gt;\    \u0026lt;/task\u0026gt;\    \u0026lt;exclusiveGateway gatewayDirection=\"Converging\" id=\"sid-805F67E0-247F-4E96-AFDA-5264C21C585C\" name=\"简单合并网关\"\u0026gt;\       \u0026lt;incoming\u0026gt;sid-2B37A9A9-2BC8-4515-A941-4D7AE0A869FE\u0026lt;/incoming\u0026gt;\       \u0026lt;incoming\u0026gt;sid-21F6C678-AA5F-4543-A5E2-5EDF2AADCC78\u0026lt;/incoming\u0026gt;\       \u0026lt;outgoing\u0026gt;sid-9E996A9E-D5B8-4D0C-8D33-EF03971EC7B4\u0026lt;/outgoing\u0026gt;\    \u0026lt;/exclusiveGateway\u0026gt;\    \u0026lt;task completionQuantity=\"1\" id=\"sid-D800469F-638A-4255-A762-1D44E279901E\" isForCompensation=\"false\" name=\"活动C\" startQuantity=\"1\"\u0026gt;\       \u0026lt;outgoing\u0026gt;sid-2B37A9A9-2BC8-4515-A941-4D7AE0A869FE\u0026lt;/outgoing\u0026gt;\    \u0026lt;/task\u0026gt;\    \u0026lt;task completionQuantity=\"1\" id=\"sid-ED4A68C8-4FC0-405D-A114-EDE9A3D6BC1D\" isForCompensation=\"false\" name=\"活动D\" startQuantity=\"1\"\u0026gt;\       \u0026lt;incoming\u0026gt;sid-9E996A9E-D5B8-4D0C-8D33-EF03971EC7B4\u0026lt;/incoming\u0026gt;\    \u0026lt;/task\u0026gt;\    \u0026lt;sequenceFlow id=\"sid-9E996A9E-D5B8-4D0C-8D33-EF03971EC7B4\" name=\"\" sourceRef=\"sid-805F67E0-247F-4E96-AFDA-5264C21C585C\" targetRef=\"sid-ED4A68C8-4FC0-405D-A114-EDE9A3D6BC1D\"/\u0026gt;\    \u0026lt;sequenceFlow id=\"sid-2B37A9A9-2BC8-4515-A941-4D7AE0A869FE\" name=\"\" sourceRef=\"sid-D800469F-638A-4255-A762-1D44E279901E\" targetRef=\"sid-805F67E0-247F-4E96-AFDA-5264C21C585C\"/\u0026gt;\    \u0026lt;sequenceFlow id=\"sid-21F6C678-AA5F-4543-A5E2-5EDF2AADCC78\" name=\"\" sourceRef=\"sid-A0DF4916-F0C0-4FF4-A767-E4F512E833D7\" targetRef=\"sid-805F67E0-247F-4E96-AFDA-5264C21C585C\"/\u0026gt;\ \u0026lt;/process\u0026gt;
\

在BPMN 2.0规范中,同样也是采用显式方案来实现简单合并模式。需要注意的是,在BPMN 2.0规范中,排他网关与简单合并网关均采用\u0026lt; exclusiveGateway \u0026gt;,只是通过gatewayDirection属性来区分是排他选择(gatewayDirection=\"diverging\")还是简单合并(gatewayDirection=\"converging\")。

\
  • 与其他模式的关系\

与排他选择模式配对使用。

\

感谢张龙对本文的审校,感谢张龙对本文的策划。

\

给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家通过新浪微博(@InfoQ)或者腾讯微博(@InfoQ)关注我们,并与我们的编辑和其他读者朋友交流。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值