用java构建企业级自动化框架(第四篇-构建框架分布式并发执行测试用例功能2)

上一节说的是有点古老的技术:rmi+jms。本期讲述的是另一个解决思路ActiveMQ,他是对jms的一个封装。更加容易操作。

activeMQ的工作原理就是一个服务器端不断的发消息(消息可以是字符和对象,这里用对象),客户端不断接收消息并处理这个消息。

首先我们来做个demo,说下activeMQ加Tomcat是怎么集群的。

首先,下载apache-activemq-5.5.zip,解压,打开,找到bin文件夹的activemq.bat打开(如果是win32的打开win32文件夹下的)。

然后,远程客户端(另一台机子)安装tomcat,并把activemq-all-5.5.1.jar的放到tomcat的lib包里,修改Context。xml,加入以下代码

<Resource name="jms/ConnectionFactory" 
auth="Container" 
type="org.apache.activemq.ActiveMQConnectionFactory"
description="JMS Connection Factory"
factory="org.apache.activemq.jndi.JNDIReferenceFactory"
brokerURL="tcp://192.168.0.9:61616"    //(这里是你activemq服务器所在的地址)
brokerName="LocalActiveMQBroker"
useEmbeddedBroker="false"/>


<Resource name="jms/topic/MyTopic"
auth="Container" 
type="org.apache.activemq.command.ActiveMQTopic"
factory="org.apache.activemq.jndi.JNDIReferenceFactory"
physicalName="MY.TEST.FOO"/>  


<Resource name="jms/queue/MyQueue"
auth="Container"
type="org.apache.activemq.command.ActiveMQQueue"
factory="org.apache.activemq.jndi.JNDIReferenceFactory"
physicalName="MY.TEST.FOO.QUEUE"/> //留心这个name

接着在代码中进行实例化消费者的操作,当消费者实例化之后,接下来在服务器端打开http://localhost:8161/admin然后点击queue这个选项,注意下我让你留意客户端配置的MY.TEST.FOO.QUEUE这个name是不是已经在服务器的这个管理页面显示出来了。


呵呵,就像你看到的一样,消费者名字已经出现在这个页面,它可以向你这个客户端发消息了。

下面开始讲述在自动化中如何运用ActiveMQ做成消息中间件集群运行脚本。

首先,每个客户端的tomcat都要配置好消费者的消息。这样每个客户端都可以充当消费者去从发布者那里接收消息。

    
    <Resource auth="Container" brokerName="ActiveMQBroker" brokerURL="tcp://qamac.homeoffice.wal-mart.com:61616?jms.prefetchPolicy.all=1" description="JMS Connection Factory" factory="org.apache.activemq.jndi.JNDIReferenceFactory" name="jms/ConnectionFactory" type="org.apache.activemq.ActiveMQConnectionFactory"/>
                    
    <Resource auth="Container" description="Safe 360 queue" factory="org.apache.activemq.jndi.JNDIReferenceFactory" name="jms/Safe360Queue" physicalName="Safe360Queue" type="org.apache.activemq.command.ActiveMQQueue"/>
    
    <Resource auth="Container" description="test run queue" factory="org.apache.activemq.jndi.JNDIReferenceFactory" name="jms/TestRunQueue" physicalName="TestRunQueue" type="org.apache.activemq.command.ActiveMQQueue"/>
    
    <Resource auth="Container" description="IE6 queue" factory="org.apache.activemq.jndi.JNDIReferenceFactory" name="jms/IE6Queue" physicalName="IE6Queue" type="org.apache.activemq.command.ActiveMQQueue"/>
     
    <Resource auth="Container" description="IE7 queue" factory="org.apache.activemq.jndi.JNDIReferenceFactory" name="jms/IE7Queue" physicalName="IE7Queue" type="org.apache.activemq.command.ActiveMQQueue"/>
    
    <Resource auth="Container" description="IE8 queue" factory="org.apache.activemq.jndi.JNDIReferenceFactory" name="jms/IE8Queue" physicalName="IE8Queue" type="org.apache.activemq.command.ActiveMQQueue"/>
    
    <Resource auth="Container" description="FF3 queue" factory="org.apache.activemq.jndi.JNDIReferenceFactory" name="jms/FF3Queue" physicalName="FF3Queue" type="org.apache.activemq.command.ActiveMQQueue"/>
    
    <Resource auth="Container" description="Safari queue" factory="org.apache.activemq.jndi.JNDIReferenceFactory" name="jms/SafariQueue" physicalName="SafariQueue" type="org.apache.activemq.command.ActiveMQQueue"/>
    
    <Resource auth="Container" description="Safari3 queue" factory="org.apache.activemq.jndi.JNDIReferenceFactory" name="jms/Safari3Queue" physicalName="Safari3Queue" type="org.apache.activemq.command.ActiveMQQueue"/>
    
    <Resource auth="Container" description="Chrome queue" factory="org.apache.activemq.jndi.JNDIReferenceFactory" name="jms/ChromeQueue" physicalName="ChromeQueue" type="org.apache.activemq.command.ActiveMQQueue"/>    

