AndroMDA Getting started(七)

14TimeTrackingService定义

我们现在处于迭代2阶段。这个迭代的目标是要构建搜索结果界面。要刷新我们的内存,下面是搜索屏幕的模型。搜索结果界面是时间卡显示的右面的内容。

 

Value Objects

要支持搜索结果面板,服务层必须提供根据匹配的特定规则获取timecard列表的能力。让我们决定我们创建一个service叫做TimeTrackingService,提供需要的功能:

public interface TimeTrackingService
    
    
{
    
    
    public TimecardSummaryVO[] findTimecards(TimecardSearchCriteriaVO criteria);
    
    
}
    
    

有一个TimecardSearchCriteriaVOvalue objecttimecard包搜索条件-如果在这个类中的一个字段被指定,这个搜索条件就被应用了;如果这个filed被保留为null,则那个查询条件不会被用到。TimecardSummaryVO是另外的一个VO,在summary information包中。基于在Search屏幕中的条件,让我们来设计这两个VO。注意,我们需要一个状态field,在所有的Value Object中包含。因此首先,创建一个项目叫做TimecardStatusorg.andromda.timetracker.domain包中。在”Domain Objects”图中创建一个类,把sterotype设置为Enumeration。像下面显示的那样,这些被增加到属性中。确认enumeration被创建在org.andromda.timetracker.domain包中。

TimecardStatus被定义了,我们能定义被findTimecards()服务方法使用的两个value object。创在org.andromda.timetracker.vo包中建立TimecardSearchCriteriaVOTimecardSummaryVO。在”Value Objects”图中创建这些value object。在TimecardSearchCriteriaVO中的每个属性的多重性被设置为0..1-指出他们的值可以有或没有。要指定一个属性的多重性,你必须在属性上双击打开它的描述,然后选择正确的多重性。在另外的两个value objects,创建一个类叫做TimecardSummaryVO[]来表现timecard summary数组。确保这三个类被创建在org.andromda.timetracker.vo包中。

TimeTrackingService

我们现在已经有了定义我们的TimeTrackingService的任何东西。在org.andromda.timetracker.service包中创建这个服务。在”Services”图中创建并且确保服务被创建在org.andromda.timetracker.service包中。已经确保findTimecards方法的返回类型为TimecardSummaryVO[]

现在,让我们要求AndroMDA来生成代码:

1.在命令提示符中,执行mvn install。确保你看到了BUILD SUCCESSFUL消息。验证下面的文件会被生成:

  1. common/target/src/org/andromda/timetracker/domain/TimecardStatus.java
  2. common/target/src/org/andromda/timetracker/vo/TimecardSummaryVO.java
  3. common/target/src/org/andromda/timetracker/vo/TimecardSearchCriteriaVO.java
  4. common/target/src/org/andromda/timetracker/service/TimeTrackingService.java
  5. core/target/src/org/andromda/timetracker/service/TimeTrackingServiceBase.java
  6. core/src/main/java/org/andromda/timetracker/service/TimeTrackingServiceImpl.java

 

TimeTrackingService Test

UserService一样,我们首先要给TimeTrackingService写一个测试用例并且写实现来使测试通过。下面的步骤用来创建TimeTrackingServiceTest并且运行:

1.       C:/timetracker-completed/core/src/test/java/org/andromda/timetracker/service位置拷贝TimeTrackingServiceTest.java在你的实现的位置中。这个文件包括对findTimecards()方法的两个测试,testFindAllTimecards()testFindTimecardsForSubmitter。确保你确认和理解这些测试。

2.       编辑在C:/timetracker/core/src/test/resources目录下的testng.xml,取消注释TimeTrackingServiceTest以便能执行。

3.       执行下面的命令,在C:/timetracker目录来运行测试。

Mvn –f core/pom.xml test

你可以看到下面的错误信息,说明了测试失败:

-------------------------------------------------------

 T E S T S

-------------------------------------------------------

Running Services Test

Tests run: 3, Failures: 2, Errors: 0, Skipped: 0, Time elapsed: 0.661 sec <<< FAILURE!

 

Results :

Tests run: 3, Failures: 2, Errors: 0, Skipped: 0

 

[INFO] ------------------------------------------------------------------------

[ERROR] BUILD FAILURE

[INFO] ------------------------------------------------------------------------

[INFO] There are test failures.

[INFO] ------------------------------------------------------------------------

[INFO] For more information, run Maven with the -e switch

[INFO] ------------------------------------------------------------------------

[INFO] Total time: 19 seconds

[INFO] Finished at: Sun Aug 06 18:38:26 EDT 2006

