32、开发 Web 应用与 Java 消息服务

开发 Web 应用与 Java 消息服务

1. 转换器和验证器

在 Java 开发中,托管 Bean 可以分配转换器和验证器。Java 类有强类型字段,但数据到达网页时需转换为字符串。JSF 会自动为 Java 数据类型执行此转换,不过也能编写自定义转换器并添加特定应用逻辑。

1.1 验证器的使用

验证器可对托管 Bean 中的数据设置约束。可以使用预定义注解(如 @NotNull 、 @Min 和 @Max )来指定 Bean 字段的数据验证约束,也能创建自定义注解实现特定应用的约束。以下是 Student 类的示例:

@ManagedBean
@RequestScoped
public class Student implements Serializable{
    @NotNull(message="you must enter Student ID")
    @Size(min=8, max=10, message="Student ID must include between 8 and 10 symbols") 
    private long studentId;

    @Size(min=2, message="Name can have less than two characters")   
    private String name;

    @NotNull(message="Please enter Class ID")
    private int classId;
    ...
}

核心 JSF 库有标准验证器,可验证长度以及长整型和双精度型值的范围。可以在 JSF 文件中封装这些验证器,示例如下:

<h:inputText  value="#{student.name}" title="name" id="name" >
    <f:validateLength minimum="2"/>
</h:inputText>

1.2 自定义验证器

要创建自定义验证器(如 CreditCardValidator ),需编写一个实现 javax.faces.validator.Validator 接口的 Java 类,并在 validate() 方法中执行验证。若值无效,该方法必须抛出 ValidatorException 。自定义验证器必须在 faces-config.xml 中注册:

<validator>
    <validator-id>CreditCardValidator</validator-id>
    <validator-class>myvalidators.CreditCardValidator</validator-class>
</validator>

自定义验证器可在 JSF 页面中使用 <f: validator> 标签,示例如下:

<h:inputText  value="#{student.creditcad}" title="card" id="card" >
    <f:validator id="CreditCardValidator"/>
</h:inputText>

2. 使用 JSF 开发股票服务器应用

2.1 需求概述

重写之前创建的股票服务器应用,这次使用 JSF。JSF 客户端应有一个文本输入框和一个提交按钮。用户输入股票代码获取价格报价,生成随机报价并返回包含报价的网页。网页应显示可用股票列表。托管 Bean 需有一个 getQuotes() 方法,用户点击提交按钮时调用。

2.2 实现步骤

  1. 创建新的 StockServer Bean :包含 getQuote() 和 getNasdaqSymbols() 方法。
  2. 定义 JSF 模板 。
  3. 创建两个 JSF 页面 :一个用于请求特定股票的价格报价,另一个用于显示结果。第一个网页的提交按钮应调用托管 Bean 上的 getQuote() 方法。
  4. 运行并测试 JSF 应用 。

2.3 开发提示

NetBeans 7 IDE 对 JSF 的支持优于 Eclipse,它有许多向导,尤其能在图形界面中轻松定义网页导航。另一个流行的 IDE IntelliJ IDEA 9 也对 JSF 提供了出色支持。

3. 介绍 JMS 和 MOM

3.1 消息传递概念

人们通过电子邮件、即时消息、Twitter、Facebook 等方式相互发送消息,也可使用传统方式如邮寄。应用程序可以使用消息导向中间件(MOM)相互发送消息,MOM 类似于快递服务。程序使用 Java 消息服务(JMS)API 将消息放入消息队列,消息会被传递到从该队列读取消息的另一个应用程序。

3.2 消息传递与传统通信的区别

之前学习了分布式 Java 应用中的几种数据交换方法(直接套接字通信、RMI 和基于 HTTP 的交互),但它们都基于远程过程调用(RPC)或请求/响应模型。MOM 能构建异步通信的分布式系统。JMS 是与 MOM 一起工作的 API,它本身不是消息传输方式,就像 JDBC 对于关系型数据库管理系统一样,Java 应用程序可以使用相同的 JMS 类与任何 MOM 提供商配合使用。