你可以看到里面有很多很多消费者,这些消费者负责从activeMQ里面得到消息。

那工程里面怎么配置呢,首先配置web.xml.把activeMQ相关信息配置进去

<resource-ref>
<res-ref-name>jms/ConnectionFactory</res-ref-name>
<res-type>org.apache.activemq.ActiveMQConnectionFactory</res-type>
<res-auth>Container</res-auth>
<res-sharing-scope>Shareable</res-sharing-scope>
</resource-ref>
<resource-ref>
<res-ref-name>jms/tcExecuteQueue</res-ref-name>
<res-type>org.apache.activemq.command.ActiveMQQueue</res-type>
<res-auth>Container</res-auth>
<res-sharing-scope>Shareable</res-sharing-scope>
</resource-ref>

接着,在servlet代码中,负责启动一个个具体消费者

public void init(ServletConfig config) throws ServletException{
// Set the App root here
String path = config.getServletContext().getRealPath("WEB-INF");
AppContext.setAppRoot(path);

//init consumers
initConsumers();
}

public void initConsumers(){

//Get host name
String hostName = null;
try{

//这里得到本机的地址
hostName = InetAddress.getLocalHost().getHostName();
}catch(Exception e){
logger.error("Unable to get host name", e);
}

if(hostName != null){
String installedBrowsers = getInstalledBrowsers(hostName);
if(installedBrowsers == null){ // if not configured these are the default consumers 
// 8 test run consumers in parallel on each server
new TestRunEventConsumer();
new TestRunEventConsumer();
new TestRunEventConsumer();
new TestRunEventConsumer();
new TestRunEventConsumer();
new TestRunEventConsumer();
new TestRunEventConsumer();
new TestRunEventConsumer();
}else{
new TestRunEventConsumer();
new TestRunEventConsumer();
new TestRunEventConsumer();
new TestRunEventConsumer();;// 4 test run consumers runs on all the machines

StringTokenizer t = new StringTokenizer(installedBrowsers, ",");
while(t.hasMoreTokens()){
String browser = t.nextToken();
if(browser.equalsIgnoreCase(BrowserTypeEnum.InterExplorer6.getBrowserName())){
new IE6EventConsumer();
}else if(browser.equalsIgnoreCase(BrowserTypeEnum.InterExplorer7.getBrowserName())){
new IE7EventConsumer();
}else if(browser.equalsIgnoreCase(BrowserTypeEnum.InterExplorer8.getBrowserName())){
new IE8EventConsumer();
}else if(browser.equalsIgnoreCase(BrowserTypeEnum.Firefox3.getBrowserName())){
new FF3EventConsumer();
}else if(browser.equalsIgnoreCase(BrowserTypeEnum.Safari.getBrowserName())){
new SafariEventConsumer();
}else if(browser.equalsIgnoreCase(BrowserTypeEnum.Safari3.getBrowserName())){
new Safari3EventConsumer();
}else if(browser.equalsIgnoreCase(BrowserTypeEnum.Safe360.getBrowserName())){
new China360SafeEventConsumer();
}else if(browser.equalsIgnoreCase(BrowserTypeEnum.Chrome5.getBrowserName())){
new ChromeEventConsumer();
}
}
}
}
}