[INFO] Final Memory: 5M / 11M

       4.让我们看测试结果,看看发生了什么。打开c:/timetracker/core/target/surefire-reports/Services Test.txt。你可以看到下面的日志:

-------------------------------------------------------------------------------
    
    
Test set: Services Test
    
    
-------------------------------------------------------------------------------
    
    
Tests run: 3, Failures: 2, Errors: 0, Skipped: 0, Time elapsed: 0.661 sec <<< FAILURE!
    
    
testFindAllTimecards  Time elapsed: 0.31 sec  <<< FAILURE!
    
    
java.lang.NullPointerException
    
    
      at ...(TimeTrackingServiceTest.java:58)
    
    
      at ...(TimeTrackingServiceTest.java:41)
    
    

   
   
    
     
   
   
testFindTimecardsForSubmitter  Time elapsed: 0.01 sec  <<< FAILURE!
    
    
java.lang.NullPointerException
    
    
      at ...(TimeTrackingServiceTest.java:58)
    
    
      at ...(TimeTrackingServiceTest.java:50)
    
    
            
    
    

很明显,服务当前返回了null而不是TimecardSummaryVO[]。这个问题会当我们实现服务后被纠正。

 

15TimeTrackingServcie实现

在这一章,我们会实现TimeTrackingService并且确保能通过测试。

我们会首先需要增强我们的领域模型来支持新value object TimecardSearchCriteriaVO并且TimecardSummaryVO。注意TimecardSearchCriteriaVO是一个简单的数据传输对象,来指定搜索参数。没有领域实体被需要表达这个概念。然而TimecardSummaryVO代表了timecard实体并且必须被增加到领域模型中。这个图如下面的显示:

TimecardUser实体有两个关系:

1.  submitter:提交timecard的人。

2.  approver:审核timecard的人。

两个关联关系的箭头指出了从TimecardPerson的方向。例如,timecard保持了对submitterapprover的饮用。在关联末端的数字和型号指明了多重性。顶端的关联指出timecard能有一个submitter,但是submitter能提交多个timecard(用*显示)。相似的,地下的关系指出了有01个审批者,一个审批者可以审批多个timecard。我们允许0审批者,以便审批是与提交延迟的。

我们在Timecard实体中增加一个方法叫做findByCriteria()TimeTrackingService会使用这个方法来查找timecard。注意在findByCriteria()方法的下划线-他表示在UML中的”classifier”范围。如果在方法上没有下划线,会实现”instance”范围。这是在AndroMDA中重要的概念,特别注意。

一个带classifier scope的方法是一种一般目的的工具方法,操作一个或更多的实体。这样的方法在DAO类中被生成。带instance scope的方法生成在实体自己上并且被生成在EntityEntityImpl类中。

我们现在准备增加Timecard实体到TimeTracker模型中,请根据下面的连接来继续。

接下来从Timecard实体到TimecardSummaryVO增加依赖关系。正如你所知,这种依赖告诉AndroMDATimecardTimecardSummaryVO对象间生成转换。转换方法被生成在TimecardDaoBase中。然而,注意TimecardSummaryVO包含被从另外一个实体中获取的属性,叫做User,因此我们会覆盖默认的转换方法在这些其他的属性中。

接下来从TimeTrackingServiceTimecard实体增加一个依赖。正如你所知,这个依赖告诉AndroMDATimeTrackingService访问Timecard实体。

现在,让我们要求AndroMDATimecard实体生成代码:

       1.在命令提示符中执行mvn install。注意构建不会成功,因为测试仍然失败,然而代码生成的部分会成功。

Eclipse User:打开AndroMDA生成的.classpath文件。注意AndroMDA不会增加TestNGclasspath路径中。增加下面的条目到到这个文件中,以确保TestNGClasspath中。如果你不想AndroMDA覆盖已经修改了的.classpath文件,通过注释在mda/pom.xml文件中的andromdapp-maven-plugin段就可以了。

 

<classpathentry kind="var" path="M2_REPO/org/testng/testng/4.7/testng-4.7-jdk15.jar"/>
    
    

 

现在,让我们开始实现findByCriteria()方法,增加下面显示的粗体行在生成的TimeTrackingServiceImpl类中。

// license-header java merge-point
    
    
/**
    
    
 * This is only generated once! It will never be overwritten.
    
    
 * You can (and have to!) safely modify it by hand.
    
    
 */
    
    
package org.andromda.timetracker.service;
    
    

   
   
    
     
   
   
import java.util.List;
    
    

   
   
    
     
   
   
import org.andromda.timetracker.vo.TimecardSummaryVO;
    
    

   
   
    
     
   
   
