Java Server Faces (JSF)页面转换与验证(二)

这个案例分析的目的是将一个单字段字符串值转换为一个  PhoneNumber对象。我们将一步一步地完成这个转换过程。

第 1 步:实现 Converter 接口

这一步实现 Converter接口。

 import javax.faces.convert.Converter; 
 import org.apache.commons.lang.StringUtils; 
 ... 
 public class PhoneConverter implements Converter { 
	 ... 
 }

第 2 步:实现 getAsObject 方法

这一步将一个字段值转换为一个 PhoneNumber对象。

public class PhoneConverter implements Converter { 
    ... 
    public Object getAsObject(FacesContext context, UIComponent component, 
    String value) { 
        if (StringUtils.isEmpty(value)){ return null;} 
        PhoneNumber phone = new PhoneNumber(); 
        String [] phoneComps = StringUtils.split(value," ,()-"); 
        String countryCode = phoneComps[0]; 
        phone.setCountryCode(countryCode); 
        if ("1".equals(countryCode)){ 
            String areaCode = phoneComps[1]; 
            String prefix = phoneComps[2]; 
            String number = phoneComps[3]; 
            phone.setAreaCode(areaCode); 
            phone.setPrefix(prefix); 
            phone.setNumber(number); 
        }else { 
            phone.setNumber(value); 
        } 
        return phone; 
     }     
 }

第 3 步:实现 getAsString 方法

这一步将一个 PhoneNumber对象转换为一个字符串。

public class PhoneConverter implements Converter { 
    ... 
    public String getAsString(FacesContext context, 
    UIComponent component, Object value) { 
        return value.toString(); 
    } 
} 
public class PhoneNumber implements Serializable { 
    ... 
    public String toString(){ 
        if (countryCode.equals("1")){ 
            return countryCode + " " + areaCode 
                              + " " + prefix + " " + number; 
        }else{ 
            return number; 
        } 
    } 
}

第 4 步:在 faces 上下文中注册自定义转换器

第 4 步可以以两种方式执行。第一种选择使用(比如)arcmind.PhoneConverter 的 id 来注册 PhoneConverter类。JSP 页中的  <f:converter/>标签会使用这个 id。下面是 第 4 步的选项 1 的代码:

 <converter> 
  <converter-id>arcmind.PhoneConverter</converter-id> 
  <converter-class>com.arcmind.converters.PhoneConverter</converter-class> 
      
 </converter>

另一种方法是注册 PhoneConverter类来自动处理所有 PhoneNumber对象,如下所示。

 <converter> 
  
  <converter-for-class>com.arcmind.value.PhoneNumber</converter-for-class> 
  <converter-class>com.arcmind.converters.PhoneConverter</converter-class> 
      
 </converter>

第 5 步:在 JSP 中使用转换器标签?

自然,下一步的执行取决于所选的注册方法。如果选择使用 arcmind.PhoneConverter 的 id 来注册 PhoneConverter类,那么就使用  <f:converter/>标签,如下所示。

 <h:inputText id="phone" value="#{UserRegistration.user.phone}"> 
        <f:converter  converterId="arcmind.PhoneConverter" /> 
 </h:inputText>

如果选择注册 PhoneConverter类来 自动 处理所有 PhoneNumber,那么就不需要在 JSP 页 中使用 <f:converter/>标签。下面是第 5  步的不带转换器标签的代码。

 <h:inputText id="phone" value="#{UserRegistration.user.phone}"> 
        [Look mom no converter!] 
 </h:inputText>

这样,我们已经完成了这个示例应用程序的转换处理代码!到目前为止完成的应用程序如下图所示。

210435_dWHE_2303434.gif

JSF 验证

如前所述,JSF 验证可以确保应用程序数据包含预期的内容,例如:

  • java.util.Date 为 MM/yyyy 格式。

  • Float 在 1.0 和 100.0 之间。

在 JSF 中有 4 种验证:

  • 自带验证组件。

  • 应用程序级验证。

  • 自定义验证组件(它实现了 Validator接口)。

  • 在 backing bean 中的验证方法(内联)。

我们将在下面的讨论中介绍并展示每一种形式。

JSF 验证生命周期和组件