3.3 常见的 MOM 软件

MOM 软件 提供商
WebSphere MQ IBM
EMS Tibco Software
SonicMQ Progress Software
ActiveMQ 开源,Apache
Open MQ 开源,Oracle

3.4 同步与异步通信

同步通信(如调用远程机器上的 placeOrder() 方法)是阻塞调用,调用程序需等待方法执行完成或抛出错误才能继续。而异步通信中,可下单但无需等待执行,就像寄信或发邮件一样,发送后可继续做其他事情,收件人也无需在线接收。

3.5 交易订单处理流程

下单过程是将描述订单的 Java 对象放入 MOM 提供商的特定消息队列。下单后,程序可继续执行,无需等待订单处理完成。多个用户可将订单放入同一队列,另一个程序负责出队和处理消息。为提高基于消息的应用程序的吞吐量,可添加多个消费者从同一队列读取消息。

4. JMS 消息传递模式

4.1 消息传递的两种模式

4.1.1 点对点(P2P)消息传递

程序将消息发送到特定队列,另一个程序从该队列接收消息,消息成功接收后即从队列中删除。

4.1.2 发布/订阅(pub/sub)模式

程序发布消息供多个接收者消费,消息发布到特定主题,许多订阅者可订阅接收。主题代表应用程序和/或用户的重要消息,如价格下跌警报、突发新闻等。在 pub/sub 模式中,消息通常在所有订阅者接收后从队列中删除。聊天室就是 pub/sub 应用的一个很好例子。

4.2 消息传递的保证模式

消息传递可以是有保证的,MOM 会像邮局一样将消息保留在队列中,直到接收者获取。在这种模式下,消息是持久的,MOM 提供商将其存储在内部存储中(可以是数据库管理系统或文件系统)。在无保证模式下,MOM 仅将消息传递给消息到达时正在运行的接收应用程序。

5. JMS API 概述

5.1 主要 JMS 接口

接口名称 描述
Queue 消息存放或获取的地方,消息按先进先出(FIFO)规则检索。消息生产者将消息放入队列,消息消费者从队列中取出消息。
QueueConnection 表示与 MOM 的特定连接,类似于 JDBC 中的 Connection 类。
QueueConnectionFactory 创建 Connection 对象,类似于 JDBC 中的 DataSource 类。
QueueSession 表示客户端与 MOM 服务器之间的特定会话,由 QueueConnection 创建。
QueueSender 实际发送消息的对象。
QueueReceiver 接收消息的对象。
TopicPublisher 发布消息,功能类似于 QueueSender 。
TopicSubscriber 接收消息,功能类似于 QueueReceiver 。
Topic 在 pub/sub 模式中表示重要应用事件的对象, TopicPublisher 向主题发布消息, TopicSubscribers 可订阅该主题。
Message 作为应用程序特定对象的包装器,可放入 JMS 队列或发布到主题。

5.2 消息类型

每个消息包含头部、可选的主体和提供附加属性的功能。头部包含消息标识(唯一消息 ID、目的地、类型等),可选属性可由程序设置,用于标记具有特定应用数据的消息。可选主体包含要传递的消息,JMS 消息有以下几种类型:
- TextMessage :可包含任何 Java 字符串。
- ObjectMessage :可保存任何可序列化的 Java 对象。
- BytesMessage :保存字节数组。
- StreamMessage :包含 Java 基本类型的流。
- MapMessage :包含任何键/值对,例如 id=123 。

5.3 发送消息的步骤

  1. 创建(或从命名服务器获取) ConnectionFactory 对象。
  2. 创建 Connection 对象并调用其 start() 方法。
  3. 创建 Session 对象。
  4. 创建 Queue 对象。
  5. 创建 MessageProducer 对象。
  6. 创建消息对象(如 TextMessage )并放入数据。
  7. 调用 QueueSender 的 send() 方法。
  8. 关闭 QueueSender 、 Session 和 Connection 对象以释放系统资源。

