mongodb与spring整合及基础dao类封装

maven文件引用

	<!-- mongodb引入 -->
		 <dependency>
              <groupId>org.springframework.data</groupId>
              <artifactId>spring-data-mongodb</artifactId>
              <version>1.3.0.RELEASE</version>
            </dependency>     
        <dependency>
            <groupId>org.mongodb</groupId>
            <artifactId>mongo-java-driver</artifactId>
            <version>2.11.1</version>
        </dependency>

spring-mongdb配置

<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns="http://www.springframework.org/schema/beans"  
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    
    xmlns:context="http://www.springframework.org/schema/context"  
    xmlns:mongo="http://www.springframework.org/schema/data/mongo"  
    xsi:schemaLocation="http://www.springframework.org/schema/context   
          http://www.springframework.org/schema/context/spring-context-3.0.xsd   
          http://www.springframework.org/schema/data/mongo   
          http://www.springframework.org/schema/data/mongo/spring-mongo-1.0.xsd   
          http://www.springframework.org/schema/beans   
          http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">  
          
    <!-- 加载mongodb的属性配置文件 -->
    <context:property-placeholder location="classpath:mongodb.properties" />
    
    <!-- 定义mongo对象,对应的是mongodb官方jar包中的Mongo,replica-set设置集群副本的ip地址和端口 -->
    <mongo:mongo id="mongo" replica-set="${mongo.hostport}">
        <!-- 一些连接属性的设置 -->    
        <mongo:options
             connections-per-host="${mongo.connectionsPerHost}"
             threads-allowed-to-block-for-connection-multiplier="${mongo.threadsAllowedToBlockForConnectionMultiplier}"
             connect-timeout="${mongo.connectTimeout}"
             max-wait-time="${mongo.maxWaitTime}"
             auto-connect-retry="${mongo.autoConnectRetry}"
             socket-keep-alive="${mongo.socketKeepAlive}"
             socket-timeout="${mongo.socketTimeout}"
             slave-ok="${mongo.slaveOk}"
             write-number="1"
             write-timeout="0"
             write-fsync="true"/>        
    </mongo:mongo>
    <mongo:db-factory dbname="database" mongo-ref="mongo" />
    
    <bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
        <constructor-arg ref="mongo" />
        <constructor-arg name="databaseName" value="db_mongo" />
    </bean>

</beans>

配置参数 mongodb.properties

mongo.hostport=127.0.0.1:27017
mongo.connectionsPerHost=8
mongo.threadsAllowedToBlockForConnectionMultiplier=4
#\u8FDE\u63A5\u8D85\u65F6\u65F6\u95F4
mongo.connectTimeout=1000
#\u7B49\u5F85\u65F6\u95F4
mongo.maxWaitTime=1500
mongo.autoConnectRetry=true
mongo.socketKeepAlive=true
#Socket\u8D85\u65F6\u65F6\u95F4
mongo.socketTimeout=1500
mongo.slaveOk=true

Dao层接口

import java.util.List;

import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;

import com.mongodb.WriteResult;

public interface BaseMongoDao<T> {
	/**
	 * 插入
	 */
	public T save(T entity);

	/**
	 * 根据ID查询
	 */
	public T findById(String id);

	/**
	 * 通过ID获取记录,并且指定了集合名(表的意思)
	 */
	public T findById(String id, String collectionName);

	/**
	 * 获得所有该类型记录
	 */
	public List<T> findAll();

	/**
	 * 获得所有该类型记录,并且指定了集合名(表的意思)
	 */
	public List<T> findAll(String collectionName);

	/**
	 * 根据条件查询
	 */
	public List<T> find(Query query);

	/**
	 * 根据条件查询一个
	 */
	public T findOne(Query query);

	/**
	 * 分页查询
	 */
	public Page<T> findPage(Page<T> page, Query query);

	/**
	 * 根据条件 获得总数
	 */
	public long count(Query query);

