Spring+Mybatis整合(3)- SSM(六)

本节是基于上一节Spring+Mybatis整合(2)- SSM(五)

本节概要,添加了ctroller层,也就是控制层,控制层的主要作用就是对数据进行转化传递,调用service层。添加了分页的方法。

1. 整体概要结构变化


这里写图片描述

  1. 首先添加一个ssm.properties文件和AppUtil.java,将项目的一些配置项变成可配。
  2. 添加了Controller层相关的类。
  3. 添加了分页相关的类。

2. 代码主要变化


2.1 新增可配文件配置文件

2.1.1 首先来看一下我们添加的配置文件ssm.properties,目前我们仅仅添加了默认的分页大小为10,即如果没有指定分页大小,我们将使用我们默认的翻页大小。配置文件是以键值对的形式配置的。
pagination.pageSize=10
2.1.2 AppUtil.java加载配置文件

加载我们ssm.properties配置文件中的配置文件,最后如果我们需要可以使用AppUtil.getPropertyValue(key)的方式拿到我们配置文件的配置选项。

package com.stephen.ssm.util;

import java.io.IOException;
import java.util.Properties;

import org.springframework.core.io.support.PropertiesLoaderUtils;

public final class AppUtil {
    private static final String RESOURCE_NAME = "ssm.properties";
    private static Properties property = null;

    public static void init() throws IOException {
        Properties property = PropertiesLoaderUtils.loadAllProperties(RESOURCE_NAME);
        AppUtil.setProperty(property);
    }

    public static void setProperty(Properties property) {
        AppUtil.property = property;
    }

    public static String getPropertyValue(String key) {
        return (String) property.get(key);
    }
}
2.2.3 AppContext.java全局的Context
package com.stephen.ssm;

import java.util.HashMap;

public class AppContext {

    private static final ThreadLocal<AppContext> appContext = new ThreadLocal<AppContext>();
    private final HashMap<String, Object> values = new HashMap<String, Object>();

    public static AppContext getContext() {
        AppContext context = appContext.get();
        if (context == null) {
            context = new AppContext();
            appContext.set(context);
        }
        return context;
    }

    public void clear() {
        AppContext context = appContext.get();
        if (context != null) {
            context.values.clear();
        }
        context = null;
    }

    public void addObject(String key, Object value) {
        values.put(key, value);
    }

    public Object getObject(String key) {
        return values.get(key);
    }

}

我们可以在当前的ThreadLocal线程中存入自己需要的变量,在这几需要的地方取,一些全局变量等,但是添加的这些变量只在当前的线程有效。很有用的一个公用类。

2. 2 关于分页类

2.2.1 PaginationDTO.java分页类

定义了分页公用类,添加分页需要的一些基本变量。

package com.stephen.ssm.dto;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.stephen.ssm.util.AppUtil;

public class PaginationDTO<T> {
    private static final String KEY_PAGE_SIZE = "pagination.pageSize";

    private Integer totalRowCount;
    private Integer pageSize;
    private Integer pageCount;
    private Integer currentPage;
    private Integer offset;
    private Integer rowCount;
    private List<T> itemList;
    private Map<String, Object> parameterMap;
    private String relativeUrl;
    private Boolean lastPage;

    public Integer getTotalRowCount() {
        return totalRowCount;
    }

    public void setTotalRowCount(Integer totalRowCount) {
        this.totalRowCount = totalRowCount;
    }

    /**
     * Get page size set by frontend. If it is not set, then get default page
     * size from application configuration file.
     * @return
     */
    public Integer getPageSize() {
        if (pageSize == null || pageSize < 1) {
            String size = AppUtil.getPropertyValue(KEY_PAGE_SIZE);
            pageSize = Integer.parseInt(size);
        }
        return pageSize;
    }

    public void setPageSize(Integer pageSize) {
        this.pageSize = pageSize;
    }

    public Integer getPageCount() {
        if (totalRowCount == null || totalRowCount < 1) {
            pageCount = 0;
            return pageCount;
        }
        pageCount = (totalRowCount - 1) / getPageSize() + 1;
        return pageCount;
    }

    public void setPageCount(Integer pageCount) {
        this.pageCount = pageCount;
    }

    public Integer getCurrentPage() {
        if (currentPage == null || currentPage < 1) {
            currentPage = 1;
        }
        return currentPage;
    }

    public void setCurrentPage(Integer currentPage) {
        this.currentPage = currentPage;
    }

    /**
     * Get the start position for MySQL limit selection.
     * @return
     */
    public Integer getOffset() {
        offset = (getCurrentPage() - 1) * getPageSize();
        return offset;
    }

