IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 【Sharding-JDBC】SpringBoot2 + sharding-jdbc实现分库分表读写分离 -> 正文阅读

[Java知识库]【Sharding-JDBC】SpringBoot2 + sharding-jdbc实现分库分表读写分离

一、背景

1.1 环境信息

组件版本版本
windows10
mysqlMysql 8.0.25
springboot2.4.10
sharding-jdbc-spring-boot-starter4.0.0-RC1

1.2 前言

前面已经使用了sharding-jdbc的分库分表功能,sharding-jdbc的另一大功能就是读写分离。对于同一时刻有大量并发读操作和较少写操作类型的应用系统来说,将数据库拆分为主库和从库,主库负责处理事务性的增删改操作,从库负责处理查询操作,能够有效的避免由数据更新导致的行锁,使得整个系统的查询性能得到极大的改善。

我们对前面讲的分库分表的案例再进行读写分离设计。开发前我们需要安装一个从库,并配置好主从同步(参考)。

1.3 表结构信息

在这里插入图片描述

1.4 数据库信息

类型信息
mysql服务主服务(localhsot:3306)从服务(localhsot:3307)
ds1逻辑数据库主库(m1):localhsot:3306/product_db_1从库(s1):localhsot:3307/product_db_1
ds2逻辑数据库主库(m2):localhsot:3306/product_db_2从库(s2):localhsot:3307/product_db_2
  1. 逻辑数据库ds1由主库m1(3306/product_db_1)和从库s1(3307/product_db_1)组成。
  2. 逻辑数据库ds1由主库m2(3306/product_db_2)和从库s2(3307/product_db_2)组成。
  3. 主服务(localhsot:3306)和从服务(localhsot:3307)之间要配置主从同步。
  4. 产品库中的产品表(product_info)和产品描述表(product_desc)根据店铺id取模的方式水平分库,分为product_db_1和product_db_2两个库。
  5. 产品表(product_info)和产品描述表(product_desc)又根据产品id取模的方式进行水平分表。product_info分为product_info_1和product_info_2,product_desc分为product_desc_1和product_desc_2。
  6. 产品表(product_info)和产品描述表(product_desc)水平分库和水平分表的方式都相同,因此可以作为绑定表
  7. 城市表(t_city)是配置表,数据量小。是作为和产品表关联查询产品信息的表。作为广播表
    请添加图片描述

二、代码实现

2.1 概述

sharding-jdbc实现读写分离,只需要引入对应的jar依赖,在配置文件里配置即可。这里的分表策略使用shrding-jdbc的行表达式分片策略实现。行表达式使用的是Groovy的表达式,提供对SQL语句中的=和IN的分片操作支持,只支持单分片键。对于简单的分片算法,可以通过简单的配置使用,从而避免繁琐的Java代码开发,如: t_user_$->{u_id % 8} 表示t_user表根据u_id模8,而分成8张表,表名称为t_user_0到t_user_7。

其他分片策略请参考:

2.2 主要代码

源码地址

2.2.1 pom.xml

<dependencies>
	<!-- web依赖 -->
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
	<!-- lombok插件 -->
	<dependency>
		<groupId>org.projectlombok</groupId>
		<artifactId>lombok</artifactId>
		<optional>true</optional>
	</dependency>
	<!--mybatis-->
	<dependency>
		<groupId>org.mybatis.spring.boot</groupId>
		<artifactId>mybatis-spring-boot-starter</artifactId>
		<version>2.1.2</version>
	</dependency>
	<!--pageHelper分页插件-->
	<dependency>
		<groupId>com.github.pagehelper</groupId>
		<artifactId>pagehelper-spring-boot-starter</artifactId>
		<version>1.2.12</version>
	</dependency>
	<!-- 指定使用5.1.44版本驱动 -->
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<version>8.0.22</version>
	</dependency>
	<!--druid数据源-->
	<dependency>
		<groupId>com.alibaba</groupId>
		<artifactId>druid-spring-boot-starter</artifactId>
		<version>1.1.10</version>
	</dependency>
	<!--工具包-->
	<dependency>
		<groupId>cn.hutool</groupId>
		<artifactId>hutool-all</artifactId>
		<version>5.6.5</version>
	</dependency>
	<!--sharding-jdbc-->
	<dependency>
		<groupId>org.apache.shardingsphere</groupId>
		<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
		<version>4.0.0-RC1</version>
	</dependency>