以下是发送消息到名为 TestQueue 的队列的代码示例:

Session session=null;
ConnectionFactory factory;
QueueConnection connection=null;

try{
    // 特定于 Open MQ 的连接创建方式
    factory = new com.sun.messaging.ConnectionFactory();  
    factory.setProperty(ConnectionConfiguration.imqAddressList, 
                        "mq://localhost:7677,mq://localhost:7677");

    connection = factory.createQueueConnection("admin","admin");

    connection.start();
    session = connection.createQueueSession( 
                        false, Session.AUTO_ACKNOWLEDGE);
    Queue ioQueue = session.createQueue("TestQueue");
    MessageProducer queueSender = session.createProducer(ioQueue);

    // 以市场价格购买 200 股 IBM 股票
    TextMessage outMsg = session.createTextMessage("IBM 200 Mkt");

    queueSender.send(outMsg);
    queueSender.close();

    System.out.println(
"Successfully placed an order to purchase 200 shares of IBM");
}
catch (JMSException e){
    System.out.println("Error: " + e.getMessage());
} 
finally{
    try{
        session.close();
        connection.close();
    } catch (Exception e) {
        System.out.println("Can’t close JMS connection/session " + 
                                                    e.getMessage());
    }
}

5.4 接收消息的方式

接收消息的程序称为消息消费者,可以是独立的 Java 程序或消息驱动 Bean(MDB)。可以使用 receive() 方法同步接收消息,也可以通过实现 MessageListener 接口并编程回调 onMessage() 方法异步接收消息。

5.4.1 同步接收消息
QueueReceiver queueReceiver = Session.createReceiver(ioQueue);
Message myMessage = queueReceiver.receive(); 
// 设置超时时间为 500 毫秒
Message myMessage = queueReceiver.receive(500); 
5.4.2 异步接收消息

异步接收消息是更好的方式,因为消息消费者无需多次向 MOM 发送请求来检查消息是否在队列中。当消息放入队列时, onMessage() 回调方法将立即被调用。以下是异步接收消息的示例代码:

package com.practicaljava.lesson30;
import javax.jms.*;
import com.sun.messaging.ConnectionFactory;
import com.sun.messaging.ConnectionConfiguration;

public class MessageReceiver implements MessageListener{
    Session session=null;
    ConnectionFactory factory;
    QueueConnection connection=null;
    MessageConsumer consumer=null;

    MessageReceiver(){
        try{
            factory = new com.sun.messaging.ConnectionFactory();  
            factory.setProperty(ConnectionConfiguration.imqAddressList, 
                                "mq://localhost:7677,mq://localhost:7677");
            connection = factory.createQueueConnection("admin","admin");

            connection.start();
            Session session = connection.createQueueSession( 
                                false, Session.AUTO_ACKNOWLEDGE);
            Queue ioQueue = session.createQueue( "TestQueue" );
            consumer = session.createConsumer(ioQueue);
            consumer.setMessageListener(this);

            System.out.println("Listening to the TestQueue...");

            // 等待消息
            Thread.sleep(100000);

        } catch (InterruptedException e){
            System.out.println("Error: " + e.getMessage());
        }
        catch (JMSException e){
            System.out.println("Error: " + e.getMessage());
        } 
        finally{
            try{
                connection.close();
            } catch (Exception e) {
                System.out.println("Can’t close JMS connection/session " 
                                                            + e.getMessage());
            }
        }      
    }

    public static void main(String[] args){
        new MessageReceiver();
    }      

    public void onMessage(Message msg){
        String msgText;
        try{
            if (msg instanceof TextMessage){
                msgText = ((TextMessage) msg).getText();
                System.out.println("Got from the queue: " + msgText);
            }else{
                System.out.println("Got a non-text message");
            }
        }
        catch (JMSException e){
            System.out.println("Error while consuming a message: " + 
                                                      e.getMessage());
        } 
    }
}

5.5 消息确认模式

