为了账号安全,请及时绑定邮箱和手机立即绑定

ShardingJDBC分库分表配置资料入门教程

标签:
Java 数据库
概述

本文详细介绍了ShardingJDBC分库分表配置的相关资料,包括环境搭建、分片策略解析及配置示例等内容,帮助开发者理解和应用ShardingJDBC分库分表配置资料。文章还提供了数据源配置、分片规则配置以及实际配置案例,确保读者能够全面掌握ShardingJDBC分库分表配置的全过程。

ShardingJDBC简介
什么是ShardingJDBC

ShardingJDBC 是阿里巴巴开源的一个轻量级 Java 分库分表中间件,它基于 JDBC 协议实现,并且兼容各种主流数据库。ShardingJDBC 可以与各种应用框架无缝集成,为应用提供高度透明的数据分片、读写分离、数据源路由等功能。通过 ShardingJDBC,可以实现数据的水平拆分,以解决单机存储容量和性能瓶颈的问题。

ShardingJDBC的作用和优势

ShardingJDBC 主要作用在于解决数据库存储容量和性能瓶颈的问题。具体优势如下:

  1. 数据水平拆分:通过将数据分布在多个数据库实例上,实现了数据的水平拆分。
  2. 读写分离:支持读写分离,提升读操作的并发能力。
  3. 透明性:ShardingJDBC 透明地实现了数据分片,对应用开发者来说是透明的,不必关心底层数据库的具体分片逻辑。
  4. 兼容性:支持多种数据库系统,如 MySQL、SQL Server、PostgreSQL 等。
  5. 灵活性:通过灵活的配置方式,可以适应不同的业务需求和数据库配置。
ShardingJDBC的应用场景

ShardingJDBC 主要适用于以下场景:

  1. 大数据量存储:当数据库表中的数据量达到百万级甚至千万级时,单个数据库实例无法满足存储需求。
  2. 高并发操作:数据查询和写入操作的并发量很高,单个数据库无法满足性能需求。
  3. 扩展性要求:应用系统需要支持灵活的扩展,通过增加数据库实例来提升系统性能和存储能力。
  4. 分布式架构:在分布式系统中,ShardingJDBC 可以帮助实现数据的分布式存储和读写分离。
环境搭建
准备工作

在开始使用 ShardingJDBC 之前,需要完成一些准备工作,包括安装 Java 开发环境、下载 ShardingJDBC 的依赖库,并设置好数据库环境。

下载ShardingJDBC
  1. 使用 Maven 仓库下载 ShardingJDBC 依赖。在项目的 pom.xml 文件中添加以下依赖:
<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>shardingsphere-jdbc-core</artifactId>
    <version>4.1.1</version>
</dependency>

确保版本号与实际使用的 ShardingJDBC 版本匹配。

  1. 下载 ShardingJDBC 的源码或者二进制包,可以从其 GitHub 仓库下载:
git clone https://github.com/apache/shardingsphere.git
配置Java开发环境
  1. 安装 JDK:确保系统中已经安装了 JDK,并配置好环境变量。
  2. 安装 IDE:推荐使用 IntelliJ IDEA 或 Eclipse。
  3. 配置 Maven:确保 Maven 已安装并配置好环境变量。
mvn -v
  1. 创建项目并添加 ShardingJDBC 依赖,如上面的 Maven 依赖配置。
分库分表概念解析
数据库分库的基本概念

数据库分库是指将一个数据库拆分成多个独立的数据库实例。通过分库,可以将数据分散存储在不同的数据库上,从而提高系统的可扩展性和性能。

分库的常见策略包括:

  1. 哈希分片:使用哈希函数将数据均匀地分布到多个数据库实例上。
  2. 范围分片:根据数据的某些属性(如时间、地理位置)将数据分布在不同的数据库实例上。
  3. 一致性哈希分片:使用一致性哈希算法来决定数据的分布,可以有效降低数据迁移的代价。
表分片的基本概念

表分片是指将一个数据库表拆分成多个独立的表,这些表可以分布在不同的数据库实例上。通过表分片,可以有效地管理大规模的数据集。