下图 显示了用户注册表单中名字字段的生命周期案例分析。代码引用被有意解释为 伪代码(pseudo-code)。

210536_0IVq_2303434.gif

下面是 JSF 提供的一组标准验证组件:

  • DoubleRangeValidator:组件的本地值必须为数字类型,必须 在由最小和 / 或最大值所指定的范围内。

  • LongRangeValidator:组件的本地值必须为数字类型,并且可以转换 为长整型,必须在由最小和 / 或最大值所指定的范围内。

  • LengthValidator:类型必须为字符串,长度必须在由最小和 / 或 最大值所指定的范围内。

标准验证

在我们的示例应用程序中,用户的年龄可以是任意有效的整数(byte、short、int)。 因为将年龄设置为(比如说)-2是无意义的,所以可能要对这个字段添加一些验证。 下面是一些简单的验证代码,用以确保年龄字段中的数据模型完整性:

 <h:inputText id="age" value="#{UserRegistration.user.age}"> 
          <f:validateLongRange maximum="150"
                                  minimum="0"/> 
 </h:inputText>

完成年龄字段后,可能希望指定对名字字段的长度加以限制。可以像这样编写这个验证:

 <h:inputText id="firstName"
                value="#{UserRegistration.user.firstName}"> 
	 <f:validateLength minimum="2" 
						 maximum="25" /> 
 </h:inputText>

下图显示了由上面标准验证示例所生成的默认详细验证消息。

210656_UcVc_2303434.jpg

尽管 JSF 自带的验证在许多情况下都可以满足,但是它有一些局限性。 在处理电子邮件验证、电话号码、URL、日期等数据时,有时编写自己的验证 器会更好一些,不过我们将在稍后对此进行讨论。

应用程序级验证

在概念上,应用程序级验证实际上是业务逻辑验证。JSF 将表单和 / 或字段级 验证与业务逻辑验证分离开。应用程序级验证主要需要在 backing bean 中添加代码,用这个模型确定绑定到模型中的数据是否合格。对于购物车,表单级验证 可以验证输入的数量是否有效,但是需要使用业务逻辑验证检查用户是否超出了他或者 她的信用额度。这是在 JSF 中分离关注点的另一个例子。

例如,假定用户单击了绑定到某个操作方法的按钮,那么就会在调用应用程序阶段调用 这个方法(有关的细节,请参见第一幅图)。假定在更新模型阶段进 行了更新,那么在对模型数据执行任何操纵之前,可以添加一些验证代码,根据应用程序的业务规则检查输入的数据是否有效。

例如,在这个示例应用程序中,用户单击了 Register按钮,这个按钮被绑定到应用程序控制器的 register()方法。 我们可以在 register()方法中添加验证代码,以确定名字字段是否为 null。如果该字段为 null,那么还可以在  FacesContext中添加一条消息,指示相关组件返回到当前页。

其实它现在并不是业务规则逻辑的一个好例子。更好的例子是检查用户是否 超出了她或者她的信用额度。在该例中,不是检查字段是否为空,我们可以调用模型对象的方法来确保当前用户已经不在系统中。

下图描绘了这个过程:

122833_oM85_2303434.gif

注意在 register()方法中,消息是如何以   ${formId}:${fieldId}的形式添加到 FacesContext中的。 图 12 显示了消息与组件 id 之间的关系。

验证消息:

Message id added as ${formId} ;{FIELDId}
<h:message associated with fieldId
(use <h:messages to display all messages)

123224_5WV8_2303434.gif


应用程序级验证的优缺点

应用级验证非常直观并且容易实现。不过,这种形式的验证是在其他形式的验证 (标准、自定义、组件)之后发生的。

应用程序级验证的优点如下:

  • 容易实现。

  • 不需要单独的类(自定义验证器)。

  • 不需要页编写者指定验证器。

应用程序级验证的缺点如下:

  • 在其他形式的验证(标准、自定义)之后发生。

  • 验证逻辑局限于 backing bean 方法,使得重用性很有限。

  • 在大型应用程序和 / 或团队环境中可能难于管理。

最终,应用程序级验证只应该用于那些需要业务逻辑验证的环境中。

自定义验证组件

