Servlet+Jsp+Tomcat+Jstl+El+Jdbc+mysql+C3p0

0.目录结构:
这里写图片描述
1.jar包准备:
这里写图片描述
2.web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
  <display-name>w-mis</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
   <context-param>
    <param-name>encoding</param-name>
    <param-value>UTF-8</param-value>
  </context-param>
  <filter>
    <display-name>EncodingFilter</display-name>
    <filter-name>EncodingFilter</filter-name>
    <filter-class>com.jinmayi.filter.EncodingFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>EncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
  <filter>
    <display-name>TranactionFilter</display-name>
    <filter-name>TranactionFilter</filter-name>
    <filter-class>com.jinmayi.filter.TranactionFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>TranactionFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
  <servlet>
      <servlet-name>Log4jInit</servlet-name>
      <servlet-class>com.jinmayi.filter.Log4jInit</servlet-class>

      <init-param>
           <param-name>log4j</param-name><!-- //这个是名字是下边路径配置的标识(好像KEY一样) -->
           <param-value>/WEB-INF/classes/log4j.properties</param-value><!-- //这是容器初始化时候加载log4j配置文件的路径(这好像一个value); -->
      </init-param>
 </servlet> 


 <filter>
    <display-name>Log4jInit</display-name>
    <filter-name>Log4jInit</filter-name>
    <filter-class>com.jinmayi.filter.Log4jInit</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>Log4jInit</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

</web-app>

3.c3p0-config.xml

<?xml version="1.0" encoding="UTF-8"?>

<c3p0-config>

  <named-config name="loginpnfy"> 

    <property name="user">root</property>
    <property name="password">123</property>
    <property name="jdbcUrl">jdbc:mysql:///w-mis</property>
    <property name="driverClass">com.mysql.jdbc.Driver</property>

    <property name="acquireIncrement">2</property>
    <property name="initialPoolSize">50</property>
    <property name="minPoolSize">20</property>
    <property name="maxPoolSize">100</property>

    <property name="maxStatements">100</property> 
    <property name="maxStatementsPerConnection">100</property>

  </named-config>
</c3p0-config>

4.log4j.properties

log4j.rootLogger = debug,E

log4j.appender.E= org.apache.log4j.DailyRollingFileAppender
log4j.appender.E.File =${catalina.home}/logs/log_
log4j.appender.E.DatePattern=yyyy-MM-dd'.log'
#log4j.appender.E.Threshold =DEBUG
log4j.appender.E.Threshold =INFO
log4j.appender.E.layout = org.apache.log4j.PatternLayout
log4j.appender.E.layout.ConversionPattern =%-d{yyyy-MM-dd HH\:mm\:ss}[%c] [%t\:%r] - [%p]  %m%n

5.Dao.java接口

package com.jinmayi.dao;

import java.util.List;

/**
 * Dao 接口, 定义 Dao 的基本操作, 由 BaseDao 提供实现.
 * 
 * @param <T>: Dao 实际操作的泛型类型
 */
public interface Dao<T> {
    /**
     * 执行 INSERT 操作, 返回插入后的记录的 ID
     * 
     * @param sql
     *            : 待执行的 SQL 语句
     * @param args
     *            : 填充占位符的可变参数
     * @return: 插入新记录的 id
     */
    int insert(String sql, Object... args);

    /**
     * 执行 UPDATE 操作, 包括 INSERT(但没有返回值), UPDATE, DELETE
     * 
     * @param sql
     *            : 待执行的 SQL 语句
     * @param args
     *            : 填充占位符的可变参数
     */
    void update(String sql, Object... args);

    /**
     * 执行单条记录的查询操作, 返回与记录对应的类的一个对象
     * 
     * @param sql
     *            : 待执行的 SQL 语句
     * @param args
     *            : 填充占位符的可变参数
     * @return: 与记录对应的类的一个对象
     */
    T query(String sql, Object... args);

    /**
     * 执行多条记录的查询操作, 返回与记录对应的类的一个 List
     * 
     * @param sql
     *            : 待执行的 SQL 语句
     * @param args
     *            : 填充占位符的可变参数
     * @return: 与记录对应的类的一个 List
     */
    List<T> queryForList(String sql, Object... args);