	/**
	 * 根据条件 更新
	 */
	public WriteResult update(Query query, Update update);

	/**
	 * 更新符合条件并sort之后的第一个文档 并返回更新后的文档
	 */
	public T updateOne(Query query, Update update);

	/**
	 * 根据传入实体ID更新
	 */
	public WriteResult update(T entity);

	/**
	 * 根据条件 删除
	 * 
	 * @param query
	 */
	public void remove(Query query);
}
Dao层实现

import java.lang.reflect.Field;
import java.util.List;

import javax.annotation.Resource;

import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;

import com.mongodb.WriteResult;
import com.trustsaving.common.base.util.ReflectionUtils;

public class BaseMongoDaoImpl<T> implements BaseMongoDao<T>{
    /**
     * spring mongodb 集成操作类 
     */
    @Resource
    protected MongoTemplate mongoTemplate;

    /**
     * 注入mongodbTemplate
     * 
     * @param mongoTemplate
     */
    protected void setMongoTemplate(MongoTemplate mongoTemplate) {
        this.mongoTemplate = mongoTemplate;
    }
    public T save(T entity) {
        mongoTemplate.insert(entity);
        return entity;
    }

    public T findById(String id) {
        return mongoTemplate.findById(id, this.getEntityClass());
    }

    public T findById(String id, String collectionName) {
        return mongoTemplate.findById(id, this.getEntityClass(), collectionName);
    }

    public List<T> findAll() {
        return mongoTemplate.findAll(this.getEntityClass());
    }

    public List<T> findAll(String collectionName) {
        return mongoTemplate.findAll(this.getEntityClass(), collectionName);
    }

    public List<T> find(Query query) {
        return mongoTemplate.find(query, this.getEntityClass());
    }

    public T findOne(Query query) {
        return mongoTemplate.findOne(query, this.getEntityClass());
    }

    public Page<T> findPage(Page<T> page, Query query) {
        //如果没有条件 则所有全部
        query=query==null?new Query(Criteria.where("_id").exists(true)):query;
        long count = this.count(query);
        // 总数
        page.setTotalCount((int) count);
        int currentPage = page.getCurrentPage();
        int pageSize = page.getPageSize();
        query.skip((currentPage - 1) * pageSize).limit(pageSize);
        List<T> rows = this.find(query);
        page.build(rows);
        return page;
    }

    public long count(Query query) {
        return mongoTemplate.count(query, this.getEntityClass());
    }

    public WriteResult update(Query query, Update update) {
        if (update==null) {
            return null;
        }
        return mongoTemplate.updateMulti(query, update, this.getEntityClass());
    }

    public T updateOne(Query query, Update update) {
        if (update==null) {
            return null;
        }
        return mongoTemplate.findAndModify(query, update, this.getEntityClass());
    }

    public WriteResult update(T entity) {
        Field[] fields = this.getEntityClass().getDeclaredFields();
        if (fields == null || fields.length <= 0) {
            return null;
        }
        Field idField = null;
        // 查找ID的field
        for (Field field : fields) {
            if (field.getName() != null
                    && "id".equals(field.getName().toLowerCase())) {
                idField = field;
                break;
            }
        }
        if (idField == null) {
            return null;
        }
        idField.setAccessible(true);
        String id=null;
        try {
            id = (String) idField.get(entity);
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        if (id == null || "".equals(id.trim()))
            return null;
        // 根据ID更新
        Query query = new Query(Criteria.where("_id").is(id));
        // 更新
        // Update update = new Update();
        // for (Field field : fields) {
        // // 不为空 不是主键 不是序列化号
        // if (field != null
        // && field != idField
        // && !"serialversionuid"
        // .equals(field.getName().toLowerCase())) {
        // field.setAccessible(true);
        // Object obj = field.get(entity);
        // if (obj == null)
        // continue;
        // update.set(field.getName(), obj);
        // }
        // }
        Update update = ReflectionUtils.getUpdateObj(entity);
        if (update == null) {
            return null;
        }
        return mongoTemplate.updateFirst(query, update, getEntityClass());
    }

    public void remove(Query query) {
        mongoTemplate.remove(query, this.getEntityClass());
    }
    /**
     * 获得泛型类
     */
    private Class<T> getEntityClass() {
        // Type genType = getClass().getGenericSuperclass();
        // if (!(genType instanceof ParameterizedType)) {
        // return (Class<T>) Object.class;
        // }
        // // 返回表示此类型实际类型参数的 Type 对象的数组。
        // Type[] params = ((ParameterizedType)
        // genType).getActualTypeArguments();
        // if (!(params[0] instanceof Class)) {
        // return (Class<T>) Object.class;
        // }
        // return (Class<T>) params[0];
        return ReflectionUtils.getSuperClassGenricType(getClass());
    }

}



