Cache技术――OSCache(转-全)

OSCache使用指南 

一、下载安装 

OSCache是一个基于web应用的组件,他的安装工作主要是对web应用进行配置,大概的步骤如下: 

1. 下载、解压缩OSCache 

从http://www.opensymphony.com/oscache/download.html下载合适的OSCache版本, 解压缩下载的文件到指定目录 。 

2、新建立一个web应用 

3、将OSCache集成到web项目当中。 

       (1)从解压缩目录取得oscache.jar 文件放到 /WEB-INF/lib 或相应类库目录中,jar文件名可能含有版本号和该版本的发布日期信息等。 

       (2)将etc目录下的oscache.properties、oscache.tld放入WEB-INF/class目录(确切说是放在项目的src目录下,编译的时候会自动生成在WEB-INF/class目录)。 

       (3)配置项目对应的oscache.properties参数信息。 

       (4)具体使用 

              A、缓存对象:直接调用API的接口即可(详见[Java]用OSCache进行缓存对象) 

              B、部分页面缓存:使用OSCache提供的taglib(修改web.xml文件,在web.xml文件中增加下面的内容,增加对OSCache提供的taglib的支持:<taglib> <taglib-uri>oscache</taglib-uri> <taglib-location>/WEB-INF/classes/ oscache.tld</taglib-location></taglib>或者在jsp页面使用以下标签 

<%@ taglib uri="/WEB-INF/classes/oscache.tld" prefix="cache"%>) 

              C、整个页面的缓存:用CashFilter实现页面级缓存,可缓存单个文件、缓存URL pattern和自己设定缓存属性的缓存。 

<filter> 

<filter-name>CacheFilter</filter-name> 

<filter-class>com.opensymphony.oscache.web.filter.CacheFilter</filter-class> 

<init-param> 

<param-name>time</param-name> 

<param-value>600</param-value> 

</init-param> 

<init-param> 

<param-name>scope</param-name> 

<param-value>session</param-value> 

</init-param> 

</filter> 

<filter-mapping> 

<filter-name>CacheFilter</filter-name> 

<!-对所有jsp页面内容进行缓存--> 

<url-pattern>*.jsp</url-pattern> 

</filter-mapping> 

[注] 只有客户访问时返回http头信息中代码为200(也就是访问已经成功)的页面信息才能够被缓存 


三、OSCache的基本用法(缓存JSP页面中部分) 

(一):Cache-OSCache提供的缓存标签 

这是OSCache提供的标签库中最重要的一个标签,包括在标签中的内容将应用缓存机制进行处理,处理的方式将取决于编程者对cache标签属性的设置。 

第一次请求到达时,标签中的内容被处理并且缓存起来,当下一个请求到达时,缓存系统会检查这部分内容的缓存是否已经失效,主要是以下几项: 

1. 缓存时间超过了cache标签设置的time或者duration属性规定的超时时间 

2. cron属性规定的时间比缓存信息的开始时间更晚 

3. 标签中缓存的内容在缓存后又被重新刷新过 

4. 其他缓存超期设定 

如果符合上面四项中的任何一项,被缓存的内容视为已经失效,这时被缓存的内容将被重新处理并且返回处理过后的信息,如果被缓存的内容没有失效,那么返回给用户的将是缓存中的信息。 



cache标签的属性说明: 

key - 标识缓存内容的关键词。在指定的作用范围内必须是唯一的。默认的key是被访问页面的URI和后面的请求字符串。 

你可以在同一个页面中使用很多cache标签而不指定他的key属性,这种情况下系统使用该页面的URI和后面的请求字符串,另外再自动给这些key增加一个索引值来区分这些缓存内容。但是不推荐采用这样的方式。 

scope - 缓存发生作用的范围,可以是application或者session 

time - 缓存内容的时间段,单位是秒,默认是3600秒,也就是一个小时,如果设定一个负值,那么这部分被缓存的内容将永远不过期。 

duration - 指定缓存内容失效的时间,是相对time的另一个选择,可以使用简单日期格式或者符合USO-8601的日期格式。如:duration=''PT5M'' duration=''5s''等 

refresh - false 或者true。 

如果refresh属性设置为true,不管其他的属性是否符合条件,这部分被缓存的内容都将被更新,这给编程者一种选择,决定什么时候必须刷新。 