    public void setOffset(Integer offset) {
        this.offset = offset;
    }

    public Integer getRowCount() {
        if (rowCount == null || rowCount < 1) {
            rowCount = getPageSize();
        }
        return rowCount;
    }

    public void setRowCount(Integer rowCount) {
        this.rowCount = rowCount;
    }

    public List<T> getItemList() {
        return itemList;
    }

    public void setItemList(List<T> itemList) {
        this.itemList = itemList;
    }

    public Map<String, Object> getParameterMap() {
        if (parameterMap == null) {
            parameterMap = new HashMap<String, Object>();
        }
        return parameterMap;
    }

    public void setParameterMap(Map<String, Object> parameterMap) {
        this.parameterMap = parameterMap;
    }

    public String getRelativeUrl() {
        return relativeUrl;
    }

    public void setRelativeUrl(String relativeUrl) {
        this.relativeUrl = relativeUrl;
    }

    public Boolean getLastPage() {
        this.lastPage = ((getCurrentPage().intValue() == getPageCount().intValue())
                || (getPageCount() == 0));
        return this.lastPage;
    }

    public void setLastPage(Boolean lastPage) {
        this.lastPage = lastPage;
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("PaginationDTO [totalRowCount=");
        builder.append(totalRowCount);
        builder.append(", pageSize=");
        builder.append(pageSize);
        builder.append(", pageCount=");
        builder.append(pageCount);
        builder.append(", currentPage=");
        builder.append(currentPage);
        builder.append(", offset=");
        builder.append(offset);
        builder.append(", rowCount=");
        builder.append(rowCount);
        builder.append(", itemList=");
        builder.append(itemList);
        builder.append(", parameterMap=");
        builder.append(parameterMap);
        builder.append(", relativeUrl=");
        builder.append(relativeUrl);
        builder.append(", lastPage=");
        builder.append(lastPage);
        builder.append("]");
        return builder.toString();
    }

}

2.3 Controller层

2.3.1. **BaseController.java**Controller的基本类备用
package com.stephen.ssm.base;

public abstract class BaseController {

}
2.3.2. BaseJsonController.java返回json数据的控制层
package com.stephen.ssm.base;

import java.util.List;

import com.stephen.ssm.AppConstants;
import com.stephen.ssm.AppContext;
import com.stephen.ssm.dto.PaginationDTO;

public class BaseJsonController extends BaseController {

    protected PaginationDTO<?> pagination(Integer currentPage,
            PaginationCallBack<?> paginationCallback) {
        return paginationCallback.execute(currentPage, null);
    }

    protected PaginationDTO<?> pagination(Integer currentPage, Integer pageSize,
            PaginationCallBack<?> paginationCallBack) {
        return paginationCallBack.execute(currentPage, pageSize);
    }

    protected abstract class PaginationCallBack<T> {
        public PaginationDTO<T> execute(Integer currentPage, Integer pageSize) {
            PaginationDTO<T> paginationDTO = new PaginationDTO<T>();
            AppContext.getContext().addObject(AppConstants.PAGINATION_DTO, paginationDTO);
            paginationDTO.setCurrentPage(currentPage);
            if (pageSize != null) {
                paginationDTO.setPageSize(pageSize);
            }
            List<T> itemList = callBack();
            paginationDTO.setItemList(itemList);
            return paginationDTO;
        }

        abstract public List<T> callBack();
    }
}

在BaseJsonController类中,我们添加分页类,将一些基本的数据如pageSize,currentPage等基本的数据我们在这里可以设置,其他的数据我们使用回调的方式让继承者的类来实现,并且我们将新建的一个pagination分页类添加到我们AppContent中,也就是当前线程的全局变量中,这样我们可以很容易拿到当前分页的一些基本数据。

2.3.3. UserController.java实现我们的一个分页控制对象
package com.stephen.ssm.controller;

import java.util.List;

import com.stephen.ssm.base.BaseJsonController;
import com.stephen.ssm.dto.PaginationDTO;
import com.stephen.ssm.model.User;
import com.stephen.ssm.service.UserService;

public class UserController extends BaseJsonController {

    private UserService userService;

    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    public Integer create(User user) {
        return userService.create(user);
    }

    public Boolean update(User user) {
        return userService.update(user);
    }

    public Boolean delete(Integer id) {
        return userService.delete(id);
    }

    public User getById(Integer id) {
        return userService.getById(id);
    }