/**
    
    
 * @see org.andromda.timetracker.service.TimeTrackingService
    
    
 */
    
    
public class TimeTrackingServiceImpl
    
    
    extends org.andromda.timetracker.service.TimeTrackingServiceBase
    
    
{
    
    

   
   
    
     
   
   
    /**
    
    
     * @see org.andromda.timetracker.service.TimeTrackingService#findTimecards(...)
    
    
     */
    
    
    protected org.andromda.timetracker.vo.TimecardSummaryVO[] handleFindTimecards(
    
    
            org.andromda.timetracker.vo.TimecardSearchCriteriaVO criteria)
    
    
        throws java.lang.Exception
    
    
    {
    
    
         List timecards = getTimecardDao().findByCriteria(criteria);
    
    
         getTimecardDao().toTimecardSummaryVOCollection(timecards);
    
    
         return (TimecardSummaryVO[])timecards.toArray(new TimecardSummaryVO[0]);
    
    
    }
    
    
}
    
    
    
    
    

我们首先在TimeDao中做一个调用findByCriteria()方法。还记得这是一个我们增加到Timecard实体的经典的方法吗?我们会手动实现这个方法。findByCriteria()方法返回一个匹配Timecard实体的列表。然而,handleFindTimecards()需要返回一个TimecardSummaryVO对象的数组,因此,我们首先使用TimecardDao帮助方法来转换实体列表为VO集合。然后我们转换集合到一个数组,返回它。

现在在TimecardDaoImpl中实现handleFindByCriteria()方法。注意,代码使用Hibernate criteria查询来实现搜索功能。因此,我们无需关注实现细节,这在本教程的范围之外。

 

package org.andromda.timetracker.domain;
    
    

   
   
    
     
   
   
import org.hibernate.Criteria;
    
    
import org.hibernate.FetchMode;
    
    
import org.hibernate.criterion.Restrictions;
    
    

   
   
    
     
   
   
/**
    
    
 * @see org.andromda.timetracker.domain.Timecard
    
    
 */
    
    
public class TimecardDaoImpl
    
    
    extends org.andromda.timetracker.domain.TimecardDaoBase
    
    
{
    
    
    ...
    
    
    protected java.util.List handleFindByCriteria(
    
    
        org.andromda.timetracker.vo.TimecardSearchCriteriaVO criteria)
    
    
    {
    
    
        // Create the timecard criteria
    
    
        Criteria timecardCriteria = this.getSession()
    
    
            .createCriteria(Timecard.class)
    
    
            .setFetchMode("submitter", FetchMode.JOIN)
    
    
            .setFetchMode("approver", FetchMode.JOIN);
    
    

   
   
    
     
   
   
        // Add submitter criteria
    
    
        if (criteria.getSubmitterId() != null) {
    
    
            timecardCriteria.createCriteria("submitter")
    
    
                .add(Restrictions.idEq(criteria.getSubmitterId()));
    
    
        }
    
    

   
   
    
     
   
   
        // Add approver criteria
    
    
        if (criteria.getApproverId() != null) {
    
    
            timecardCriteria.createCriteria("approver")
    
    
                .add(Restrictions.idEq(criteria.getApproverId()));
    
    
        }
    
    

   
   
    
     
   
   
        // Add status criteria
    
    
        if (criteria.getStatus() != null) {
    
    
          timecardCriteria.add(Restrictions.eq("status", criteria.getStatus()));
    
    
        }
    
    

   
   
    
     
   
   
        // Add startDateMin criteria
    
    
        if (criteria.getStartDateMin() != null) {
    
    
          timecardCriteria.add(
    
    
              Restrictions.ge("startDate", criteria.getStartDateMin()));
    
    
        }
    
    

   
   
    
     
   
   
        // Add startDateMax criteria
    
    
        if (criteria.getStartDateMax() != null) {
    
    
          timecardCriteria.add(
    
    
              Restrictions.le("startDate", criteria.getStartDateMax()));
    
    
        }
    
    

   
   
    
     
   
   
        java.util.List timecards = timecardCriteria.list();
    
    
        if (logger.isDebugEnabled()) {
    
    
            logger.debug(timecards.size() + " timecards found");
    
    
        }
    
    
        return timecards;
    
    
    }
    
    
    ...
    
    
}
    
    

现在改变在TimecardDaoImpl类的TimecardSummaryVO()方法的默认实现,因为他不正确。注意,这个方法要被TimeTrackerService.findByCriteria()来调用,用来转换Timecard实体。目前,toTimecardSummaryVO()简单调用了在父类中的方法(TimecardDaoBase)。如果你还记得,在DaoBase中的默认的转换实现非常简单化。特殊的,TimecardDaoBase类只拷贝Timecard属性到TimecardSummaryVO;他不在从相关的实体的属性中填充,叫做submitterNameapproverName。因此在TimecardDaoImpl类的TimecardSummaryVO()方法中做下面的修改:

 

