Ehcache 整合Spring 使用页面、对象缓存

转载自:http://blog.csdn.net/ibm_hoojo/article/details/7739181


Ehcache在很多项目中都出现过,用法也比较简单。一般的加些配置就可以了,而且Ehcache可以对页面、对象、数据进行缓存,同时支持集群/分布式缓存。如果整合Spring、Hibernate也非常的简单,Spring对Ehcache的支持也非常好。EHCache支持内存和磁盘的缓存,支持LRU、LFU和FIFO多种淘汰算法,支持分布式的Cache,可以作为Hibernate的缓存插件。同时它也能提供基于Filter的Cache,该Filter可以缓存响应的内容并采用Gzip压缩提高响应速度。

 

Email:hoojo_@126.com

Blog:http://blog.csdn.net/IBM_hoojo

http://hoojo.cnblogs.com/

 

一、准备工作

如果你的系统中已经成功加入Spring、Hibernate;那么你就可以进入下面Ehcache的准备工作。

1、  下载jar包

Ehcache对象、数据缓存:http://ehcache.org/downloads/destination?name=ehcache-core-2.5.2-distribution.tar.gz&bucket=tcdistributions&file=ehcache-core-2.5.2-distribution.tar.gz

Web页面缓存:http://ehcache.org/downloads/destination?name=ehcache-web-2.0.4-distribution.tar.gz&bucket=tcdistributions&file=ehcache-web-2.0.4-distribution.tar.gz

 

2、  需要添加如下jar包到lib目录下

ehcache-core-2.5.2.jar

ehcache-web-2.0.4.jar 主要针对页面缓存

 

3、  当前工程的src目录中加入配置文件

ehcache.xml

ehcache.xsd

这些配置文件在ehcache-core这个jar包中可以找到

 

二、Ehcache基本用法

[java]  view plain copy print ?
  1. CacheManager cacheManager = CacheManager.create();  
  2. // 或者  
  3. cacheManager = CacheManager.getInstance();  
  4. // 或者  
  5. cacheManager = CacheManager.create("/config/ehcache.xml");  
  6. // 或者  
  7. cacheManager = CacheManager.create("http://localhost:8080/test/ehcache.xml");  
  8. cacheManager = CacheManager.newInstance("/config/ehcache.xml");  
  9. // .......  
  10.   
  11. // 获取ehcache配置文件中的一个cache  
  12. Cache sample = cacheManager.getCache("sample");  
  13. // 获取页面缓存  
  14. BlockingCache cache = new BlockingCache(cacheManager.getEhcache("SimplePageCachingFilter"));  
  15. // 添加数据到缓存中  
  16. Element element = new Element("key""val");  
  17. sample.put(element);  
  18. // 获取缓存中的对象,注意添加到cache中对象要序列化 实现Serializable接口  
  19. Element result = sample.get("key");  
  20. // 删除缓存  
  21. sample.remove("key");  
  22. sample.removeAll();  
  23.   
  24. // 获取缓存管理器中的缓存配置名称  
  25. for (String cacheName : cacheManager.getCacheNames()) {  
  26.     System.out.println(cacheName);  
  27. }  
  28. // 获取所有的缓存对象  
  29. for (Object key : cache.getKeys()) {  
  30.     System.out.println(key);  
  31. }  
  32.   
  33. // 得到缓存中的对象数  
  34. cache.getSize();  
  35. // 得到缓存对象占用内存的大小  
  36. cache.getMemoryStoreSize();  
  37. // 得到缓存读取的命中次数  
  38. cache.getStatistics().getCacheHits();  
  39. // 得到缓存读取的错失次数  
  40. cache.getStatistics().getCacheMisses();  
 

三、页面缓存

页面缓存主要用Filter过滤器对请求的url进行过滤,如果该url在缓存中出现。那么页面数据就从缓存对象中获取,并以gzip压缩后返回。其速度是没有压缩缓存时速度的3-5倍,效率相当之高!其中页面缓存的过滤器有CachingFilter,一般要扩展filter或是自定义Filter都继承该CachingFilter。

