Using the Jakarta Commons, Part 1

Jakarta Commons是Jakarta的一个子项目,目的是创建和维护独立于其他框架和产品的程序包(packages)。这些程序包是一些服务于小范围的有效组件的集合,通常适用于服务器端编程。
   Commons项目分为两部分:Sandbox和Commons库。Sandbox用于测试。本文关注库组件,包括它们什么时候使用,在那里,以及用例子说明如何使用。

简要介绍
   Jakarta Commons项目源于重用,其中的程序包必须确保能够重用。有一些包来自于其他项目,例如通用日志包是Jakarta Struts的一部分。当开发者发现某个包对于其他项目很有用,可以缩短开发周期,他们决定将这些包做成通用组件。这就是Jakarta Commons项目。
   要真正做到可重用,每个程序包必须独立于其他较大的框架和项目。因此,Commons项目中的每个包在很大程度上是独立的,不仅相对于其他项目,甚至对于其他包也是如此。违反这一原则的情况是存在的,但决大多数情况是使用成熟的APIs。例如,Betwixt包建立在XML APIs基础之上。尽管这个项目的本意是建立不依赖其他组件的程序包。

   大多数程序包十分简洁,以至于缺少必要的文档、维护和帮助。有些包甚至只有错误的连接和极少的文档。大多数情况下,你只能自己摸索如何使用它们,为什么使用它们。希望这篇文章对你有帮助。

    注意:Jakarta Commons与Apache Commons是不同的。后者是Apache Software Foundation(ASF)的顶级项目。而前者是ASF的另一个顶级项目Jakarta的子项目,是本文介绍的对象。而且,Jakarta Commons只使用Java。在本文中Commons指的是Jakarta Commons。

 
组件

    为了组织方便,我将18个(包括EL,Latka和Jexl)Commons组件分为五类。如下表:

组件类别
 组件
 
Web相关
 FileUpload,HTTPClient和Net 
XML相关
 Betwixt,Digester,Jelly和JXPath 
工具
 BeanUtils,Logging,DBCP,Pool和 Validator 
打包
 Codec 和 Modeler 
小程序
 CLI,Discovery,Lang和 Collections
 

       要注意的是这个分类只是对本文而言,在Commons项目中是不存在的。在某种程度上分类是重叠的。本文将介绍Web相关和小程序类,下篇文章包括XML相关和打包类,工具类在最后一篇文章中。

小程序类
     将CLI,Discovery,Lang和 Collections归入小程序类是因为它们都是为了一个小而实用的目的编写的。

1.CLI

     概要:CLI(Command Line Interface)为你的Java程序提供读取和解析命令行参数的通用接口。
     在那得到:主页,程序,源代码。
     何时使用:需要统一操作命令行参数时。
     例子程序:CLIDemo.java,需要将commons-cli-1.0.jarcommons加入CLASSPATH中。
     描述:通常在完成一个Java程序时不得不重写应用程序输入参数的处理部分。如果有一个唯一的接口用来定义﹑解析和读取输入参数,以决定程序的运行方式不是很好吗?CLI就是答案。
     对于CLI,命令行中每个要处理的参数都是一个Option。创建一个Options对象,将Option对象添加进去,然后用CLI提供的函数解析用户的输入参数。一个Option也许也需要用户输入一个值,例如文件名。这时Option必须在指定处创建。
     CLI使用步骤如下:
       1.创建Options:
         Options options = new Options();
         Options.addOption(“t”,false,”current time”);

       2.创建解析器解析用户输入:
         CommandLineParser parser = new BasicParser();
         CommandLine cmd;
            try{
              cmd = parser.parse(options, args);
            } catch(ParseException pe) {
              usage(options);
              return;
            }
      3.根据用户输入执行相应操作:
           if(cmd.hasOption(“n”)) {
                 System.err.println(“Nice to meet you: ”  
                 +   cmd.getOptionvalue(‘n’));
           }
      以上基本就是使用CLI的全过程。当然,CLI提供其他高级选项用于控制各种格式和解析器,但基本操作是相同的。完整的例子可以看demo。