    /**
     * 执行一个属性或值的查询操作, 例如查询某一条记录的一个字段, 或查询某个统计信息, 返回要查询的值
     * 
     * @param sql
     *            : 待执行的 SQL 语句
     * @param args
     *            : 填充占位符的可变参数
     * @return: 执行一个属性或值的查询操作, 例如查询某一条记录的一个字段, 或查询某个统计信息, 返回要查询的值
     */
    <V> V getSingleVal(String sql, Object... args);

    /**
     * 执行批量更新操作
     * 
     * @param sql
     *            : 待执行的 SQL 语句
     * @param args
     *            : 填充占位符的可变参数
     */
    void batch(String sql, Object[]... params);
}

6.BaseDao.java

package com.jinmayi.dao;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.List;

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;

import com.jinmayi.dao.Dao;
import com.jinmayi.db.JDBCUtils;
import com.jinmayi.util.ReflectionUtils;
import com.jinmayi.web.ConnectionContext;


public class BaseDao<T> implements Dao<T> {
    private QueryRunner queryRunner = new QueryRunner();

    private Class<T> clazz;

    public BaseDao() {
        clazz = ReflectionUtils.getSuperGenericType(getClass());
    }

    @Override
    public int insert(String sql, Object... args) {

        int id = 0;

        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;

        try {
            connection = ConnectionContext.getInstance().get();
            preparedStatement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);

            if(args != null){
                for(int i = 0; i < args.length; i++){
                    preparedStatement.setObject(i + 1, args[i]);
                }
            }

            preparedStatement.executeUpdate();

            //获取生成的主键值
            resultSet = preparedStatement.getGeneratedKeys();
            if(resultSet.next()){
                id = resultSet.getInt(1);
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally{
            JDBCUtils.release(resultSet, preparedStatement);
        }

        return id;
    }

    @Override
    public void update(String sql, Object... args) {
        Connection connection = null;

        try {
            connection = ConnectionContext.getInstance().get();
            queryRunner.update(connection, sql, args);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public T query(String sql, Object... args) {

        Connection connection = null;

        try {
            connection = ConnectionContext.getInstance().get();
            return queryRunner.query(connection, sql, new BeanHandler<T>(clazz), args);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    @Override
    public List<T> queryForList(String sql, Object... args) {
        Connection connection = null;

        try {
            connection = ConnectionContext.getInstance().get();
            return queryRunner.query(connection, sql, new BeanListHandler<T>(clazz), args);
        } catch (Exception e) {
            e.printStackTrace();
        } 
        return null;
    }

    @SuppressWarnings("unchecked")
    @Override
    public <V> V getSingleVal(String sql, Object... args) {
        Connection connection = null;

        try {
            connection = ConnectionContext.getInstance().get();
            return (V)queryRunner.query(connection, sql, new ScalarHandler(), args);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    @Override
    public void batch(String sql, Object[]... params) {
        Connection connection = null;

        try {
            connection = ConnectionContext.getInstance().get();
            queryRunner.batch(connection, sql, params);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

7.JDBCUtils.java

package com.jinmayi.db;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import javax.sql.DataSource;

import com.jinmayi.exception.DBException;
import com.mchange.v2.c3p0.ComboPooledDataSource;

/**
 * 
 * JDBC 的工具类
 *
 */
public class JDBCUtils {

    private static DataSource dataSource = null;

    static{
        dataSource = new ComboPooledDataSource("loginpnfy");
    }

    //获取数据库连接
    public static Connection getConnection(){  
        try {
            return dataSource.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
            throw new DBException("数据库连接错误!");
        }
    }

    //关闭数据库连接
    public static void release(Connection connection) {
        try {
            if(connection != null){
                connection.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
            throw new DBException("数据库连接错误!");
        }
    }

    //关闭数据库连接
    public static void release(ResultSet rs, Statement statement) {
        try {
            if(rs != null){
                rs.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
            throw new DBException("数据库连接错误!");
        }

        try {
            if(statement != null){
                statement.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
            throw new DBException("数据库连接错误!");
        }
    }

}

8.Page.java

package com.jinmayi.domain;

import java.util.List;

public class Page<T> {

    // 当前第几页
    private Long pageNow;

    // 当前页的 List
    private List<T> list;

    // 每页显示多少条记录
    private long pageSize = 10;

    // 共有多少条记录
    private long totalItemNumber;

    // 构造器中需要对 pageNow 进行初始化
    public Page(Long pageNow) {
        super();
        this.pageNow = pageNow;
    }

    // 需要校验一下
    public long getpageNow() {
        if (pageNow <= 0)
            pageNow = 1l;

        if (getTotalPageNumber() != 0 && pageNow > getTotalPageNumber()) {
            pageNow = getTotalPageNumber();
        }

        return pageNow;
    }

    public long getPageSize() {
        return pageSize;
    }

    public long setPageSize(long pageSize) {
        return this.pageNow = pageSize;
    }

    public void setList(List<T> list) {
        this.list = list;
    }

    public List<T> getList() {
        return list;
    }

    // 获取总页数
    public Long getTotalPageNumber() {

        long totalPageNumber =  totalItemNumber / pageSize;

        if (totalItemNumber % pageSize != 0) {
            totalPageNumber++;
        }

        return totalPageNumber;
    }

    public void setTotalItemNumber(long totalItemNumber) {
        this.totalItemNumber = totalItemNumber;
    }

    public boolean isHasNext() {
        if (getpageNow() < getTotalPageNumber()) {
            return true;
        }

        return false;
    }

    public boolean isHasPrev() {
        if (getpageNow() > 1) {
            return true;
        }

        return false;
    }

    public long getPrevPage() {
        if (isHasPrev()) {
            return getpageNow() - 1;
        }

        return getpageNow();
    }

    public long getNextPage() {
        if (isHasNext()) {
            return getpageNow() + 1;
        }

        return getpageNow();
    }

    @Override
    public String toString() {
        return "Page [pageNow=" + pageNow + ", list=" + list + ", pageSize="
                + pageSize + ", totalItemNumber=" + totalItemNumber + "]";
    }

}

9.DBException.java

package com.jinmayi.exception;

public class DBException extends RuntimeException {

    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    public DBException() {
        // TODO Auto-generated constructor stub
    }

    public DBException(String msg) {
        super(msg);
    }

    public DBException(String msg, Exception ex) {
        super(msg, ex);
    }
}

10.EncodingFilter.java

package com.jinmayi.filter;

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

/**
 * Servlet Filter implementation class EncodingFilter
 */
public class EncodingFilter implements Filter {

    /**
     * Default constructor. 
     */
    public EncodingFilter() {
        // TODO Auto-generated constructor stub
    }

    /**
     * @see Filter#destroy()
     */
    @Override
    public void destroy() {
        // TODO Auto-generated method stub
    }

    /**
     * @see Filter#doFilter(ServletRequest, ServletResponse, FilterChain)
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        String encoding = filterConfig.getServletContext().getInitParameter("encoding");
        request.setCharacterEncoding(encoding);
        response.setCharacterEncoding(encoding);
        chain.doFilter(request, response);

    }

    private FilterConfig filterConfig = null;

    @Override
    public void init(FilterConfig fConfig) throws ServletException {
        this.filterConfig = fConfig;
    }

}

11.Log4jInit.java

package com.jinmayi.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.log4j.PropertyConfigurator;

public class Log4jInit extends HttpServlet implements Filter {
    /**
     * 
     */
    private static final long serialVersionUID = 1L;
     private Log log = LogFactory.getLog(this.getClass());
     private String filterName;
    @Override
    public void destroy() {
        super.destroy();
    }

    public Log4jInit() {
        super();
    }

    /**
     * Initialization of the servlet. <br>
     *
     * @throws ServletException
     *             if an error occurs
     */
    @Override
    public void init() throws ServletException {
        String file = this.getInitParameter("log4j");// 从web.xml配置读取,名字一定要和web.xml配置一致
        if (file != null) {
            PropertyConfigurator.configure(file);
        }
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res,
            FilterChain chain) throws IOException, ServletException {
        // TODO Auto-generated method stub
         HttpServletRequest request = (HttpServletRequest) req;
         HttpServletResponse response = (HttpServletResponse) res;
         long startTime = System.currentTimeMillis();                                   //运行前的时间
         String requestURI = request.getRequestURI();                                   //获取访问的URI
         requestURI = request.getQueryString() == null ? requestURI                    //所有的地址栏参数对比
                     : (requestURI + "?" + request.getQueryString());

         chain.doFilter(request, response);

         long endTime = System.currentTimeMillis();
         //消耗的总时间
         log.info(request.getRemoteAddr() + " " + requestURI + " "+ (endTime - startTime) + " " + request.getHeader("user-agent") + " " + response.getStatus());//时间单位:毫秒

    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // TODO Auto-generated method stub
        filterName = filterConfig.getFilterName();//获取 Filter的 name,启动Filter
        log.info("启动 Filter: " + filterName);//
        log.info("ip, 请求的内容, 响应的时间, 电脑及浏览器的版本, 返回的状态码");
    }
}

12.TranactionFilter.java

package com.jinmayi.filter;

import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.jinmayi.db.JDBCUtils;
import com.jinmayi.web.ConnectionContext;


/**
 * Servlet Filter implementation class TranactionFilter
 */
public class TranactionFilter implements Filter {

    /**
     * Default constructor.
     */
    public TranactionFilter() {
        // TODO Auto-generated constructor stub
    }

    /**
     * @see Filter#destroy()
     */
    @Override
    public void destroy() {
        // TODO Auto-generated method stub
    }

    /**
     * @see Filter#doFilter(ServletRequest, ServletResponse, FilterChain)
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {

        Connection connection = null;

        try {

            // 1. 获取连接
            connection = JDBCUtils.getConnection();

            // 2. 开启事务
            connection.setAutoCommit(false);

            // 3. 利用 ThreadLocal 把连接和当前线程绑定
            ConnectionContext.getInstance().bind(connection);

            // 4. 把请求转给目标 Servlet
            chain.doFilter(request, response);

            // 5. 提交事务
            connection.commit();
        } catch (Exception e) {
            e.printStackTrace();

            // 6. 回滚事务
            try {
                connection.rollback();
            } catch (SQLException e1) {
                e1.printStackTrace();
            }

            HttpServletResponse resp = (HttpServletResponse) response;
            HttpServletRequest req = (HttpServletRequest) request;
            resp.sendRedirect(req.getContextPath() + "/error-1.jsp");

        } finally {
            // 7. 解除绑定
            ConnectionContext.getInstance().remove();

            // 8. 关闭连接
            JDBCUtils.release(connection);

        }

    }

    /**
     * @see Filter#init(FilterConfig)
     */
    @Override
    public void init(FilterConfig fConfig) throws ServletException {
        // TODO Auto-generated method stub
    }

}

13.ReflectionUtils.java

package com.jinmayi.util;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

/**
 * 反射的 Utils 函数集合 提供访问私有变量, 获取泛型类型 Class, 提取集合中元素属性等 Utils 函数
 * 
 * @author Administrator
 *
 */
public class ReflectionUtils {

    /**
     * 通过反射, 获得定义 Class 时声明的父类的泛型参数的类型 如: public EmployeeDao extends
     * BaseDao<Employee, String>
     * 
     * @param clazz
     * @param index
     * @return
     */
    public static Class<?> getSuperClassGenricType(Class<?> clazz, int index) {
        Type genType = clazz.getGenericSuperclass();

        if (!(genType instanceof ParameterizedType)) {
            return Object.class;
        }

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

        if (index >= params.length || index < 0) {
            return Object.class;
        }

        if (!(params[index] instanceof Class)) {
            return Object.class;
        }

        return (Class<?>) params[index];
    }

    /**
     * 通过反射, 获得 Class 定义中声明的父类的泛型参数类型 如: public EmployeeDao extends
     * BaseDao<Employee, String>
     * 
     * @param <T>
     * @param clazz
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T> Class<T> getSuperGenericType(Class<?> clazz) {
        return (Class<T>) getSuperClassGenricType(clazz, 0);
    }

    /**
     * 循环向上转型, 获取对象的 DeclaredMethod
     * 
     * @param object
     * @param methodName
     * @param parameterTypes
     * @return
     */
    public static Method getDeclaredMethod(Object object, String methodName,
            Class<?>[] parameterTypes) {

        for (Class<?> superClass = object.getClass(); superClass != Object.class; superClass = superClass
                .getSuperclass()) {
            try {
                // superClass.getMethod(methodName, parameterTypes);
                return superClass.getDeclaredMethod(methodName, parameterTypes);
            } catch (NoSuchMethodException e) {
                // Method 不在当前类定义, 继续向上转型
            }
            // ..
        }

        return null;
    }

    /**
     * 使 filed 变为可访问
     * 
     * @param field
     */
    public static void makeAccessible(Field field) {
        if (!Modifier.isPublic(field.getModifiers())) {
            field.setAccessible(true);
        }
    }

    /**
     * 循环向上转型, 获取对象的 DeclaredField
     * 
     * @param object
     * @param filedName
     * @return
     */
    public static Field getDeclaredField(Object object, String filedName) {

        for (Class<?> superClass = object.getClass(); superClass != Object.class; superClass = superClass
                .getSuperclass()) {
            try {
                return superClass.getDeclaredField(filedName);
            } catch (NoSuchFieldException e) {
                // Field 不在当前类定义, 继续向上转型
            }
        }
        return null;
    }

    /**
     * 直接调用对象方法, 而忽略修饰符(private, protected)
     * 
     * @param object
     * @param methodName
     * @param parameterTypes
     * @param parameters
     * @return
     * @throws InvocationTargetException
     * @throws IllegalArgumentException
     */
    public static Object invokeMethod(Object object, String methodName,
            Class<?>[] parameterTypes, Object[] parameters)
            throws InvocationTargetException {

        Method method = getDeclaredMethod(object, methodName, parameterTypes);

        if (method == null) {
            throw new IllegalArgumentException("Could not find method ["
                    + methodName + "] on target [" + object + "]");
        }

        method.setAccessible(true);

        try {
            return method.invoke(object, parameters);
        } catch (IllegalAccessException e) {
            System.out.println("不可能抛出的异常");
        }

        return null;
    }

    /**
     * 直接设置对象属性值, 忽略 private/protected 修饰符, 也不经过 setter
     * 
     * @param object
     * @param fieldName
     * @param value
     */
    public static void setFieldValue(Object object, String fieldName,
            Object value) {
        Field field = getDeclaredField(object, fieldName);

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

        makeAccessible(field);

        try {
            field.set(object, value);
        } catch (IllegalAccessException e) {
            System.out.println("不可能抛出的异常");
        }
    }

    /**
     * 直接读取对象的属性值, 忽略 private/protected 修饰符, 也不经过 getter
     * 
     * @param object
     * @param fieldName
     * @return
     */
    public static Object getFieldValue(Object object, String fieldName) {
        Field field = getDeclaredField(object, fieldName);

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

        makeAccessible(field);

        Object result = null;

        try {
            result = field.get(object);
        } catch (IllegalAccessException e) {
            System.out.println("不可能抛出的异常");
        }

        return result;
    }
}

14.ConnectionContext.java

package com.jinmayi.web;

import java.sql.Connection;

public class ConnectionContext {

    private ConnectionContext(){}

    private static ConnectionContext instance = new ConnectionContext();

    public static ConnectionContext getInstance() {
        return instance;
    }

    private ThreadLocal<Connection> connectionThreadLocal = 
            new ThreadLocal<Connection>();

    public void bind(Connection connection){
        connectionThreadLocal.set(connection);
    }

    public Connection get(){
        return connectionThreadLocal.get();
    }

    public void remove(){
        connectionThreadLocal.remove();
    }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值