对于标准 JSF 验证器不支持的数据类型,则需要建立自己的自定义验证组件,其中包括电子邮件地址和邮政编码。如果需要明确控制显示给最终用户的消息, 那么还需要建立自己的验证器。在 JSF 中,可以创建可在整个 Web 应用程序中重复使用 的可插入验证组件。

创建自定义验证器的步骤如下,我们将一步步地分析:

  1. 创建一个实现了 Validator接口的类 (javax.faces.validator.Validator)。

  2. 实现 validate方法。

  3. 在 faces-confix.xml 文件中注册自定义验证。

  4. 在 JSP 页中使用 <f:validator/>标签。

下面是创建自定义验证器的分步示例代码。

第 1:实现 Validator 接口

第一步是实现 Validator接口。

 import javax.faces.validator.Validator; 
 import javax.faces.validator.ValidatorException; 
 ... 
 public class ZipCodeValidator implements Validator{ 
	 private boolean plus4Required; 
	 private boolean plus4Optional; 
	 /** Accepts zip codes like 85710 */ 
	 private static final String ZIP_REGEX = "[0-9]{5}"; 
	
	 /** Accepts zip code plus 4 extensions like "-1119" or " 1119" */ 
	 private static final String PLUS4_REQUIRED_REGEX = "[ |-]{1}[0-9]{4}"; 
	
	 /** Optionally accepts a plus 4 */ 
	 private static final String PLUS4_OPTIONAL_REGEX = "([ |-]{1}[0-9]{4})?"; 
	 ... 
 }

第 2 步:实现验证方法

接下来,需要实现 validate方法。

public void validate(FacesContext context, UIComponent component, Object value) 
throws ValidatorException { 
		 
      /* Create the correct mask */ 
      Pattern mask =  null; 
		 
      /* more on this method later */ 
      initProps(component); 
      if (plus4Required){ 
            mask = Pattern.compile(ZIP_REGEX + PLUS4_REQUIRED_REGEX); 
      } else if (plus4Optional){ 
            mask = Pattern.compile(ZIP_REGEX + PLUS4_OPTIONAL_REGEX); 
      } else if (plus4Required && plus4Optional){ 
            throw new IllegalStateException("Plus 4 is either optional or required");
      } 
      else { 
            mask = Pattern.compile(ZIP_REGEX); 
      } 
            /* Get the string value of the current field */ 
      String zipField = (String)value; 
		 	
            /* Check to see if the value is a zip code */ 
    Matcher matcher = mask.matcher(zipField); 
	     
    if (!matcher.matches()){ 
	     	
       FacesMessage message = new FacesMessage(); 
       message.setDetail("Zip code not valid"); 
       message.setSummary("Zip code not valid"); 
       message.setSeverity(FacesMessage.SEVERITY_ERROR); 
       throw new ValidatorException(message); 
    } 
 }

第 3 步:在 FacesContext 中注册自定义验证器

您现在应该熟悉在 FacesContext中注册自定义验证器的代码了。

<validator> 
  <validator-id>arcmind.zipCodeValidator</validator-id> 
  <validator-class>
      com.arcmind.jsfquickstart.validation.ZipCodeValidator
  </validator-class> 
</validator>

第 4 步:在 JSP 中使用 <f:validator/> 标签

<f:validator/>标签声明使用  zipCodeValidator<f:attribute/> 标签将 plus4Optional属性设置为 true。 注意,它定义了 inputText 组件的属性,而 不是验证器的属性!

  <h:inputText id="zipCode" value="#{UserRegistration.user.zipCode}"> 
      <f:validator validatorId="armind.zipCodeValidator"/> 
      <f:attribute name="plus4Optional" value="true"/> 
  </h:inputText>

为了读取 zipCodeinputText组件的 plus4Optional属性,请完成以下步骤::

 private void initProps(UIComponent component) { 
  Boolean optional = Boolean.valueOf((String) component.getAttributes(). 
                                   get("plus4Optional")); 
  Boolean required = Boolean.valueOf((String) component.getAttributes(). 
                                   get("plus4Required")); 
  plus4Optional = optional==null ? plus4Optional : 
					     optional.booleanValue(); 
  plus4Required = required==null ? plus4Optional : 
					     required.booleanValue(); 
 }