2.Discovery

       概要:discovery模式的实现,提供定位与实例化类或其他资源的通用方法。
       在那得到:主页,程序,源代码。该包处于pre-release状态。
       何时使用:需要快速找到你的代码中Java接口的实现时。
       例子程序:DiscoveryDemo.java,MyInterface.java,MyImpl1.java,MyImpl2.java, MyInterface。需要将commons-discovery.jar和commons-logging.jar添到CLASSPATH中。

       描述:Discovery的目的是使用最好的算法得到接口的所有实现。当用户想找到所有的提供某一服务的提供商时,这将特别有用。
       假设你写了一个针对某一难题的接口。这个接口的所有实现将以唯一的编码方式解决这一难题。真正的用户在实际解决这一难题时将会有多种选择。他怎么才能知道接口的那种实现在他的系统中是可行的?
       这就是Service与Service Provider结构。Service就是你定义的接口。Service Providers提供Service的实现。用户需要选择Service Providers。Discovery组件用多种方法提供帮助。注意Discovery不仅用于发现实现类,而且可以寻找资源,例如图像或其他文件。它遵照Sun的Service Provider Architecture规范。
       同样,Discovery的使用也很简单。例子程序中,MyImpl1和MyImpl2是MyInterface接口的实现。MyInterface文件必须在META-INF/services目录下。注意这个文件必须对应接口的全路径。如果接口在包内,那么文件名也要相应改动。

       1.取得ClassLoader:

              ClassLoaders loaders =
              ClassLoaders.getAppLoaders(MyInterface.class, 
                                      getClass(), false);
       2.创建DiscoverClass用于查找实现类:
              DiscoverClass discover = new DiscoverClass(loaders);

       3.查找实现类:
              Class implClass = discover.find(MyInterface.class);
              System.err.println
                 ("Implementing Provider: " + implClass.getName());

       运行以上代码(DiscoveryDemo.java)将得到MyInterface文件中注册的类,如下所示。再次提醒,如果实现包含在一个包结构内,文件名必须做相应的修改。如果这个文件不在规定目录下,或实现类不能实例化或定位,将抛出DiscoveryException异常,表明找不到MyInterface的实现。

       MyImpl2 # Implementation 2

       当然,这不是注册实现的唯一方法,否则Discovery还有什么用!事实上,这是Discovery发现类的内部机制的最后一步。其他方法包括在系统属性或用户属性中定义实现类的名字。例如,删除META-INF/services目录下的文件,按以下输入运行demo,结果相同。这里系统属性是接口名,而值是接口实现提供者。

       java -DMyInterface=MyImpl1 DiscoveryDemo

       Discovery也可用于创建(单例)服务提供者的实例并调用它们的函数。如下:

       ((MyInterface)discover.newInstance(MyInterface.class)).myMethod();

       注意此时我们并不知道那个服务提供者实现myMethod函数,我们也不关心。这个函数的实现取决于以何种方式运行以上代码以及注册的服务提供者。