</dependencies>

2.2.3 application.properties

server.port=8081
spring.profiles.active=dxfl
spring.main.allow-bean-definition-overriding=true
############## mybatis Configuration ###########
mybatis.mapper-locations=classpath:mapper/*Mapper.xml
mybatis.type-aliases-package=com.lh.boot.sharding.jdbc.entity
############## mybatis Configuration ###########
############ PageHelper Configuration ########
#数据库的方言
pagehelper.helper-dialect=mysql
#启用合理化,如果pageNum < 1会查询第一页,如果pageNum > pages会查询最后一页
pagehelper.reasonable=true
#是否将参数offset作为PageNum使用
pagehelper.offset-as-page-num=true
#是否进行count查询
pagehelper.row-bounds-with-count=true

2.2.4 application-dxfl.properties

################## 读写分离配置 ###########################
# ======================================================
# sharding-jdbc公共配置
# ======================================================
datasource.type=com.alibaba.druid.pool.DruidDataSource
datasource.driver-class-name=com.mysql.cj.jdbc.Driver
datasource.filters=stat
datasource.maxActive=100
datasource.initialSize=40
datasource.maxWait=10000
datasource.minIdle=40
datasource.timeBetweenEvictionRunsMillis=60000
datasource.minEvictableIdleTimeMillis=300000
datasource.validationQuery=SELECT 1
datasource.testWhileIdle=true
datasource.testOnBorrow=false
datasource.testOnReturn=false
datasource.poolPreparedStatements=true
datasource.maxOpenPreparedStatements=20
# ======================================================
# sharding-jdbc配置,数据源名称
# ======================================================
spring.shardingsphere.datasource.names=m1,m2,s1,s2
# ======================================================
# 数据源m1配置信息
# ======================================================
spring.shardingsphere.datasource.m1.url=jdbc:mysql://localhost:3306/product_db_1?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&autoReconnectForPools=true&noAccessToProcedureBodies=true&useSSL=false&serverTimezone=Asia/Shanghai
spring.shardingsphere.datasource.m1.username=root
spring.shardingsphere.datasource.m1.password=123456
spring.shardingsphere.datasource.m1.type=${datasource.type}
spring.shardingsphere.datasource.m1.driver-class-name=${datasource.driver-class-name}
spring.shardingsphere.datasource.m1.filters=${datasource.filters}
spring.shardingsphere.datasource.m1.maxActive=${datasource.maxActive}
spring.shardingsphere.datasource.m1.initialSize=${datasource.initialSize}
spring.shardingsphere.datasource.m1.maxWait=${datasource.maxWait}
spring.shardingsphere.datasource.m1.minIdle=${datasource.minIdle}
spring.shardingsphere.datasource.m1.timeBetweenEvictionRunsMillis=${datasource.timeBetweenEvictionRunsMillis}
spring.shardingsphere.datasource.m1.minEvictableIdleTimeMillis=${datasource.minEvictableIdleTimeMillis}
spring.shardingsphere.datasource.m1.validationQuery=${datasource.validationQuery}
spring.shardingsphere.datasource.m1.testWhileIdle=${datasource.testWhileIdle}
spring.shardingsphere.datasource.m1.testOnBorrow=${datasource.testOnBorrow}
spring.shardingsphere.datasource.m1.testOnReturn=${datasource.testOnReturn}
spring.shardingsphere.datasource.m1.poolPreparedStatements=${datasource.poolPreparedStatements}
spring.shardingsphere.datasource.m1.maxOpenPreparedStatements=${datasource.maxOpenPreparedStatements}
#======================================================
# 数据源m2配置信息
#======================================================
spring.shardingsphere.datasource.m2.url=jdbc:mysql://localhost:3306/product_db_2?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&autoReconnectForPools=true&noAccessToProcedureBodies=true&useSSL=false&serverTimezone=Asia/Shanghai
spring.shardingsphere.datasource.m2.username=root
spring.shardingsphere.datasource.m2.password=123456
spring.shardingsphere.datasource.m2.type=${datasource.type}
spring.shardingsphere.datasource.m2.driver-class-name=${datasource.driver-class-name}
spring.shardingsphere.datasource.m2.filters=${datasource.filters}
spring.shardingsphere.datasource.m2.maxActive=${datasource.maxActive}
spring.shardingsphere.datasource.m2.initialSize=${datasource.initialSize}
spring.shardingsphere.datasource.m2.maxWait=${datasource.maxWait}
spring.shardingsphere.datasource.m2.minIdle=${datasource.minIdle}
spring.shardingsphere.datasource.m2.timeBetweenEvictionRunsMillis=${datasource.timeBetweenEvictionRunsMillis}
spring.shardingsphere.datasource.m2.minEvictableIdleTimeMillis=${datasource.minEvictableIdleTimeMillis}
spring.shardingsphere.datasource.m2.validationQuery=${datasource.validationQuery}
spring.shardingsphere.datasource.m2.testWhileIdle=${datasource.testWhileIdle}
spring.shardingsphere.datasource.m2.testOnBorrow=${datasource.testOnBorrow}
spring.shardingsphere.datasource.m2.testOnReturn=${datasource.testOnReturn}
spring.shardingsphere.datasource.m2.poolPreparedStatements=${datasource.poolPreparedStatements}
spring.shardingsphere.datasource.m2.maxOpenPreparedStatements=${datasource.maxOpenPreparedStatements}
# ======================================================
# 数据源s1配置信息
# ======================================================
spring.shardingsphere.datasource.s1.url=jdbc:mysql://localhost:3307/product_db_1?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&autoReconnectForPools=true&noAccessToProcedureBodies=true&useSSL=false&serverTimezone=Asia/Shanghai
spring.shardingsphere.datasource.s1.username=root
spring.shardingsphere.datasource.s1.password=123456
spring.shardingsphere.datasource.s1.type=${datasource.type}
spring.shardingsphere.datasource.s1.driver-class-name=${datasource.driver-class-name}
spring.shardingsphere.datasource.s1.filters=${datasource.filters}
spring.shardingsphere.datasource.s1.maxActive=${datasource.maxActive}
spring.shardingsphere.datasource.s1.initialSize=${datasource.initialSize}
spring.shardingsphere.datasource.s1.maxWait=${datasource.maxWait}
spring.shardingsphere.datasource.s1.minIdle=${datasource.minIdle}
spring.shardingsphere.datasource.s1.timeBetweenEvictionRunsMillis=${datasource.timeBetweenEvictionRunsMillis}
spring.shardingsphere.datasource.s1.minEvictableIdleTimeMillis=${datasource.minEvictableIdleTimeMillis}
spring.shardingsphere.datasource.s1.validationQuery=${datasource.validationQuery}
spring.shardingsphere.datasource.s1.testWhileIdle=${datasource.testWhileIdle}
spring.shardingsphere.datasource.s1.testOnBorrow=${datasource.testOnBorrow}
spring.shardingsphere.datasource.s1.testOnReturn=${datasource.testOnReturn}
spring.shardingsphere.datasource.s1.poolPreparedStatements=${datasource.poolPreparedStatements}
spring.shardingsphere.datasource.s1.maxOpenPreparedStatements=${datasource.maxOpenPreparedStatements}
#======================================================
# 数据源m2配置信息
#======================================================
spring.shardingsphere.datasource.s2.url=jdbc:mysql://localhost:3307/product_db_2?useUnicode=true&characterEncoding=utf-8&autoReconnect=true&autoReconnectForPools=true&noAccessToProcedureBodies=true&useSSL=false&serverTimezone=Asia/Shanghai
spring.shardingsphere.datasource.s2.username=root
spring.shardingsphere.datasource.s2.password=123456
spring.shardingsphere.datasource.s2.type=${datasource.type}
spring.shardingsphere.datasource.s2.driver-class-name=${datasource.driver-class-name}
spring.shardingsphere.datasource.s2.filters=${datasource.filters}
spring.shardingsphere.datasource.s2.maxActive=${datasource.maxActive}
spring.shardingsphere.datasource.s2.initialSize=${datasource.initialSize}
spring.shardingsphere.datasource.s2.maxWait=${datasource.maxWait}
spring.shardingsphere.datasource.s2.minIdle=${datasource.minIdle}
spring.shardingsphere.datasource.s2.timeBetweenEvictionRunsMillis=${datasource.timeBetweenEvictionRunsMillis}
spring.shardingsphere.datasource.s2.minEvictableIdleTimeMillis=${datasource.minEvictableIdleTimeMillis}
spring.shardingsphere.datasource.s2.validationQuery=${datasource.validationQuery}
spring.shardingsphere.datasource.s2.testWhileIdle=${datasource.testWhileIdle}
spring.shardingsphere.datasource.s2.testOnBorrow=${datasource.testOnBorrow}
spring.shardingsphere.datasource.s2.testOnReturn=${datasource.testOnReturn}
spring.shardingsphere.datasource.s2.poolPreparedStatements=${datasource.poolPreparedStatements}
spring.shardingsphere.datasource.s2.maxOpenPreparedStatements=${datasource.maxOpenPreparedStatements}
#======================================================
# 主库从库逻辑数据源定义
#======================================================
# ds1 主库为m1,从库为s1
spring.shardingsphere.sharding.master-slave-rules.ds1.master-data-source-name=m1
spring.shardingsphere.sharding.master-slave-rules.ds1.slave-data-source-names=s1
# ds2 主库为m2,从库为s2
spring.shardingsphere.sharding.master-slave-rules.ds2.master-data-source-name=m2
spring.shardingsphere.sharding.master-slave-rules.ds2.slave-data-source-names=s2
#======================================================
# product_info 产品信息表分库分表配置
#======================================================
# 指定 product_info 表的数据分布情况,配置数据节点 ds1.product_info_1,ds1.product_info_2,ds2.product_info_1,ds2.product_info_2
spring.shardingsphere.sharding.tables.product_info.actual-data-nodes=ds$->{1..2}.product_info_$->{1..2}
# 指定 product_info 表的主键生成策略 (sharding-jdbc生成时打开,自己在代码里生成时注释掉)
#spring.shardingsphere.sharding.tables.product_info.key-generator.column=product_id
#spring.shardingsphere.sharding.tables.product_info.key-generator.type=SNOWFLAKE
# 配置分库策略
spring.shardingsphere.sharding.tables.product_info.database-strategy.inline.sharding-column=store_id
spring.shardingsphere.sharding.tables.product_info.database-strategy.inline.algorithm-expression=ds$->{store_id % 2 + 1}
# 指定product_info表的分片策略,分片策略包含分片键和分片算法
spring.shardingsphere.sharding.tables.product_info.table-strategy.inline.sharding-column=product_id
spring.shardingsphere.sharding.tables.product_info.table-strategy.inline.algorithm-expression=product_info_$->{product_id % 2 +1}
#======================================================
# product_desc 分库分表配置
#======================================================
# 指定 product_desc 表的数据分布情况,配置数据节点 m1.product_desc_1,m1.product_desc_2,m2.t_product_desc_1,m2.t_product_desc_2
spring.shardingsphere.sharding.tables.product_desc.actual-data-nodes=ds$->{1..2}.product_desc_$->{1..2}
# 指定 product_desc 表的主键生成策略
#spring.shardingsphere.sharding.tables.product_desc.key-generator.column=product_id
#spring.shardingsphere.sharding.tables.product_desc.key-generator.type=SNOWFLAKE
# 配置分库策略
spring.shardingsphere.sharding.tables.product_desc.database-strategy.inline.sharding-column=store_id
spring.shardingsphere.sharding.tables.product_desc.database-strategy.inline.algorithm-expression=ds$->{store_id % 2 + 1}
# 指定 product_desc 表的分片策略,分片策略包含分片键和分片算法
spring.shardingsphere.sharding.tables.product_desc.table-strategy.inline.sharding-column=product_id
spring.shardingsphere.sharding.tables.product_desc.table-strategy.inline.algorithm-expression=product_desc_$->{product_id % 2 +1}
#======================================================
# 绑定表配置
#======================================================
# 设置 product_info,product_desc 为绑定表
spring.shardingsphere.sharding.binding-tables[0]=product_info,product_desc
#======================================================
# 广播表配置
#======================================================
# 设置t_city为广播表(公共表),每次更新操作会发送至所有数据源
spring.shardingsphere.sharding.broadcast-tables=t_city
#======================================================
# 日志配置
#======================================================
spring.shardingsphere.props.sql.show=true
logging.level.root=info
logging.level.org.springframework.web=info
logging.level.druid.sql=debug
logging.level.com.lh.boot.sharding.jdbc.mapper=debug

2.2.5 ProductInfoMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lh.boot.sharding.jdbc.mapper.ProductInfoMapper">
    <resultMap id="BaseResultMap" type="com.lh.boot.sharding.jdbc.entity.ProductInfo">
        <constructor>
            <idArg column="product_id" javaType="java.lang.Long" jdbcType="BIGINT"/>
            <arg column="store_id" javaType="java.lang.Long" jdbcType="BIGINT"/>
            <arg column="price" javaType="java.math.BigDecimal" jdbcType="DECIMAL"/>
            <arg column="product_name" javaType="java.lang.String" jdbcType="VARCHAR"/>
            <arg column="city" javaType="java.lang.String" jdbcType="VARCHAR"/>
            <arg column="status" javaType="java.lang.String" jdbcType="VARCHAR"/>
        </constructor>
    </resultMap>
    <sql id="Base_Column_List">
        product_id, store_id, price, product_name, city, status
    </sql>
    <select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List"/>
        from product_info
        where product_id = #{productId,jdbcType=BIGINT}
    </select>

    <delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
        delete from product_info
        where product_id = #{productId,jdbcType=BIGINT}
    </delete>
    <insert id="insert" parameterType="productInfo">
        insert into product_info (product_id, store_id, price,
        product_name, city, status
        )
        values (#{productId,jdbcType=BIGINT}, #{storeId,jdbcType=BIGINT}, #{price,jdbcType=DECIMAL},
        #{productName,jdbcType=VARCHAR}, #{city,jdbcType=VARCHAR}, #{status,jdbcType=VARCHAR}
        )
    </insert>
    <insert id="insertSelective" parameterType="productInfo">
        insert into product_info
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="productId != null">
                product_id,
            </if>
            <if test="storeId != null">
                store_id,
            </if>
            <if test="price != null">
                price,
            </if>
            <if test="productName != null">
                product_name,
            </if>
            <if test="city != null">
                city,
            </if>
            <if test="status != null">
                status,
            </if>
        </trim>
        <trim prefix="values (" suffix=")" suffixOverrides=",">
            <if test="productId != null">
                #{productId,jdbcType=BIGINT},
            </if>
            <if test="storeId != null">
                #{storeId,jdbcType=BIGINT},
            </if>
            <if test="price != null">
                #{price,jdbcType=DECIMAL},
            </if>
            <if test="productName != null">
                #{productName,jdbcType=VARCHAR},
            </if>
            <if test="city != null">
                #{city,jdbcType=VARCHAR},
            </if>
            <if test="status != null">
                #{status,jdbcType=VARCHAR},
            </if>
        </trim>
    </insert>
    <update id="updateByPrimaryKeySelective" parameterType="productInfo">
        update product_info
        <set>
            <if test="storeId != null">
                store_id = #{storeId,jdbcType=BIGINT},
            </if>
            <if test="price != null">
                price = #{price,jdbcType=DECIMAL},
            </if>
            <if test="productName != null">
                product_name = #{productName,jdbcType=VARCHAR},
            </if>
            <if test="city != null">
                city = #{city,jdbcType=VARCHAR},
            </if>
            <if test="status != null">
                status = #{status,jdbcType=VARCHAR},
            </if>
        </set>
        where product_id = #{productId,jdbcType=BIGINT}
    </update>
    <update id="updateByPrimaryKey" parameterType="productInfo">
        update product_info
        set store_id = #{storeId,jdbcType=BIGINT},
        price = #{price,jdbcType=DECIMAL},
        product_name = #{productName,jdbcType=VARCHAR},
        city = #{city,jdbcType=VARCHAR},
        status = #{status,jdbcType=VARCHAR}
        where product_id = #{productId,jdbcType=BIGINT}
    </update>
    <resultMap type="com.lh.boot.sharding.jdbc.entity.vo.ProductInfoVO" id="ProductInfoResultMap">
        <id property="productId" column="product_id"/>
        <result property="storeId" column="store_id"/>
        <result property="price" column="price"/>
        <result property="productName" column="product_name"/>
        <result property="city" column="city"/>
        <result property="cityName" column="city_name"/>
        <result property="status" column="status"/>
        <result property="productSize" column="product_size"/>
        <result property="stock" column="stock"/>
        <result property="descInfo" column="desc_info"/>
    </resultMap>
    <select id="selectProductInfoVOList" resultMap="ProductInfoResultMap">
        select
            pri.product_id ,
            pri.product_name ,
            pri.price ,
            pri.status ,
            pri.store_id ,
            pri.city ,
            pd.desc_info ,
            pd.product_size ,
            pd.stock ,
            tc.city_name
        from
            product_info pri
        left join product_desc pd on
            pd.product_id = pri.product_id
        left join t_city tc on
            tc.city_code = pri.city
        order by pri.product_id
    </select>
</mapper>

2.2.6 ProductInfoMapper.java

package com.lh.boot.sharding.jdbc.mapper;


import com.lh.boot.sharding.jdbc.entity.ProductInfo;
import com.lh.boot.sharding.jdbc.entity.vo.ProductInfoVO;

import java.util.List;

public interface ProductInfoMapper {
    int deleteByPrimaryKey(Long productId);

    int insert(ProductInfo record);

    int insertSelective(ProductInfo record);

    ProductInfo selectByPrimaryKey(Long productId);

    int updateByPrimaryKeySelective(ProductInfo record);

    int updateByPrimaryKey(ProductInfo record);

    List<ProductInfoVO> selectProductInfoVOList();
}

三、个人总结

  • 在使用sharding-jdbc读写分离功能前,需要我们自己配置数据库的主从同步。
  • sharding-jdbc会分析我们要执行的sql语句,然后将增删改操作路由到主库,就是我们案例上面的m1和m2数据库。会将查询操作路由到从库,即s1和s2。
  • sharding-jdbc的读写分离和分库分表可以分开来使用,我在平常开发中可以根据自己的具体业务选择合适的方式即可。
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-08-26 11:59:28  更:2021-08-26 12:00:16 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 8:57:08-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码