 反射工具类 

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

public class ReflectionUtils {

    private static Logger logger = LoggerFactory
            .getLogger(ReflectionUtils.class);

    /**
     * 调用Getter方法.
     */
    public static Object invokeGetterMethod(Object obj, String propertyName) {
        String getterMethodName = "get" + StringUtils.capitalize(propertyName);
        return invokeMethod(obj, getterMethodName, new Class[] {},
                new Object[] {});
    }

    /**
     * 调用Setter方法.使用value的Class来查找Setter方法.
     */
    public static void invokeSetterMethod(Object obj, String propertyName,
            Object value) {
        invokeSetterMethod(obj, propertyName, value, null);
    }

    /**
     * 调用Setter方法.
     * 
     * @param propertyType
     *            用于查找Setter方法,为空时使用value的Class替代.
     */
    public static void invokeSetterMethod(Object obj, String propertyName,
            Object value, Class<?> propertyType) {
        Class<?> type = propertyType != null ? propertyType : value.getClass();
        String setterMethodName = "set" + StringUtils.capitalize(propertyName);
        invokeMethod(obj, setterMethodName, new Class[] { type },
                new Object[] { value });
    }

    /**
     * 直接读取对象属性值, 无视private/protected修饰符, 不经过getter函数.
     */
    public static Object getFieldValue(final Object obj, final String fieldName) {
        Field field = getAccessibleField(obj, fieldName);

        if (field == null) {
            throw new IllegalArgumentException("Could not find field ["
                    + fieldName + "] on target [" + obj + "]");
        }

        Object result = null;
        try {
            result = field.get(obj);
        } catch (IllegalAccessException e) {
            logger.error("不可能抛出的异常{}", e.getMessage());
        }
        return result;
    }

    /**
     * 直接设置对象属性值, 无视private/protected修饰符, 不经过setter函数.
     */
    public static void setFieldValue(final Object obj, final String fieldName,
            final Object value) {
        Field field = getAccessibleField(obj, fieldName);

        if (field == null) {
            throw new IllegalArgumentException("Could not find field ["
                    + fieldName + "] on target [" + obj + "]");
        }

        try {
            field.set(obj, value);
        } catch (IllegalAccessException e) {
            logger.error("不可能抛出的异常:{}", e.getMessage());
        }
    }

    /**
     * 循环向上转型, 获取对象的DeclaredField, 并强制设置为可访问.
     * 
     * 如向上转型到Object仍无法找到, 返回null.
     */
    public static Field getAccessibleField(final Object obj,
            final String fieldName) {
        Assert.notNull(obj, "object不能为空");
        Assert.hasText(fieldName, "fieldName");
        for (Class<?> superClass = obj.getClass(); superClass != Object.class; superClass = superClass
                .getSuperclass()) {
            try {
                Field field = superClass.getDeclaredField(fieldName);
                field.setAccessible(true);
                return field;
            } catch (NoSuchFieldException e) {// NOSONAR
                // Field不在当前类定义,继续向上转型
            }
        }
        return null;
    }