3.Lang
       概要:java.lang包的扩展,增加许多对String的操作。提供类C语言的枚举。
       在那得到:主页,程序,源代码。这里介绍的是Lang1.0,翻译本文时Lang2.0已经发布。
       何时使用:当对java.lang提供的默认实现感到厌烦,想更好的控制String的操作,数值函数以及系统属性时,还有,想使用C语言风格的枚举时。
       例子程序:LangDemo.java, Mortgage.java, OnTV.java。需要将commons-lang.jar加入CLASSPATH中。
       描述:这个包中提供的很多工具函数可以简化Java程序员的工作。这些函数可以减少实现日常功能的编程量。特别是StringUtils类,它提供比标准的java.lang.String包更强的操作字符串的功能。它们的使用十分简单,只要用正确的参数调用一个静态函数。例如,要将一句话变为以大写开头只要:
       StringUtils.capitalise("name");
       这个函数的输出就象我们需要的“Name”。浏览StringUtils API的其他静态函数,你可能会发现对你有用的。例子程序中使用了一些。
       另一个有趣的类是RandomStringUtils。这个类中的函数产生随机字符串,这在生成随机密码时很有用。
       类NumberUtils提供数据操作的函数,包括最大最小值函数,以及将字符串转换为数字的函数。NumberRange和CharRange分别处理数字与字符的范围。
       Builder包中的类提供为类添加toString,hashCode,compareTo和equals函数的功能。也就是说,自己不需编码就可以在类中添加高质量的toString,hashCode,compareTo和equals函数,只要使用Builder包中的函数就可以了。例如,用ToStringBuilder函数给类添加toString方法:
        public class Mortgage {
               private float rate;
               private int years;
               ....
               public String toString() {
                      return new ToStringBuilder(this).append
                                 ("rate",  this.rate) 
                                                      .append
                                 ("years", this.years). toString();
               }
        }
       为什么使用这个函数那?因为它使用通用的方法处理所有的数据类型,恰当的返回null,同时可以控制对象和聚集的细节层次。这对于所有builder中的函数有效,而且语法同上所示相近。
       作为一个Java程序员,如果你怀念C语言风格的枚举,那么这个包中的类型安全的Enum数据类型将填补这一空白。Enum类是抽象类,所以要创建自己的枚举,必须扩展这个类。如下例所示:
       1.定义并扩展枚举类:

              import org.apache.commons.lang.enum.Enum;
              import java.util.Map;
              import java.util.List;
              import java.util.Iterator;
              public final class OnTV extends Enum {
                 public static final OnTV IDOL     = new OnTV("Idol");
                 public static final OnTV SURVIVOR = new OnTV("Survivor");
                 public static final OnTV SEINFELD = new OnTV("Seinfeld");
       
                 private OnTV(String show) {
                     super(show);
                 }
                 public static OnTV getEnum(String show){
                     return (OnTV) getEnum(OnTV.class, show);
                 }
                 public static Map getEnumMap() {
                      return getEnumMap(OnTV.class);
                 }

                 public static List getEnumList() {
                      return getEnumList(OnTV.class);
                 }
                 public static Iterator iterator() {
                      return iterator(OnTV.class);
                 }

             }

       2.现在就可以在你的方法中使用枚举了:
       OnTV.getEnum("Idol");
       该行代码从我们创建的枚举数据类型中返回Idol项。Enum类还有其他有用的函数。
4.Collections
       概要:对Java Collection框架的扩展,加入了新的数据结构,iterators和比较器。
       在那得到:主页,程序,源代码。
       何时使用:强烈建议在需要处理数据结构的Java项目尽可能使用Collections API,从中你会获得比Java默认实现大的多的好处。
       例子程序:CollectionsDemo.java。需要将commons-collections.jar加入CLASSPATH中。
       描述:Collections API中有很多类,很难在一节中全介绍出来。所以这里我着重介绍最重要的类,希望你能感兴趣去仔细研究其他类。API附带的文档对每个类都有详细的描述。
       Bag接口扩展了Java Collections,加入了对所有成员的计数,它在要统计进入进出元素数时很有用。因为Bag是一个接口,所以实际使用的是它的实现类,如HashBag或TreeBag。顾名思义,HashBag是基于HashMap的Bag的实现,而Treebag是基于TreeMap的。Bag接口中的两个重要方法是getCount(Object o)和uniqueSet()。前者返回Bag中某特殊元素的个数,后者返回Bag中的唯一元素(译者理解为:元素类型,原文:the unique elements)的集合。

       Buffer接口允许按照预定顺序从聚集中删除对象,可以是后进先出,先进先出,或自定义的顺序。下面的例子演示了如何以自然排序的顺序删除元素的:

       1.BinaryHeap类实现Buffer接口,并按自然排序删除元素。若想以反自然排序删除,以false为参数即可:
       BinaryHeap heap = new BinaryHeap();
       2.添加元素:
       heap.add(new Integer(-1));
       heap.add(new Integer(-10));
       heap.add(new Integer(0));
       heap.add(new Integer(-3));
       heap.add(new Integer(5));
       3.调用remove方法。按自然排序,-10将被删除,反之5被删除:

       heap.remove();
       FastArrayList﹑FastHashMap和FastTreeMap类使用两种模式操作相应的Collection类。第一种为慢模式,是这些类初始化后的默认模式。在慢模式下,这些类的结构变化是同步的。在快模式下,对这些类的访问被认为是只读的,因此更快一些,而且不发生同步。在快模式下要改变类的结构,要先克隆该类,在克隆类上修改,然后覆盖该类。这些类在多数访问为只读的多线程环境中十分有用。
      Iterator包提供了许多常规Java Collections包中没有的聚集和对象的iterator。例子程序中演示了iterator数组的ArrayIterator。这些iterator同普通Java iterators的用法相同。
      最后,comparator包中提供了一些有用的比较器,它们用来定义比较和决定同一类的两个对象的顺序。例如,在我们前面提到的Buffer中,可以定义自己比较器,用它替代自然排序决定顺序。如下:
      1.这次使用NullComparator创建BinaryHeap。NullComparator根据标志nullAreHigh的值决定null与其他对象的大小。如果取值为false,表示null比其他对象小:
      BinaryHeap heap2 = new BinaryHeap(new NullComparator(false));
      2.添加对象,包括一些null
      heap2.add(null);
      heap2.add(new Integer("6"));
      heap2.add(new Integer("-6"));
      heap2.add(null);
      3.最后,执行删除操作。因为null小于其他对象,Bag最后剩下的是null
      heap2.remove();
      到这里,小程序类就介绍完了。更多的细节请看API文档,或者这些包的源代码。