消息确认模式在创建 Session 对象时定义。 createSession() 方法有两个参数,如果第一个参数为 true ,会话是事务性的,第二个参数的值无关紧要,消费者可以提交或回滚消息。如果调用了 commit() 方法,消息将从队列中删除; rollback() 方法会将消息保留在队列中。如果会话是非事务性的,第二个参数定义确认模式:
- AUTO_ACKNOWLEDGE 模式: onMessage() 方法成功完成后立即发送确认。
- CLIENT_ACKNOWLEDGE 模式:需要显式确认,如 msg.acknowledge() ,这允许从队列中删除消息。
- DUP_OK_ACKNOWLEDGE 模式:用于服务器故障的情况,同一消息可能会多次传递,在某些用例中是可以接受的。

总结

本文介绍了 JSF 开发中的转换器和验证器,包括自定义验证器的创建和使用。同时详细阐述了 JMS 和 MOM 的概念、消息传递模式、JMS API 以及消息的发送和接收方式,还介绍了消息确认模式。通过这些知识,可以更好地开发基于消息传递的分布式 Java 应用程序。

6. 消息传递架构中的 Java EE 应用服务器

在消息传递架构中引入 Java EE 应用服务器具有诸多好处。Java EE 应用服务器可以提供更强大的管理和监控功能,确保消息传递的可靠性和性能。它可以管理连接池、事务处理和资源分配,使得消息传递系统更加稳定和高效。

6.1 连接管理

Java EE 应用服务器可以管理 JMS 连接,避免手动创建和关闭连接带来的资源浪费和错误。通过连接池技术,服务器可以重用连接,提高系统的响应速度。例如,在高并发的情况下,连接池可以快速提供可用的连接,减少连接创建的时间开销。

6.2 事务处理

在 Java EE 应用服务器中,可以使用 JTA(Java Transaction API)来管理消息传递的事务。事务处理可以确保消息的一致性和完整性。例如,在一个业务流程中,如果消息的发送和数据库的更新是一个原子操作,那么可以使用事务来保证要么两者都成功,要么两者都失败。

6.3 监控和管理

Java EE 应用服务器提供了丰富的监控和管理工具,可以实时监控消息队列的状态、消息的处理情况和资源的使用情况。管理员可以通过这些工具及时发现和解决问题,确保系统的正常运行。

7. 消息驱动 Bean 的价值

消息驱动 Bean(MDB)是 Java EE 中的一种特殊的企业 Bean,它可以异步处理 JMS 消息。MDB 为消息传递架构带来了很多价值。

7.1 异步处理

MDB 可以异步处理消息,使得应用程序可以在不阻塞主线程的情况下处理消息。这对于提高系统的吞吐量和响应速度非常有帮助。例如,在一个电商系统中,当用户下单后,系统可以将订单消息发送到消息队列,MDB 可以异步处理这些订单,而主线程可以继续处理其他请求。

7.2 解耦

MDB 可以将消息的发送者和接收者解耦。发送者只需要将消息发送到消息队列,而不需要关心谁来处理这些消息。接收者只需要从消息队列中接收消息并处理,而不需要关心消息是从哪里来的。这种解耦可以提高系统的灵活性和可维护性。

7.3 可扩展性

MDB 可以很容易地进行扩展。可以通过增加 MDB 的实例数量来提高消息处理的能力。例如,在高并发的情况下,可以增加 MDB 的实例数量,使得系统可以同时处理更多的消息。

8. 实际应用案例

以下是一个简单的实际应用案例,展示如何使用 JMS 和 MDB 来构建一个分布式系统。

8.1 系统架构

graph LR
    A[客户端] -->|发送消息| B(消息队列)
    B -->|接收消息| C(MDB)
    C -->|处理消息| D(数据库)

8.2 代码实现

8.2.1 消息发送端
import javax.jms.*;
import com.sun.messaging.ConnectionFactory;
import com.sun.messaging.ConnectionConfiguration;