CachingFilter功能可以对HTTP响应的内容进行缓存。这种方式缓存数据的粒度比较粗,例如缓存整张页面。它的优点是使用简单、效率高,缺点是不够灵活,可重用程度不高。

EHCache使用SimplePageCachingFilter类实现Filter缓存。该类继承自CachingFilter,有默认产生cache key的calculateKey()方法,该方法使用HTTP请求的URI和查询条件来组成key。也可以自己实现一个Filter,同样继承CachingFilter类,然后覆写calculateKey()方法,生成自定义的key。

CachingFilter输出的数据会根据浏览器发送的Accept-Encoding头信息进行Gzip压缩。

在使用Gzip压缩时,需注意两个问题:

1. Filter在进行Gzip压缩时,采用系统默认编码,对于使用GBK编码的中文网页来说,需要将操作系统的语言设置为:zh_CN.GBK,否则会出现乱码的问题。

2. 默认情况下CachingFilter会根据浏览器发送的请求头部所包含的Accept-Encoding参数值来判断是否进行Gzip压缩。虽然IE6/7浏览器是支持Gzip压缩的,但是在发送请求的时候却不带该参数。为了对IE6/7也能进行Gzip压缩,可以通过继承CachingFilter,实现自己的Filter,然后在具体的实现中覆写方法acceptsGzipEncoding。

具体实现参考:

protectedboolean acceptsGzipEncoding(HttpServletRequest request) {

    boolean ie6 = headerContains(request,"User-Agent", "MSIE 6.0");

    boolean ie7 = headerContains(request,"User-Agent", "MSIE 7.0");

    return acceptsEncoding(request,"gzip") || ie6 || ie7;

}

 

在ehcache.xml中加入如下配置

[html]  view plain copy print ?
  1. <?xml version="1.0" encoding="gbk"?>  
  2. <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd">  
  3.     <diskStore path="java.io.tmpdir"/>  
  4.   
  5.     <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="30" timeToLiveSeconds="30" overflowToDisk="false"/>  
  6.     <!--   
  7.         配置自定义缓存  
  8.         maxElementsInMemory:缓存中允许创建的最大对象数  
  9.         eternal:缓存中对象是否为永久的,如果是,超时设置将被忽略,对象从不过期。  
  10.         timeToIdleSeconds:缓存数据的钝化时间,也就是在一个元素消亡之前,  
  11.                     两次访问时间的最大时间间隔值,这只能在元素不是永久驻留时有效,  
  12.                     如果该值是 0 就意味着元素可以停顿无穷长的时间。  
  13.         timeToLiveSeconds:缓存数据的生存时间,也就是一个元素从构建到消亡的最大时间间隔值,  
  14.                     这只能在元素不是永久驻留时有效,如果该值是0就意味着元素可以停顿无穷长的时间。  
  15.         overflowToDisk:内存不足时,是否启用磁盘缓存。  
  16.         memoryStoreEvictionPolicy:缓存满了之后的淘汰算法。  
  17.           
  18.     -->  
  19.     <cache name="SimplePageCachingFilter"   
  20.         maxElementsInMemory="10000"   
  21.         eternal="false"  
  22.         overflowToDisk="false"   
  23.         timeToIdleSeconds="900"   
  24.         timeToLiveSeconds="1800"  
  25.         memoryStoreEvictionPolicy="LFU" />  
  26.   
  27. </ehcache>  

具体代码:

[java]  view plain copy print ?
  1. package com.hoo.ehcache.filter;  
  2.   
  3. import java.util.Enumeration;  
  4. import javax.servlet.FilterChain;  
  5. import javax.servlet.http.HttpServletRequest;  
  6. import javax.servlet.http.HttpServletResponse;  
  7. import net.sf.ehcache.CacheException;  
  8. import net.sf.ehcache.constructs.blocking.LockTimeoutException;  
  9. import net.sf.ehcache.constructs.web.AlreadyCommittedException;  
  10. import net.sf.ehcache.constructs.web.AlreadyGzippedException;  
  11. import net.sf.ehcache.constructs.web.filter.FilterNonReentrantException;  
  12. import net.sf.ehcache.constructs.web.filter.SimplePageCachingFilter;  
  13. import org.apache.commons.lang.StringUtils;  
  14. import org.apache.log4j.Logger;  
  15.   
  16. /** 
  17.  * <b>function:</b> mobile 页面缓存过滤器 
  18.  * @author hoojo 
  19.  * @createDate 2012-7-4 上午09:34:30 
  20.  * @file PageEhCacheFilter.java 
  21.  * @package com.hoo.ehcache.filter 
  22.  * @project Ehcache 
  23.  * @blog http://blog.csdn.net/IBM_hoojo 
  24.  * @email hoojo_@126.com 
  25.  * @version 1.0 
  26.  */  
  27. public class PageEhCacheFilter extends SimplePageCachingFilter {  
  28.   
  29.     private final static Logger log = Logger.getLogger(PageEhCacheFilter.class);  
  30.       
  31.     private final static String FILTER_URL_PATTERNS = "patterns";  
  32.     private static String[] cacheURLs;  
  33.       
  34.     private void init() throws CacheException {  
  35.         String patterns = filterConfig.getInitParameter(FILTER_URL_PATTERNS);  
  36.         cacheURLs = StringUtils.split(patterns, ",");  
  37.     }  
  38.       
  39.     @Override  
  40.     protected void doFilter(final HttpServletRequest request,  
  41.             final HttpServletResponse response, final FilterChain chain)  
  42.             throws AlreadyGzippedException, AlreadyCommittedException,  
  43.             FilterNonReentrantException, LockTimeoutException, Exception {  
  44.         if (cacheURLs == null) {  
  45.             init();  
  46.         }  
  47.           
  48.         String url = request.getRequestURI();  
  49.         boolean flag = false;  
  50.         if (cacheURLs != null && cacheURLs.length > 0) {  
  51.             for (String cacheURL : cacheURLs) {  
  52.                 if (url.contains(cacheURL.trim())) {  
  53.                     flag = true;  
  54.                     break;  
  55.                 }  
  56.             }  
  57.         }  
  58.         // 如果包含我们要缓存的url 就缓存该页面,否则执行正常的页面转向  
  59.         if (flag) {  
  60.             String query = request.getQueryString();  
  61.             if (query != null) {  
  62.                 query = "?" + query;  
  63.             }  
  64.             log.info("当前请求被缓存:" + url + query);  
  65.             super.doFilter(request, response, chain);  
  66.         } else {  
  67.             chain.doFilter(request, response);  
  68.         }  
  69.     }  
  70.       
  71.     @SuppressWarnings("unchecked")  
  72.     private boolean headerContains(final HttpServletRequest request, final String header, final String value) {  
  73.         logRequestHeaders(request);  
  74.         final Enumeration accepted = request.getHeaders(header);  
  75.         while (accepted.hasMoreElements()) {  
  76.             final String headerValue = (String) accepted.nextElement();  
  77.             if (headerValue.indexOf(value) != -1) {  
  78.                 return true;  
  79.             }  
  80.         }  
  81.         return false;  
  82.     }  
  83.       
  84.     /** 
  85.      * @see net.sf.ehcache.constructs.web.filter.Filter#acceptsGzipEncoding(javax.servlet.http.HttpServletRequest) 
  86.      * <b>function:</b> 兼容ie6/7 gzip压缩 
  87.      * @author hoojo 
  88.      * @createDate 2012-7-4 上午11:07:11 
  89.      */  
  90.     @Override  
  91.     protected boolean acceptsGzipEncoding(HttpServletRequest request) {  
  92.         boolean ie6 = headerContains(request, "User-Agent""MSIE 6.0");  
  93.         boolean ie7 = headerContains(request, "User-Agent""MSIE 7.0");  
  94.         return acceptsEncoding(request, "gzip") || ie6 || ie7;  
  95.     }  
  96. }  

这里的PageEhCacheFilter继承了SimplePageCachingFilter,一般情况下SimplePageCachingFilter就够用了,这里是为了满足当前系统需求才做了覆盖操作。使用SimplePageCachingFilter需要在web.xml中配置cacheName,cacheName默认是SimplePageCachingFilter,对应ehcache.xml中的cache配置。

 