表分片的常见策略包括:

  1. 哈希分片:使用哈希函数将数据均匀地分布在多个表上。
  2. 范围分片:根据数据的某些属性(如时间、ID)将数据分布在不同的表上。
  3. 一致性哈希分片:使用一致性哈希算法来决定数据的分布,可以有效降低数据迁移的代价。
数据分片策略介绍

数据分片策略是指如何将数据分配到不同的数据库实例或表上的规则。常见的数据分片策略包括:

  1. 单表分片:将一个表的数据分散到多个表上,每个表可以分布在不同的数据库实例上。
  2. 多表分片:将多个表的数据分散到多个数据库实例上,每个表可以分布在不同的实例上。
  3. 混合分片:结合使用单表分片和多表分片,以满足复杂的业务需求。
示例代码

假设要将一个订单表 order 分片到多个数据库实例上:

import org.apache.shardingsphere.api.config.sharding.KeyGeneratorConfiguration;
import org.apache.shardingsphere.api.config.sharding.TableRuleConfiguration;
import org.apache.shardingsphere.api.config.sharding.strategy.StandardShardingStrategyConfiguration;
import org.apache.shardingsphere.api.config.sharding.strategy.StandardShardingAlgorithm;
import org.apache.shardingsphere.api.config.sharding.TableShardingStrategyConfiguration;
import org.apache.shardingsphere.api.config.sharding.ShardingRuleConfiguration;
import org.apache.shardingsphere.api.config.sharding.strategy.StandardShardingAlgorithm;
import org.apache.shardingsphere.core.config.DatabaseConfiguration;
import org.apache.shardingsphere.core.config.DataSourceConfiguration;
import org.apache.shardingsphere.shardingjdbc.api.config.ShardingRuleConfiguration;
import org.apache.shardingsphere.shardingjdbc.api.config.ShardingContext;

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

public class ShardingJDBCDemo {
    public static void main(String[] args) {
        TableRuleConfiguration orderTableRuleConfig = new TableRuleConfiguration("t_order", "t_order_%s");
        orderTableRuleConfig.setDatabaseShardingStrategyConfig(new StandardShardingStrategyConfiguration("order_id", new StandardShardingAlgorithm() {
            @Override
            public String doSelectShardingValue(Object[] values) {
                return "db_" + ((String) values[0]).substring(0, 1);
            }
        }));
        orderTableRuleConfig.setTableShardingStrategyConfig(new TableShardingStrategyConfiguration("order_id", new StandardShardingAlgorithm() {
            @Override
            public String doSelectShardingValue(Object[] values) {
                return "t_order_" + ((String) values[0]).substring(0, 1);
            }
        }));
        orderTableRuleConfig.setKeyGeneratorConfig(new KeyGeneratorConfiguration("SNOWFLAKE", "order_id"));

        ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
        shardingRuleConfig.getTableRuleConfigs().add(orderTableRuleConfig);

        Map<String, DataSourceConfiguration> dataSourceMap = new HashMap<>();
        dataSourceMap.put("db_0", new DataSourceConfiguration("com.zaxxer.hikari.HikariDataSource", "jdbc:mysql://localhost:3306/db_0", "root", "root"));
        dataSourceMap.put("db_1", new DataSourceConfiguration("com.zaxxer.hikari.HikariDataSource", "jdbc:mysql://localhost:3306/db_1", "root", "root"));

        DatabaseConfiguration databaseConfig = new DatabaseConfiguration("sharding_db", dataSourceMap, shardingRuleConfig);
        ShardingContext shardingContext = new ShardingContext(databaseConfig);
    }
}
ShardingJDBC配置示例
数据源配置

数据源配置是使用 ShardingJDBC 的第一步,需要配置好所有的数据源信息。数据源配置主要包括数据库连接信息、连接池配置等。

  1. 创建数据源配置文件:在项目中创建一个配置文件,如 datasource.yaml,配置各个数据源的信息。
db_0:
  url: jdbc:mysql://localhost:3306/db_0
 username: root
 password: root
 connectionTimeoutMilliseconds: 30000
 maxLifetimeMilliseconds: 1800000
 maxPoolSize: 50
 minPoolSize: 10