mode - 如果编程者不希望被缓存的内容增加到给用户的响应中,可以设置mode属性为"silent" 

其它可用的属性还包括:cron 、groups、language、refreshpolicyclass、refreshpolicyparam。 

上面的这些属性可以单独使用,也可以根据需要组合使用,下面的例子将讲解这些常用属性的使用方式。 



(二) Cache标签实例分析: 

1. 最简单的cache标签用法 

使用默认的关键字来标识cache内容,超时时间是默认的3600秒 

<cache:cache> 

<% //自己的JSP代码内容 %> 

</cache:cache> 



2. 用自己指定的字符串标识缓存内容,并且设定作用范围为session。 

<cache:cache key="foobar" scope="session"> 

<% //自己的JSP代码内容 %> 

</cache:cache> 



3.动态设定key值,使用自己指定的time属性设定缓存内容的超时时间,使用动态refresh值决定是否强制内容刷新。 

因为OSCache使用key值来标识缓存内容,使用相同的key值将会被认为使用相同的的缓存内容,所以使用动态的key值可以自由的根据不同的角色、不同的要求决定使用不同的缓存内容。 

<cache:cache key="<%= product.getId() %>" time="1800" refresh="<%= needRefresh %>"> 

<% //自己的JSP代码内容 %> 

</cache:cache> 



4. 设置time属性为负数使缓存内容永不过期 

<cache:cache time="-1"> 

<% //自己的JSP代码内容 %> 

</cache:cache> 



5. 使用duration属性设置超期时间 

<cache:cache duration=''PT5M''> 

<% //自己的JSP代码内容 %> 

</cache:cache> 



6. 使用mode属性使被缓存的内容不加入给客户的响应中 

<cache:cache mode=''silent''> 

<% //自己的JSP代码内容 %> 

</cache:cache> 


四、缓存过滤器 CacheFilter 

用CashFilter实现页面级缓存 

在OSCache组件中提供了一个CacheFilter用于实现页面级的缓存,主要用于对web应用中的某些动态页面进行缓存,尤其是那些需要生成pdf格式文件/报表、图片文件等的页面,不仅减少了数据库的交互、减少数据库服务器的压力,而且对于减少web服务器的性能消耗有很显著的效果。 

这种功能的实现是通过在web.xml中进行配置来决定缓存哪一个或者一组页面,而且还可以设置缓存的相关属性,这种基于配置文件的实现方式对于J2EE来说应该是一种标准的实现方式了。 

[注] 只有客户访问时返回http头信息中代码为200(也就是访问已经成功)的页面信息才能够被缓存 


1. 缓存单个文件 

修改web.xml,增加如下内容,确定对/testContent.jsp页面进行缓存。 

<filter> <filter-name>CacheFilter</filter-name> 

<filter-class>com.opensymphony.oscache.web.filter.CacheFilter</filter-class> </filter> 

<filter-mapping> 

<filter-name>CacheFilter</filter-name> 

<!-对/testContent.jsp页面内容进行缓存--> 

<url-pattern>/testContent.jsp</url-pattern> 

</filter-mapping> 



2. 缓存URL pattern 

修改web.xml,增加如下内容,确定对*.jsp页面进行缓存。 

<filter> <filter-name>CacheFilter</filter-name> 

<filter-class>com.opensymphony.oscache.web.filter.CacheFilter</filter-class> </filter> 

<filter-mapping> 

<filter-name>CacheFilter</filter-name> 

<!-对所有jsp页面内容进行缓存--> 

<url-pattern>*.jsp</url-pattern> 

</filter-mapping> 



3. 自己设定缓存属性 

在页面级缓存的情况下,可以通过设置CacheFilter的初始属性来决定缓存的一些特性:time属性设置缓存的时间段,默认为3600秒,可以根据自己的需要只有的设置,而scope属性设置,默认为application,可选项包括application、session 

<filter> <filter-name>CacheFilter</filter-name> 

<filter-class>com.opensymphony.oscache.web.filter.CacheFilter</filter-class> <init-param> 

<param-name>time</param-name> 

<param-value>600</param-value> 

</init-param> 

<init-param> 

<param-name>scope</param-name> 

<param-value>session</param-value> 