总体而言,创建自定义验证器是相当直观的,并且可以使该验证在许多应用程序中重复使用。缺点是必须创建一个类,并在 faces 上下文中管理验证器注册。 不过,通过创建一个使用这个验证器的自定义标签,使其看上去像是一个自带的验证,可以进一步实现自定义验证器。对于常见的验证问题,如电子邮件 验证,这种方法可以支持这样一种设计理念,即代码重用和一致的应用程序行为是 最重要的。

backing bean 中的验证方法

作为创建单独的验证器类的替代方法,可以只在 backing bean 的方法中实现自定义 验证,只要这个方法符合 Validator接口的  validate方法的参数签名即可。例如,可以编写以下方法:

 [SomeBackingBean.java] 
 public void validateEmail(FacesContext context, 
                          UIComponent toValidate, 
                          Object value) { 
    String email = (String) value; 
    if (email.indexOf('@') == -1) { 
       ((UIInput)toValidate).setValid(false); 
        FacesMessage message = new FacesMessage("Invalid Email"); 
        context.addMessage(toValidate.getClientId(context), message); 
    } 
 }

之后,可通过如下所示的 validator属性在 JSF 中使用这个方法:

  <h:inputText id="email" 
               value="#{UserRegistration.user.email}"
               validator="#{UserRegistration.validateEmail}"   
               required="true"> 
  </h:inputText>

JSF 用 validateEmail方法对绑定到   user.email模型属性的 inputText 组件值进行自定义验证。如果电子邮件格式无效,那么就在相关组件的 faces 上下文中添加 消息。考虑到这种验证方法实际上是 backing bean 的一部分,为什么通常必须用某个值与相关组件的关联来评估该值,而不是直接检查本地 bean 属性呢?线索就在前面的生命周期图中。如果现在不能马上找到 答案,也不要担心,我们将在本文的最后对此加以说明。

默认验证

注意上面 email标签的 required属性。 利用 required 属性是一种 默认验证形式。如果这个属性是  true,那么相应的组件必须有一个值。一个重要的 说明:如果 required属性为 false, 那么就不用对这个标签 / 组件指派验证,这样,JSF 将跳过对这个组件的验证,并让值和组件的状态保持不变。

下图显示出我们讨论过的验证形式:

123515_Ybaf_2303434.gif

自定义消息

您可能注意到了,JSF 提供的默认转换和验证消息非常长,这会让那些总是输入无效表单数据的最终用户感到困惑和恼火。幸运的是,您可以通过 创建自己的消息资源绑定来改变 JSF 提供的默认消息。jsf-impl.jar  (或类似的文件中)中包含了一个 message.properties 文件,该文件包含图 14   所示的默认消息。

下图是默认的JSF转换和验证消息

125146_LdTs_2303434.jpg

通过创建自己的 message.properties 文件并断开指定场所的 faces 上下文中绑定 的消息资源,您可以更改默认消息,如下图所示。

125311_R9w7_2303434.gif

关于在 JSF 中创建自定义转换和验证消息的更多内容请参前阅参考资料。

处理 JSF 生命周期

我们在本文前面留下了一些问题让您考虑,现在可以解决它们了! 我们提到的一件事是对 UICommand按钮使用 immediate 属性,比如 commandLink或者 commandButtons。现在请您考虑希望在什么样的场景中跳过验证。

基本上只要用户需要输入数据,就需要对这个数据进行验证。不过,如果整个数据 项是可选的,那么就不需要进行验证。一种避免 JSF 生命周期的验证阶段的方法是利用  UICommand组件的 immediate属性,该属性可以在处理验证阶段 之前的应用请求值阶段期间(而不是在处理验证阶段 之后的调用应用程序阶段) 强制调用这个操作。

immediate属性允许您通过标准浏览规则控制 页流程,并绕过验证。可以针对特定的场景实现这项技术,比如带有可选步骤和 / 或表单的在线 向导(如当用户单击 Skip按钮以进入下一视图),或者在用户因为某种原因而取消某个表单的情况下。