    public PaginationDTO<?> find(Integer currentPage, Integer pageSize,
            final String key){
        PaginationDTO<?> paginationDTO = pagination(currentPage, pageSize,
                new PaginationCallBack<User>() {

            @Override
            public List<User> callBack() {
                return userService.find(key);
            }
        });

        return paginationDTO;
    }
}

我们将关于user的CRUD的入口都改到控制器。当然本身也是在控制器完成的,但是控制器实质只是一个数据转化,权限验证提供API的层,业务逻辑当然我们还是放在Service层中处理,我们首先要使用set方法将我们需要的业务逻辑处理层的对象注入到我们的Spring容器,这里我们需要使用UserService,既需要setUserService();

另外就是要说一下我们的find方法,分页带搜索的功,有三个参数分别是当前页,分页大小,收索关键字,我们使用回调的方式让我们对我们的数据进行获取,这里就实现了分页代码的复用。

2.4 Service层

2.4.1. UserService.java
package com.stephen.ssm.service;

import java.util.List;

import com.stephen.ssm.model.User;

public interface UserService {

    Integer create(User user);

    Boolean update(User user);

    Boolean delete(Integer id);

    User getById(Integer id);

    List<User> findByName(String name);

    List<User> find(String key);

}
2.4.2. UserServiceImpl.java
package com.stephen.ssm.service.impl;

import java.util.ArrayList;
import java.util.List;

import com.stephen.ssm.base.BaseService;
import com.stephen.ssm.dao.UserDao;
import com.stephen.ssm.model.User;
import com.stephen.ssm.service.UserService;
import com.stephen.ssm.util.StringUtil;

public class UserServiceImpl extends BaseService implements UserService {

    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public Integer create(User user) {
        if (user == null) {
            //check logic
            return null;
        }
        userDao.add(user);
        return user.getId();
    }

    @Override
    public Boolean update(User user) {
        if (user == null || user.getId() == null) {
            //check logic
            return null;
        }
        return userDao.update(user);
    }

    @Override
    public Boolean delete(Integer id) {
        return userDao.delete(id);
    }

    @Override
    public User getById(Integer id) {
        return userDao.getById(id);
    }

    @Override
    public List<User> findByName(String name) {
        name = name.trim();
        List<User> users = new ArrayList<User>();
        users = userDao.findByName(name);
        return users;
    }

    @Override
    public List<User> find(String key) {
        if (StringUtil.isEmpty(key)) {
            key = null;
        }
        return userDao.find(key);
    }

}

2.5 dao层

2.5.1 UserDao.java
package com.stephen.ssm.dao;

import java.util.List;

import com.stephen.ssm.base.IBaseDao;
import com.stephen.ssm.model.User;

public interface UserDao extends IBaseDao<User, Integer> {

    List<User> findByName(String name);

    List<User> find(String key);
}
2.5.2 BaseDao.java

我们添加了getParameterMap()类,我们添加该方法的主要目的就是确定当前的方法是否需要分页,我们可以看到他会去全局的AppContenxt中去拿这个PaginationDTO这个对象,如果需要分页,我们将会在Controller层创建PaginationDTO这个对象并且添加到AppContext中去 ,在我们dao层就可以取到,如果没有取到这个分页类,说明我们当前的方法是不是需要分页的。

package com.stephen.ssm.base;

import java.lang.reflect.ParameterizedType;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import org.springframework.beans.factory.annotation.Autowired;

import com.stephen.ssm.AppConstants;
import com.stephen.ssm.AppContext;
import com.stephen.ssm.dto.PaginationDTO;

public abstract class BaseDao<T, K> extends SqlSessionDaoSupport {

    protected static String SQL_ID_ADD = ".add";
    protected static String SQL_ID_UPDATE = ".update";
    protected static String SQL_ID_DELETE = ".delete";
    protected static String SQL_ID_BY_ID = ".getById";
    private static final String KEY_OFFSET = "offset";
    private static final String KEY_ROW_COUNT = "rowCount";

    private Class<T> clz;