在web.xml中加入如下配置

[html]  view plain copy print ?
  1. <!-- 缓存、gzip压缩核心过滤器 -->  
  2. <filter>  
  3.     <filter-name>PageEhCacheFilter</filter-name>  
  4.     <filter-class>com.hoo.ehcache.filter.PageEhCacheFilter</filter-class>  
  5.     <init-param>  
  6.         <param-name>patterns</param-name>  
  7.         <!-- 配置你需要缓存的url -->  
  8.         <param-value>/cache.jsp, product.action, market.action </param-value>  
  9.     </init-param>  
  10. </filter>  
  11. <filter-mapping>  
  12.     <filter-name>PageEhCacheFilter</filter-name>  
  13.     <url-pattern>*.action</url-pattern>  
  14. </filter-mapping>  
  15. <filter-mapping>  
  16.     <filter-name>PageEhCacheFilter</filter-name>  
  17.     <url-pattern>*.jsp</url-pattern>  
  18. </filter-mapping>  

当第一次请求这些页面后,这些页面就会被添加到缓存中,以后请求这些页面将会从缓存中获取。你可以在cache.jsp页面中用小脚本来测试该页面是否被缓存。<%=new Date()%>如果时间是变动的,则表示该页面没有被缓存或是缓存已经过期,否则则是在缓存状态了。

四、对象缓存

对象缓存就是将查询的数据,添加到缓存中,下次再次查询的时候直接从缓存中获取,而不去数据库中查询。

对象缓存一般是针对方法、类而来的,结合Spring的Aop对象、方法缓存就很简单。这里需要用到切面编程,用到了Spring的MethodInterceptor或是用@Aspect。

 

代码如下:

[java]  view plain copy print ?
  1. package com.hoo.common.ehcache;  
  2.   
  3. import java.io.Serializable;  
  4. import net.sf.ehcache.Cache;  
  5. import net.sf.ehcache.Element;  
  6. import org.aopalliance.intercept.MethodInterceptor;  
  7. import org.aopalliance.intercept.MethodInvocation;  
  8. import org.apache.log4j.Logger;  
  9. import org.springframework.beans.factory.InitializingBean;  
  10.   
  11. /** 
  12.  * <b>function:</b> 缓存方法拦截器核心代码  
  13.  * @author hoojo 
  14.  * @createDate 2012-7-2 下午06:05:34 
  15.  * @file MethodCacheInterceptor.java 
  16.  * @package com.hoo.common.ehcache 
  17.  * @project Ehcache 
  18.  * @blog http://blog.csdn.net/IBM_hoojo 
  19.  * @email hoojo_@126.com 
  20.  * @version 1.0 
  21.  */  
  22. public class MethodCacheInterceptor implements MethodInterceptor, InitializingBean {  
  23.   
  24.     private static final Logger log = Logger.getLogger(MethodCacheInterceptor.class);  
  25.       
  26.     private Cache cache;  
  27.   
  28.     public void setCache(Cache cache) {  
  29.         this.cache = cache;  
  30.     }  
  31.   
  32.     public void afterPropertiesSet() throws Exception {  
  33.         log.info(cache + " A cache is required. Use setCache(Cache) to provide one.");  
  34.     }  
  35.   
  36.     public Object invoke(MethodInvocation invocation) throws Throwable {  
  37.         String targetName = invocation.getThis().getClass().getName();  
  38.         String methodName = invocation.getMethod().getName();  
  39.         Object[] arguments = invocation.getArguments();  
  40.         Object result;  
  41.   
  42.         String cacheKey = getCacheKey(targetName, methodName, arguments);  
  43.         Element element = null;  
  44.         synchronized (this) {  
  45.             element = cache.get(cacheKey);  
  46.             if (element == null) {  
  47.                 log.info(cacheKey + "加入到缓存: " + cache.getName());  
  48.                 // 调用实际的方法  
  49.                 result = invocation.proceed();  
  50.                 element = new Element(cacheKey, (Serializable) result);  
  51.                 cache.put(element);  
  52.             } else {  
  53.                 log.info(cacheKey + "使用缓存: " + cache.getName());  
  54.             }  
  55.         }  
  56.         return element.getValue();  
  57.     }  
  58.   
  59.     /** 
  60.      * <b>function:</b> 返回具体的方法全路径名称 参数 
  61.      * @author hoojo 
  62.      * @createDate 2012-7-2 下午06:12:39 
  63.      * @param targetName 全路径 
  64.      * @param methodName 方法名称 
  65.      * @param arguments 参数 
  66.      * @return 完整方法名称 
  67.      */  
  68.     private String getCacheKey(String targetName, String methodName, Object[] arguments) {  
  69.         StringBuffer sb = new StringBuffer();  
  70.         sb.append(targetName).append(".").append(methodName);  
  71.         if ((arguments != null) && (arguments.length != 0)) {  
  72.             for (int i = 0; i < arguments.length; i++) {  
  73.                 sb.append(".").append(arguments[i]);  
  74.             }  
  75.         }  
  76.         return sb.toString();  
  77.     }  
  78. }  