我们在本文中留下的第二个问题是:既然验证方法实际上是 backing bean 的 一部分,那么为什么通常必须利用组件关联来判断它的值。请参阅前面的  JSF 应用程序生命周期,看看您能否找到答案。

这里的密诀是:尽管 validateEmail嵌入 验证方法是实际的 backing bean 的一部分,但是该方法必须通过组件关联来引用这,而不是直接访问本地属性来引用值。由于验证发生在组件值绑定到模型 之前(在更新模型值阶段),所以模型处于未知状态。 因此,必须编写嵌入自定义验证逻辑,就像使用一个自定义 Validator对象处理验证一样。这也解释了维护相同方法签名的需求。

这些尚待解决的枝节问题有什么意义呢,当然,它们最终将我们带回 JSF 应用程序生命周期。将这些问题汇总在一起,就能体现充分理解生命周期的重要性 —— 向后、向前或由内向外,这样您就可以在需要的时候操纵它。

结束语

在本文中我们讨论了相当多的 JSF 转换和验证的基本内容。事实上, 我们讨论了在自己的应用程序中使用这些过程需要知道的大部分内容 (至少对这个版本的 JSF 而言)!

当然,我们不可能讨论到 所有内容。例如,您可能想要了解 MyFaces (请参阅 参考资料)中 JSF 没有提供、或者这里没有讨论到的验证器组件。 此外,虽然我们讨论了大多数常用的转换和验证技术,但还有一些没有包含在内。 例如,在编写自定义组件时,可以在组件的解码 / 编码过程中直接处理转换和 / 或验证 (取决于组件的类型及其功能),但是我们只能将对自定义组件开发的更深入讨论留 到以后进行了。

其他要牢记的是转换和验证不一定会很好地协同工作。转换将字符串转换 为对象,而大多数标准验证是对字符串进行的。因此,在同时使用自定义转换 和验证必须格外小心。例如,PhoneNumber 对象不能与长度验证器一起使用。在这种情况下,要么编写自定义验证器,要么在自定义转换器中添加一个特别的验证逻辑。我们偏向后一种方法,因为 它让我们可以将自定义转换器(自带验证逻辑)与特定的对象类型相关联,并让  JSF 处理这种对象类型。JSF 自动为我们做这项工作,不需要在 JSP 中包含任何 特定的转换器 id。(当然,有人会称它为懒惰编程,它也不是对所有用例都适用的最佳解决方案。)

我们认为本月文章中的讨论再次声明了以下这点,即 JSF 提供了一种灵活的、强大的可插入式 Web 应用程序开发框架。除了标准转换器和验证器之外,JSF 还可以促进同时满足应用程序和框架开发人员的要求的自定义实现。最终,要由您来确定选择何种转换和验证策略。JSF 使您能够在原型制造阶段很快、很容易地上手(标准转换器、验证器、内部验证等),并在以后的开发阶段移植到更 复杂的生产解决方案中(自定义对象、自定义消息等)。JSF 生命周期在所有阶段都提供了 可靠的基础设施,始终如一地保证数据模型的完整性。