</init-param> 

</filter> 

<filter-mapping> 

<filter-name>CacheFilter</filter-name> 

<!-对所有jsp页面内容进行缓存--> 

<url-pattern>*.jsp</url-pattern> 

</filter-mapping> 



五、缓存对象 
一、对象缓存 

1、Cache操作类 
Java代码   收藏代码
  1.    import java.util.Date;  
  2.   
  3. import com.opensymphony.oscache.base.NeedsRefreshException;  
  4.   
  5. import com.opensymphony.oscache.general.GeneralCacheAdministrator;  
  6.   
  7. public class BaseCache extends GeneralCacheAdministrator {       
  8.   
  9.     private int refreshPeriod; //过期时间(单位为秒);           
  10.   
  11.     private String keyPrefix; //关键字前缀字符;              
  12.   
  13.     private static final long serialVersionUID = -4397192926052141162L;          
  14.   
  15.     public BaseCache(String keyPrefix,int refreshPeriod){      
  16.   
  17.         super();      
  18.   
  19.         this.keyPrefix = keyPrefix;      
  20.   
  21.         this.refreshPeriod = refreshPeriod;      
  22.   
  23.     }      
  24.   
  25.     //添加被缓存的对象;      
  26.   
  27.     public void put(String key,Object value){      
  28.   
  29.         this.putInCache(this.keyPrefix+"_"+key,value);      
  30.   
  31.     }      
  32.   
  33.     //删除被缓存的对象;      
  34.   
  35.     public void remove(String key){      
  36.   
  37.         this.flushEntry(this.keyPrefix+"_"+key);      
  38.   
  39.     }      
  40.   
  41.     //删除所有被缓存的对象;      
  42.   
  43.     public void removeAll(Date date){      
  44.   
  45.         this.flushAll(date);      
  46.   
  47.     }             
  48.   
  49.     public void removeAll(){      
  50.   
  51.         this.flushAll();      
  52.   
  53.     }      
  54.   
  55.     //获取被缓存的对象;      
  56.   
  57.     public Object get(String key) throws Exception{      
  58.   
  59.         try{      
  60.   
  61.             return this.getFromCache(this.keyPrefix+"_"+key,this.refreshPeriod);      
  62.   
  63.         } catch (NeedsRefreshException e) {      
  64.   
  65.             this.cancelUpdate(this.keyPrefix+"_"+key);      
  66.   
  67.             throw e;      
  68.   
  69.         }        
  70.   
  71.     }              
  72.   
  73. }     

2、Cache管理类 

Java代码   收藏代码
  1. public class CacheManager {          
  2.   
  3.     private BaseCache newsCache;              
  4.   
  5.     private static CacheManager instance;      
  6.   
  7.     private static Object lock = new Object();             
  8.   
  9.     private CacheManager() {      
  10.   
  11.         //这个根据配置文件来,初始BaseCache而已;      
  12.   
  13.         newsCache = new BaseCache("news",120);           
  14.   
  15.     }              
  16.   
  17.     public static CacheManager getInstance(){      
  18.   
  19.         if (instance == null){      
  20.   
  21.             synchronized( lock ){      
  22.   
  23.                 if (instance == null){      
  24.   
  25.                     instance = new CacheManager();      
  26.   
  27.                 }      
  28.   
  29.             }      
  30.   
  31.         }      
  32.   
  33.         return instance;      
  34.   
  35.     }         
  36.   
  37.   
  38.    
  39.   
  40.   
  41.     public void removeAllNews() {      
  42.   
  43.         newsCache.removeAll();      
  44.   
  45.     }         
  46.   
  47.     public void putUser(User news) { newsCache.put(news.getId()+"",news);      }         
  48.   
  49.     public void removeUser(String newsID) {  newsCache.remove(newsID);       }         
  50.   
  51.     public User getUser(int newsID) {      
  52.   
  53.         try {      
  54.   
  55.             return (User) newsCache.get(newsID+"");      
  56.   
  57.         } catch (Exception e) {      
  58.   
  59.             System.out.println("getNews>>newsID["+newsID+"]>>"+e.getMessage());      
  60.   
  61.             User news = new User(newsID);      
  62.   
  63.             this.putUser(news);      
  64.   
  65.             return news;      
  66.   
  67.         }      
  68.   
  69.     }         
  70. }  