这里我们已经把各个消费者启动起来了,注意里面的这句话,String installedBrowsers = getInstalledBrowsers(hostName);getInstalledBrowsers这个方法是负责读取这个文件executiongrid.properties里面的内容,下面是里面的内容

automationjst12=FF3,IE6,Safari,CHROME5
automationjst13=FF3,IE6,Safari,CHROME5
automationjst14=FF3,IE6,Safari
automationjst15=FF3,IE8,Safari
automationjst16=FF3,IE8,Safari
automationjst17=FF3,IE8,Safari3
automationjst18=FF3,IE6,Safari3
automationjst19=FF3,IE8,Safari3,CHROME5
automationjst20=FF3,IE8,Safari3,CHROME5
automationjst21=FF3,IE7
automationjst22=FF3,IE7
automationjst23=FF3,IE7
automationjst24=FF3,IE7
automationjst25=FF3,IE7
automationjst26=FF3,IE7
automationjst27=FF3,IE7
automationjst28=FF3,IE7
automationjst29=FF3,IE7
automationjst30=FF3,IE7
automationjst31=FF3,IE8
automationjst32=FF3,IE8
automationjst33=FF3,IE8
automationjst34=FF3,IE8
automationjst35=FF3,IE8

这个里面是什么东西呢,是每个客户端装的浏览器,如果你看下面的那些“if(browser.equalsIgnoreCase(BrowserTypeEnum.InterExplorer6.getBrowserName())){”等内容,它就根据你机子里面装的浏览器去启动对应的消费者。

下面给出其中一个消费者的内容,这个消费者得到消息后,就会在重写的方法onMessage里的用executor.executeTestCase去运行这个消息,根据executor.executeTestCase这个方法所需要参数里我们也可以猜到服务器发来的消息实际上就是一个脚本实例。

我把以前的疑问说下,消费者启动后,当消息发来时我如何动态去接收它,因为我消费者已经启动了不可能在重新启动一次。注意r implements MessageListener 这个东西可以动态的监听发布者发来的消息并去执行它。在consumer.setMessageListener(this);里注入监听。刚才说在它的onMessage这个方法里,可以对消息进行处理啦。

public class IE6EventConsumer implements MessageListener{

private static final String LOCAL_HOST = "127.0.0.1";


private static Logger logger = Logger.getLogger(IE6EventConsumer.class);

private Context jndiContext = null;
private ConnectionFactory connectionFactory = null;
private Connection connection = null;
private Session session = null;
private Destination destination = null;
private MessageConsumer consumer = null;

public IE6EventConsumer(){

try {
            jndiContext = new InitialContext();
            connectionFactory = (ConnectionFactory)jndiContext.lookup(JMSProperties.getConnectionactory());
            destination = (Destination)jndiContext.lookup(JMSProperties.getIE6Queue());
        } catch (NamingException e) {
            logger.info("Could not create JNDI API context: " + e.toString());
            return;
        }
        
try {
connection = connectionFactory.createConnection();
connection.start();
connection.setExceptionListener(this);
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
consumer = session.createConsumer(destination);
consumer.setMessageListener(this);
} catch (JMSException e) {
            logger.info("Exception occurred: " + e);
            return;
        }

}


@Override
public void onMessage(Message message) {
logger.info("Starting test case execute consumer consumer");
try {


if (message != null) {
if (message instanceof ObjectMessage) {
logger.info("IE6 Message received");

ObjectMessage objMessage = (ObjectMessage) message;
IE6Event event = (IE6Event) objMessage.getObject();

logger.info("Event ID:" + objMessage.getJMSMessageID());
logger.info("Event delivery mode:" + objMessage.getJMSDeliveryMode());
logger.info("Event redelivered?:" + objMessage.getJMSRedelivered());
logger.info("Event Destination:" + objMessage.getJMSDestination());
logger.info("Event Timestamp:" + new Date(objMessage.getJMSTimestamp()));
logger.info("JMS Type:" + objMessage.getJMSType());
logger.info("Event Expiration Timestamp" + new Date(objMessage.getJMSExpiration()));
logger.info("Testcase ID:" + event.getTestCaseId());
logger.info("Run ID:" + event.getRunId());
logger.info("External Run ID:" + event.getExternalRunId());
logger.info("Data Set ID:" + event.getDataSetId());
logger.info("Executing On Browser:" + event.getBrowser().name());


setupExecutionEnvironment(event);
TestCaseExecutor executor = new TestCaseExecutor();
executor.executeTestCase(event.getRunId(), event.getTestCaseId(), 
LOCAL_HOST, event.getEnvironment(), 1, event.getRunType(), 
event.getBrowser(), event.getExternalRunId(), event.getExternalRunType(),
event.getDataSetId());

} else {
logger.debug("Received unknown message: " + message.getClass().getName());
}
}
} catch (Throwable e) {
logger.error("Exception while processing the IE6 event. Continue with another event",e);
} finally {
resetExecutionEnvironment();
}
}


@Override
public void onException(JMSException e) {
logger.error("Exception in IE6 Consumer", e);
}

注意里面的这个代码connectionFactory = (ConnectionFactory)jndiContext.lookup(JMSProperties.getConnectionactory());它负责读取jmsproperties.properties这个文件。里面是这个内容ConnectionFactory=java:comp/env/jms/ConnectionFactory
TestRunQueue=java:comp/env/jms/TestRunQueue
Safe360Queue=java:comp/env/jms/Safe360Queue
IE6Queue=java:comp/env/jms/IE6Queue
IE7Queue=java:comp/env/jms/IE7Queue
IE8Queue=java:comp/env/jms/IE8Queue
FF2Queue=java:comp/env/jms/FF2Queue
FF3Queue=java:comp/env/jms/FF3Queue
FF4Queue=java:comp/env/jms/FF4Queue
SafariQueue=java:comp/env/jms/SafariQueue
Safari3Queue=java:comp/env/jms/Safari3Queue
ChromeQueue=java:comp/env/jms/ChromeQueue

然后根据这个jms/IE7Queue等名字去tomcat我刚才说的tomcat配置里读取相应的消费者配置信息并在下面代码中启动起来。

这样如果消息发布者一发送要运行的脚本实例,消费者立刻去接收并去运行它。刚才看到我们的这个executiongrid.properties文件,你可以看到我们大概配了20多台机器去做客户端运行脚本。每个客户端都要在tomcat的webapp里装入工程。并启动它。当然你看之前的代码,工程启动消费者自然就启动了。


下面介绍下服务器端运行示例。       

                    public class TestCaseExecuteEventPublisher {

private static Logger logger = Logger.getLogger(TestCaseExecuteEventPublisher.class);

private static TestCaseExecuteEventPublisher s_instance = new TestCaseExecuteEventPublisher();

private Context jndiContext = null;
private ConnectionFactory connectionFactory = null;
private Connection connection = null;
private Session session = null;

private MessageProducer testRunProducer = null;
private MessageProducer IE6Producer = null;
private MessageProducer IE7Producer = null;
private MessageProducer IE8Producer = null;
private MessageProducer FF2Producer = null;
private MessageProducer FF3Producer = null;
private MessageProducer FF4Producer = null;
private MessageProducer safariProducer = null;
private MessageProducer safari3Producer = null;
private MessageProducer safe360Producer = null;
private MessageProducer chromeProducer = null;


private Destination testRunDestination = null;
private Destination ie6Destination = null;
private Destination ie7Destination = null;
private Destination ie8Destination = null;
private Destination ff2Destination = null;
private Destination ff3Destination = null;
private Destination ff4Destination = null;
private Destination safariDestination = null;
private Destination safari3Destination = null;
private Destination safe360Destination = null;
private Destination chromeDestination = null;

public static TestCaseExecuteEventPublisher getInstance(){
return s_instance;
}



private TestCaseExecuteEventPublisher(){
try {
            jndiContext = new InitialContext();
        } catch (NamingException e) {
            logger.info("Could not create JNDI API context: " + e.toString()); //$NON-NLS-1$
            return;
        }
        
        try {           
connectionFactory = (ActiveMQConnectionFactory)jndiContext.lookup(JMSProperties.getConnectionactory());

            testRunDestination = (ActiveMQQueue)jndiContext.lookup(JMSProperties.getTestRunQueue());
            ie6Destination = (ActiveMQQueue)jndiContext.lookup(JMSProperties.getIE6Queue());
            ie7Destination = (ActiveMQQueue)jndiContext.lookup(JMSProperties.getIE7Queue());
            ie8Destination = (ActiveMQQueue)jndiContext.lookup(JMSProperties.getIE8Queue());
            //ff2Destination = (ActiveMQQueue)jndiContext.lookup(JMSProperties.getFF2Queue());
            ff3Destination = (ActiveMQQueue)jndiContext.lookup(JMSProperties.getFF3Queue());
            //ff4Destination = (ActiveMQQueue)jndiContext.lookup(JMSProperties.getFF4Queue());
            safariDestination = (ActiveMQQueue)jndiContext.lookup(JMSProperties.getSafariQueue());
            safari3Destination = (ActiveMQQueue)jndiContext.lookup(JMSProperties.getSafari3Queue());
            safe360Destination = (ActiveMQQueue)jndiContext.lookup(JMSProperties.getSafe360Queue());
            chromeDestination = (ActiveMQQueue)jndiContext.lookup(JMSProperties.getChromeQueue());
        } catch (NamingException e) {
            logger.info("JNDI API lookup failed: " + e); //$NON-NLS-1$
            return;
        }
        
        try {
            connection = connectionFactory.createConnection();
            session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);   
            
            testRunProducer = session.createProducer(testRunDestination);
            IE6Producer = session.createProducer(ie6Destination);
            IE7Producer = session.createProducer(ie7Destination);
            IE8Producer = session.createProducer(ie8Destination);
            FF2Producer = session.createProducer(ff2Destination);
            FF3Producer = session.createProducer(ff3Destination);
            FF4Producer = session.createProducer(ff4Destination);
            safariProducer = session.createProducer(safariDestination);
            safari3Producer = session.createProducer(safari3Destination);
            safe360Producer = session.createProducer(safe360Destination);
            chromeProducer = session.createProducer(chromeDestination);
        } catch (JMSException e) {
            logger.info("Exception occurred: " + e); //$NON-NLS-1$
            return;
        }      

}

public void publishTestRunEvent(final TestRunEvent event){
try {
ObjectMessage eventMessage = session.createObjectMessage();
eventMessage.setObject(event);

testRunProducer.send(eventMessage);
} catch (JMSException e) {
logger.error("Exception while publishing the test run event", e); //$NON-NLS-1$
}


}

private void publishIE6Event(EventPayload payload) {

IE6Event event = new IE6Event();
setEventDetails(payload, event);
try {
ObjectMessage eventMessage = session.createObjectMessage();
eventMessage.setObject(event);

IE6Producer.send(eventMessage);
} catch (JMSException e) {
logger.error("Exception while publishing the IE6 event", e); //$NON-NLS-1$
}


}

private void publishIE7Event(EventPayload payload) {

IE7Event event = new IE7Event();
setEventDetails(payload, event);


try {
ObjectMessage eventMessage = session.createObjectMessage();
eventMessage.setObject(event);

IE7Producer.send(eventMessage);
} catch (JMSException e) {
logger.error("Exception while publishing the IE7 event", e); //$NON-NLS-1$
}


}

private void publishIE8Event(EventPayload payload) {

IE8Event event = new IE8Event();
setEventDetails(payload, event);


try {
ObjectMessage eventMessage = session.createObjectMessage();
eventMessage.setObject(event);

IE8Producer.send(eventMessage);
} catch (JMSException e) {
logger.error("Exception while publishing the IE8 event", e); //$NON-NLS-1$
}


}

private void publishFF2Event(EventPayload payload) {

FF2Event event = new FF2Event();
setEventDetails(payload, event);


try {
ObjectMessage eventMessage = session.createObjectMessage();
eventMessage.setObject(event);

FF2Producer.send(eventMessage);
} catch (JMSException e) {
logger.error("Exception while publishing the FF2 event", e); //$NON-NLS-1$
}


}

private void publishFF3Event(EventPayload payload) {

FF3Event event = new FF3Event();
setEventDetails(payload, event);


try {
ObjectMessage eventMessage = session.createObjectMessage();
eventMessage.setObject(event);

FF3Producer.send(eventMessage);
} catch (JMSException e) {
logger.error("Exception while publishing the FF3 event", e); //$NON-NLS-1$
}


}

private void publishFF4Event(EventPayload payload) {

FF4Event event = new FF4Event();
setEventDetails(payload, event);


try {
ObjectMessage eventMessage = session.createObjectMessage();
eventMessage.setObject(event);

FF4Producer.send(eventMessage);
} catch (JMSException e) {
logger.error("Exception while publishing the FF4 event", e); //$NON-NLS-1$
}


}

private void publishSafariEvent(EventPayload payload) {

SafariEvent event = new SafariEvent();
setEventDetails(payload, event);


try {
ObjectMessage eventMessage = session.createObjectMessage();
eventMessage.setObject(event);

safariProducer.send(eventMessage);
} catch (JMSException e) {
logger.error("Exception while publishing the Safari event", e); //$NON-NLS-1$
}


}

private void publishSafari3Event(EventPayload payload) {

Safari3Event event = new Safari3Event();
setEventDetails(payload, event);
try {
ObjectMessage eventMessage = session.createObjectMessage();
eventMessage.setObject(event);

safari3Producer.send(eventMessage);
} catch (JMSException e) {
logger.error("Exception while publishing the Safari3 event", e); //$NON-NLS-1$
}


}

private void publishChina360SafeEvent(EventPayload payload) {

China360SafeEvent event = new China360SafeEvent();
setEventDetails(payload, event);


try {
ObjectMessage eventMessage = session.createObjectMessage();
eventMessage.setObject(event);

safe360Producer.send(eventMessage);
} catch (JMSException e) {
logger.error("Exception while publishing the China360SafeEvent event", e); //$NON-NLS-1$
}


}

private void publishChromeEvent(EventPayload payload) {

ChromeEvent event = new ChromeEvent();
setEventDetails(payload, event);


try {
ObjectMessage eventMessage = session.createObjectMessage();
eventMessage.setObject(event);

chromeProducer.send(eventMessage);
} catch (JMSException e) {
logger.error("Exception while publishing the ChromeEvent event", e); 
}


}






private void setEventDetails(EventPayload payload, TestCaseExecuteBaseEvent event) {
event.setEnvironment(payload.getEnvironment());
event.setRunId(payload.getRunId());
event.setRunType(payload.getRunType());
event.setTestCaseId(payload.getTestCaseId());
event.setUserId(payload.getUserId());
event.setUserName(payload.getUserName());
event.setCountry(payload.getCountry());
event.setExternalRunId(payload.getExternalRunId());
event.setExternalRunType(payload.getExternalRunType());
event.setRunAttempt(payload.getRunAttempt());
event.setDataSetId(payload.getDataSetId());
}

public void publishEvent(EventPayload payload) {
BrowserTypeEnum browser = payload.getBrowser();


switch (browser) {


case InterExplorer6:
publishIE6Event(payload);
break;
case InterExplorer7:
publishIE7Event(payload);
break;
case InterExplorer8:
publishIE8Event(payload);
break;
// case Firefox2:
// publishFF2Event(payload);
// break;
case Firefox3:
publishFF3Event(payload);
break;
// case Firefox4:
// publishFF4Event(payload);
// break;
case Safari:
publishSafariEvent(payload);
break;
case Safari3:
publishSafari3Event(payload);
break;
case Safe360:
publishChina360SafeEvent(payload);
break;
case Chrome5:
publishChromeEvent(payload);
break;
default:
logger.error("Invalid event type"); //$NON-NLS-1$
}
}


}


这样它就可以把脚本实例一个个的按照这种形式给发出去,每一个FF2Event这种类装载的就是一个个脚本实例,里面是脚本内容和必要的元素如在什么环境运行它及浏览器是等等。     一个消息发出后,客户端就可以接收到这个消息运行这个消息啦。                                                                                                                                                    

当然如果你是第一次接触activeMQ这个东西,你可以先做个试验。用两台机子,其中一个启动activeMq,然后两台机子一个机子做消费者一个做发布者做下demo,这样很容易就有个直观的印象。


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值