【JavaWeb】JDBC学习

目录

一、什么JDBC?

1.什么是数据库驱动

2.为什么学习JDBC

二、JDBC的入门

1.JDBC的环境准备

2.JDBC的代码实现

三、JDBC的API详解

1.JDBC的API详解之DriverManager:驱动管理类

1.1作用一:注册驱动

1.2作用二:获得连接

2.JDBC的API详解之Connection:与数据库连接对象

2.1作用一:创建执行SQL语句的对象

2.2作用二:管理事务

3.JDBC的API详解之Statement:执行SQL

3.1作用一:执行SQL

3.2作用二:执行批处理

4.JDBC的API详解之ResultSet:结果集。

4.1结果集的遍历

4.2结果集的获取

 四、JDBC的资源释放

 五、JDBC的CRUD操作

1.JDBC的CRUD操作之保存操作

1.1保存操作代码实现

2.JDBC的CURD操作之修改操作

2.1修改操作代码实现

3.JDBC的CRUD操作之删除操作

3.1删除操作的代码实现

4.JDBC的CRUD操作之查询操作

4.1查询多条记录

4.2查询一条记录

六、JDBC的工具类的抽取

1.抽取一个JDBC的工具类

2.测试工具类

七、JDBC的配置信息提取到配置文件

1.配置文件:

2.提取信息到配置文件

3.在工具类中解析属性文件

八、JDBC的SQL注入漏洞

1.什么是SQL注入漏洞

2.演示SQL注入漏洞

2.1基本登录功能实现

2.2演示SQL注入漏洞

3.JDBC的SQL注入漏洞分析和解决

3.1.SQL注入漏洞分析

3.2SQL注入漏洞解决

九、JDBC的CRUD操作之PreparedStatement

1.JDBC的CRUD操作之PreparedStatement的保存操作

1.1保存操作代码实现

2.JDBC的CRUD操作之PreparedStatement的修改操作

2.1修改操作代码实现

3.JDBC的CRUD操作之PreparedStatement的删除操作

3.1删除操作的代码实现

4.JDBC的CRUD操作之PreparedStatement的查询操作

十、JDBC的批处理操作

1.什么是批处理

2.批处理基本使用

3.批量插入(使用PreparedStatement)

总结


一、什么JDBC?

1.什么是数据库驱动

         驱动是两个设备(应用)之间通信的桥梁。

2.为什么学习JDBC

        没有JDBC的时候,如果现在要开发一套系统,使用Java连接MySQL数据库,那么这时候Java程序员需要了解MySQL驱动API,如果使用Java连接Oracle数据库,那么这个时候Java程序员需要了解Oracle数据库驱动API。

        SUN公司提供一套统一的规范(接口)。然后各个数据库生产商提供这套接口的实现。这套接口规范就是JDBC的规范。

二、JDBC的入门

1.JDBC的环境准备

1.1创建数据库和表

create database web_test3;
use web_test3;
create table user(
	id int primary key auto_increment,
	username varchar(20),
	password varchar(20),
	nickname varchar(20),
	age int
);
insert into user values (null,'aaa','123','小丽',34);
insert into user values (null,'bbb','123','大王',32);
insert into user values (null,'ccc','123','小明',28);
insert into user values (null,'ddd','123','大黄',21);

1.2创建项目,引入jar包

2.JDBC的代码实现

2.1JDBC的开发步骤

  1. 第一步:加载驱动
  2. 第二步:获得连接
  3. 第三步:基本操作
  4. 第四步:释放资源

JDBC的代码实现

import org.junit.Test;

/**
 * JDBC的入门程序
 * @author jt
 *
 */
public class JDBCDemo1 {

	@Test
	/**
	 * JDBC的入门
	 */
	public void demo1() throws Exception{
		// 1.加载驱动
		Class.forName("com.mysql.jdbc.Driver");
		// 2.获得连接
		Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/web_test3", "root", "abc");
		// 3.基本操作:执行SQL
		// 3.1获得执行SQL语句的对象
		Statement statement = conn.createStatement();
		// 3.2编写SQL语句:
		String sql = "select * from user";
		// 3.3执行SQL:
		ResultSet rs = statement.executeQuery(sql);
		// 3.4遍历结果集:
		while(rs.next()){
			System.out.print(rs.getInt("id")+" ");
			System.out.print(rs.getString("username")+" ");
			System.out.print(rs.getString("password")+" ");
			System.out.print(rs.getString("nickname")+" ");
			System.out.print(rs.getInt("age"));
			System.out.println();
		}
		// 4.释放资源
		rs.close();
		statement.close();
		conn.close();
	}
}