Web相关类

      Web相关类中组件帮助Java程序员完成Web相关的任务。
1.FileUpload

       概要:现成的文件上传组件。

       在那得到:主页。

       何时使用:当Java服务器环境中需要简单易用并且高效的文档上传组件时。

       例子程序:fileuploaddemo.jsp,fileuploaddemo.htm,msg.jsp。需要将commons-fileupload-1.0-dev.jar添加到程序的WEB-INF/lib目录下。

       描述:FileUpload解决了文件上传时服务端的常见问题,提供了一个控制文件上传的易用的接口,可用在JSP页和servlet中。它符合RFC1867协议标准,解析输入请求,并将上传到服务器的一系列文件的分块交给应用程序。上传的文件保存在内存中或临时目录中(这由一个表示文件大小的参数决定,如果上传的文件大小超过该参数值,文件将被写入临时目录)。你也可以设置其他参数,例如可接收的文件的最大尺寸以及临时文件目录。

       FileUpload的使用分为几步,我将用一个在一个页中同时上传两个不同文件的例子说明。

       1.创建HTML页。注意为了确保上传文件的类型是被允许的,enctype参数必须为multipart/form-data,请求参数method必须为POST。还有一点要注意的是该页面不但有两个文件控件还有一个普通文本控件。

       <form name="myform" action="fileuploaddemo.jsp" 
             method="post" enctype="multipart/form-data">
         Specify your name:<br/>
         <input type="text" name="name" size="15"/><br />
               Specify your Image:<br />
         <input type="file" name="myimage"><br/>
               Specify your file&:<br />
         <input type="file" name="myfile"><br /><br />
         <input type="submit" name="Submit" value="Submit your files"/>
       </form>
       2.创建JSP页。
       a.检查输入请求是不是多段数据。
        boolean isMultipart = FileUpload.isMultipartContent(request);
       b.创建请求处理器,解析请求,结果存于一个list中。
        DiskFileUpload upload = new DiskFileUpload();
        List items = upload.parseRequest(request);
       c.遍历这个list访问每个单独的文件项。用isFormField()函数区分上传文件和常规类型域。根据需要,可以逐字节的读取上传的文件,或者使用输入流。
       Iterator itr = items.iterator();
       while(itr.hasNext()) {
           FileItem item = (FileItem) itr.next();
           // check if the current item is a form field or an uploaded file
           if(item.isFormField()) {
           // get the name of the field
           String fieldName = item.getFieldName();
           // if it is name, we can set it in request to thank the user
           if(fieldName.equals("name"))
             request.setAttribute("msg", "Thank You: " + item.getString());
           } else {
           // the item must be an uploaded file save it to disk. Note that there
           // seems to be a bug in item.getName
           () as it returns the full path on
           // the client's machine for the uploaded file name, instead of the file
           // name only. To overcome that, I have used a workaround using
           // fullFile.getName().
           File fullFile  = new File(item.getName());  
           File savedFile = new File(getServletContext().getRealPath("/"),
           fullFile.getName());
           item.write(savedFile);
           }
        }
       可以在上传处理器中用upload.setSizeMax限制上传文件的最大尺寸,当上传文件大小超过该尺寸将会抛出异常。上例中,若将该尺寸设为-1,就可以上传任何大小的文件。
       这个例子还可以有一个小变化。想上面提到的,可以使用输入流上传文件。过程是将上传的内容驻留在内存中直到某一阈值,取得内容的类型,把它们存为字符串或字节数组,最后从内存中删除。FileItem中函数完成了该过程(DefaultFileItem是它的实现)。