db_1:
  url: jdbc:mysql://localhost:3306/db_1
 username: root
 password: root
 connectionTimeoutMilliseconds: 30000
 maxLifetimeMilliseconds: 1800000
 maxPoolSize: 50
 minPoolSize: 10
  1. 读取数据源配置:在代码中读取并初始化数据源配置。
import org.apache.shardingsphere.api.config.sharding.KeyGeneratorConfiguration;
import org.apache.shardingsphere.api.config.sharding.TableRuleConfiguration;
import org.apache.shardingsphere.api.config.sharding.strategy.StandardShardingStrategyConfiguration;
import org.apache.shardingsphere.api.config.sharding.ShardingRuleConfiguration;
import org.apache.shardingsphere.api.config.sharding.TableShardingStrategyConfiguration;
import org.apache.shardingsphere.api.config.sharding.strategy.StandardShardingAlgorithm;
import org.apache.shardingsphere.shardingjdbc.api.config.ShardingRuleConfiguration;
import org.apache.shardingsphere.shardingjdbc.api.config.ShardingContext;
import org.apache.shardingsphere.shardingjdbc.api.config.DataSourceConfiguration;
import org.apache.shardingsphere.shardingjdbc.api.config.DatabaseConfiguration;

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

public class ShardingJDBCDemo {
    public static void main(String[] args) {
        Map<String, DataSourceConfiguration> dataSourceMap = new HashMap<>();
        dataSourceMap.put("db_0", new DataSourceConfiguration("com.zaxxer.hikari.HikariDataSource", "jdbc:mysql://localhost:3306/db_0", "root", "root"));
        dataSourceMap.put("db_1", new DataSourceConfiguration("com.zaxxer.hikari.HikariDataSource", "jdbc:mysql://localhost:3306/db_1", "root", "root"));

        ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
        shardingRuleConfig.getTableRuleConfigs().add(new TableRuleConfiguration("t_order", "t_order_%s"));

        DatabaseConfiguration databaseConfig = new DatabaseConfiguration("sharding_db", dataSourceMap, shardingRuleConfig);
        ShardingContext shardingContext = new ShardingContext(databaseConfig);
    }
}
分片规则配置

分片规则配置是 ShardingJDBC 的核心配置,它定义了数据分片的逻辑。分片规则主要包含数据库分片策略和表分片策略。

  1. 定义分片策略:根据业务需求定义分片策略。例如,使用 order_id 字段进行分片。
StandardShardingStrategyConfiguration dbStrategyConfig = new StandardShardingStrategyConfiguration("order_id", new StandardShardingAlgorithm() {
    @Override
    public String doSelectShardingValue(Object[] values) {
        return "db_" + ((String) values[0]).substring(0, 1);
    }
});

StandardShardingStrategyConfiguration tableStrategyConfig = new StandardShardingStrategyConfiguration("order_id", new StandardShardingAlgorithm() {
    @Override
    public String doSelectShardingValue(Object[] values) {
        return "t_order_" + ((String) values[0]).substring(0, 1);
    }
});
  1. 配置分片规则:将定义好的分片策略应用到具体的表上。
TableRuleConfiguration orderTableRuleConfig = new TableRuleConfiguration("t_order", "t_order_%s");
orderTableRuleConfig.setDatabaseShardingStrategyConfig(dbStrategyConfig);
orderTableRuleConfig.setTableShardingStrategyConfig(tableStrategyConfig);
orderTableRuleConfig.setKeyGeneratorConfig(new KeyGeneratorConfiguration("SNOWFLAKE", "order_id"));

ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
shardingRuleConfig.getTableRuleConfigs().add(orderTableRuleConfig);
实际配置案例解析

假设我们有一个 t_order 表,需要将其分片到 db_0db_1 两个数据库实例上,且分片规则是根据 order_id 字段进行分片。