    /**
     * 直接调用对象方法, 无视private/protected修饰符. 用于一次性调用的情况.
     */
    public static Object invokeMethod(final Object obj,
            final String methodName, final Class<?>[] parameterTypes,
            final Object[] args) {
        Method method = getAccessibleMethod(obj, methodName, parameterTypes);
        if (method == null) {
            throw new IllegalArgumentException("Could not find method ["
                    + methodName + "] on target [" + obj + "]");
        }

        try {
            return method.invoke(obj, args);
        } catch (Exception e) {
            throw convertReflectionExceptionToUnchecked(e);
        }
    }

    /**
     * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问. 如向上转型到Object仍无法找到, 返回null.
     * 
     * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object...
     * args)
     */
    public static Method getAccessibleMethod(final Object obj,
            final String methodName, final Class<?>... parameterTypes) {
        Assert.notNull(obj, "object不能为空");

        for (Class<?> superClass = obj.getClass(); superClass != Object.class; superClass = superClass
                .getSuperclass()) {
            try {
                Method method = superClass.getDeclaredMethod(methodName,
                        parameterTypes);

                method.setAccessible(true);

                return method;

            } catch (NoSuchMethodException e) {// NOSONAR
                // Method不在当前类定义,继续向上转型
            }
        }
        return null;
    }

    /**
     * 通过反射, 获得Class定义中声明的父类的泛型参数的类型. 如无法找到, 返回Object.class. eg. public UserDao
     * extends HibernateDao<User>
     * 
     * @param clazz
     *            The class to introspect
     * @return the first generic declaration, or Object.class if cannot be
     *         determined
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public static <T> Class<T> getSuperClassGenricType(final Class clazz) {
        return getSuperClassGenricType(clazz, 0);
    }

    /**
     * 通过反射, 获得Class定义中声明的父类的泛型参数的类型. 如无法找到, 返回Object.class.
     * 
     * 如public UserDao extends HibernateDao<User,Long>
     * 
     * @param clazz
     *            clazz The class to introspect
     * @param index
     *            the Index of the generic ddeclaration,start from 0.
     * @return the index generic declaration, or Object.class if cannot be
     *         determined
     */
    @SuppressWarnings("rawtypes")
    public static Class getSuperClassGenricType(final Class clazz,
            final int index) {

        Type genType = clazz.getGenericSuperclass();

        if (!(genType instanceof ParameterizedType)) {
            logger.warn(clazz.getSimpleName()
                    + "'s superclass not ParameterizedType");
            return Object.class;
        }

        Type[] params = ((ParameterizedType) genType).getActualTypeArguments();

        if (index >= params.length || index < 0) {
            logger.warn("Index: " + index + ", Size of "
                    + clazz.getSimpleName() + "'s Parameterized Type: "
                    + params.length);
            return Object.class;
        }
        if (!(params[index] instanceof Class)) {
            logger.warn(clazz.getSimpleName()
                    + " not set the actual class on superclass generic parameter");
            return Object.class;
        }

        return (Class) params[index];
    }

    /**
     * 将反射时的checked exception转换为unchecked exception.
     */
    public static RuntimeException convertReflectionExceptionToUnchecked(
            Exception e) {
        if (e instanceof IllegalAccessException
                || e instanceof IllegalArgumentException
                || e instanceof NoSuchMethodException) {
            return new IllegalArgumentException("Reflection Exception.", e);
        } else if (e instanceof InvocationTargetException) {
            return new RuntimeException("Reflection Exception.",
                    ((InvocationTargetException) e).getTargetException());
        } else if (e instanceof RuntimeException) {
            return (RuntimeException) e;
        }
        return new RuntimeException("Unexpected Checked Exception.", e);
    }