public class MessageSender {
    public static void main(String[] args) {
        Session session = null;
        ConnectionFactory factory;
        QueueConnection connection = null;

        try {
            factory = new com.sun.messaging.ConnectionFactory();
            factory.setProperty(ConnectionConfiguration.imqAddressList,
                    "mq://localhost:7677,mq://localhost:7677");

            connection = factory.createQueueConnection("admin", "admin");
            connection.start();
            session = connection.createQueueSession(
                    false, Session.AUTO_ACKNOWLEDGE);
            Queue ioQueue = session.createQueue("OrderQueue");
            MessageProducer queueSender = session.createProducer(ioQueue);

            TextMessage outMsg = session.createTextMessage("Order: 123");
            queueSender.send(outMsg);
            queueSender.close();

            System.out.println("Successfully sent an order message");
        } catch (JMSException e) {
            System.out.println("Error: " + e.getMessage());
        } finally {
            try {
                session.close();
                connection.close();
            } catch (Exception e) {
                System.out.println("Can’t close JMS connection/session " +
                        e.getMessage());
            }
        }
    }
}
8.2.2 消息驱动 Bean
import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;

@MessageDriven(activationConfig = {
        @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
        @ActivationConfigProperty(propertyName = "destination", propertyValue = "OrderQueue")
})
public class OrderMDB implements MessageListener {

    @Override
    public void onMessage(Message message) {
        try {
            if (message instanceof TextMessage) {
                TextMessage textMessage = (TextMessage) message;
                String order = textMessage.getText();
                System.out.println("Received order: " + order);
                // 处理订单,例如将订单信息存入数据库
            } else {
                System.out.println("Got a non-text message");
            }
        } catch (JMSException e) {
            System.out.println("Error while consuming a message: " +
                    e.getMessage());
        }
    }
}

8.3 案例分析

在这个案例中,客户端将订单消息发送到消息队列,MDB 从消息队列中接收消息并处理订单。通过这种方式,客户端和 MDB 实现了解耦,系统的可扩展性和灵活性得到了提高。同时,使用 Java EE 应用服务器可以管理连接和事务,确保消息传递的可靠性。

9. 总结与展望

9.1 总结

本文全面介绍了 JSF 开发中的转换器和验证器,为 Web 应用的数据处理提供了有效的手段。同时,深入探讨了 JMS 和 MOM 的相关知识,包括消息传递模式、JMS API、消息的发送和接收方式以及消息确认模式。此外,还介绍了 Java EE 应用服务器在消息传递架构中的作用和消息驱动 Bean 的价值,并通过实际案例展示了如何构建基于消息传递的分布式 Java 应用程序。

9.2 展望

随着技术的不断发展,消息传递在分布式系统中的应用将越来越广泛。未来,我们可以期待更高效、更可靠的消息传递技术的出现。例如,随着云计算和容器化技术的发展,消息传递系统可以更加灵活地部署和扩展。同时,人工智能和机器学习技术也可以应用于消息传递系统,实现智能的消息路由和处理。在开发过程中,我们需要不断学习和掌握新的技术,以适应不断变化的需求。

通过对这些知识的掌握和应用,开发者可以构建出更加稳定、高效和灵活的分布式 Java 应用程序,满足不同业务场景的需求。在实际项目中,需要根据具体的需求选择合适的技术和架构,充分发挥消息传递的优势,提高系统的性能和可靠性。

相关推荐

WebLogic Server 11g 64位版本详解及应用

WebLogic Server 11g是Oracle公司推出的一款企业级的应用服务器产品,广泛应用于大型企业应用的部署和管理。作为Java EE的实现之一,它提供了全面的服务来支持企业应用的开发、集成、部署和管理。在本章中,我们将介绍WebLogic Server 11g的基本概念、关键特性和技术优势,为读者构建起一个关于该产品的初步了解框架。WebLogic Administration Console是WebLogic Server的图形用户界面管理工具,它允许管理员进行各种服务器配置和管理任务。