三、JDBC的API详解

1.JDBC的API详解之DriverManager:驱动管理类

1.1作用一:注册驱动

这个方法可以完成驱动的注册,但是实际开发中一般不会使用这个方法完成驱动的注册!!!

原因:

如果需要注册驱动,就会使用DriverManager.registerDriver(new Driver());,但是查看源代码发现,在代码中有一段静态代码块,静态代码块已经调用了注册驱动的方法。

如果再手动调用该方法注册驱动,就会导致驱动被注册两次。实际开发中一般会采用:

 

1.2作用二:获得连接

这个方法就是用来获得与数据库连接的方法:这个方法中有三个参数:

  1. url               :与数据库连接的路径
  2. user            :与数据库连接的用户名
  3. password   :与数据库连接的密码

主要关注的是url的写法:

jdbc:mysql://localhost:3306/web_test3

  1. jdbc                    :连接数据库的协议
  2. mysql                 :是jdbc的子协议
  3. localhost        :连接的MySQL数据库服务器的主机地址。(连接是本机就可以写成localhost),如果连接不是本机的,就需要写上连接主机的IP地址。
  4. 3306                   :MySQL数据库服务器的端口号
  5. web_test3           :数据库名称

url如果连接的是本机的路径,可以简化为如下格式:

 jdbc:mysql:///web_test3

2.JDBC的API详解之Connection:与数据库连接对象

2.1作用一:创建执行SQL语句的对象

 

执行SQL语句对象:

  1. Statement                           :执行SQL
  2. CallableStatement              :执行数据库中存储过程
  3. PreparedStatement            :执行SQL.对SQL进行预处理。解决SQL注入漏洞。

 

2.2作用二:管理事务

 

3.JDBC的API详解之Statement:执行SQL

3.1作用一:执行SQL

 

 

执行SQL的方法:

  • boolean execute(String sql):

执行查询,修改,添加,删除的SQL语句。

  • ResultSet executeQuery(String sql):

执行查询(执行select语句)。

  • int executeUpate(String sql):

执行修改,添加,删除的SQL语句。

3.2作用二:执行批处理

4.JDBC的API详解之ResultSet:结果集。

通过select语句的查询结果。

4.1结果集的遍历

结果集遍历原理:

代码实现:

4.2结果集的获取

 

结果集获取可以使用结果集中的getXXX()

该方法通常都会有一个重载的方法。

  1. getXXX(int columnIndex);
  2. getXXX(String columnName);

 四、JDBC的资源释放

JDBC程序执行结束后,将与数据库进行交互的对象释放掉,通常是ResultSet,Statement,Connection。

这几个对象中尤其是Connection对象是非常稀有的。这个对象一定要做到尽量晚创建,尽早释放掉。

  1. 将资源释放的代码写入到finally的代码块中。
  2. 资源释放的代码应该写的标准:
if(rs !=null){
				try {
					rs.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
				
				rs = null;
			}
			
			if(statement !=null){
				try {
					statement.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
				
				statement = null;
			}
			
			
			if(conn !=null){
				try {
					conn.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
				
				conn = null;
			}

 

 五、JDBC的CRUD操作

1.JDBC的CRUD操作之保存操作

1.1保存操作代码实现

@Test
	/**
	 * 保存操作的代码实现
	 */
	public void demo1(){
		Connection conn = null;
		Statement stmt = null;
		try{
			// 注册驱动:
			Class.forName("com.mysql.jdbc.Driver");
			// 获得连接:
			conn = DriverManager.getConnection("jdbc:mysql:///web_test3", "root", "abc");
			// 执行操作:
			// 创建执行SQL语句对象:
			stmt = conn.createStatement();
			// 编写SQL语句:
			String sql = "insert into user values (null,'eee','123','阿黄',21)";
			// 执行SQL语句:
			int num = stmt.executeUpdate(sql);
			if(num > 0){
				System.out.println("保存用户成功!!!");
			}
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			// 资源释放:
			if(stmt != null){
				try {
					stmt.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
				
				stmt = null;
			}
			if(conn != null){
				try {
					conn.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
				conn = null;
			}
		}
	}

2.JDBC的CURD操作之修改操作

2.1修改操作代码实现

@Test
	/**
	 * 修改操作的代码实现
	 */
	public void demo2(){
		Connection conn = null;
		Statement stmt  = null;
		try{
			// 注册驱动:
			Class.forName("com.mysql.jdbc.Driver");
			// 获得连接
			conn = DriverManager.getConnection("jdbc:mysql:///web_test3", "root", "abc");
			// 执行操作:
			// 创建执行SQL语句的对象:
			stmt = conn.createStatement();
			// 编写SQL语句:
			String sql = "update user set password='abc',nickname='旺财' where id = 5";
			// 执行SQL语句:
			int num = stmt.executeUpdate(sql);
			if(num > 0){
				System.out.println("修改用户成功!!!");
			}
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			// 资源释放:
			if(stmt != null){
				try {
					stmt.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
				
				stmt = null;
			}
			if(conn != null){
				try {
					conn.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
				conn = null;
			}
		}
	}

3.JDBC的CRUD操作之删除操作

3.1删除操作的代码实现

@Test
	/**
	 * 删除操作的代码实现
	 */
	public void demo3(){
		Connection conn = null;
		Statement stmt = null;
		try{
			// 注册驱动:
			Class.forName("com.mysql.jdbc.Driver");
			// 获得连接:
			conn = DriverManager.getConnection("jdbc:mysql:///web_test3", "root", "abc");
			// 创建执行SQL语句对象:
			stmt = conn.createStatement();
			// 编写SQL:
			String sql = "delete from user where id = 5";
			// 执行SQL:
			int num = stmt.executeUpdate(sql);
			if(num > 0){
				System.out.println("删除用户成功!!!");
			}
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			// 资源释放:
			if(stmt != null){
				try {
					stmt.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
				
				stmt = null;
			}
			if(conn != null){
				try {
					conn.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
				conn = null;
			}
		}
	}

4.JDBC的CRUD操作之查询操作

4.1查询多条记录

@Test
	/**
	 * 查询多条记录
	 */
	public void demo4(){
		Connection conn = null;
		Statement stmt = null;
		ResultSet rs = null;
		try{
			// 注册驱动
			Class.forName("com.mysql.jdbc.Driver");
			// 获得连接
			conn = DriverManager.getConnection("jdbc:mysql:///web_test3", "root", "abc");
			// 执行操作
			// 创建执行SQL语句的对象:
			stmt = conn.createStatement();
			// 编写SQL:
			String sql = "select * from user";
			// 执行SQL:
			rs = stmt.executeQuery(sql);
			// 遍历结果集:
			while(rs.next()){
				System.out.println(rs.getInt("id")+" "+rs.getString("username")+" "+rs.getString("password"));
			}
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			// 资源释放:
			if(rs != null){
				try {
					rs.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
				
				rs = null;
			}
			if(stmt != null){
				try {
					stmt.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
				
				stmt = null;
			}
			if(conn != null){
				try {
					conn.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
				conn = null;
			}
		}
	}

4.2查询一条记录

@Test
	/**
	 * 查询一条记录
	 */
	public void demo5(){
		Connection conn = null;
		Statement stmt = null;
		ResultSet rs = null;
		try{
			// 注册驱动
			Class.forName("com.mysql.jdbc.Driver");
			// 获得连接
			conn = DriverManager.getConnection("jdbc:mysql:///web_test3", "root", "abc");
			// 执行SQL
			// 创建执行SQL语句对象:
			stmt = conn.createStatement();
			// 编写SQL:
			String sql = "select * from user where id = 4";
			rs = stmt.executeQuery(sql);
			// 判断就可以:
			if(rs.next()){
				System.out.println(rs.getInt("id")+" "+rs.getString("username")+" "+rs.getString("password"));
			}
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			// 资源释放:
			if(rs != null){
				try {
					rs.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
				
				rs = null;
			}
			if(stmt != null){
				try {
					stmt.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
				
				stmt = null;
			}
			if(conn != null){
				try {
					conn.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
				conn = null;
			}
		}
	}

六、JDBC的工具类的抽取

1.抽取一个JDBC的工具类

因为传统JDBC的开发,注册驱动,获得连接,释放资源这些代码都是重复编写的。所以可以将重复的代码提取到一个类中来完成。

/**
 * JDBC的工具类
 * @author jt
 *
 */
public class JDBCUtils {
	private static final String driverClassName;
	private static final String url;
	private static final String username;
	private static final String password;
	
	static{
		driverClassName="com.mysql.jdbc.Driver";
		url="jdbc:mysql:///web_test3";
		username="root";
		password="abc";
	}

	/**
	 * 注册驱动的方法
	 */
	public static void loadDriver(){
		try {
			Class.forName(driverClassName);
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 获得连接的方法
	 */
	public static Connection getConnection(){
		Connection conn = null;
		try{
			// 将驱动一并注册:
			loadDriver();
			// 获得连接
			conn = DriverManager.getConnection(url,username, password);
		}catch(Exception e){
			e.printStackTrace();
		}
		return conn;
	}
	
	/**
	 * 释放资源的方法
	 */
	public static void release(Statement stmt,Connection conn){
		if(stmt != null){
			try {
				stmt.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			
			stmt = null;
		}
		if(conn != null){
			try {
				conn.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			conn = null;
		}
	}
	
	public static void release(ResultSet rs,Statement stmt,Connection conn){
		// 资源释放:
		if(rs != null){
			try {
				rs.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			
			rs = null;
		}
		if(stmt != null){
			try {
				stmt.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			
			stmt = null;
		}
		if(conn != null){
			try {
				conn.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			conn = null;
		}
	}
}

2.测试工具类

@Test
	/**
	 * 查询操作:使用工具类
	 */
	public void demo1(){
		Connection conn = null;
		Statement stmt = null;
		ResultSet rs = null;
		try{
			// 获得连接:
			conn = JDBCUtils.getConnection();
			// 创建执行SQL语句的对象:
			stmt = conn.createStatement();
			// 编写SQL:
			String sql = "select * from user";
			// 执行查询:
			rs = stmt.executeQuery(sql);
			// 遍历结果集:
			while(rs.next()){
				System.out.println(rs.getInt("id")+" "+rs.getString("username")+" "+rs.getString("password"));
			}
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			// 释放资源:
			JDBCUtils.release(rs, stmt, conn);
		}
	}

七、JDBC的配置信息提取到配置文件

1.配置文件:

  1. 属性文件
    1. 格式:扩展名是.properties
    2. 内容:key=value
  2. XML文件

2.提取信息到配置文件

定义一个配置文件:

3.在工具类中解析属性文件

  1. 获取到具体内容为常量赋值:

 

 

八、JDBC的SQL注入漏洞

1.什么是SQL注入漏洞

在早期互联网上SQL注入漏洞普遍存在。有一个网站,用户需要进行注册,用户注册以后根据用户名和密码完成登录。假设现在用户名已经被其他人知道了,但是其他人不知道你的密码,也可以登录到网站上进行相应的操作。

2.演示SQL注入漏洞

2.1基本登录功能实现

 

2.2演示SQL注入漏洞

  1. 输入用户名
    1. aaa’ or ‘1=1 密码随意
    2. aaa’ --      密码随意

 

3.JDBC的SQL注入漏洞分析和解决

3.1.SQL注入漏洞分析

3.2SQL注入漏洞解决

需要采用PreparedStatement对象解决SQL注入漏洞。这个对象将SQL预先进行编译,使用?作为占位符。?所代表内容是SQL所固定。再次传入变量(包含SQL的关键字)。这个时候也不会识别这些关键字。

public class UserDao {
	
	public boolean login(String username,String password){
		Connection conn = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		// 定义一个变量:
		boolean flag = false;
		try{
			// 获得连接:
			conn = JDBCUtils.getConnection();
			// 编写SQL语句:
			String sql = "select * from user where username = ? and password = ?";
			// 预编译SQL
			pstmt = conn.prepareStatement(sql);
			// 设置参数:
			pstmt.setString(1, username);
			pstmt.setString(2, password);
			// 执行SQL语句:
			rs = pstmt.executeQuery();
			if(rs.next()){
				// 说明根据用户名和密码可以查询到这条记录
				flag = true;
			}
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			JDBCUtils.release(rs, pstmt, conn);
		}
		return flag;
	}

 

九、JDBC的CRUD操作之PreparedStatement

1.JDBC的CRUD操作之PreparedStatement的保存操作

1.1保存操作代码实现

@Test
	/**
	 * 保存操作
	 */
	public void demo1(){
		Connection conn = null;
		PreparedStatement pstmt = null;
		try{
			// 获得连接:
			conn = JDBCUtils.getConnection();
			// 编写SQL语句:
			String sql = "insert into user values (null,?,?,?,?)";
			// 预编译SQL:
			pstmt = conn.prepareStatement(sql);
			// 设置参数:
			pstmt.setString(1, "eee");
			pstmt.setString(2, "abc");
			pstmt.setString(3, "旺财");
			pstmt.setInt(4, 32);
			// 执行SQL
			int num = pstmt.executeUpdate();
			if(num > 0){
				System.out.println("保存成功!");
			}
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			JDBCUtils.release(pstmt, conn);
		}
	}

2.JDBC的CRUD操作之PreparedStatement的修改操作

2.1修改操作代码实现

@Test
	/**
	 * 修改操作
	 */
	public void demo2(){
		Connection conn = null;
		PreparedStatement pstmt = null;
		try{
			// 获得连接:
			conn = JDBCUtils.getConnection();
			// 编写SQL语句:
			String sql = "update user set username = ?,password =?,nickname=?,age = ? where id = ?";
			// 预编译SQL:
			pstmt = conn.prepareStatement(sql);
			// 设置参数:
			pstmt.setString(1, "abc");
			pstmt.setString(2, "1234");
			pstmt.setString(3, "旺旺");
			pstmt.setInt(4, 23);
			pstmt.setInt(5, 6);
			// 执行SQL:
			int num = pstmt.executeUpdate();
			if(num > 0){
				System.out.println("修改成功!");
			}
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			JDBCUtils.release(pstmt, conn);
		}
	}

3.JDBC的CRUD操作之PreparedStatement的删除操作

3.1删除操作的代码实现

@Test
	/**
	 * 删除操作
	 */
	public void demo3(){
		Connection conn = null;
		PreparedStatement pstmt  = null;
		try{
			// 获得连接:
			conn = JDBCUtils.getConnection();
			// 编写SQL语句:
			String sql = "delete from user where id = ?";
			// 预编译SQL
			pstmt = conn.prepareStatement(sql);
			// 设置参数:
			pstmt.setInt(1, 4);
			// 执行SQL:
			int num = pstmt.executeUpdate();
			if(num > 0){
				System.out.println("删除成功!");
			}
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			JDBCUtils.release(pstmt, conn);
		}
	}

4.JDBC的CRUD操作之PreparedStatement的查询操作

4.1查询操作代码实现

@Test
	/**
	 * 查询操作
	 */
	public void demo4(){
		Connection conn = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		try{
			// 获得连接:
			conn = JDBCUtils.getConnection();
			// 编写SQL:
			String sql = "select * from user";
			// 预编译SQL:
			pstmt = conn.prepareStatement(sql);
			// 设置参数:
			// 执行SQL:
			rs = pstmt.executeQuery();
			// 遍历结果集:
			while(rs.next()){
				System.out.println(rs.getInt("id")+" "+rs.getString("username")+" "+rs.getString("password")+" "+rs.getString("nickname"));
			}
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			JDBCUtils.release(rs, pstmt, conn);
		}
	}

十、JDBC的批处理操作

1.什么是批处理

之前进行JDBC的操作的时候,都是一条SQL语句执行。现在如果使用批处理,可以将一批SQL一起执行。

2.批处理基本使用

@Test
	/**
	 * 批处理基本操作
	 */
	public void demo1(){
		Connection conn = null;
		Statement stmt = null;
		try{
			// 获得连接:
			conn = JDBCUtils.getConnection();
			// 创建执行批处理对象:
			stmt = conn.createStatement();
			// 编写一批SQL语句:
			String sql1 = "create database test1";
			String sql2 = "use test1";
			String sql3 = "create table user(id int primary key auto_increment,name varchar(20))";
			String sql4 = "insert into user values (null,'aaa')";
			String sql5 = "insert into user values (null,'bbb')";
			String sql6 = "insert into user values (null,'ccc')";
			String sql7 = "update user set name = 'mmm' where id = 2";
			String sql8 = "delete from user where id = 1";
			// 添加到批处理
			stmt.addBatch(sql1);
			stmt.addBatch(sql2);
			stmt.addBatch(sql3);
			stmt.addBatch(sql4);
			stmt.addBatch(sql5);
			stmt.addBatch(sql6);
			stmt.addBatch(sql7);
			stmt.addBatch(sql8);
			// 执行批处理:
			stmt.executeBatch();
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			JDBCUtils.release(stmt, conn);
		}
	}

3.批量插入(使用PreparedStatement)

@Test
	/**
	 * 批量插入记录:
	 * * 默认情况下MySQL批处理没有开启的,需要在url后面拼接一个参数即可。
	 */
	public void demo2(){
		// 记录开始时间:
		long begin = System.currentTimeMillis();
		Connection conn = null;
		PreparedStatement pstmt = null;
		try{
			// 获得连接:
			conn = JDBCUtils.getConnection();
			// 编写SQL语句:
			String sql = "insert into user values (null,?)";
			// 预编译SQL:
			pstmt = conn.prepareStatement(sql);
			for(int i=1;i<=10000;i++){
				pstmt.setString(1, "name"+i);
				// 添加到批处理
				pstmt.addBatch();
				// 注意问题:
				// 执行批处理
				if(i % 1000 == 0){
					// 执行批处理:
					pstmt.executeBatch();
					// 清空批处理:
					pstmt.clearBatch();
				}
			}
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			JDBCUtils.release(pstmt, conn);
		}
		long end = System.currentTimeMillis();
		System.out.println((end-begin));
	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值