import org.apache.shardingsphere.api.config.sharding.KeyGeneratorConfiguration;
import org.apache.shardingsphere.api.config.sharding.TableRuleConfiguration;
import org.apache.shardingsphere.api.config.sharding.strategy.StandardShardingStrategyConfiguration;
import org.apache.shardingsphere.api.config.sharding.ShardingRuleConfiguration;
import org.apache.shardingsphere.api.config.sharding.TableShardingStrategyConfiguration;
import org.apache.shardingsphere.api.config.sharding.strategy.StandardShardingAlgorithm;
import org.apache.shardingsphere.shardingjdbc.api.config.ShardingRuleConfiguration;
import org.apache.shardingsphere.shardingjdbc.api.config.ShardingContext;
import org.apache.shardingsphere.shardingjdbc.api.config.DataSourceConfiguration;
import org.apache.shardingsphere.shardingjdbc.api.config.DatabaseConfiguration;

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

public class ShardingJDBCDemo {
    public static void main(String[] args) {
        Map<String, DataSourceConfiguration> dataSourceMap = new HashMap<>();
        dataSourceMap.put("db_0", new DataSourceConfiguration("com.zaxxer.hikari.HikariDataSource", "jdbc:mysql://localhost:3306/db_0", "root", "root"));
        dataSourceMap.put("db_1", new DataSourceConfiguration("com.zaxxer.hikari.HikariDataSource", "jdbc:mysql://localhost:3306/db_1", "root", "root"));

        ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
        shardingRuleConfig.getTableRuleConfigs().add(new TableRuleConfiguration("t_order", "t_order_%s"));

        StandardShardingStrategyConfiguration dbStrategyConfig = new StandardShardingStrategyConfiguration("order_id", new StandardShardingAlgorithm() {
            @Override
            public String doSelectShardingValue(Object[] values) {
                return "db_" + ((String) values[0]).substring(0, 1);
            }
        });

        StandardShardingStrategyConfiguration tableStrategyConfig = new StandardShardingStrategyConfiguration("order_id", new StandardShardingAlgorithm() {
            @Override
            public String doSelectShardingValue(Object[] values) {
                return "t_order_" + ((String) values[0]).substring(0, 1);
            }
        });

        TableRuleConfiguration orderTableRuleConfig = new TableRuleConfiguration("t_order", "t_order_%s");
        orderTableRuleConfig.setDatabaseShardingStrategyConfig(dbStrategyConfig);
        orderTableRuleConfig.setTableShardingStrategyConfig(tableStrategyConfig);
        orderTableRuleConfig.setKeyGeneratorConfig(new KeyGeneratorConfiguration("SNOWFLAKE", "order_id"));

        shardingRuleConfig.getTableRuleConfigs().add(orderTableRuleConfig);

        DatabaseConfiguration databaseConfig = new DatabaseConfiguration("sharding_db", dataSourceMap, shardingRuleConfig);
        ShardingContext shardingContext = new ShardingContext(databaseConfig);
    }
}
ShardingJDBC使用指南
如何编写SQL语句

使用 ShardingJDBC 编写 SQL 语句时,需要注意数据分片的透明性。开发者编写 SQL 语句时,可以像操作单个数据库一样来操作分片后的数据库。

  1. 标准 SQL 语句:编写标准的 SQL 语句,不要关心数据分片的具体逻辑。
  2. 自动分片:ShardingJDBC 会自动根据分片规则将 SQL 语句路由到正确的数据库实例和表上。
-- 插入数据
INSERT INTO t_order (order_id, user_id, order_amount) VALUES ('o1', 'u1', 100.00);

-- 查询数据
SELECT * FROM t_order WHERE order_id = 'o1';
如何处理分库分表后的数据查询

处理分库分表后的数据查询时,可以使用 ShardingJDBC 提供的查询功能,这些功能会自动处理跨库和跨表的查询。

  1. 多表查询:可以同时查询多个表的数据。
  2. 跨库查询:可以查询分布在不同数据库实例上的数据。
  3. 分页查询:支持分页查询,适用于大数据量的查询。
-- 查询多个表的数据
SELECT * FROM t_order UNION ALL SELECT * FROM t_order_shard_1;

-- 跨库查询
SELECT * FROM t_order UNION ALL SELECT * FROM db_1.t_order_shard_1;
连接池配置和优化

连接池是数据库操作中的一个重要组件,它能够提高数据库操作的性能和稳定性。ShardingJDBC 支持多种连接池配置。

  1. 配置连接池属性:根据业务需求配置连接池的各种属性,如最大连接数、连接超时时间等。