public void toTimecardSummaryVO(
    
    
        org.andromda.timetracker.domain.Timecard sourceEntity,
    
    
        org.andromda.timetracker.vo.TimecardSummaryVO targetVO)
    
    
    {
    
    
        super.toTimecardSummaryVO(sourceEntity, targetVO);
    
    
        targetVO.setSubmitterName(sourceEntity.getSubmitter().getUsername());
    
    
        if (sourceEntity.getApprover() != null) {
    
    
            targetVO.setApproverName(sourceEntity.getApprover().getUsername());
    
    
        }
    
    
    }
    
    

 

最后一件我们需要做的事情是在数据库中创建Timecard表并且填充数据。下面的步骤会完成这项工作:

1.    在命令提示符中运行下面的命令。要求androMDA创建我们的数据库schema:

Mvn –f core/pom.xml andromdapp:schema –Dtask=create

确保看到了构建成功的字样

2.    打开MySQL Query Browser。用timetracker登录。你能看到usertimecard表。

3.    选择文件->New Script粘贴下面的脚本

insert into USERS (ID, USERNAME, FIRST_NAME, LAST_NAME)
    
    
    values (1, 'nbhatia',      'Naresh', 'Bhatia');
    
    
insert into USERS (ID, USERNAME, FIRST_NAME, LAST_NAME)
    
    
    values (2, 'lcoude',       'Louis',  'Coude');
    
    
insert into USERS (ID, USERNAME, FIRST_NAME, LAST_NAME)
    
    
    values (3, 'ecrutchfield', 'Eric',   'Crutchfield');
    
    
insert into USERS (ID, USERNAME, FIRST_NAME, LAST_NAME)
    
    
    values (4, 'cmicali',      'Chris',  'Micali');
    
    

   
   
    
     
   
   
insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK)
    
    
    values ( 1, 'Approved',  '
    
    
     
     2006/05/15
    
    ', 'Timecard 01', 1, 2);
    
    
insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK)
    
    
    values ( 2, 'Approved',  '
    
    
     
     2006/05/15
    
    ', 'Timecard 02', 2, 3);
    
    
insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK)
    
    
    values ( 3, 'Approved',  '
    
    
     
     2006/05/15
    
    ', 'Timecard 03', 3, 4);
    
    
insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK)
    
    
    values ( 4, 'Approved',  '
    
    
     
     2006/05/15
    
    ', 'Timecard 04', 4, 1);
    
    

   
   
    
     
   
   
insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK)
    
    
    values ( 5, 'Rejected',  '
    
    
     
     2006/05/22
    
    ', 'Timecard 05', 1, 2);
    
    
insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK)
    
    
    values ( 6, 'Rejected',  '
    
    
     
     2006/05/22
    
    ', 'Timecard 06', 2, 3);
    
    
insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK)
    
    
    values ( 7, 'Rejected',  '
    
    
     
     2006/05/22
    
    ', 'Timecard 07', 3, 4);
    
    
insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK)
    
    
    values ( 8, 'Rejected',  '
    
    
     
     2006/05/22
    
    ', 'Timecard 08', 4, 1);
    
    

   
   
    
     
   
   
insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK)
    
    
    values ( 9, 'Submitted', '
    
    
     
     2006/05/29
    
    ', 'Timecard 09', 1, 2);
    
    
insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK)
    
    
    values (10, 'Submitted', '
    
    
     
     2006/05/29
    
    ', 'Timecard 10', 2, 3);
    
    
insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK)
    
    
    values (11, 'Submitted', '
    
    
     
     2006/05/29
    
    ', 'Timecard 11', 3, 4);
    
    
insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK)
    
    
    values (12, 'Submitted', '
    
    
     
     2006/05/29
    
    ', 'Timecard 12', 4, 1);
    
    

   
   
    
     
   
   
insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK)
    
    
    values (13, 'Draft',     '
    
    
     
     2006/06/05
    
    ', 'Timecard 13', null, 2);
    
    
insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK)
    
    
    values (14, 'Draft',     '
    
    
     
     2006/06/05
    
    ', 'Timecard 14', null, 3);
    
    
insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK)
    
    
    values (15, 'Draft',     '
    
    
     
     2006/06/05
    
    ', 'Timecard 15', null, 4);
    
    
insert into TIMECARD (ID, STATUS, START_DATE, COMMENTS, APPROVER_FK, SUBMITTER_FK)
    
    
    values (16, 'Draft',     '
    
    
     
     2006/06/05
    
    ', 'Timecard 16', null, 1);
    
    

2.  在顶端靠右点击执行按钮。

好,我们认为TimeTrackingService现在完成了。让我们执行下面的命令来再次运行测试,并且看看发生了什么:

C:/timetracker>mvn -f core/pom.xml test
    
    
...
    
    
...
    
    
-------------------------------------------------------
    
    
 T E S T S
    
    
-------------------------------------------------------
    
    
Running Services Test
    
    
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.272 sec
    
    

   
   
    
     
   
   
Results :
    
    
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0
    
    

   
   
    
     
   
   
[INFO] ------------------------------------------------------------------------
    
    
[INFO] BUILD SUCCESSFUL
    
    
[INFO] ------------------------------------------------------------------------
    
    
[INFO] Total time: 30 seconds
    
    
[INFO] Finished at: Fri Aug 11 21:05:53 EDT 2006
    
    
[INFO] Final Memory: 
    
    
     
     6M
    
    /
    
    
     
     14M
    
    
    
    
[INFO] ------------------------------------------------------------------------
    
    
    
    
    

棒极了!所有的测试都通过了。打开文件C:/timetracker/core/timetracker-test.log

21:05:46.850 INFO  - Initializing ServiceLocator
    
    
21:05:46.870 INFO  - Initializing UserService
    
    
21:05:51.767 INFO  - Initializing ServiceLocator
    
    
21:05:51.767 INFO  - Initializing TimeTrackingService
    
    
21:05:51.858 INFO  - testFindAllTimecards:
    
    
...
    
    
21:05:52.819 DEBUG - 16 timecards found
    
    
21:05:52.859 INFO  - Submitter     Approver      Status     Start Date
    
    
21:05:52.869 INFO  - nbhatia       cmicali       Approved   
    
    
     
     05/15/06
    
    
    
    
21:05:52.869 INFO  - nbhatia       cmicali       Rejected   
    
    
     
     05/22/06
    
    
    
    
21:05:52.869 INFO  - nbhatia       cmicali       Submitted  
    
    
     
     05/29/06
    
    
    
    
21:05:52.869 INFO  - nbhatia       null          Draft      
    
    
     
     06/05/06
    
    
    
    
21:05:52.869 INFO  - lcoude        nbhatia       Approved   
    
    
     
     05/15/06
    
    
    
    
21:05:52.869 INFO  - lcoude        nbhatia       Rejected   
    
    
     
     05/22/06
    
    
    
    
21:05:52.869 INFO  - lcoude        nbhatia       Submitted  
    
    
     
     05/29/06
    
    
    
    
21:05:52.869 INFO  - lcoude        null          Draft      
    
    
     
     06/05/06
    
    
    
    
21:05:52.869 INFO  - ecrutchfield  lcoude        Approved   
    
    
     
     05/15/06
    
    
    
    
21:05:52.869 INFO  - ecrutchfield  lcoude        Rejected   
    
    
     
     05/22/06
    
    
    
    
21:05:52.869 INFO  - ecrutchfield  lcoude        Submitted  
    
    
     
     05/29/06
    
    
    
    
21:05:52.869 INFO  - ecrutchfield  null          Draft      
    
    
     
     06/05/06
    
    
    
    
21:05:52.869 INFO  - cmicali       ecrutchfield  Approved   
    
    
     
     05/15/06
    
    
    
    
21:05:52.869 INFO  - cmicali       ecrutchfield  Rejected   
    
    
     
     05/22/06
    
    
    
    
21:05:52.869 INFO  - cmicali       ecrutchfield  Submitted  
    
    
     
     05/29/06
    
    
    
    
21:05:52.869 INFO  - cmicali       null          Draft      
    
    
     
     06/05/06
    
    
    
    
21:05:52.879 INFO  - testFindTimecardsForSubmitter:
    
    
...
    
    
21:05:52.959 DEBUG - 4 timecards found
    
    
21:05:52.959 INFO  - Submitter     Approver      Status     Start Date
    
    
21:05:52.969 INFO  - nbhatia       cmicali       Approved   
    
    
     
     05/15/06
    
    
    
    
21:05:52.969 INFO  - nbhatia       cmicali       Rejected   
    
    
     
     05/22/06
    
    
    
    
21:05:52.969 INFO  - nbhatia       cmicali       Submitted  
    
    
     
     05/29/06
    
    
    
    
21:05:52.969 INFO  - nbhatia       null          Draft      
    
    
     
     06/05/06
    
    
    
    
...
    
    

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值