weixin_34779181的博客 739

晶体管的 栅极gate 材料选用 多晶硅polysilicon,并采用 自对准工艺 self-aligned IC后端版图 【VLSI】

本文为了解决为什么要使用多晶硅作栅极触点的问题,先介绍了MOS管的工作原理是通过栅极上所加的电压控制漏极与源极之间电流,然后解释了栅极的材料为什么选用多晶硅,然后介绍了晶体管里栅极的自对准工艺(Self-Aligned Gate )。

Jie_MSD的博客 1万+

XGBoost的简单安装及入门使用

XGBoost安装及简单入门 XGBoost支持多种操作系统,如Windows, Linux, MacOS等,并支持多种语言版本,如Python, R, Scale, Java等。XGBoost的安装方式一般有两种,一种是直接通过pip安装(适合用于Python),另外一种是通过源码编译安装 1、通过pip安装 通过pip安装Python包既简单又方便,只需执行如下的命令: pip install -i https://pypi.tuna.tsinghua.edu.cn/simple xgboost 使用

qq_41513230的博客 3万+

从X86到ARM:全面解析服务器CPU架构的演变与未来趋势

本文深入解析了服务器CPU架构从X86到ARM的演变历程与未来趋势。文章从CISC与RISC两种设计哲学切入,分析了X86凭借生态建立的帝国地位及其面临的功耗挑战,并重点阐述了ARM架构凭借其卓越的能效比优势,正从移动端向数据中心市场发起冲击。未来,异构计算、软硬协同与专用芯片将成为服务器CPU发展的核心方向。

gold的博客 1127

Simulink机电仿真避坑指南:如何用MATLAB解决液压系统建模的3大典型问题

本文针对液压系统建模在Simulink动态仿真中常见的三大难题——参数物理意义缺失、模型收敛性差及性能优化瓶颈,提供了专业的MATLAB解决方案。文章详细阐述了如何从物理参数推导仿真模型、破解代数环与刚性系统问题,并分享了模型降阶与加速技巧,旨在帮助工程师构建精确高效的机电系统仿真模型。

o9p0q1的博客 295

搭建AutoSAR开发环境

任何开发都需要一套开发环境,包括软硬件环境,笔者后续将介绍的是 NXP 的 S32K144 配套的开发板。接下来详细介绍怎么获取这些软硬件工具。仅供个人学习使用!

cfl927096306的博客 6111

《QT+PCL》点云配准进阶——GROR配准

GROR点云配准

阁楼 1318

华为OD机试真题2026双机位C卷 Python&JS 实现【明日之星选举】

给定一组选票votes。votes[i]代表第 i 张选票的内容,包含一个字符串"zhangsan,hanmei",表示推荐zhangsan为明日之星,反对 hanmei 称为明日之星。赞成或反对使用逗号(,)分割。 给定一个数组n,表示最后将筛选出赞成票最多的n个人,如果赞成票相同,则反对票越少,排序越靠前;如果赞成票和反对票都相同,则按照姓名字典序升序排序。

MISAYAONE的博客 318

GEE训练教程:如何批量从Google assets中将时序影像批量导出到Google drive

本文介绍了一种使用Google Earth Engine(GEE)批量导出2000-2023年叶绿素a(Chl-a)浓度数据的方法。通过JavaScript代码自动生成多个年份的导出任务,将数据保存至Google Drive指定文件夹。文章详细说明了代码参数配置、使用步骤和注意事项,包括资产命名规则验证、导出任务启动方式,以及分辨率、区域范围等关键参数的调整建议。同时提供了常见问题的解决方案,如处理缺失年份数据、解决导出像素超限等问题。该方法可显著提高多年份Chl-a数据批量导出的效率,适用于水体环境研究的

此星光明博客 90

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

上一篇: 58、基于机器学习技术的糖尿病视网膜病变分级
下一篇: 27、单向函数与基本假设解读
废话输出机427
博客等级 码龄4年 414粉丝 1552原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值