db_0:
 url: jdbc:mysql://localhost:3306/db_0
 username: root
 password: root
 connectionTimeoutMilliseconds: 30000
 maxLifetimeMilliseconds: 1800000
 maxPoolSize: 50
 minPoolSize: 10

db_1:
 url: jdbc:mysql://localhost:3306/db_1
 username: root
 password: root
 connectionTimeoutMilliseconds: 30000
 maxLifetimeMilliseconds: 1800000
 maxPoolSize: 50
 minPoolSize: 10
  1. 连接池优化:通过调整连接池的配置参数,优化数据库操作的性能,如提高连接池的最大连接数、减少连接池的闲置时间等。
import org.apache.shardingsphere.api.config.sharding.KeyGeneratorConfiguration;
import org.apache.shardingsphere.api.config.sharding.TableRuleConfiguration;
import org.apache.shardingsphere.api.config.sharding.strategy.StandardShardingStrategyConfiguration;
import org.apache.shardingsphere.api.config.sharding.ShardingRuleConfiguration;
import org.apache.shardingsphere.api.config.sharding.TableShardingStrategyConfiguration;
import org.apache.shardingsphere.api.config.sharding.strategy.StandardShardingAlgorithm;
import org.apache.shardingsphere.shardingjdbc.api.config.ShardingRuleConfiguration;
import org.apache.shardingsphere.shardingjdbc.api.config.ShardingContext;
import org.apache.shardingsphere.shardingjdbc.api.config.DataSourceConfiguration;
import org.apache.shardingsphere.shardingjdbc.api.config.DatabaseConfiguration;

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

public class ShardingJDBCDemo {
    public static void main(String[] args) {
        Map<String, DataSourceConfiguration> dataSourceMap = new HashMap<>();
        dataSourceMap.put("db_0", new DataSourceConfiguration("com.zaxxer.hikari.HikariDataSource", "jdbc:mysql://localhost:3306/db_0", "root", "root", 30000, 1800000, 50, 10));
        dataSourceMap.put("db_1", new DataSourceConfiguration("com.zaxxer.hikari.HikariDataSource", "jdbc:mysql://localhost:3306/db_1", "root", "root", 30000, 1800000, 50, 10));

        ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
        shardingRuleConfig.getTableRuleConfigs().add(new TableRuleConfiguration("t_order", "t_order_%s"));

        StandardShardingStrategyConfiguration dbStrategyConfig = new StandardShardingStrategyConfiguration("order_id", new StandardShardingAlgorithm() {
            @Override
            public String doSelectShardingValue(Object[] values) {
                return "db_" + ((String) values[0]).substring(0, 1);
            }
        });

        StandardShardingStrategyConfiguration tableStrategyConfig = new StandardShardingStrategyConfiguration("order_id", new StandardShardingAlgorithm() {
            @Override
            public String doSelectShardingValue(Object[] values) {
                return "t_order_" + ((String) values[0]).substring(0, 1);
            }
        });

        TableRuleConfiguration orderTableRuleConfig = new TableRuleConfiguration("t_order", "t_order_%s");
        orderTableRuleConfig.setDatabaseShardingStrategyConfig(dbStrategyConfig);
        orderTableRuleConfig.setTableShardingStrategyConfig(tableStrategyConfig);
        orderTableRuleConfig.setKeyGeneratorConfig(new KeyGeneratorConfiguration("SNOWFLAKE", "order_id"));

        shardingRuleConfig.getTableRuleConfigs().add(orderTableRuleConfig);

        DatabaseConfiguration databaseConfig = new DatabaseConfiguration("sharding_db", dataSourceMap, shardingRuleConfig);
        ShardingContext shardingContext = new ShardingContext(databaseConfig);
    }
}
常见问题与解决方法
常见错误及解决办法
  1. 连接失败:检查数据库连接信息是否正确,确保数据库已经启动。
  2. SQL 语句执行失败:检查 SQL 语句是否有语法错误,确保分片规则配置正确。
  3. 性能问题:优化连接池配置,减少连接池的闲置时间,提高最大连接数。