3、对象Bean 

Java代码   收藏代码
  1. public class User {  
  2.   
  3.     private int id;  
  4.   
  5.     private String name;  
  6.   
  7.     private String sex;  
  8.   
  9.     private int age;  
  10.   
  11.     private Date accessTime; public User(int id) {  
  12.   
  13.        super();  
  14.   
  15.        this.id = id;  
  16.   
  17.        this.accessTime = new Date(System.currentTimeMillis());  
  18.   
  19.     }  
  20.   
  21.     public String toString() {  
  22.   
  23.        return "User info is : id=" + id + "  accessTime="  
  24.   
  25.               + accessTime.toString();  
  26.   
  27.     }  
  28.   
  29.     public User(String name, String sex, int age) {  
  30.   
  31.        super();  
  32.   
  33.        this.name = name;  
  34.   
  35.        this.sex = sex;  
  36.   
  37.        this.age = age;  
  38.   
  39.     }  
  40.   
  41.     public User() {  
  42.   
  43.     }  
  44.   
  45.     public int getAge() {  
  46.   
  47.        return age;  
  48.   
  49.     }  
  50.   
  51.     public void setAge(int age) {  
  52.   
  53.        this.age = age;  
  54.   
  55.     }  
  56.   
  57.     public String getName() {  
  58.   
  59.        return name;  
  60.   
  61.     }  
  62.   
  63.     public void setName(String name) {  
  64.   
  65.        this.name = name;  
  66.   
  67.     }  
  68.   
  69.     public String getSex() {  
  70.   
  71.        return sex;  
  72.   
  73.     }  
  74.   
  75.     public void setSex(String sex) {  
  76.   
  77.        this.sex = sex;  
  78.   
  79.     }  
  80.   
  81.     public int getId() {  
  82.   
  83.        return id;  
  84.   
  85.     }  
  86.   
  87.     public void setId(int id) {  
  88.   
  89.        this.id = id;  
  90.   
  91.     }  
  92.   
  93.     public Date getAccessTime() {  
  94.   
  95.        return accessTime;  
  96.   
  97.     }  
  98.   
  99.     public void setAccessTime(Date accessTime) {  
  100.   
  101.        this.accessTime = accessTime;  
  102.   
  103.     }  
  104.   
  105. }  

4、测试类 

Java代码   收藏代码
  1. public class TestObjectCache {  
  2.   
  3.     public static void main(String[] args) {  
  4.   
  5.        CacheManager cm=CacheManager.getInstance();  
  6.   
  7.          
  8.   
  9.        TestObjectCache test=new TestObjectCache();  
  10.   
  11.        test.print(cm);  
  12.   
  13.     }  
  14.   
  15.       
  16.   
  17.     public void print(CacheManager cm){  
  18.   
  19.        User user=null;  
  20.   
  21.        for (int i = 0; i < 1000; i++) {  
  22.   
  23.            user=cm.getUser(100);  
  24.   
  25.            System.out.println("<<"+i+">>: "+user);            
  26.   
  27.            if(i==10){  
  28.   
  29.               //删除缓存id的对象  
  30.   
  31.               cm.removeUser(100+"");  
  32.   
  33.            }            
  34.   
  35.            if(i==20){  
  36.   
  37.               //删除所有缓存的对象  
  38.   
  39.               cm.removeAllNews();  
  40.   
  41.            }            
  42.   
  43.            // 睡眠部分  
  44.   
  45.            try {  
  46.   
  47.               Thread.sleep(30000);  
  48.   
  49.            } catch (Exception e) {  
  50.   
  51.            }  
  52.   
  53.        }  
  54.   
  55.     }  
  56.   
  57. }  


CacheManager 类 