2.HttpClient

       概要:扩展java.net包,提供类似浏览器的功能。
       在那得到:主页,程序,源代码。源代码和程序为beta1版。
       何时使用:当要实现Web浏览器时,或你的程序需要有效的控制HTTP/HTTPS连接时。
       例子程序:HttpClientDemo.java。需要将commons-httpclient.jar和common-logging.jar加入CLASSPATH中,JDK为1.4或更高版本。

       描述:HttpClient是java.net的扩展程序包,它提供许多函数帮助你创建基于HTTP协议的各种分布式应用或者嵌入应用程序处理HTTP操作。这个库提供比Commons的其他包更详细的文档,并附带很多例子。这里将讲解怎样开发一个提取Web网页的程序。HttpClient附带的文档中有一个类似的例子,我将扩展它使它支持SSL。注意这个例子必须运行于JDK 1.4或更高版本上,因为它需要JDK1.4中的Java Secure Socket Connection库。

       1.找一个可以通过HTTPS下载的网页,例如https://www.paypal.com/。确保文件%JAVA_HOME%/jre/lib/security/java.security有类似如下的一行:

       security.provider.2=com.sun.net.ssl.internal.ssl.Provider

       这样,至少在你的程序中处理HTTPS连接的方式是没有区别的。如果远端的站点需要验证,那么你必须做相应的配置。

       2.创建HttpClient类的一个实例,所有的函数都将用到它。这个类包含一个连接管理器操作实际的连接。HttpConnectionManager接口允许你创建自己的管理器,否则可以使用内建的SimpleHttpConnectionManager或MultiThreadedHttpConnectionManager。如果无参数创建HttpClient,那么默认连接管理器为SimpleHttpConnectionManager。

       HttpClient client = new HttpClient();

       3.创建一个method实例,用来定义使用那种HTTP方法与远端站点传递信息,可以选择的方法有GET, POST, PUT, DELETE, HEAD, OPTIONS和TRACE。这些method类是HttpMethod接口的不同实现。在这个例子中使用GetMethod,创建时将要GET的URL作为参数。

       HttpMethod method = new GetMethod(url);

       4.连接这个URL,也就是用刚才定义的方法连接URL。这个方法将返回server返回的状态码。注意executeMethod是client的函数而不是method的。

       statusCode = client.executeMethod(method);

       5.读取服务器的返回。如果连接失败,将抛出HttpException或IOException异常。IOException异常说明是网络出了问题,重试也不会成功。返回值可以字节数组﹑输入流或字符串的格式读取。这样,就可以随意处理输入了。

       byte[] responseBody = method.getResponseBody();

       6.最后,释放连接,使之在需要时可重用。

       method.releaseConnection();

       这时一个关于HttpClient库的很粗略的介绍,它还有很多功能,十分健壮。

3.Net

       概要:基本Internet协议的底层API。

       在那得到:主页,程序,源代码。

       何时使用:当在Java应用程序中需要Internet协议的底层互连时。

       例子程序:NetDemo.java。需要将commons-net-1.0.0.jar加入CLASSPATH中。

       描述:Net包是很多健壮的和专业的类的集合。这些类来自于一个叫做NetComponents的商业产品的一部分。

       Net包中的类既提供对协议的底层访问也有高层的抽象。在大多数情况下,抽象是足够的,它可以使你不必编写解析各种协议的底层套接字的代码。使用抽象不会损失任何功能。

       SocketClient是所有协议的基类,它是一个抽象类包含所有协议的共同功能。各种协议的使用方法是很相近的:首先使用connect方法建立与远端服务器的连接,执行服务,最后断开连接。让我们通过例子来看:

       1.创建一个client。我们将使用一个NNTPClient从新闻服务器上下载新闻组列表。

              client = new NNTPClient();

       2.连接服务器,我用的是新闻组列表较短的服务器。

              client.connect("aurelia.deine.net");

       3.提取新闻组列表。如下的命令返回NewsGroupInfo数组。如果服务器上没有新闻组则数组为空,出错则返回null。注意当新闻组列表很大时,这个命令会花很长时间。每个NewsGroupInfo对象包含关于新闻组的详细信息,有公用函数可以解析它们(如文章数,最后发表的文章,发表权限等)。

              list = client.listNewsgroups();

       4.最后,断开与服务器的连接。

              if (client.isConnected())

                                   client.disconnect();

   其余的client如FingerClient, POP3Client, TelnetClient等用法相似。