转载于:https://my.oschina.net/guopengfei/blog/374768

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JSF是一种用于构建Java Web 应用程序的标准框架(是Java Community Process 规定的JSR-127标准)。JSF(Java Server Faces)技术为开发基于网络用户界面的Java开发者提供了标准的编程接口API以及标签库。就像Struts框架一样,JSF定义了一套JSF标签 JSF的全称   1、Joint Strike Fighter (JSF)   2、Java Server Faces (JSF) Java Server Faces (JSF)   JSF是一种用于构建Java Web 应用程序的标准框架(是Java Community Process 规定的JSR-127标准)。它提供了一种以组件为中心的用户界面(UI)构建方法,从而简化了Java服务器端应用程序的开发。由于由Java Community Process (JCP) 推动,属于Java EE 5中的技术规范,而受到了厂商的广泛支持。   JSF(Java Server Faces)技术为开发基于网络用户界面的Java开发者提供了标准的编程接口API以及标签库。就像Struts框架一样,JSF定义了一套JSF标签,能够生成与JavaBean属性绑定在一起的HTML表单元素。从应用开发者的角度看,两种框架十分相似,但是JSF可能会得到更多的支持,因为JSFJava的标准。在未来的发展中,有可能所有的J2EE应用服务器都需要支持JSFJava Server Faces技术好处   引入了基于组件和事件驱动的开发模式,使开发人员可以使用类似于处理传统界面的方式来开发Web应用程序。提供了行为与表达的清晰分离。 不用特别的脚本语言或者标记语言来连接UI组件和Web层。JSF技术API被直接分层在Servlet API的顶端。 技术为管理组件状态提供一个丰富的体系机构、处理组件数据、确认用户输入和操作事件。 Java Server Faces应用程序   典型的JSF应用程序包含下列部分:   一组JSP页面   一组后台bean(为在一个页面上的UI组件定义的属性和函数的JavaBean组件)   应用程序配置资源文件(定义页面导航规则、配置bean和其它的自定义对象,如自定义组件)   部署描述文件( web.xml )   一组由应用程序开发者创建的自定义对象(有可能)   一些可能包含自定义组件、约束、转换器或者监听器的对象   为在页面中表现自定义对象的一组自定义tag   包含JSP页面JSF应用程序也使用由为了表现UI组件和在页面上的其他对象的JSF技术而定义的标准的tag库。 Java Server Faces技术的重要开发框架 sun-ri、myfaces、icefaces、richfaces、seam
如果您是从使用的角度来看 JSF,则您不用理会HTTP、数据转换等细节,JSF 将细节 都隐藏起来了,无论您是网页设计人员或是应用程序设计人员,都可以使用自己熟悉的方式 来看JSF。 • 入门 藉由以下的几个主题,可以大致了解JSF 的轮廓与特性,我们来看看网页设计人员 与应用程序设计人员各负责什么。 o 简介 JSF o 第一个 JSF 程序 o 简单的导航 Navigation o 导航规则设置 o JSF Expression Language o 国际化讯息 • Managed Beans JSF 使用 Bean 来达到逻辑层与表现层分离的目的,Bean 的管理集中在组态档 案中,您只要修改组态档案,就可以修改 Bean 之间的相依关系。 o Backing Beans o Beans 的组态与设定 o Beans 上的 List, Map • 数据转换验证 转换器(Converter)协助模型与视图之间的数据转换验证器(Validator)协助 进行语意检验(Semantic Validation)。 o 标准转换器 o 自订转换器 o 标准验证器 o 自订验证器 o 错误讯息处理 o 自订转换, 验证标签 • 事件处理 JSF 的事件模型提供一个近似的桌面GUI 事件模式,让熟悉GUI 设计的人员也能 快速上手Web 程序设计。 o 动作事件 o 实时事件 o 值变事件 o Phase 事件 JSF 标签 Tellixu 整理 2005-12-16 JSF 教程 2 网页设计人员要作的就是了解JSF 的标签的使用方式,这就像是学习进阶的HTML 标 签,另一件事就是与程序设计人员沟通好各个Bean 的名称绑定。 • 标签入门 卷标的相关属性查询,您可以参考 Tag Library Documentation,这边的介绍只 是一些简单的入门实例。 o 简介 JSF 标准标签 o 输出类标签 o 输入类标签 o 命令类标签 o 选择类标签一 o 选择类标签 o 其它标签 • 表格处理 对于必须使用表格方式呈现的数据,JSF 的 <h:dataTable> 卷标协助您进行动 态表格数据的输出。 o 简单的表格 o 表头, 表尾 o TableModel 类别 自订组件 JSF 让您可以自订组件,每个组件都是可替换的,这使得组件在搭配时更有弹性,但 相对的却使开发组件的过程复杂的多,这边对自订JSF 组件只是个入门砖,更多有关自订 组件的细节可得要专书来说明。 • JSF 生命周期与组件概述 要开发 JSF 组件,您需要更深入了解 JSF 的一些处理细节,包括了 JSF 生命周 期以及 JSF 框架。 o JSF 生命周期 o 概述自订组件 • 简单实例 在不考虑组件有子组件的情况下,这边以实际的一个例子来说明开发组件的过程, 至于考虑子组件的情况请参考专书介绍。 o 编码, 解碼 o 组件卷标 o 使用自订组件 o 自订 Renderer

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值