Sharding-JDBC(三)按月分表

news2025/3/1 19:33:51

目录

    • 1.Maven 依赖
    • 2.创建表结构
    • 3.yml 配置
    • 4.TimeShardingAlgorithm.java 分片算法类
    • 5.ShardingAlgorithmTool.java 分片工具类
    • 6.ShardingTablesLoadRunner.java 初始化缓存类
    • 7.SpringUtil.java Spring工具类
    • 8.代码测试
    • 9.测试结果

背景: 项目用户数据库表量太大,对数据按月分表,需要满足如下需求:

  1. 将数据库按月分表;
  2. 自动建表;
  3. 数据自动跨表查询。

1.Maven 依赖

<!-- ShardingJDBC -->
<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>shardingsphere-jdbc-core</artifactId>
    <version>4.0.1</version>
</dependency>

2.创建表结构

-- ------------------------------
-- 用户表
-- ------------------------------
CREATE TABLE `t_user` (
  `id` bigint(16) NOT NULL COMMENT '主键',
  `username` varchar(64) NOT NULL COMMENT '用户名',
  `password` varchar(64) NOT NULL COMMENT '密码',
  `age` int(8) NOT NULL COMMENT '年龄',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表';

-- ------------------------------
-- 用户表202201
-- ------------------------------
CREATE TABLE `t_user_202201` (
  `id` bigint(16) NOT NULL COMMENT '主键',
  `username` varchar(64) NOT NULL COMMENT '用户名',
  `password` varchar(64) NOT NULL COMMENT '密码',
  `age` int(8) NOT NULL COMMENT '年龄',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表202201';

3.yml 配置

server:
  port: 8081

spring:
  shardingsphere:
    # 打印sql
#    props:
#      sql:
#        show: true
    datasource:
      names: mydb
      mydb:
        type: com.alibaba.druid.pool.DruidDataSource
        url: jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
        driver-class-name: com.mysql.cj.jdbc.Driver
        username: root
        password: root
        # 数据源其他配置
        initialSize: 5
        minIdle: 5
        maxActive: 20
        maxWait: 60000
        timeBetweenEvictionRunsMillis: 60000
        minEvictableIdleTimeMillis: 300000
        validationQuery: SELECT 1 FROM DUAL
        testWhileIdle: true
        testOnBorrow: false
        testOnReturn: false
        poolPreparedStatements: true
        # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
        #filters: stat,wall,log4j
        maxPoolPreparedStatementPerConnectionSize: 20
        useGlobalDataSourceStat: true
        connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
    sharding:
      # 表策略配置
      tables:
        # t_user 是逻辑表
        t_user:
          # 配置数据节点,这里是按月分表
          # 示例1:时间范围设置在202201 ~ 210012
          # actualDataNodes: mydb.t_user_$->{2022..2100}0$->{1..9},mydb.t_user_$->{2022..2100}1$->{0..2}
          # 示例2:时间范围设置在202201 ~ 202203
          actualDataNodes: mydb.t_user_20220$->{1..3}
          tableStrategy:
            # 使用标准分片策略
            standard:
              # 配置分片字段
              shardingColumn: create_time
              # 配置精准分片算法
              preciseAlgorithmClassName: com.demo.module.config.sharding.TimeShardingAlgorithm
              # 配置范围分片算法
              rangeAlgorithmClassName: com.demo.module.config.sharding.TimeShardingAlgorithm
          # 配置主键及生成算法
          keyGenerator:
            column: id
            type: SNOWFLAKE

# mybatis-plus
mybatis-plus:
  mapper-locations: classpath*:/mapper/*Mapper.xml
  # 实体扫描,多个package用逗号或者分号分隔
  typeAliasesPackage: cn.agile.stats.*.entity
  # 测试环境打印sql
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

4.TimeShardingAlgorithm.java 分片算法类

import com.google.common.collect.Range;
import lombok.extern.slf4j.Slf4j;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingValue;

import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.function.Function;

/**
 * <p> @Title TimeShardingAlgorithm
 * <p> @Description 分片算法,按月分片
 *
 * @author zhj
 * @date 2022/12/20 11:33
 */
@Slf4j
public class TimeShardingAlgorithm implements PreciseShardingAlgorithm<Timestamp>, RangeShardingAlgorithm<Timestamp> {

    /**
     * 分片时间格式
     */
    private static final DateTimeFormatter TABLE_SHARD_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMM");

    /**
     * 完整时间格式
     */
    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd HH:mm:ss");

    /**
     * 表分片符号,例:t_user_202201 中,分片符号为 "_"
     */
    private final String TABLE_SPLIT_SYMBOL = "_";


    /**
     * 精准分片
     * @param tableNames 对应分片库中所有分片表的集合
     * @param preciseShardingValue 分片键值,其中 logicTableName 为逻辑表,columnName 分片键,value 为从 SQL 中解析出来的分片键的值
     * @return 表名
     */
    @Override
    public String doSharding(Collection<String> tableNames, PreciseShardingValue<Timestamp> preciseShardingValue) {
        log.info(">>>>>>>>>> 【INFO】精准分片,节点配置表名:{},数据库实时表名:{},数据库缓存表名:{}", tableNames,
                ShardingAlgorithmTool.getAllTableNameBySchema(), ShardingAlgorithmTool.getTableNameCache());
        LocalDateTime dateTime = preciseShardingValue.getValue().toLocalDateTime();
        String logicTableName = preciseShardingValue.getLogicTableName();
        String resultTableName = logicTableName + "_" + dateTime.format(TABLE_SHARD_TIME_FORMATTER);
        // 检查分表获取的表名是否存在,不存在则自动建表
        return ShardingAlgorithmTool.getShardingTableAndCreate(logicTableName, resultTableName);
    }

    /**
     * 范围分片
     * @param tableNames 对应分片库中所有分片表的集合
     * @param rangeShardingValue 分片范围
     * @return 表名集合
     */
    @Override
    public Collection<String> doSharding(Collection<String> tableNames, RangeShardingValue<Timestamp> rangeShardingValue) {
        log.info(">>>>>>>>>> 【INFO】范围分片,节点配置表名:{},数据库实时表名:{},数据库缓存表名:{}", tableNames,
                ShardingAlgorithmTool.getAllTableNameBySchema(), ShardingAlgorithmTool.getTableNameCache());

        // between and 的起始值
        Range<Timestamp> valueRange = rangeShardingValue.getValueRange();
        boolean hasLowerBound = valueRange.hasLowerBound();
        boolean hasUpperBound = valueRange.hasUpperBound();

        // 获取最大值和最小值
        Set<String> tableNameCache = ShardingAlgorithmTool.getTableNameCache();
        LocalDateTime min = hasLowerBound ? valueRange.lowerEndpoint().toLocalDateTime() :getLowerEndpoint(tableNameCache);
        LocalDateTime max = hasUpperBound ? valueRange.upperEndpoint().toLocalDateTime() :getUpperEndpoint(tableNameCache);

        // 循环计算分表范围
        Set<String> resultTableNames = new LinkedHashSet<>();
        String logicTableName = rangeShardingValue.getLogicTableName();
        while (min.isBefore(max) || min.equals(max)) {
            String tableName = logicTableName + TABLE_SPLIT_SYMBOL + min.format(TABLE_SHARD_TIME_FORMATTER);
            resultTableNames.add(tableName);
            min = min.plusMinutes(1);
        }
        return ShardingAlgorithmTool.getShardingTablesAndCreate(logicTableName, resultTableNames);
    }

    // --------------------------------------------------------------------------------------------------------------
    // 私有方法
    // --------------------------------------------------------------------------------------------------------------

    /**
     * 获取 最小分片值
     * @param tableNames 表名集合
     * @return 最小分片值
     */
    private LocalDateTime getLowerEndpoint(Collection<String> tableNames) {
        Optional<LocalDateTime> optional = tableNames.stream()
                .map(o -> LocalDateTime.parse(o.replace(TABLE_SPLIT_SYMBOL, "") + "01 00:00:00", DATE_TIME_FORMATTER))
                .min(Comparator.comparing(Function.identity()));
        if (optional.isPresent()) {
            return optional.get();
        } else {
            log.error(">>>>>>>>>> 【ERROR】获取数据最小分表失败,请稍后重试,tableName:{}", tableNames);
            throw new IllegalArgumentException("获取数据最小分表失败,请稍后重试");
        }
    }

    /**
     * 获取 最大分片值
     * @param tableNames 表名集合
     * @return 最大分片值
     */
    private LocalDateTime getUpperEndpoint(Collection<String> tableNames) {
        Optional<LocalDateTime> optional = tableNames.stream()
                .map(o -> LocalDateTime.parse(o.replace(TABLE_SPLIT_SYMBOL, "") + "01 00:00:00", DATE_TIME_FORMATTER))
                .max(Comparator.comparing(Function.identity()));
        if (optional.isPresent()) {
            return optional.get();
        } else {
            log.error(">>>>>>>>>> 【ERROR】获取数据最大分表失败,请稍后重试,tableName:{}", tableNames);
            throw new IllegalArgumentException("获取数据最大分表失败,请稍后重试");
        }
    }
}

5.ShardingAlgorithmTool.java 分片工具类

import com.alibaba.druid.util.StringUtils;
import com.demo.module.utils.SpringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.env.Environment;

import java.sql.*;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p> @Title ShardingAlgorithmTool
 * <p> @Description 按月分片算法工具
 *
 * @author zhj
 * @date 2022/12/20 14:03
 */
@Slf4j
public class ShardingAlgorithmTool {

    /** 已存在表名集合缓存 */
    private static final Set<String> tableNameCache = new HashSet<>();
    /** 用户表名 */
    private static final String userTableName = "t_user";
    /** 表分片符号,例:t_user_202201 中,分片符号为 "_" */
    private static final String tableSplitSymbol = "_";
    /** 数据库配置 */
    private static final Environment env = SpringUtil.getApplicationContext().getEnvironment();
    private static final String url = env.getProperty("spring.shardingsphere.datasource.mydb.url");
    private static final String username = env.getProperty("spring.shardingsphere.datasource.mydb.username");
    private static final String password = env.getProperty("spring.shardingsphere.datasource.mydb.password");

    /**
     * 检查分表获取的表名是否存在,不存在则自动建表
     * @param logicTableName 逻辑表名,例:t_user
     * @param resultTableNames 真实表名,例:t_user_202201
     * @return 存在于数据库中的真实表名集合
     */
    public static Set<String> getShardingTablesAndCreate(String logicTableName, Collection<String> resultTableNames) {
        return resultTableNames.stream().map(o -> getShardingTableAndCreate(logicTableName, o)).collect(Collectors.toSet());
    }

    /**
     * 检查分表获取的表名是否存在,不存在则自动建表
     * @param logicTableName 逻辑表名,例:t_user
     * @param resultTableName 真实表名,例:t_user_202201
     * @return 确认存在于数据库中的真实表名
     */
    public static String getShardingTableAndCreate(String logicTableName, String resultTableName) {
        // 缓存中有此表则返回,没有则判断创建
        if (tableNameCache.contains(resultTableName)) {
            return resultTableName;
        } else {
            // 未创建的表返回逻辑空表
            boolean isSuccess = createShardingTable(logicTableName, resultTableName);
            return isSuccess ? resultTableName : logicTableName;
        }
    }

    /**
     * 缓存重载
     */
    public static void tableNameCacheReload() {
        // 读取数据库中|所有表名
        List<String> tableNameList = getAllTableNameBySchema();
        // 删除旧的缓存(如果存在)
        tableNameCache.clear();
        // 写入新的缓存
        ShardingAlgorithmTool.tableNameCache.addAll(tableNameList);
    }

    /**
     * 获取所有表名
     * @return 表名集合
     */
    public static List<String> getAllTableNameBySchema() {
        List<String> tableNames = new ArrayList<>();
        if (StringUtils.isEmpty(url) || StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
            log.error(">>>>>>>>>> 【ERROR】数据库连接配置有误,请稍后重试,url:{}, username:{}, password:{}", url, username, password);
            throw new IllegalArgumentException("数据库连接配置有误,请稍后重试");
        }
        try (Connection conn = DriverManager.getConnection(url, username, password);
             Statement st = conn.createStatement()) {
            try (ResultSet rs = st.executeQuery("show TABLES like 't_user_%'")) {
                while (rs.next()) {
                    tableNames.add(rs.getString(1));
                }
            }
        } catch (SQLException e) {
            log.error(">>>>>>>>>> 【ERROR】数据库连接失败,请稍后重试,原因:{}", e.getMessage(), e);
            throw new IllegalArgumentException("数据库连接失败,请稍后重试");
        }
        return tableNames;
    }

    /**
     * 获取表名缓存
     * @return 表名缓存
     */
    public static Set<String> getTableNameCache() {
        return tableNameCache;
    }


    // --------------------------------------------------------------------------------------------------------------
    // 私有方法
    // --------------------------------------------------------------------------------------------------------------


    /**
     * 创建分表
     * @param logicTableName 逻辑表名,例:t_user
     * @param resultTableName 真实表名,例:t_user_202201
     * @return 创建结果(true创建成功,false未创建)
     */
    private static boolean createShardingTable(String logicTableName, String resultTableName) {
        // 根据日期判断,当前月份之后月份分表不提前创建
        String month = resultTableName.replace(logicTableName + tableSplitSymbol,"");
        YearMonth shardingMonth = YearMonth.parse(month, DateTimeFormatter.ofPattern("yyyyMM"));
        if (shardingMonth.isAfter(YearMonth.now())) {
            return false;
        }

        synchronized (logicTableName.intern()) {
            // 缓存中有此表 返回
            if (tableNameCache.contains(resultTableName)) {
                return false;
            }
            // 缓存中无此表,则建表并添加缓存
            List<String> sqlList = getCreateTableSql(logicTableName);
            for (int i = 0; i < sqlList.size(); i++) {
                sqlList.set(i, sqlList.get(i).replace("CREATE TABLE", "CREATE TABLE IF NOT EXISTS").replace(logicTableName, resultTableName));
            }
            executeSql(sqlList);
            tableNameCache.add(resultTableName);
        }
        return true;
    }

    /**
     * 执行SQL
     * @param sqlList SQL集合
     */
    private static void executeSql(List<String> sqlList) {
        if (StringUtils.isEmpty(url) || StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
            log.error(">>>>>>>>>> 【ERROR】数据库连接配置有误,请稍后重试,url:{}, username:{}, password:{}", url, username, password);
            throw new IllegalArgumentException("数据库连接配置有误,请稍后重试");
        }
        try (Connection conn = DriverManager.getConnection(url, username, password)) {
            try (Statement st = conn.createStatement()) {
                conn.setAutoCommit(false);
                for (String sql : sqlList) {
                    st.execute(sql);
                }
            } catch (Exception e) {
                conn.rollback();
                log.error(">>>>>>>>>> 【ERROR】数据表创建执行失败,请稍后重试,原因:{}", e.getMessage(), e);
                throw new IllegalArgumentException("数据表创建执行失败,请稍后重试");
            }
        } catch (SQLException e) {
            log.error(">>>>>>>>>> 【ERROR】数据库连接失败,请稍后重试,原因:{}", e.getMessage(), e);
            throw new IllegalArgumentException("数据库连接失败,请稍后重试");
        }
    }

    /**
     * 获取建表语句
     * @param tableName 表名,例:t_user
     * @return 建表语句集合
     */
    private static List<String> getCreateTableSql(String tableName) {
        List<String> sqlList = new ArrayList<>();
        if (tableName.equals(userTableName)) {
            // 表结构
            sqlList.add("CREATE TABLE `t_user` (\n" +
                    "  `id` bigint(16) NOT NULL COMMENT '主键',\n" +
                    "  `username` varchar(64) NOT NULL COMMENT '用户名',\n" +
                    "  `password` varchar(64) NOT NULL COMMENT '密码',\n" +
                    "  `age` int(8) NOT NULL COMMENT '年龄',\n" +
                    "  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',\n" +
                    "  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',\n" +
                    "  PRIMARY KEY (`id`)\n" +
                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表202203';");
            // 表索引
            sqlList.add("ALTER TABLE `t_user` ADD INDEX IDX_USERNAME ( `username` ) USING BTREE");
        }
        return sqlList;
    }
}

6.ShardingTablesLoadRunner.java 初始化缓存类

import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * <p> @Title ShardingTablesLoadRunner
 * <p> @Description 项目启动后,读取已有分表,进行缓存
 *
 * @author zhj
 * @date 2022/12/20 15:41
 */
@Order(value = 1) // 数字越小,越先执行
@Component
public class ShardingTablesLoadRunner implements CommandLineRunner {

    @Override
    public void run(String... args) {
        // 读取已有分表,进行缓存
        ShardingAlgorithmTool.tableNameCacheReload();
    }
}

7.SpringUtil.java Spring工具类

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

/**
 * <p> @Title SpringUtil
 * <p> @Description Spring工具类
 *
 * @author zhj
 * @date 2022/12/20 14:39
 */
@Component
public class SpringUtil implements ApplicationContextAware {

    private static ApplicationContext applicationContext = null;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringUtil.applicationContext = applicationContext;
    }

    public static ApplicationContext getApplicationContext() {
        return SpringUtil.applicationContext;
    }

    public static <T> T getBean(Class<T> cla) {
        return applicationContext.getBean(cla);
    }

    public static <T> T getBean(String name, Class<T> cal) {
        return applicationContext.getBean(name, cal);
    }

    public static String getProperty(String key) {
        return applicationContext.getBean(Environment.class).getProperty(key);
    }
}

8.代码测试

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.demo.module.entity.TUser;
import com.demo.module.service.TUserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;

@SpringBootTest
class SpringbootDemoApplicationTests {

    private final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    @Autowired
    private TUserService userService;

    @Test
    void saveTest() {
        List<TUser> users = new ArrayList<>(3);
        LocalDateTime time1 = LocalDateTime.parse("2022-01-01 00:00:00", DATE_TIME_FORMATTER);
        LocalDateTime time2 = LocalDateTime.parse("2022-02-01 00:00:00", DATE_TIME_FORMATTER);
        LocalDateTime time3 = LocalDateTime.parse("2022-03-01 00:00:00", DATE_TIME_FORMATTER);
        users.add(new TUser("ACGkaka_1", "123456", 10, time1, time1));
//        users.add(new TUser("ACGkaka_2", "123456", 11, time2, time2));
//        users.add(new TUser("ACGkaka_3", "123456", 12, time3, time3));
        userService.saveBatch(users);
    }

    @Test
    void listTest() {
        LocalDateTime timeStart1 = LocalDateTime.parse("2022-01-01 00:00:00", DATE_TIME_FORMATTER);
        LocalDateTime timeEnd1 = LocalDateTime.parse("2022-01-31 23:59:59", DATE_TIME_FORMATTER);
        List<TUser> users = userService.list(new QueryWrapper<TUser>().between("create_time", timeStart1, timeEnd1));
        System.out.println(">>>>>>>>>> 【Result】<<<<<<<<<< ");
        users.forEach(System.out::println);
    }
}

9.测试结果

在这里插入图片描述





参考地址:

1.sharding-jdbc 实现按月分表,https://blog.csdn.net/u013515384/article/details/125237140

2.SharDingJDBC-5.1.0按月水平分表+读写分离,自动创表、自动刷新节点表,https://blog.csdn.net/m0_54850467/article/details/125242908

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/104548.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

20221220英语学习

今日新词&#xff1a; hurt adj.&#xff08;身体上&#xff09;受伤的, &#xff08;感情上&#xff09;受伤的 blood n.血, 血统, 有…类型的血的, 家世 sister adj.姐妹的, 同类(型)的 humour n.&#xff08; humor&#xff09;幽默, 心情, 情绪, 脾气 holiness n.神圣…

UX设计师是做什么的,现在怎么样

对设计领域略知一点的学生都知道UX设计已经流行了很长一段时间&#xff0c;几年前它的平均工资甚至超过了程序员&#xff0c;那么它的未来会一如既往地保持它的热度吗&#xff1f;未来稳定增长的高薪会下降吗&#xff1f;UX可以称之为科技领域的新秀。根据去年的数据统计&#…

AnQICMS 安装步骤教程

AnQICMS 安装步骤教程 支持的系统 支持 Windows 7、Windows 8、Windows 10、Windows 11、Windows server 各个版本。 Windows XP 未测试 支持 Ubuntu、Centos、Red Hat、Debian 等 基于 X86 的 Linux 版本。 支持 MacOS。 Linux服务器上部署AnQiCMS 先从 https://www.anqic…

力扣(LeetCode)169. 多数元素(C++)

抵消法 多数元素的数量比其他所有元素的总数还多。那么从前往后遍历&#xff0c;遇到相同元素&#xff0c;计数 111 &#xff0c;遇到不同元素&#xff0c;计数 −1-1−1 &#xff0c;考虑边界&#xff0c;当旧数的出现次数减到 000 &#xff0c;那么新数就可以替换旧数&#…

携手华为,瑞金医院病理科为健康数字化保驾护航

作者 | 曾响铃 文 | 响铃说 人生在世&#xff0c;沧桑流转&#xff0c;到了晚年&#xff0c;各种疾病袭来&#xff0c;总是无法避免&#xff0c;总要坦然接受。 只是&#xff0c;这个时候&#xff0c;能够知道自己究竟得的是什么病&#xff0c;才好去积极面对、笑对苦难。 …

前端简单案例——扩展卡

效果展示 色块可以替换成图片&#xff0c;改变background-color为background-image即可。 html代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content&quo…

图片加载引入的内存溢出问题分析

Android ImageView进行图片加载时&#xff0c;经常会遇到内存溢出的问题&#xff0c;本文针对于这一问题出现的定义、原理、过程、解决方案做统一总结。 1.一些定义 在分析具体问题之前&#xff0c;我们先了解一些基本概念&#xff0c;这样可以帮助理解后面的原理部分。当然了…

实时通讯技术Ajax,WebSocket,SSE

实时通讯技术是一项基于web开发的重要技术&#xff0c;网站是需要前后端通讯的&#xff0c;因此数据刷新的时间就是获取信息的时间&#xff0c;为了能准确而有快速的获取信息需要尽可能的提高信息的刷新效率。 常见的实时通讯技术&#xff1a; 通讯方式AjaxCometWebSocketSSE…

从0到1学会开发前端脚手架

【课程简介】 在前端开发中经常会用到create-vue, create-react-app这类脚手架&#xff0c;它可以帮助我们快速生成一个配置化的项目&#xff0c;提高开发效率。现在很多大厂都有自己研发的脚手架&#xff0c;掌握脚手架的使用&#xff0c;并且自己能开发脚手架&#xff0c;能…

涵盖全场景构建方方面面!魅族2023-2025年产品矩阵曝光

在万物互联的时代大背景下&#xff0c;一众以智能手机闻名的科技厂商们开始了全场景概念上的推进构建&#xff0c;形如早前作为国产智能手机「领头羊」的老牌手机厂商魅族&#xff0c;就在近日公布了2023-2025年全场景多终端沉浸式的全方位产品矩阵。 从中可以看到&#xff0c…

解读最佳实践:倚天 710 ARM 芯片的 Python+AI 算力优化 | 龙蜥技术

编者按&#xff1a;在刚刚结束的 PyCon China 2022 大会上&#xff0c;龙蜥社区开发者朱宏林分享了主题为《ARM 芯片的 PythonAI 算力优化》的技术演讲。本次演讲&#xff0c;作者将向大家介绍他们在倚天 710 ARM 芯片上开展的 PythonAI 优化工作&#xff0c;以及在 ARM 云平台…

SCI 论文插图格式一般要求

插图是反映 SCI 文章品质的核心指标之一&#xff01;&#xff01;&#xff01; 图片格式要求:图片一般可以保存为TIFF、JPEG、EPS这三种常见格式,并存为独立文件。 二、图片色彩要求:一般要求为CMYK或RGB色彩。 1.尺寸符合杂志社的要求(宽度8.3~17.6厘米,高度一般不超过20厘米…

云服务下半场,企业增长的超级入口在哪?

随着数字技术的加速突破以及相关鼓励政策的出台&#xff0c;云服务市场正在进入爆发性增长阶段。 在市场、政策的双轮驱动下IT架构转型已是大势所向&#xff0c;而作为时代命题&#xff0c;传统厂商们如果还无法让市场看到转型的成果和信心&#xff0c;或将逐渐在市场端失去选…

专访实在智能孙林君:颠覆传统RPA的实在IPA模式,如何做到真正人人可用?

文/王吉伟 “RPA人人可用”这个愿景&#xff0c;在一线大厂提出后立即得到广大厂商的认同与推崇&#xff0c;之后它几乎成了所有厂商的“口头禅”。 如果RPA能够消除使用门槛实现真正人人可用&#xff0c;意味着每个组织都能通过引入RPA快速通过流程自动化快速实现增效降本。…

正大周二数据 产品介绍 主账户怎么拿到留4的条件?

《今日关注数据》 09:15 中国至12月20日一年期贷款市场报价利率 17:00 欧元区10月季调后经常帐(亿欧元) 21:30 加拿大10月零售销售月率 21:30 美国11月新屋开工总数年化(万户) 21:30 美国11月营建许可总数(万户) 美国银行在报告中指出&#xff0c;儘管美联准会 (Fed) 最近…

OLAP系统林林总总

大数据需求通常可分为三大类&#xff1a;离线数据统计&#xff0c;实时数据计算&#xff0c;即席查询。 离线数据统计&#xff0c;通常是T1出数&#xff0c;是最典型的数据仓库解决的问题。计算复杂性最高&#xff0c;所以是时间就不能要求太高&#xff0c;否则对资源的要求将…

【OpenFeign】【源码+图解】【三】FeignClient的配置信息

【OpenFeign】【源码图解】【二】注册OpenFeign接口的实例 目录4. FeignClient的配置信息4.1 FeignClientFactoryBean4.2 FeignClientFactoryBean.getObject()4.2.1 FeignContext4.2.2 Feign.Builder4.2.2.1 Bean的配置方式YMLFeignClient.configurationEnableFeignClients.def…

5.Node中的模块

目录 1 模块化 2 加载模块 3 模块作用域 4 module对象 4.1 初识module对象 4.2 module.exports 4.2.1 module.exports与require() 4.2.2 添加变量与方法 5 exports对象 5.1 初识exports对象 5.2 添加变量与方法 1 模块化 向我们之前引入的 fs,path,htt…

Baklib|为什么说企业需要重视客户体验?

数十年来&#xff0c;人们都在寻求使自己的品牌获得成功的秘密&#xff0c;企业和品牌也在不断地想着怎样在竞争中胜出&#xff0c;从而获得顾客的青睐。 有些公司已经竭尽全力争取顾客的信赖&#xff0c;并竭力争取更多的市场份额。 但是&#xff0c;即便那些著名的公司不会…

5G无线技术基础自学系列 | RF优化原理

素材来源&#xff1a;《5G无线网络规划与优化》 一边学习一边整理内容&#xff0c;并与大家分享&#xff0c;侵权即删&#xff0c;谢谢支持&#xff01; 附上汇总贴&#xff1a;5G无线技术基础自学系列 | 汇总_COCOgsta的博客-CSDN博客 RF优化主要是依据各种收集到的数据&…