零基础学JavaWeb开发(六)之 JDBC快速入门

29 篇文章 2 订阅

5、jdbc实现用户登录与注册

CREATE TABLE `mayikt_users` (
  `id` int NOT NULL AUTO_INCREMENT,
  `phone` varchar(11) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `pwd` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb3;
package com.mayikt.entity;

public class UserEntity {
    /**
     * 用户id
     */
    private Long id;
    /**
     * 用户手机号码
     */
    private String phone;
    /**
     * 用户的密码
     */
    private String pwd;

    public UserEntity(Long id, String phone, String pwd) {
        this.id = id;
        this.phone = phone;
        this.pwd = pwd;
    }

    public UserEntity(String phone, String pwd) {
        this.phone = phone;
        this.pwd = pwd;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }
}
package com.mayikt.dao;

import com.mayikt.entity.StudentEntity;
import com.mayikt.entity.UserEntity;
import com.mayikt.utils.MayiktJdbcUtils;

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

public class UserDao {
    /**
    * 用户的注册
    * 思考点?如果用户一直使用相同的手机号码注册?
    * 该手机号码存在 不需要重复注册?给手机号码----在数据库中不存在
    */
    public int registerUser(UserEntity userEntity) {
        Connection connection = null;
        Statement statement = null;
        try {
            //A.java连接mysql数据库查询所有数据
            //1.导入mysql驱动jar包;
            //2. 注册驱动 javase 反射机制Class.forName()
            connection = MayiktJdbcUtils.getConnection();
            //4. 获取执行者对象
            statement = connection.createStatement();
            //5. 执行sql语句并获取返回结果 executeUpdate执行 insert sql语句
            String insertUserSql = "INSERT INTO `mayikt`.`mayikt_users` (`id`, `phone`, `pwd`) VALUES (null, '" + userEntity.getPhone() + "', '" + userEntity.getPwd() + "');";
            System.out.println("insertStudentSql:" + insertUserSql);
            // log输出
            int result = statement.executeUpdate(insertUserSql);
            // 执行该sql语句 影响行数
            return result;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        } finally {
            //  7. 释放jdbc资源
            MayiktJdbcUtils.closeConnection(statement, connection);
        }
    }
    
    /**
    * 根据手机号码查询用户的信息
    *
    * @param phone
    * @return
    */
    public UserEntity getByPhoneUser(String phone) {
        // 判断用户输入的手机号码是否是为null(正则表达式)
        //phone="";
        if (phone == null || phone.length() == 0) {
            return null;
        }
        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;
        try {
            //A.java连接mysql数据库查询所有数据
            //1.导入mysql驱动jar包;
            //2. 注册驱动 javase 反射机制Class.forName()
            connection = MayiktJdbcUtils.getConnection();
            //4. 获取执行者对象
            statement = connection.createStatement();
            //5. 执行sql语句并获取返回结果 自己拼接 查询sql语句
            String getByPhoneUserSql = "select * from mayikt_users where phone='" + phone + "'";
            System.out.println(getByPhoneUserSql);
            resultSet = statement.executeQuery(getByPhoneUserSql);
            boolean result = resultSet.next(); // 查询不到数据 false
            // 判断如果查询不到数据 则不会取值
            if (!result) {
                return null;
            }
            //6. 对结果进行处理
            // 获取该行数据的第一列 id
            Long dbId = resultSet.getLong("id");
            // 获取该行数据的第二列 phone
            String dbPhone = resultSet.getString("phone");
            // 获取该行数据的第三列 pwd
            String dbPwd = resultSet.getString("pwd");
            // 将db中查询到数据封装成user对象
            return new UserEntity(dbId, dbPhone, dbPwd);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            //  7. 释放jdbc资源
            MayiktJdbcUtils.closeConnection(resultSet, statement, connection);
            
        }
    }
    
    /**
    * 用户登录的方法
    *
    * @return
    */
    public UserEntity login(UserEntity userEntity) {
        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;
        try {
            //A.java连接mysql数据库查询所有数据
            //1.导入mysql驱动jar包;
            //2. 注册驱动 javase 反射机制Class.forName()
            connection = MayiktJdbcUtils.getConnection();
            //4. 获取执行者对象
            statement = connection.createStatement();
            //5. 执行sql语句并获取返回结果 自己拼接 查询sql语句
            String loginSql = "select * from mayikt_users where phone='" + userEntity.getPhone() + "' and pwd='" + userEntity.getPwd() + "';";
            System.out.println(loginSql);
            resultSet = statement.executeQuery(loginSql);
            boolean result = resultSet.next(); // 查询不到数据 false
            // 判断如果查询不到数据 则不会取值
            if (!result) {
                return null;
            }
            //6. 对结果进行处理
            // 获取该行数据的第一列 id
            Long dbId = resultSet.getLong("id");
            // 获取该行数据的第二列 phone
            String dbPhone = resultSet.getString("phone");
            // 获取该行数据的第三列 pwd
            String dbPwd = resultSet.getString("pwd");
            // 将db中查询到数据封装成user对象
            return new UserEntity(dbId, dbPhone, dbPwd);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            //  7. 释放jdbc资源
            MayiktJdbcUtils.closeConnection(resultSet, statement, connection);
            
        }
    }
    
}
package com.mayikt.test;

import com.mayikt.entity.UserEntity;
import com.mayikt.serivce.UserSerivce;

import java.util.Scanner;

public class UserTest {
    private UserSerivce userSerivce = new UserSerivce();

    /**
     * 该登录代码 bug ----sql注入破解登录  手机号码和密码输入错误的 但是可以登录
     *
     * @param args
     */
    public static void main(String[] args) {
        UserTest userTest = new UserTest();
        userTest.index();
    }

    public void index() {
        Scanner scanner = new Scanner(System.in);
        System.out.println("输入数字1:用户注册");
        System.out.println("输入数字2:用户登录");
        int number = scanner.nextInt();
        switch (number) {
            case 1:
                registerUser();
            case 2:
                login();
        }
    }

    public void registerUser() {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入用户的手机号码");
        String phone = scanner.nextLine();
        System.out.println("请输入用户的密码");
        String pwd = scanner.nextLine();
        int result = userSerivce.registerUser(new UserEntity(phone, pwd));
        if (result > 0) {
            // 用户注册成功
            System.out.println("用户注册成功");
        } else {
            System.out.println("用户注册失败啦");
        }
    }

    public void login() {
        //给用户输入手机号码或者是密码错误机会 三次 直接退出程序
        for (int i = 1; i <= 3; i++) {
            Scanner scanner = new Scanner(System.in);
            System.out.println("请输入用户的手机号码");
            String phone = scanner.nextLine();
            System.out.println("请输入用户的密码");
            String pwd = scanner.nextLine();
            // 调用 登录业务逻辑方法
            UserEntity dbUserEntity = userSerivce.login(new UserEntity(phone, pwd));
            if (dbUserEntity != null) {
                System.out.println("登录成功");
                return;//退出循环
            } else {
                System.out.println("用户输入的手机号码或者密码不正确! 错误的次数:" + i);
            }
        }

    }
}

6、什么是JDBC SQL注入漏洞

什么是SQL注入攻击?

就是利用SQL语句的漏洞实现对系统攻击

底层原理就是 通过传递参数(or 1=1 )拼接的SQL语句 导致其成立可以查询到数据。

登录SQL语句:

select * from mayikt_users where phone='18140668751' and p wd='12';

但是黑客传递参数:

黑客传递的参数 ' or 1='1

select * from mayikt_users where phone='' and pwd='' or 1='1';

如果我们使用sql语句拼接的情况下 很容易导致 被黑客sql注入

7、如何解决SQL注入漏洞

解决办法:

使用PreparedStatemnet(预编译执行者对象)

在sql语句执行之前,将sql语句进行提前编译,明确SQL语句的格式后,传递的参数 就是参数 不会拼接sql语句。

用?占位符赋值的方式 setxxx(参数1,参数2)

xxx: 数据类型

参数1:?的位置从编号1开始

参数2:?的实际参数

String loginSql = "select * from mayikt_users where phone=? and pwd=?;";
statement = connection.prepareStatement(loginSql);
statement.setString(1, userEntity.getPhone());
statement.setString(2, userEntity.getPwd());

8、jdbc事务管理器

mysql中的事务

事务是必须满足4个条件(ACID):原子性(Atomicity,或称不可分割性)、一致性(Consistency)、隔离性(Isolation,又称独立性)、持久性(Durability)。

1.原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。

2.一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的数据必须完全符合所有的预设规则,这包含数据的精确度、串联性以及后续数据库可以自发性地完成预定的工作。

3.隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。

4.持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

开启事务-----

{

sql写的操作

update、insert、delete

}

提交事务或者回滚事务

提交事务----事务里面做的写操作 可以查询到 写完之后的数据

回滚事务----事务里面做的写操作 直接回滚了 反悔了 查询不到

如果开启了事务,但是没有提交或者回滚事务 我们是查询不到未提交的数据。

回滚事务----查询不到数据

1.直接用 SET 来改变 MySQL 的自动提交模式:

SET AUTOCOMMIT=0 禁止自动提交

SET AUTOCOMMIT=1 开启自动提交

2.用 BEGIN, ROLLBACK, COMMIT来实现

BEGIN 开始一个事务

ROLLBACK 事务回滚

COMMIT 事务确认

演示:

select * from mayikt_users;
SET AUTOCOMMIT=0;
begin;
insert into mayikt_users values(
null,'15527339852','124'
)
commit;

注意:我们在使用手动提交事务,如果长期不提交数据也不回滚数据 容易引发行锁问题 导致该行数据一直被锁住,无法被其他线程修改。

事务如果不提交、不回滚,会发生哪些问题?

我们在使用手动提交事务,如果不提交数据也不回滚数据 容易引发行锁问题 导致该行数据一直被锁住,无法被其他线程修改。


BEGIN;
UPDATE `mayikt`.`mayikt_users` SET `id`='1',
 `phone`='15523733967', `pwd`='2221' WHERE (`id`='1');

select * from information_schema.innodb_trx

kill 17; 手动释放行锁

kill 19;手动释放行锁

在mysql InnoDB存储引擎中 多个线程如果同时修改同一行数据 最终只会有一个线程修改成功。

InnoDB存储引擎---行锁

jdbc中的事务管理器

同一事务中所有的操作,都在使用同一个Connection对象。

①JDBC中的事务

Connection的三个方法与事务有关:

setAutoCommit(boolean):设置是否为自动提交事务,如果true(默认值为true)表示自动提交,也就是每条执行的SQL语句都是一个单独的事务,如果设置为false,那么相当于开启了事务了;con.setAutoCommit(false) 表示开启事务。

commit():提交结束事务。

rollback():回滚结束事务。

jdbc整合mysql中事务呢?手动事务呢?

事务呢?

增加、删除、修改数据---写操作 加上事务

查询操作---不需要加上事务的?

开启事务

提交事务

回滚事务

 MySQL多表联合查询操作

9、jdbc数据库连接池

什么是数据库连接池

1.我们在JDBC编程中,每次创建和断开Connection对象都会消耗一定的时间和IO资源,如果需要频繁的与数据库打交道,该过程效率非常低。因为在Java程序与数据库之间建立连接时,数据库端要验证用户名和密码,并且要为这个连接分配资源,Java程序则要把代表连接的java.sql.Connection对象等加载到内存中,所以建立数据库连接的开销很大。

2.为了避免频繁的创建数据库连接,与时我们可以通过数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用现有的数据库连接,而不是重新建立。

3.数据库连接池大致实现原理:

数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,当应用程序访问数据库时并不是直接创建Connection,而是向连接池“申请”一个Connection。如果连接池中有空闲的Connection,则将其返回,否则创建新的Connection。使用完毕后,连接池会将该Connection回收,并交付其他的线程复用使用,以减少创建和断开数据库连接的次数,提高数据库的访问效率。

整合c3p0数据库连接池

快速入门

1.导入依赖Jar包

c3p0-0.9.5.2

mchange-commons-java-0.2.12.jar

mysql-connector-java-8.0.13.jar

jar包相关下载:有道云笔记

2.使用c3p0连接池

  ComboPooledDataSource pool = new ComboPooledDataSource();// 创建c3p0数据库连接池
  pool.setUser("root");// 用户名称
  pool.setPassword("root");// 用户密码
  pool.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/mayikt?zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai");// MySQL数据库连接url
  pool.setDriverClass("com.mysql.jdbc.Driver"); // 加载驱动
   public UserEntity getUserEntity(Long id) {
        ResultSet resultSet = null;
        Connection connection = null;
        ComboPooledDataSource pool = new ComboPooledDataSource();// 创建c3p0数据库连接池
        try {
            pool.setUser("root");// 用户名称
            pool.setPassword("root");// 用户密码
            pool.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/mayikt?zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai");// MySQL数据库连接url
            pool.setDriverClass("com.mysql.jdbc.Driver"); // 加载驱动
            //2.获取连接对象
            connection = pool.getConnection();
            PreparedStatement preparedStatement = connection.prepareStatement("select * from  mayikt_users where id=?");
            preparedStatement.setLong(1, id);
            resultSet = preparedStatement.executeQuery();
            if (!resultSet.next()) {
                return null;
            }
            // 获取该行数据的第一列 id
            Long dbId = resultSet.getLong("id");
            // 获取该行数据的第二列 phone
            String dbPhone = resultSet.getString("phone");
            // 获取该行数据的第三列 pwd
            String dbPwd = resultSet.getString("pwd");
            // 将db中查询到数据封装成user对象
            return new UserEntity(dbId, dbPhone, dbPwd);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            if (resultSet != null) {
                try {
                    resultSet.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
        }
    }

改成配置文件形式

1.在src目录创建c3p0.properties 或者c3p0-config.xml

2.特别的名称要求,程序会自动去找寻这个配置文件

<c3p0-config>
    <!-- 使用默认的配置读取连接池对象 -->
    <default-config>
        <!--  连接参数 -->
        <!--需要修改自己数据库路径、用户账号、密码-->
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://127.0.0.1:3306/mayikt?serverTimezone=UTC</property>
        <property name="user">root</property>
        <property name="password">root</property>
    </default-config>

</c3p0-config>

在jdbc自己代码配置jdbc连接信息 注释掉。

自动就可以找到c3p0-config.xml

我们也可以在c3p0-config.xml 配置多个jdbc数据库连接信息

代码指定使用数据库连接配置信息

ComboPooledDataSource pool = new ComboPooledDataSource("mayikt-otherc3p0");

不指定就使用默认的。

<c3p0-config>
    <!-- 使用默认的配置读取连接池对象 -->
    <default-config>
        <!--  连接参数 -->
        <!--需要修改自己数据库路径、用户账号、密码-->
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://127.0.0.1:3306/mayikt?serverTimezone=UTC</property>
        <property name="user">root</property>
        <property name="password">root</property>
    </default-config>
    <named-config name="mayikt-otherc3p0">
        <!--  连接参数 -->
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/meite?serverTimezone=UTC</property>
        <property name="user">root</property>
        <property name="password">root</property>
    </named-config>

</c3p0-config>

核心配置文件说明

1.连接池核心参数


    <!--初始化申请的连接数量-->
    <property name="initialPoolSize">5</property>
    <!--最大的连接数量-->
    <property name="maxPoolSize">10</property>
    <!--超时时间(单位毫秒)-->
    <property name="checkoutTimeout">3000</property>

2.基本原理是在内部对象池中维护一定数量的数据库连接,并对外暴露数据库连接获取和返回方法。用户可通过getConnection()方法获取连接,使用完毕后再通过Connection.close()方法将连接返回,注意此时连接并没有关闭,而是由连接池管理器回收,并为下一次使用做好准备。

数据库连接池的好处:

1. 节约资源

2. 用户访问高效

        ComboPooledDataSource pool = new ComboPooledDataSource();
        for (int i = 1; i <=20; i++) {
            Connection connection = pool.getConnection();
            System.out.println(i + "," + connection);
            if(i==10){
                // 归还
                connection.close();
            }

        }

线程池 底层原理与数据库连接池基本是相同的

整合druid数据库连接池

Druid(德鲁伊):数据库连接池实现技术,由阿里巴巴提供的, 与C3P0数据库连接池 底层实现原理一样。

1.需要导入druid-1.2.8.jar 依赖jar包

依赖jar包下载:有道云笔记

2.定义配置文件

是properties形式的

可以叫任意名称,可以放在任意目录下。程序不会自动寻找,因此在使用时需要手动加载相应的配置文件。

druid.properties

# 加载数据库驱动
driverClassName=com.mysql.jdbc.Driver
# 连接数据库的url,db1表示数据库名,useSSL=false表示不使用SSL规范
url=jdbc:mysql://127.0.0.1:3306/mayikt?useSSL=false&characterEncoding=UTF-8&serverTimezone=UTC
# 用户登录数据库的账号和密码
username=root
password=root
# 初始化连接数量
initialSize=5
# 最大连接数量
maxActive=10
# 最大等待时间
maxWait=3000

3.加载配置文件。使用Properties集合

4.获取数据库连接池对象:通过工厂来获取 DruidDataSourceFactory

5.获取连接:getConnection()

        //读取druid.properties
        Properties properties = new Properties();
        InputStream resourceAsStream =
                Test03.class.getClassLoader().getResourceAsStream("druid.properties");
        properties.load(resourceAsStream);
        // 创建druid.properties数据库连接池
        DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
        Connection connection = dataSource.getConnection();
        // 4.获取预编译执行者对象 防止 sql注入的问题
        PreparedStatement preparedStatement =
                connection.prepareStatement("select * from mayikt_users  where id=? ");
        // 5.设置参数
        preparedStatement.setLong(1, 2);
        // 6.执行sql语句
        ResultSet resultSet = preparedStatement.executeQuery();
        if (!resultSet.next()) {
            return;
        }
        //6. 对结果进行处理
        // 获取该行数据的第一列 id
        Long dbId = resultSet.getLong("id");
        // 获取该行数据的第二列 phone
        String dbPhone = resultSet.getString("phone");
        // 获取该行数据的第三列 pwd
        String dbPwd = resultSet.getString("pwd");
        // 将db中查询到数据封装成user对象
        UserEntity userEntity = new UserEntity(dbId, dbPhone, dbPwd);
        System.out.println(userEntity);
        resultSet.close();
        connection.close();

druid数据库连接池工具类

public class DataSourceUtils {
    /**
     * DataSource 数据库连接池对象
     */
    private static DataSource dataSource;

    /**
     * 私有化构造方法
     */
    private DataSourceUtils() {
    }

    /**
     * 使用静态代码块加载配置文件
     */
    static {
        try {
            // 读取配置文件
            Properties properties = new Properties();
            InputStream resourceAsStream = DataSourceUtils.class.getClassLoader().getResourceAsStream("druid.properties");
            properties.load(resourceAsStream);
            // 获取数据库连接池
            dataSource = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 封装获取连接方法
     *
     * @return
     * @throws SQLException
     */
    public static Connection getConnection() {
        try {
            return dataSource.getConnection();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 封装关闭连接方法
     */
    public static void close(Connection connection, Statement statement, ResultSet resultSet) {
        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        if (statement != null) {
            try {
                statement.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        if (resultSet != null) {
            try {
                resultSet.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }
}

10、常见问题

问题1

  connection = JdbcUtils.getConnection();//创建数据库连接
            String findUserByPhoneSQL = "SELECT * FROM mayikt_users where phone = ?";//明确SQL语句格式
            statement = connection.prepareStatement(findUserByPhoneSQL); //不要用拼接MySQL, 而是用调用预编译
            statement.setString(1, userPhone); //设置第一个参数
            System.out.println(findUserByPhoneSQL);
            resultSet = statement.executeQuery(findUserByPhoneSQL); //执行SQL语句,获得结果 --resultSet

报错:

java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax;

check the manual that corresponds to your MySQL server version for the right syntax to use near '?' at line 1

原因:

将ppstm.executeQuery(sql);中的sql去掉,改成ppstm.executeQuery();

问题2

closing inbound before receiving peer s close_notify

jdbc地址连接后面加上 &useSSL=false 就好了

问题3

在执行 预编译sql时,报错

错误案例:

 Connection connection = null;
        PreparedStatement statement = null;
        try {
            String SQL = "insert into mayikt_student values(null,?,?,?)";
            connection = JdbcUtils.getConnection();
            statement = connection.prepareStatement(SQL);
            statement.setString(1, name);
            statement.setInt(2, age);
            statement.setString(3, address);
            System.out.println(SQL);
            int i = statement.executeUpdate(sql);
            if (i == 1) {
                System.out.println("插入成功");
            } else if (i == 0) {
                System.out.println("插入失败");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JdbcUtils.closeConnection(statement, connection);
        }

报错内容:

You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '?,?,?,?)'

改成

int i = statement.executeUpdate(); 去掉在executeUpdate执行 sql语句

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

出世&入世

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值