Java代码   收藏代码
  1. public class CacheManager {  
  2.   
  3.     private BaseCache newsCache;  
  4.   
  5.     private static CacheManager instance;  
  6.   
  7.     private static Object lock = new Object();  
  8.   
  9.    
  10.   
  11.     private CacheManager() {  
  12.   
  13.        // 这个根据配置文件来,初始BaseCache而已;  
  14.   
  15.        newsCache = new BaseCache("hrms"300);  
  16.   
  17.     }  
  18.   
  19.    
  20.   
  21.     public static CacheManager getInstance() {  
  22.   
  23.        if (instance == null) {  
  24.   
  25.            synchronized (lock) {  
  26.   
  27.               if (instance == null) {  
  28.   
  29.                   instance = new CacheManager();  
  30.   
  31.               }  
  32.   
  33.            }  
  34.   
  35.        }  
  36.   
  37.        return instance;  
  38.   
  39.     }  
  40.   
  41.    
  42.   
  43.     public void put(Object news,String key,String[] groups) {  
  44.   
  45.        newsCache.put(key, news,groups);  
  46.   
  47.     }  
  48.   
  49.     public void remove(String key) {  
  50.   
  51.        newsCache.remove(key);  
  52.   
  53.     }  
  54.   
  55.     public Object get(String key) {  
  56.   
  57.        try {  
  58.   
  59.            return newsCache.get(key);  
  60.   
  61.        } catch (Exception e) {  
  62.   
  63.            return null;  
  64.   
  65.        }  
  66.   
  67.     }  
  68.   
  69.     public void removeAll() {  
  70.   
  71.        newsCache.removeAll();  
  72.   
  73.     }     
  74.   
  75.     public void removeObjectByGroup(String group){  
  76.   
  77.        newsCache.removeObjectByGroup(group);  
  78.   
  79.     }  
  80.   
  81. }  


BaseCache 类增加的2个方法如下: 

    // 添加被缓存的对象; 

    public void put(String key, Object value,String[] groups) { 

       this.putInCache(this.keyPrefix + "_" + key, value,groups); 

    } 

    //删除该组的缓存对象 

    public void removeObjectByGroup(String group){ 

       this.flushGroup(group); 

    } 

第六部分:小结及其引申 



缓存是在提升系统响应时常用的一种技术,在系统缓存上通常采用的是有页面缓存、处理缓存和数据缓存这三种具体的类别,应该说这三种缓存在实现上还是稍有不同,尽管底层的缓存实现是一样的。 

页面缓存 

页面缓存是指对页面中的内容片断进行缓存的方案。比如页面中有一个部分是显示栏目中的内容的,那么就可以缓存这个部分,在进行第二次请求的时候就直接从缓存中取出这部分的内容(其实就是这部分的html了),这种情况下,缓存的作用其实非常明显,在典型的action+service+dao这样的结构中,在采用页面缓存后就意味着不需要经过action、service、dao这些层次的处理了,而是直接就返回了,对于系统响应速度的提升来说是非常明显的。 

页面缓存通常采用oscache来进行实现,oscache提供了一个jsp  tag,可通过这个tag来包含需要缓存的内容部分,当然,缓存的这个内容部分需要有对服务器的请求或逻辑计算等的,可想而知,去缓存一段静态html是没有意义的。 

其次需要定义缓存的这段内容的key,例如我们要去缓存页面中某个栏目的某页的内容,对于这段内容而言唯一的key就是栏目ID以及当前页数,这样就组成了这段缓存的key了,其实这个部分看起来好像是很简单,但有些时候会很麻烦,要仔细的想清楚这段内容的唯一的标识的key到底是什么,^_^,通常的做法其实可以从action中需要获取的参数或service接口的参数来决定.... 

页面缓存中还需要做的一个步骤就是通知缓存需要更新,页面缓存和其他缓存稍有不同,需要告诉它,这个时候不能再使用缓存中的内容了,需要从后台再重新获取来生成新的缓存内容,这个其实很简单,因为很难在后台发生变化的时候自己来更新缓存的内容,只能是去通知它,然后让它再次发起请求来生成新的内容放入缓存中。 

页面的缓存的使用对于系统的响应速度确实会有很大的提升,在实现页面缓存时最麻烦的主要是缓存的key的定义以及缓存更新的通知,缓存key的定义这个自然框架是没法解决的,不过缓存更新的通知其实在框架中可以考虑一种通知模型的,^_^,就像事件通知那样........在实际的项目中,可以自己去实现一个这样的通知模型或者就是简单的采用单例方式来标识某个key是否需要更新。 

页面缓存在实际的项目中使用非常的多。 

处理缓存 