    /**
     * 根据对象获得mongodb Update语句
     * 除id字段以外,所有被赋值的字段都会成为修改项
     */
    public static Update getUpdateObj(final Object obj) {
        if (obj == null)
            return null;
        Field[] fields = obj.getClass().getDeclaredFields();
        Update update = null;
        boolean isFirst = true;
        for (Field field : fields) {
            field.setAccessible(true);
            try {
                Object value = field.get(obj);
                if (value != null) {
                    if ("id".equals(field.getName().toLowerCase())|| "serialversionuid".equals(field.getName().toLowerCase()))
                        continue;
                    if (isFirst) {
                        update = Update.update(field.getName(),value);
                        isFirst = false;
                    } else {
                        update = update.set(field.getName(), value);
                    }
                }

            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return update;
    }

    /**
     * 根据对象获得mongodb Query语句
     * 
     * 1.时间范围查询:在时间字段前增加begin或end,为这两个字段分别赋值
     * 例:private Date createDate; 
     * 开始时间
     * private Date beginCreateDate;
     * 结束时间
     * private Date endCreateDate;
     * 分析后结果:where createDate >= beginCreateDate and createDate < beginCreateDate
     * 
     * 2.排序
     * 定义并赋值VO中 orderBy 字段,以英文“,”分割多个排序,以空格分隔排序方向 asc可不写
     * 例:private String orderBy;
     * orderBy="createDate desc,sendDate asc,id"
     * 分析结构:order by createDate desc,sendDate asc,id asc
     * 
     * 3.固定值搜索
     * 定义并赋值VO中的任意字段,搜索时会把以赋值的字段当作为搜索条件
     */
    public static Query getQueryObj(final Object obj) {
        if (obj == null)
            return null;
        Field[] fields = obj.getClass().getDeclaredFields();
        // Sort sort=new Sort(new Order(Direction.DESC,"createDate"));
        Query query=new Query();
        //存放日期范围或者确定日期
        Map<String, Criteria> dateMap=new HashMap<String, Criteria>();
        String sortStr = null;
        for (Field field : fields) {
            field.setAccessible(true);
            try {
                Object value = field.get(obj);
                if (value != null) {
                    if("serialversionuid".equals(field.getName().toLowerCase())){
                        continue;
                    }
                    if ("orderby".equals(field.getName().toLowerCase())) {
                        sortStr=String.valueOf(value);
                        continue;
                    }
                    //如果是日期类型
                    if (field.getType().getSimpleName().equals("Date")) {
                        if (field.getName().toLowerCase().startsWith("begin")) {
                            String beginName=field.getName().substring(5);
                            if (beginName.isEmpty()) {
                                dateMap.put("begin",Criteria.where("begin").is(value));
                            }else {
                                beginName=StringUtil.toLowerCaseFirstOne(beginName);
                                Criteria criteria=dateMap.get(beginName)==null?Criteria.where(beginName).gte(value):dateMap.get(beginName).gte(value);
                                dateMap.put(beginName, criteria);
                            }
                            continue;
                        }
                        if (field.getName().toLowerCase().startsWith("end")) {
                            String endName=field.getName().substring(3);
                            if (endName.isEmpty()) {
                                dateMap.put("end",Criteria.where("end").is(value));
                            }else {
                                endName=StringUtil.toLowerCaseFirstOne(endName);
                                Criteria criteria=dateMap.get(endName)==null?Criteria.where(endName).lt(value):dateMap.get(endName).lt(value);
                                dateMap.put(endName, criteria);
                            }
                            continue;
                        }
                        dateMap.put(field.getName(),Criteria.where(field.getName()).is(value));
<pre name="code" class="java">continue;
                     }                    query.addCriteria(Criteria.where(field.getName()).is(value));                                    }            } catch (IllegalArgumentException e) {                e.printStackTrace();            } catch (IllegalAccessException e) {                e.printStackTrace();            }        }        //日期类型查询条件        for (String key : dateMap.keySet()) {            if(dateMap.get(key)!=null){                query.addCriteria(dateMap.get(key));            }        }        //排序        if (sortStr!=null&&!sortStr.trim().isEmpty()) {            Sort sort=null;            String [] strs=sortStr.split(",");            for (String str : strs) {                str=str.trim();                if(str.isEmpty()){continue;}                int i=str.indexOf(" ");                if(i<0){                    if (sort==null) {                         sort=new Sort(Direction.ASC, str);                    }else {                        sort=sort.and(new Sort(Direction.ASC, str));                    }                }else {                    String name=str.substring(0,i);                    String dire=str.substring(i+1).trim();                    Sort sn=null;                    if ("desc".equals(dire.toLowerCase())) {                        sn=new Sort(Direction.DESC,name);                    }else {                        sn=new Sort(Direction.ASC,name);                    }                    if (sort==null) {                        sort=sn;                    }else {                        sort=sort.and(sn);                    }                }            }            if(sort!=null){                query.with(sort);            }        }        return query;    }    public static void main(String[] a) {        // Smslog p = new Smslog();        // p.setBusinessNo("123");        // p.setMobileNo("18521");        // p.setCreateDate(new Date());        // Update u = ReflectionUtils.getUpdateObj(p);//        String ab="begin".substring(5);//        System.out.println("begin".substring(5));//        if (ab.isEmpty()) {//            System.out.println("aaa");//        }//        System.out.println(StringUtil.toLowerCaseFirstOne("ASSS"));        String string="sss   aa";        int i=string.indexOf(" ");        System.out.println(i);        System.out.println(string.substring(0,i));        System.out.println(string.substring(i+1).trim());        Map<String, String> map=new HashMap<String, String>();        map.put("a", "123");        map.put("b", "456");        System.out.println(map.get("a"));        map.put("a", "444");        System.out.println(map.get("a"));        System.out.println(map.get("c"));            }} 
 

封装的分页参数 与上个版本修改名称

import java.io.Serializable;
import java.util.List;


public class Page<T> implements Serializable {
	private static final long serialVersionUID = 5760097915453738435L;
	public static final int DEFAULT_PAGE_SIZE = 10;
	/**
	 * 每页显示个数
	 */
	private int pageSize;
	/**
	 * 当前页数
	 */
	private int currentPage;
	/**
	 * 总页数
	 */
	private int totalPage;
	/**
	 * 总记录数
	 */
	private int totalCount;
	/**
	 * 结果列表
	 */
	private List<T> rows;
	
	public Page(){
		 this.currentPage = 1;
	     this.pageSize = DEFAULT_PAGE_SIZE;
	}
	public Page(int currentPage,int pageSize){
		this.currentPage=currentPage<=0?1:currentPage;
		this.pageSize=pageSize<=0?1:pageSize;
	}
	public int getPageSize() {
		return pageSize;
	}
	public void setPageSize(int pageSize) {
		this.pageSize = pageSize;
	}
	public int getCurrentPage() {
		return currentPage;
	}
	public void setCurrentPage(int currentPage) {
		this.currentPage = currentPage;
	}
	public int getTotalPage() {
		return totalPage;
	}
	public void setTotalPage(int totalPage) {
		this.totalPage = totalPage;
	}
	public int getTotalCount() {
		return totalCount;
	}
	public void setTotalCount(int totalCount) {
		this.totalCount = totalCount;
	}

	/**
	 * 设置结果 及总页数
	 * @param list
	 */
	 public void build(List<T> rows) {  
	        this.setRows(rows);  
	        int count =  this.getTotalCount();  
	        int divisor = count / this.getPageSize();  
	        int remainder = count % this.getPageSize();  
	        this.setTotalPage(remainder == 0 ? divisor == 0 ? 1 : divisor : divisor + 1);  
	    }
	public List<T> getRows() {
		return rows;
	}
	public void setRows(List<T> rows) {
		this.rows = rows;
	}  
}


  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值