db_0:
 url: jdbc:mysql://localhost:3306/db_0
 username: root
 password: root
 connectionTimeoutMilliseconds: 30000
 maxLifetimeMilliseconds: 1800000
 maxPoolSize: 100
 minPoolSize: 10
  1. 数据不一致:确保事务管理正确,使用正确的隔离级别以避免数据不一致的问题。
性能优化建议
  1. 优化连接池配置:根据实际业务需求调整连接池的配置参数,如最大连接数、连接超时时间等。
  2. 使用缓存:可以使用缓存技术减少对数据库的访问,提高系统性能。
  3. 合理设计分片策略:选择合适的分片策略,避免数据热点问题,提高系统整体性能。
ShardingJDBC更新日志与版本兼容性

ShardingJDBC 的更新日志记录了每个版本的改进和修复的问题。一般来说,ShardingJDBC 的新版本会包含更多的性能优化和新的特性。在使用新版本时,需要注意版本之间的兼容性问题,确保新版本不会影响现有系统的运行。

dependencies:
  sharding-jdbc:
    version: 4.1.1

例如,如果从 4.0.0 升级到 4.1.1,需要确保新的版本配置不会影响现有系统的分片规则配置。

import org.apache.shardingsphere.api.config.sharding.KeyGeneratorConfiguration;
import org.apache.shardingsphere.api.config.sharding.TableRuleConfiguration;
import org.apache.shardingsphere.api.config.sharding.strategy.StandardShardingStrategyConfiguration;
import org.apache.shardingsphere.api.config.sharding.ShardingRuleConfiguration;
import org.apache.shardingsphere.api.config.sharding.TableShardingStrategyConfiguration;
import org.apache.shardingsphere.api.config.sharding.strategy.StandardShardingAlgorithm;
import org.apache.shardingsphere.shardingjdbc.api.config.ShardingRuleConfiguration;
import org.apache.shardingsphere.shardingjdbc.api.config.ShardingContext;
import org.apache.shardingsphere.shardingjdbc.api.config.DataSourceConfiguration;
import org.apache.shardingsphere.shardingjdbc.api.config.DatabaseConfiguration;

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

public class ShardingJDBCDemo {
    public static void main(String[] args) {
        Map<String, DataSourceConfiguration> dataSourceMap = new HashMap<>();
        dataSourceMap.put("db_0", new DataSourceConfiguration("com.zaxxer.hikari.HikariDataSource", "jdbc:mysql://localhost:3306/db_0", "root", "root"));
        dataSourceMap.put("db_1", new DataSourceConfiguration("com.zaxxer.hikari.HikariDataSource", "jdbc:mysql://localhost:3306/db_1", "root", "root"));

        ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
        shardingRuleConfig.getTableRuleConfigs().add(new TableRuleConfiguration("t_order", "t_order_%s"));

        StandardShardingStrategyConfiguration dbStrategyConfig = new StandardShardingStrategyConfiguration("order_id", new StandardShardingAlgorithm() {
            @Override
            public String doSelectShardingValue(Object[] values) {
                return "db_" + ((String) values[0]).substring(0, 1);
            }
        });

        StandardShardingStrategyConfiguration tableStrategyConfig = new StandardShardingStrategyConfiguration("order_id", new StandardShardingAlgorithm() {
            @Override
            public String doSelectShardingValue(Object[] values) {
                return "t_order_" + ((String) values[0]).substring(0, 1);
            }
        });

        TableRuleConfiguration orderTableRuleConfig = new TableRuleConfiguration("t_order", "t_order_%s");
        orderTableRuleConfig.setDatabaseShardingStrategyConfig(dbStrategyConfig);
        orderTableRuleConfig.setTableShardingStrategyConfig(tableStrategyConfig);
        orderTableRuleConfig.setKeyGeneratorConfig(new KeyGeneratorConfiguration("SNOWFLAKE", "order_id"));

        shardingRuleConfig.getTableRuleConfigs().add(orderTableRuleConfig);

        DatabaseConfiguration databaseConfig = new DatabaseConfiguration("sharding_db", dataSourceMap, shardingRuleConfig);
        ShardingContext shardingContext = new ShardingContext(databaseConfig);
    }
}
点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消