    @Autowired
    @Override
    public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
        super.setSqlSessionTemplate(sqlSessionTemplate);
    }

    @SuppressWarnings("unchecked")
    protected Class<T> getClz() {
        if (clz == null)
            clz = (Class<T>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
        return clz;
    }

    public Boolean add(T obj) {
        getSqlSession().insert(this.getClz().getName() + SQL_ID_ADD, obj);
        return Boolean.TRUE;
    }

    public Boolean delete(K id) {
        getSqlSession().delete(this.getClz().getName() + SQL_ID_DELETE, id);
        return Boolean.TRUE;
    }

    public Boolean update(T obj) {
        getSqlSession().update(this.getClz().getName() + SQL_ID_UPDATE, obj);
        return Boolean.TRUE;
    }

    @SuppressWarnings("unchecked")
    public T getById(K id) {
        return (T) getSqlSession().selectOne(this.getClz().getName() + SQL_ID_BY_ID, id);
    }

    @SuppressWarnings("unchecked")
    public List<T> findByQueryId(String sqlId, Object parameters) {
        return (List<T>) getSqlSession().selectList(this.getClz().getName() + sqlId, parameters);
    }

    protected Map<String, Object> getParameterMap() {
        Map<String, Object> parameterMap = null;
        PaginationDTO<?> paginationDTO = (PaginationDTO<?>) AppContext.getContext().getObject(
                AppConstants.PAGINATION_DTO);
        if (paginationDTO == null) {
            // don't need to pagination
            parameterMap = new HashMap<String, Object>();
        } else {
            parameterMap = paginationDTO.getParameterMap();
            parameterMap.put(KEY_OFFSET, paginationDTO.getOffset());
            parameterMap.put(KEY_ROW_COUNT, paginationDTO.getRowCount());
        }
        return parameterMap;
    }

}
2.5.3 UserDaoImpl.java

我们首先使用BaseDao中的getParameterMap()拿到是否需要分页的参数,如果需要分页我们的分页相关的row_count,offset,添加到我们的map分页参数中。如果需要分页我们接下来就是计算记录的总条数,并将结果设置到我们的哦分页类中去,最终在Controller层会把我们的分页获取的对象返回给我的调用者。

package com.stephen.ssm.dao.impl;

import java.util.List;
import java.util.Map;

import com.stephen.ssm.AppConstants;
import com.stephen.ssm.AppContext;
import com.stephen.ssm.base.BaseDao;
import com.stephen.ssm.dao.UserDao;
import com.stephen.ssm.dto.PaginationDTO;
import com.stephen.ssm.model.User;

public class UserDaoImpl extends BaseDao<User, Integer> implements UserDao {

    private static final String CLASS_NAME = User.class.getName();
    private static final String SQL_ID_FIND_BY_NAME = ".findByName";
    private static final String SQL_ID_FIND = ".find";
    private static final String SQL_ID_FIND_COUNT = ".getCount";

    @Override
    public List<User> findByName(String name) {
        return getSqlSession().selectList(CLASS_NAME + SQL_ID_FIND_BY_NAME, name);
    }

    @Override
    public List<User> find(String key) {
        Map<String, Object> params = getParameterMap();
        params.put(AppConstants.USER_NAME, key);
        @SuppressWarnings("unchecked")
        PaginationDTO<User> paginationDTO = (PaginationDTO<User>)
            AppContext.getContext().getObject(AppConstants.PAGINATION_DTO);
        if (paginationDTO != null) {
            Integer count = getCount(params);
            paginationDTO.setTotalRowCount(count);
        }
        return getSqlSession().selectList(CLASS_NAME + SQL_ID_FIND, params);
    }

    private Integer getCount(Map<String, Object> params) {
        return getSqlSession().selectOne(CLASS_NAME + SQL_ID_FIND_COUNT, params);
    }

}
2.5.4 UserMapper.xml

