iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > Python >Java中ShardingSphere 数据分片的实现
  • 603
分享到

Java中ShardingSphere 数据分片的实现

2024-04-02 19:04:59 603人浏览 独家记忆

Python 官方文档:入门教程 => 点击学习

摘要

目录前言ShardingSphere介绍为什么不用mycat实践前的准备工作代码案例前言 其实很多人对分库分表多少都有点恐惧,其实我也是,总觉得这玩意是运维干的、数据量上来了或者sq

前言

其实很多人对分库分表多少都有点恐惧,其实我也是,总觉得这玩意是运维干的、数据量上来了或者sql过于复杂、一些数据分片的中间件支持的也不是很友好、配置繁琐等多种问题。

我们今天用ShardingSphere 给大家演示数据分片,包括分库分表、只分表不分库进行说明。

下一节有时间的话在讲讲读写分离吧。

GitHub地址:https://github.com/362460453/boot-sharding-JDBC

ShardingSphere介绍

ShardingSphere是一套开源分布式数据库中间件解决方案组成的生态圈,它由Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar(计划中)这3款相互独立的产品组成。 他们均提供标准化的数据分片、分布式事务数据库治理功能,可适用于如Java同构、异构语言、容器云原生等各种多样化的应用场景。

ShardingSphere的功能能帮助我们做什么

  • 数据分片
  • 读写分离
  • 编排治理
  • 分布式事务

2016年初Sharding-JDBC被开源,这个产品是当当的,加入了Apache 后改名为 ShardingSphere 。他是我们应用和数据库之间的中间层,虽代码入侵性很强,但不会对现有业务逻辑进行改变。

更多文档请点击官网:Https://shardingsphere.apache.org/document/current/en/overview/

为什么不用mycat

大家如果去查相关资料会知道,mycat和ShardingSphere是同类型的中间件,主要的功能,数据分片和读写分离两个都能去做,但是姿势却有很大的差别, 从字面意义上看Sharding 含义是分片、碎片的意思,所以不难理解ShardingSphere 对数据分片有很强对能力,对于99%对sql都是支持的,官网也有sql支持的相关内容,大家详细阅读,只有 类似sum 这种函数不支持,而且对 ORM框架和常用数据库基本都兼容,所以个人建议如果你们做数据分片,也就是是分库分表对话,强烈建议选择ShardingSphere,因为我私下也和一些朋友交流过,mycat 的数据分片对多表查询不是很友好,而且用 mycat 要有很强的运维来做,还有一点就是mycat 都是靠xml配置的,没有代码入侵,所以这也算是他的优点吧。如果你们只做读写分离对话,那么我建议用mycat,是没问题的。

实践前的准备工作

启动你的Mysql,创建两个数据库,分别叫 sharding_master 和 sharding_salve分别在这两个数据库执行如下sql


CREATE TABLE IF NOT EXISTS `t_order_0` (
  `order_id` INT NOT NULL,
  `user_id`  INT NOT NULL,
  PRIMARY KEY (`order_id`)
);
CREATE TABLE IF NOT EXISTS `t_order_1` (
  `order_id` INT NOT NULL,
  `user_id`  INT NOT NULL,
  PRIMARY KEY (`order_id`)
);

做完以上两步结果如下

代码案例

环境

工具 版本
jdk

1.8.0_144

SpringBoot 2.0.4.RELEASE
sharding 1.3.1
mysql 5.7

创建一个springboot工程,我们使用 JdbcTemplate 框架,如果用mybatis也是无影响的。

pom引用依赖如下