处理缓存是指对于action、service、dao或者系统层次中的某方法进行缓存,说直接点,就是对某个类的某个方法的结果做缓存,这样在下次进行完全相同的请求的时候就可以直接取缓存了,这种响应速度的提升也是非常明显的。 

处理缓存在现在的情况下其实采用任务的缓存工具包都可以实现,如oscache、ehcache、jbosscache等,但目前还没有处理缓存框架的出现,这个和处理缓存是否应该存在的意义也是有关系的,处理缓存框架要做到的其实就像拦截一样的方式,和oscache  tag类似。 

同样,处理缓存的麻烦也在于怎么样去定义这个key,很多情况下可以根据方法的输入作为key,方法的输出作为key的值,但也会有其他一些复杂的情况,这个时候定义key就会变得复杂些了。 

处理缓存同样有通知更新缓存的情况,和页面缓存基本是一样的。 

应该说,处理缓存和页面缓存非常的相似,从实现上来说基本是完全一致的,在使用上来讲处理缓存使用的好像不多。 

数据缓存 

数据缓存估计大家都很熟悉,就是对系统的数据进行缓存的方式,典型的就是Hibernate的一级、二级数据缓存。 

数据缓存在实现上如果是用hibernate的话更多的是直接使用hibernate的一级、二级以及查询缓存,如果自己要实现的话可以去参考hibernate的实现机制。 

数据缓存的key在一级、二级缓存中采用的都是数据的标识键的值的方式,查询缓存采用的是查询参数、查询语句的方式。 

数据缓存的更新则是hibernate在进行存储时直接更新缓存的内容,而对于查询缓存则是采用全部直接清除的方式,这样在下次进行查询时自然会重新去查询,^_^,大家可能会想,为什么页面缓存和处理缓存不采用这样的方式来实现缓存的更新,稍微想想就知道了,在后台发生改变的时候其实是不知道需要移除哪些key的,所以hibernate为了避免这个麻烦,采用的就是当数据一旦发生改变的时候就清除全部的查询缓存,而不是只去清除相关的缓存,其实这里可以采用一种订阅式的模型,呵呵,当然,也增加了框架的复杂度。 

数据缓存使用的应该是最多的,效果也是很明显的。 

以上三种缓存是目前缓存实现时通常碰到的三种状况,里面按使用的多少来排序应该是:数据缓存、页面缓存和处理缓存;实现的难度上从难到易的顺序应该是:处理缓存、页面缓存、数据缓存;对于系统响应速度提升的效果来说从最好到好的顺序应该是:页面缓存、处理缓存、数据缓存。 





补充部分: 

       在SSH项目应用中,可以以对象的形式来缓存展现给用户的数据信息。对象的缓存要充分利用分组带来的好处(可以分组删除被缓存的对象),这样在执行数据库的CUD操作时,可以调用删除相应组别的缓存对象。 

示例代码: 

    private CacheManager cm; 

    

    private final static String CACHE_KEY_SUB="RetJobs"; 



    public JobAction() { 

       //获取缓存管理对象 

       cm = CacheManager.getInstance(); 

    } 

         

查询部分 

       page=(Page<RetJob>)(cm.get(CACHE_KEY_SUB+"_"+currentPage)); 

       if(page==null){ 

           //--------------------需要缓存对象部分----------------------- 

           page = retJobBaseModel.getJobs(currentPage, pageSize, statusCondition); 

           //------------------------------------------- 

           //缓存对象(含所属分组信息) 

           cm.put(page, CACHE_KEY_SUB+"_"+currentPage,new String[]{CACHE_KEY_SUB}); 

       } 



CUD操作部分 

setCacheDisabled(CACHE_KEY_SUB); 



    private void setCacheDisabled(String group) { 

       //通过组别信息来删除缓存的对象。 

       cm.removeObjectByGroup(group); 

    } 


----------------------------------------偶很华丽----------------------- 
其他帮助信息: 
参阅资料: 

[0]:http://www.opensymphony.com/oscache/ 

[1]:OSCache简介 

[2]:OSCache分析 

[3]:OSCache——学习笔记 

[4]:应用OSCache提升J2EE系统运行性能 

[5]:[Java]用OSCache进行缓存对象 

[6]:osCache 配置说明 

[7]:缓存漫谈 


转载自:http://shijincheng0223.iteye.com/blog/1412128

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值