接下来就是我们分页的sql写发了,当然带上了我们根据name收索的条件。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.stephen.ssm.model.User">
    <resultMap type="User" id="userMap">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="age" column="age"/>
    </resultMap>

    <sql id="pagination">
        <if test="offset != null and rowCount!= null">
            <![CDATA[
                LIMIT #{offset},#{rowCount}
            ]]>
        </if>
    </sql>

    <insert id="add" parameterType="User" useGeneratedKeys="true" keyProperty="id">
        <![CDATA[
            INSERT INTO users(name, age) VALUES(#{name}, #{age})
        ]]>
    </insert>

    <select id="getById" resultMap="userMap" parameterType="Integer">
        <![CDATA[
            SELECT * FROM users WHERE id = #{id}
        ]]>
    </select>

    <delete id="delete" parameterType="Integer">
        <![CDATA[
            DELETE FROM users WHERE id = #{id}
        ]]>
    </delete>

    <update id="update" parameterType="User">
        <![CDATA[
            UPDATE users SET name = #{name}, age = #{age} WHERE id = #{id}
        ]]>
    </update>

    <select id="findByName" parameterType="String" resultMap="userMap">
        <![CDATA[
            SELECT * FROM users WHERE name LIKE CONCAT('%', #{name}, '%')
        ]]>
    </select>

    <select id="find" resultMap="userMap" parameterType="map">
        <![CDATA[
            SELECT * FROM users WHERE 1 = 1
        ]]>
        <if test="name != null">
            <![CDATA[
                AND name LIKE CONCAT('%', #{name}, '%')
            ]]>
        </if>
        <![CDATA[
            ORDER BY id ASC
        ]]>
        <include refid="pagination" />
    </select>

    <select id="getCount" resultType="Integer" parameterType="map">
        <![CDATA[
            SELECT COUNT(id) FROM users WHERE 1 = 1
        ]]>
        <if test="name != null">
            <![CDATA[
                AND name LIKE CONCAT('%', #{name}, '%')
            ]]>
        </if>
    </select>

</mapper>

2.6 applcationContxt.xml

我们的上下文的主要变化就是添加了我们需要注入到容器的我bean,及其bean的依赖。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:hdp="http://www.springframework.org/schema/hadoop"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:util="http://www.springframework.org/schema/util"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
        http://www.springframework.org/schema/cache
        http://www.springframework.org/schema/cache/spring-cache.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.1.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
        http://www.springframework.org/schema/util
        http://www.springframework.org/schema/util/spring-util-3.1.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx-3.1.xsd">

    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
           <list>
                <value>classpath:jdbc.properties</value>
           </list>
        </property>
    </bean>

    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">  
       <property name="driverClassName" value="${driver}" />
       <property name="url" value="${url}" />
       <property name="username" value="${username}" />
       <property name="password" value="${password}" />
    </bean>

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="mapperLocations" value="classpath:com/stephen/ssm/model/*Mapper.xml" />
        <property name="typeAliasesPackage" value="com.stephen.ssm.model" />
    </bean>

    <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg index="0" ref="sqlSessionFactory" />
    </bean>

    <bean class="com.stephen.ssm.util.SpringUtil"></bean>
    <bean class="com.stephen.ssm.util.AppUtil" init-method="init"></bean>

    <!-- dao bean -->
    <bean id="baseDao" abstract="true" class="com.stephen.ssm.base.BaseDao">
        <property name="sqlSessionTemplate" ref="sqlSessionTemplate" />
    </bean>

    <bean id="userDao" parent="baseDao" class="com.stephen.ssm.dao.impl.UserDaoImpl"></bean>

    <!-- service bean -->
    <bean id="baseService" abstract="true" class="com.stephen.ssm.base.BaseService"></bean>

    <bean id="userService" parent="baseService" class="com.stephen.ssm.service.impl.UserServiceImpl">
        <property name="userDao" ref="userDao"></property>
    </bean>

    <bean id="userController" class="com.stephen.ssm.controller.UserController">
        <property name="userService" ref="userService"></property>
    </bean>
</beans>

3. 测试分页类

3.1 TestPagination.java

我们的分页测试类我们使用 springUtil拿到我们的controller,然后组装参数调用我们的分页类。是不是很简单的我们就可以实现我们的分页了,而且是一个公用的方法只要需要分页我们构建一个我们的分页对象就可以很方便的实现分页了。

package com.stephen.ssm.test;

import com.stephen.ssm.controller.UserController;
import com.stephen.ssm.dto.PaginationDTO;
import com.stephen.ssm.util.SpringUtil;

public class TestSpringMybatis {

    /**
     * @param args
     */
    public static void main(String[] args) {

        Integer pageSize = 2;
        Integer currentPage = 1;
        UserController controller = (UserController) SpringUtil.getBean("userController");
        PaginationDTO<?> pagination = controller.find(currentPage, pageSize, null);
        System.out.println(pagination);

    }

}

3.2 测试结果

测试结果我们返回了我们的分页对象,拿到我们的分页对象我们就可以很便的根据需求去实现我们的页码跳转及数据记录的一些基本信息。

log4j:WARN No appenders could be found for logger (org.springframework.core.env.StandardEnvironment).
log4j:WARN Please initialize the log4j system properly.
PaginationDTO [totalRowCount=6, pageSize=2, pageCount=null, currentPage=1, offset=0, rowCount=2, itemList=[User [id=1, name=xiaohua, age=99], User [id=2, name=xiaobai, age=18]], parameterMap={name=null, rowCount=2, offset=0}, relativeUrl=null, lastPage=null]

到这里我们今天要完成的任务就结束了,当然没有最好,只有更好,代码里面还是有很多地方需要进的,也许有很多地方说的不是很清楚明了,胆码是最好的思想表达,所以最好还是理解代码,文字就不必太深究了。接下来我会继续对控制优化,已成处理,数据验证验证等。


GitHub代码参考下载地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值