<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.4.RELEASE</version>
    </parent>
 
    <properties>
        <java.version>1.8</java.version>
        <druid.version>1.0.26</druid.version>
        <sharding.jdbc.core.version>1.3.3</sharding.jdbc.core.version>
    </properties>
 
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-WEB</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.dangdang</groupId>
            <artifactId>sharding-jdbc-core</artifactId>
            <version>${sharding.jdbc.core.version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>${druid.version}</version>
        </dependency>
    </dependencies>

application.yml 配置如下


server:
  port: 8050
sharding:
  jdbc: 
    driverClassName: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/sharding_master?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&failOverReadOnly=false
    username: root
    passWord: 123456
    filters: stat
    maxActive: 100
    initialSize: 1
    maxWait: 15000
    minIdle: 1
    timeBetweenEvictionRunsMillis: 30000
    minEvictableIdleTimeMillis: 180000
    validationQuery: SELECT 'x'
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: false
    maxPoolPreparedStatementPerConnectionSize: 20
    removeAbandoned: true
    removeAbandonedTimeout: 600
    logAbandoned: false
    connectionInitSqls: 
    
    url0: jdbc:mysql://localhost:3306/sharding_master?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&failOverReadOnly=false
    username0: root
    password0: 123456
    
    url1: jdbc:mysql://localhost:3306/sharding_salve?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&failOverReadOnly=false
    username1: root
    password1: 123456

yml映射成Bean


@Data
@ConfigurationProperties(prefix="sharding.jdbc")
public class ShardDataSourceProperties {
	
	private String driverClassName;
	
	private String url;
	
	private String username;
	
	private String password;
	
	private String url0;
	
	private String username0;
	
	private String password0;
	
	private String url1;
	
	private String username1;
	
	private String password1;
	
	private String filters;
	
	private int maxActive;
	
	private int initialSize;
	
	private int maxWait;
	
	private int minIdle;
	
	private int timeBetweenEvictionRunsMillis;
	
	private int minEvictableIdleTimeMillis;
	
	private String validationQuery;
	
	private boolean testWhileIdle;
	
	private boolean testOnBorrow;
	
	private boolean testOnReturn;
	
	private boolean poolPreparedStatements;
	
	private int maxPoolPreparedStatementPerConnectionSize;
	
	private boolean removeAbandoned;
 
	private int removeAbandonedTimeout;
	
	private boolean logAbandoned;
	
	private List<String> connectionInitSqls;
//省略geter setter

分库策略


//通过实现SingleKeyDatabaseShardingAlGorithm接口实现分库
public class ModuloDatabaseShardingAlgorithm implements SingleKeyDatabaseShardingAlgorithm<Integer> {
 
	@Override
	public String doEqualSharding(Collection<String> availableTargetNames, ShardingValue<Integer> shardingValue) {
		for (String each : availableTargetNames) {
            if (each.endsWith(shardingValue.getValue() % 2 + "")) {
                return each;
            }
        }
        throw new IllegalArgumentException();
	}
 
	@Override
	public Collection<String> doInSharding(Collection<String> availableTargetNames,
			ShardingValue<Integer> shardingValue) {
		Collection<String> result = new LinkedHashSet<>(availableTargetNames.size());
        for (Integer value : shardingValue.getValues()) {
            for (String targetName : availableTargetNames) {
                if (targetName.endsWith(value % 2 + "")) {
                    result.add(targetName);
                }
            }
        }
        return result;
	}
 
	@Override
	public Collection<String> doBetweenSharding(Collection<String> availableTargetNames,
			ShardingValue<Integer> shardingValue) {
		Collection<String> result = new LinkedHashSet<>(availableTargetNames.size());
        Range<Integer> range = (Range<Integer>) shardingValue.getValueRange();
        for (Integer i = range.lowerEndpoint(); i <= range.upperEndpoint(); i++) {
            for (String each : availableTargetNames) {
                if (each.endsWith(i % 2 + "")) {
                    result.add(each);
                }
            }
        }
        return result;
	}
}

分表策略


public class ModuloTableShardingAlgorithm implements SingleKeyTableShardingAlgorithm<Integer> {
 
    
	@Override
    public String doEqualSharding(final Collection<String> tableNames, final ShardingValue<Integer> shardingValue) {
        for (String each : tableNames) {
            if (each.endsWith(shardingValue.getValue() % 2 + "")) {
                return each;
            }
        }
        throw new IllegalArgumentException();
    }
    
    
	@Override
    public Collection<String> doInSharding(final Collection<String> tableNames, final ShardingValue<Integer> shardingValue) {
        Collection<String> result = new LinkedHashSet<>(tableNames.size());
        for (Integer value : shardingValue.getValues()) {
            for (String tableName : tableNames) {
                if (tableName.endsWith(value % 2 + "")) {
                    result.add(tableName);
                }
            }
        }
        return result;
    }
    
    
	@Override
    public Collection<String> doBetweenSharding(final Collection<String> tableNames, final ShardingValue<Integer> shardingValue) {
        Collection<String> result = new LinkedHashSet<>(tableNames.size());
        Range<Integer> range = (Range<Integer>) shardingValue.getValueRange();
        for (Integer i = range.lowerEndpoint(); i <= range.upperEndpoint(); i++) {
            for (String each : tableNames) {
                if (each.endsWith(i % 2 + "")) {
                    result.add(each);
                }
            }
        }
        return result;
    } 
}

对特定表和库,进行特定的分库分表规则

简单说,就是分库按照了user_id的奇偶区分,分表按照order_id 的奇偶区分,

如果你有多个表进行分片,就写多个TableRule,

配置两个数据源,分别是我在yml里的配置,根据你的需求个性化配置就可以。


@Configuration
@EnableConfigurationProperties(ShardDataSourceProperties.class)
public class ShardDataSourceConfig {
 
	@Autowired
	private ShardDataSourceProperties shardDataSourceProperties;
 
	private DruidDataSource parentDs() throws SQLException {
		DruidDataSource ds = new DruidDataSource();
		ds.setDriverClassName(shardDataSourceProperties.getDriverClassName());
		ds.setUsername(shardDataSourceProperties.getUsername());
		ds.setUrl(shardDataSourceProperties.getUrl());
		ds.setPassword(shardDataSourceProperties.getPassword());
		ds.setFilters(shardDataSourceProperties.getFilters());
		ds.setMaxActive(shardDataSourceProperties.getMaxActive());
		ds.setInitialSize(shardDataSourceProperties.getInitialSize());
		ds.setMaxWait(shardDataSourceProperties.getMaxWait());
		ds.setMinIdle(shardDataSourceProperties.getMinIdle());
		ds.setTimeBetweenEvictionRunsMillis(shardDataSourceProperties.getTimeBetweenEvictionRunsMillis());
		ds.setMinEvictableIdleTimeMillis(shardDataSourceProperties.getMinEvictableIdleTimeMillis());
		ds.setValidationQuery(shardDataSourceProperties.getValidationQuery());
		ds.setTestWhileIdle(shardDataSourceProperties.isTestWhileIdle());
		ds.setTestOnBorrow(shardDataSourceProperties.isTestOnBorrow());
		ds.setTestOnReturn(shardDataSourceProperties.isTestOnReturn());
		ds.setPoolPreparedStatements(shardDataSourceProperties.isPoolPreparedStatements());
		ds.setMaxPoolPreparedStatementPerConnectionSize(
				shardDataSourceProperties.getMaxPoolPreparedStatementPerConnectionSize());
		ds.setRemoveAbandoned(shardDataSourceProperties.isRemoveAbandoned());
		ds.setRemoveAbandonedTimeout(shardDataSourceProperties.getRemoveAbandonedTimeout());
		ds.setLogAbandoned(shardDataSourceProperties.isLogAbandoned());
		ds.setConnectionInitSqls(shardDataSourceProperties.getConnectionInitSqls());
		return ds;
	}
 
	private DataSource ds0() throws SQLException {
		DruidDataSource ds = parentDs();
		ds.setUsername(shardDataSourceProperties.getUsername0());
		ds.setUrl(shardDataSourceProperties.getUrl0());
		ds.setPassword(shardDataSourceProperties.getPassword0());
		return ds;
	}
 
	private DataSource ds1() throws SQLException {
		DruidDataSource ds = parentDs();
		ds.setUsername(shardDataSourceProperties.getUsername1());
		ds.setUrl(shardDataSourceProperties.getUrl1());
		ds.setPassword(shardDataSourceProperties.getPassword1());
		return ds;
	}
 
	private DataSourceRule dataSourceRule() throws SQLException {
		Map<String, DataSource> dataSourceMap = new HashMap<>(2);
		dataSourceMap.put("ds_0", ds0());
		dataSourceMap.put("ds_1", ds1());
		DataSourceRule dataSourceRule = new DataSourceRule(dataSourceMap);
		return dataSourceRule;
	}
//对order对策略
	private TableRule orderTableRule() throws SQLException {
		TableRule orderTableRule = TableRule.builder("t_order").actualTables(Arrays.asList("t_order_0", "t_order_1"))
				.dataSourceRule(dataSourceRule()).build();
		return orderTableRule;
	}
 
//分库分表策略
	private ShardingRule shardingRule() throws SQLException {
		ShardingRule shardingRule = ShardingRule.builder().dataSourceRule(dataSourceRule())
				.tableRules(Arrays.asList(orderTableRule(), orderItemTableRule()))
				.databaseShardingStrategy(
						new DatabaseShardingStrategy("user_id", new ModuloDatabaseShardingAlgorithm()))
				.tableShardingStrategy(new TableShardingStrategy("order_id", new ModuloTableShardingAlgorithm()))
				.build();
		return shardingRule;
	}
 
	@Bean
	public DataSource dataSource() throws SQLException {
		return ShardingDataSourceFactory.createDataSource(shardingRule());
	}
 
 
    @Bean
    public PlatformTransactionManager transactionManager() throws SQLException {
        return new DataSourceTransactionManager(dataSource());
    }
}

我们需要从controller调用接口进行对数据的增加和查询

下面所有的类都是用来模拟请求进行测试


@RestController
@RequestMapping("/order")
public class OrderController {
    @Autowired
    private OrderDao orderDao;
 
    @RequestMapping(path = "/createOrder/{userId}/{orderId}", method = {RequestMethod.GET})
    public String createOrder(@PathVariable("userId") Integer userId, @PathVariable("orderId") Integer orderId) {
        Order order = new Order();
        order.setOrderId(orderId);
        order.setUserId(userId);
        orderDao.createOrder(order);
        return "success";
    }
 
    @RequestMapping(path = "/{userId}", method = {RequestMethod.GET})
    public List<Order> getOrderListByUserId(@PathVariable("userId") Integer userId) {
        return orderDao.getOrderListByUserId(userId);
    }
}
 
 
---------------------------------------------------
public interface OrderDao {
    List<Order> getOrderListByUserId(Integer userId);
 
    void createOrder(Order order);
}
---------------------------------------------------
@Service
public class OrderDaoImpl implements OrderDao {
    @Autowired
    JdbcTemplate jdbcTemplate;
 
 
    @Override
    public List<Order> getOrderListByUserId(Integer userId) {
 
        StringBuilder sqlBuilder = new StringBuilder();
        sqlBuilder
                .append("select order_id, user_id from t_order where user_id=? ");
        return jdbcTemplate.query(sqlBuilder.toString(), new Object[]{userId},
                new int[]{Types.INTEGER}, new BeanPropertyRowMapper<Order>(
                        Order.class));
    }
 
    @Override
    public void createOrder(Order order) {
        StringBuffer sb = new StringBuffer();
        sb.append("insert into t_order(user_id, order_id)");
        sb.append("values(");
        sb.append(order.getUserId()).append(",");
        sb.append(order.getOrderId());
        sb.append(")");
        jdbcTemplate.update(sb.toString());
 
    }
}
 
---------------------------------------------------
public class Order implements Serializable {
 
	private int userId;
 
	private int orderId;
 
---------------------------------------------------
@SpringBootApplication
public class Application {
	
	public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

测试

启动项目,访问:http://localhost:8050/order/createOrder/1/1

更换参数多次访问,可以插入多条记录,观察你的数据库入库情况,已经按照我们制定的分库分表策略进行划分了。

需要注意的是

shareding是不支持jdbctemplate的批量修改操作的。

表名前不要加上库名,原生的情况加库名,不加库名其实是一样的,但使用shareding的表就会报错。

如果想进行只分表不分库的话

  • 注释掉 ModuloDatabaseShardingAlgorithm 类
  • 还有ShardDataSourceConfig.shardingRule() 中的分库策略那行代码
  • 还有相关数据源配置改成 1 个

到此这篇关于Java中ShardingSphere 数据分片的实现的文章就介绍到这了,更多相关ShardingSphere 数据分片内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: Java中ShardingSphere 数据分片的实现

本文链接: https://www.lsjlt.com/news/136614.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Java中ShardingSphere 数据分片的实现
    目录前言ShardingSphere介绍为什么不用mycat实践前的准备工作代码案例前言 其实很多人对分库分表多少都有点恐惧,其实我也是,总觉得这玩意是运维干的、数据量上来了或者sq...
    99+
    2022-11-12
  • Java基于ShardingSphere实现分库分表的实例详解
    目录一、简介二、项目使用1、引入依赖2、数据库3、实体类4、mapper5、yml配置6、测试类7、数据一、简介   Apache ShardingSphere ...
    99+
    2022-11-13
  • ShardingSphere jdbc集成多数据源的实现步骤
    目录集成sharding jdbc1. 引入依赖2. 配置分表规则问题集成多数据源1. 引入依赖2. 多数据源配置3. 增加多数据源配置4. 使用总结最近有个项目的几张表,数量级在千...
    99+
    2022-11-12
  • Redis数据分片如何实现
    今天小编给大家分享一下Redis数据分片如何实现的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。Twemproxy的介绍Twi...
    99+
    2023-06-27
  • ShardingSphere x Seata,一致性更强的分布式数据库中间件
    日前,分布式数据库中间件 ShardingSphere 将 Seata 分布式事务能力进行整合,旨在打造一致性更强的分布式数据库中间件。背景数据库领域,分布式事务的实现主要包含:两阶段的 XA 和 BASE 柔性事务。X...
    99+
    2023-06-05
  • PHP实现MongoDB数据库分片的方法
    随着数据量的增加,单个MongoDB实例的存储和处理能力可能会受到限制,导致性能下降。为了更好地处理大量数据,MongoDB提供了分片的功能,在多个服务器上分散数据以提高性能和可用性。PHP作为一种常用的Web编程语言,本文将介绍如何使用P...
    99+
    2023-05-18
    PHP MongoDB 分片
  • PHP实现Redis数据库分片的方法
    Redis是一款高性能的NoSQL数据库,而分片是一种常用的数据分布式处理方法,可以提高数据库的性能和扩展性。本文将介绍如何使用PHP实现Redis数据库分片。Redis分片概述Redis分片是将一个大的Redis数据库拆分成多个较小的Re...
    99+
    2023-05-17
    PHP Redis数据库 分片 (Sharding)
  • Mycat中间件实现Mysql数据分片( 下篇)
    9.数据按节点(DataNode)分片 [root@k8s01 conf]# vim schema.xml [root@k8s01 conf]# vim rule.xml id:表示字段...
    99+
    2022-10-18
  • Mycat中间件如何实现Mysql数据分片
    这篇文章主要介绍了Mycat中间件如何实现Mysql数据分片,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。架构图:机器规划:IP地址主机名角...
    99+
    2022-10-18
  • PHP实现数据库分片缩容的方法
    随着互联网应用的发展,数据量的快速增长已经成为了公司面临的一大难题,而数据库分片技术正是为了应对这个问题而诞生的。数据库分片(Sharding),是将大型数据库分割成多个较小数据库的技术。通过分片,可以将数据分散到多个服务器上,从而实现水平...
    99+
    2023-05-18
    PHP 缩容 数据库分片
  • Java的分片上传功能的实现
    目录整体思路前端代码后端代码结尾起因:最近在工作中接到了一个大文件上传下载的需求,要求将文件上传到share盘中,下载的时候根据前端传的不同条件对单个或多个文件进行打包并设置目录下载...
    99+
    2023-02-14
    Java 分片上传
  • Redis如何实现数据分片扩展功能
    Redis是一款被广泛应用的开源Key-Value数据库,以其高性能、低延迟、高并发等优点深受开发者的青睐。然而随着数据量的不断增加,单节点的Redis已经无法满足业务需求。为了解决这个问题,Redis引入了数据分片(Sharding)功能...
    99+
    2023-11-07
    Redis数据分片 数据分片扩展 Redis扩展性
  • 怎么使用PHP实现Memcached数据库分片
    这篇文章主要介绍了怎么使用PHP实现Memcached数据库分片的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇怎么使用PHP实现Memcached数据库分片文章都会有所收获,下面我们一起来看看吧。Memcach...
    99+
    2023-07-06
  • 57-4 数据库分片概念及mongodb sharding的实现
    04 数据库分片的概念及mongodb sharding的实现配置环境:node1: 192.168.1.121 CentOS release 6.7node2: 192.168.1.122 CentOS ...
    99+
    2022-10-18
  • java如何实现图片转化为数据流
    目录实现图片转化为数据流方法如下使用方法如下把图片转换成二进制流的代码java中如何把图片转换成二进制流的代码从SQLServer数据库读取Image类型的数据实现图片转化为数据流 ...
    99+
    2022-11-13
  • java怎么实现图片转化为数据流
    这篇文章主要介绍了java怎么实现图片转化为数据流的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇java怎么实现图片转化为数据流文章都会有所收获,下面我们一起来看看吧。实现图片转化为数据流方法如下public&...
    99+
    2023-06-29
  • Java中xxl-job实现分片广播任务的示例
    目录xxl-job 分片广播任务示例1示例2总结xxl-job 是一个分布式任务调度平台,支持定时任务和分片任务。其中,分片任务可以将一个大任务拆分成多个小任务,分布式地执行,提高任...
    99+
    2023-03-19
    Java xxl-job分片广播 xxl-job分片广播
  • SpringBoot使用Sharding-JDBC实现数据分片和读写分离的方法
    目录一、Sharding-JDBC简介二、具体的实现方式 1、maven引用2、数据库准备3、Spring配置4、精准分片算法和范围分片算法的Java代码5、测试一、Sha...
    99+
    2022-11-12
  • Java中xxl-job如何实现分片广播任务
    本篇内容介绍了“Java中xxl-job如何实现分片广播任务”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!xxl-job 是一个分布式任务调...
    99+
    2023-07-05
  • 如何使用MongoDB实现数据的复制和分片功能
    如何使用MongoDB实现数据的复制和分片功能引言:MongoDB是一个十分流行的NoSQL数据库系统,它具有高性能、可扩展性和可靠性等特点。在大数据时代,数据量的增长是一种常态,因此数据的复制和分片成为了保证数据可靠性和性能的关键功能。本...
    99+
    2023-10-22
    MongoDB 数据复制 分片功能
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作