这里的方法拦截器主要是对你要拦截的类的方法进行拦截,然后判断该方法的类路径+方法名称+参数值组合的cache key在缓存cache中是否存在。如果存在就从缓存中取出该对象,转换成我们要的返回类型。没有的话就把该方法返回的对象添加到缓存中即可。值得主意的是当前方法的参数和返回值的对象类型需要序列化。

 

我们需要在src目录下添加applicationContext.xml完成对MethodCacheInterceptor拦截器的配置,该配置主意是注入我们的cache对象,哪个cache来管理对象缓存,然后哪些类、方法参与该拦截器的扫描。

添加配置如下:

[html]  view plain copy print ?
  1. <context:component-scan base-package="com.hoo.common.interceptor"/>   
  2.   
  3. <!-- 配置eh缓存管理器 -->  
  4. <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"/>  
  5.   
  6. <!-- 配置一个简单的缓存工厂bean对象 -->  
  7. <bean id="simpleCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean">  
  8.     <property name="cacheManager" ref="cacheManager" />  
  9.     <!-- 使用缓存 关联ehcache.xml中的缓存配置 -->  
  10.     <property name="cacheName" value="mobileCache" />  
  11. </bean>  
  12.   
  13. <!-- 配置一个缓存拦截器对象,处理具体的缓存业务 -->  
  14. <bean id="methodCacheInterceptor" class="com. hoo.common.interceptor.MethodCacheInterceptor">  
  15.     <property name="cache" ref="simpleCache"/>  
  16. </bean>  
  17.   
  18. <!-- 参与缓存的切入点对象 (切入点对象,确定何时何地调用拦截器) -->  
  19. <bean id="methodCachePointCut" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">  
  20.     <!-- 配置缓存aop切面 -->  
  21.     <property name="advice" ref="methodCacheInterceptor" />  
  22.     <!-- 配置哪些方法参与缓存策略 -->  
  23.     <!--    
  24.         .表示符合任何单一字元                    
  25.         ###  +表示符合前一个字元一次或多次                    
  26.         ###  *表示符合前一个字元零次或多次                    
  27.         ###  \Escape任何Regular expression使用到的符号                    
  28.     -->                   
  29.     <!-- .*表示前面的前缀(包括包名) 表示print方法-->  
  30.     <property name="patterns">  
  31.         <list>  
  32.             <value>com.hoo.rest.*RestService*\.*get.*</value>  
  33.             <value>com.hoo.rest.*RestService*\.*search.*</value>  
  34.         </list>  
  35.     </property>  
  36. </bean>  


在ehcache.xml中添加如下cache配置

[html]  view plain copy print ?
  1. <cache name="mobileCache"  
  2.         maxElementsInMemory="10000"  
  3.         eternal="false"  
  4.         overflowToDisk="true"  
  5.         timeToIdleSeconds="1800"  
  6.         timeToLiveSeconds="3600"  




附1:

表1. <ehcache:annotation-driven/> 设置

AttributeDefaultDescription
cache-managercacheManagerThe bean name of the CacheManager that is to be used to drive caching. This attribute is not required, and only needs to be specified explicitly if the bean name of the desired CacheManager is not 'cacheManager'.
create-missing-cachesfalseShould cache names from @Cacheable annotations that don't exist in the CacheManager be created based on the default cache or should an exception be thrown?
default-cache-key-generatorNot SetDefault CacheKeyGenerator implementation to use. If not specified HashCodeCacheKeyGenerator will be used as the default.
self-populating-cache-scopesharedAre the SelfPopulatingCache wrappers scoped to the method or are they shared among all methods using each self populating cache.
proxy-target-classfalseApplies to proxy mode only. Controls what type of caching proxies are created for classes annotated with the@Cacheable annotation. If the proxy-target-class attribute is set to true, then class-based proxies are created. If proxy-target-class is false or if the attribute is omitted, then standard JDK interface-based proxies are created. (See Spring Reference Section 7.6, “Proxying mechanisms” for a detailed examination of the different proxy types.)
orderOrdered.LOWEST_PRECEDENCEDefines the order of the caching advice that is applied to beans annotated with @Cacheable. (For more information about the rules related to ordering of AOP advice, see Spring Reference Section 7.2.4.7, “Advice ordering”.) No specified ordering means that the AOP subsystem determines the order of the advice.

@Cacheable设置

PropertyTypeDescription
cacheNameStringRequired name of the cache to use
selfPopulatingbooleanOptional if the method should have self-populating semantics. This results in calls to the annotated method to be made within a EhCache CacheEntryFactory. This is useful for expensive shared resources that should be retrieved a minimal number of times.
keyGenerator@KeyGeneratorOptional inline configuration of a CacheKeyGenerator to use for generating cache keys for this method.
keyGeneratorNameStringOptional bean name of a CacheKeyGenerator to use for generating cache keys for this method.
exceptionCacheNameStringOptional name of the cache to use for caching exceptions. If not specified exceptions result in nothing being cached. If specified the thrown exception is stored in the cache for the generated key and re-thrown for subsequent method calls as long as it remains in the exception cache.


附2:keyGenerator的使用


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
本实例的环境 eclipse + maven + spring + ehcache + junit EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider。Ehcache是一种广泛使用的开 源Java分布式缓存。主要面向通用缓存,Java EE和轻量级容器。它具有内存和磁盘存储,缓存加载器,缓存扩展,缓存异常处理程序,一个gzip缓存servlet过滤器,支持REST和SOAP api等特点。 优点: 1. 快速 2. 简单 3. 多种缓存策略 4. 缓存数据有两级:内存和磁盘,因此无需担心容量问题 5. 缓存数据会在虚拟机重启的过程中写入磁盘 6. 可以通过RMI、可插入API等方式进行分布式缓存 7. 具有缓存缓存管理器的侦听接口 8. 支持多缓存管理器实例,以及一个实例的多个缓存区域 9. 提供Hibernate的缓存实现 缺点: 1. 使用磁盘Cache的时候非常占用磁盘空间:这是因为DiskCache的算法简单,该算法简单也导致Cache的效率非常高。它只是对元素直接追加存储。因此搜索元素的时候非常的快。如果使用DiskCache的,在很频繁的应用中,很快磁盘会满。 2. 不能保证数据的安全:当突然kill掉java的时候,可能会产生冲突,EhCache的解决方法是如果文件冲突了,则重建cache。这对于Cache 数据需要保存的时候可能不利。当然,Cache只是简单的加速,而不能保证数据的安全。如果想保证数据的存储安全,可以使用Bekeley DB Java Edition版本。这是个嵌入式数据库。可以确保存储安全和空间的利用率。 EhCache的分布式缓存有传统的RMI,1.5版的JGroups,1.6版的JMS。分布式缓存主要解决集群环境中不同的服务器间的数据的同步问题。 使用Spring的AOP进行整合,可以灵活的对方法的返回结果对象进行缓存

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值