原文地址:
http://www.matrix.org.cn/article/460.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
资源简介 Jakarta commons docs API CHM 格式带索引和全文搜索,方便携带和查询。 Jakarta commons 包含很多可复用的通用组件commons-attributes 让开发者可以使用 C# 或 .net 样式的 attributes, 是一种运行时的 api, 有点类似 doclet commons-beanutils 提供对 Java 反射和自省API的包装 commons-betwixt 这个组件提供一个XML自省(introspection)机制用来把Java Bean映射到XML文档 commons-chain 提供实现组织复杂的处理流程的“责任链模式” commons-cli 命令行参数解析 , 由 Werken, Avalon 和 Optz 组成 commons-codec 包含一些通用的编码解码算法。包括一些语音编码器, Hex, Base64, 以及URL encoder. commons-collections 扩充了 java.util.collection 接口 commons-compress 操作压缩文件的 API commons-configuration 可以从xml、properties、JNDI、JDBC、System properties、Applet parameters和Servlet parameters等读取信息 commons-daemon 可以帮你实现将一个普通的 Java 应用变成系统的一个后台服务 commons-dbcp 数据连接池服务 commons-dbutils JDBC 辅助类 commons-digester XML 文件到 Java 对象的映射机制 commons-discovery 提供工具来定位资源 (包括类) ,通过使用各种模式来映射服务/引用名称和资源名称。 commons-el 提供在JSP2.0规范中定义的EL表达式的解释器. commons-email 提供一组用于发送Email的API,它基于Java Mail API基础之上并进行了简化 commons-exec 处理外部进程的执行或管理外部环境 commons-fileupload 使得在你可以在应用和Servlet中容易的加入强大和高性能的文件上传能力 commons-io 用来帮助进行IO功能开发.它包含三个主要的领域:Utility classes-提供一些静态方法来完成公共任务.Filters-提供文件过滤器的各种实现.Streams-提供实用的Stream,reader与 writer实现 commons-jelly 能够把XML转换成可执行代码,所以Jelly是一个基于XML与Java的脚本和处理引擎 commons-jexl 是一个表达式语言,通过借鉴来自于Velocity的经验扩展了JSTL定义的表达式语言 commons-jxpath 定义了一个简单的XPath表达式语言的解析器.JXPath应用XPath表达式来操纵各种类型的对象包括:JavaBeans,Maps,Servlet contexts,DOM等 commons-lang commons的基础包 commons-launcher 提供多平台的 Java 程序加载 commons-logging 提供日志的实现 commons-math 是一个轻量的,自包含的数学和统计组件,解决了许多非常通用但没有及时出现在Java标准语言中的实践问题. commons-modeler 创建符合 JMX 规范的 MBeans 机制 commons-net 网络和协议实现的集合 commons-pool 提供了通用对象池接口,一个用于创建模块化对象池的工具包,以及通常的对象池实 commons-primitives java 简单类型使用的扩展 commons-proxy 创建动态代理的库 commons-scxml commons-transaction 处理多级事务的集合 commons-validator 提供了一个简单的,可扩展的框架来在一个XML文件中定义校验器 (校验方法)和校验规则 commons-vfs 虚拟文件系统 httpcomponents-client 和 http 协议客户端一起协作的框架 httpcomponents-core jakarta-oro 一套文本处理工具,能提供perl5.0兼容的正则表达式,AWK-like正则表达式, glob表达式。还提供替换,分割,文件名过虑等功能 jakarta-regexp 是一个100%纯java正则式处理包,是Jonathan Locke捐给Apache软件基金会的
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值