当前位置: 首页 > news >正文

基于Spring包扫描工具和MybatisPlus逆向工程组件的数据表自动同步机制

公司产品产出的项目较多。同步数据库表结构工作很麻烦。一个alter语句要跑到N个客户机上执行脚本。超级费时麻烦。介于此,原有方案是把增量脚本放到一resource包下,项目启动时执行逐行执行一次。但由于模块开发人员较多,总有那么一两个机灵鬼漏写脚本。加上项目分支的原因,导致更新客户表结构后埋雷,炸雷。投诉不断。因此本人开发了一款可靠、自动的表结构更新构件。

原理说明:
1、由于全部表OR映射实体采用了MyBatisPlus的@TableName,TableField注解,所以利用组件(https://blog.csdn.net/qq_37148232/article/details/131821497?spm=1001.2014.3001.5501),扫描出项目中所有带@TableName的实体类。然后解析出表名、字段以及自定义注解上的建表类型,建表长度,注释,默认值等。得到最新的表结构。
2、利用从MyBatisPlus逆向工程中扒出来的表结构查询工具。稍做封装,得到项目数据库当前的表结构。
3、对比最新的表结构和当前表结构。得到缺哪些表、缺哪些字段、哪些字段更新了、
哪些字段删了。
4、将对比结果生成可执行的alter、ceate、drop、modify语句。更新数据库。为保险起见,drop和modify默认不启用。
5、开机自动运行生成的语句集。更新表结构。

代码结构如下:
在这里插入图片描述
各类说明:
ColumnInfo:包扫描实体类后得到的字段信息封装。
CreateTableHandler:自动建表处理器。
DBJDBCType:java-jdbcType映射接口。
MySqlJDBCType:Mysql系列数据库类型映射支持
OtherJDBCTYpe:Oracle系列数据库映射支持。
JDBCSupport:类型映射支持
SchemaAnalyserAutoConfiguration:主配置类
SchemaAnalyserExecutor:自动建表执行入口
SchemaDefinition:扫描后得到的表和字段信息
SchemaDefinitionLoader:包扫描及全面目字段信息加载类
SchemaExistedDefinitionLoader:项目库表结构加载类
UpdateTableHandler:Alter语句处理类

主要类代码:

public class ColumnInfo {private Boolean isPrimary;private String columnName;private String type;public String toCreateColumn() {if (isPrimary) {return columnName + " " + type + " " + "primary key";} else {return columnName + " " + type;}}
}
public class CreateTableHandler {@Autowiredprivate JDBCSupport jdbcSupport;public void createTable(SchemaDefinition schemaDefinition) {if (!FrameworkProperties.enableAutoAlterTableCreate) {return;}List<String> columns = schemaDefinition.getColumns();Map<String, ColumnName> columnContainer = schemaDefinition.getColumnContainer();Map<String, Field> propContainer = schemaDefinition.getPropContainer();List<ColumnInfo> columnInfos = new ArrayList<>();TableId tableId = schemaDefinition.getTableId();if (null != tableId) {ColumnInfo columnInfo = new ColumnInfo();columnInfo.setColumnName(tableId.value());columnInfo.setIsPrimary(true);columnInfo.setType(jdbcSupport.decideJDBCPrimaryType());columnInfos.add(columnInfo);}for (String column : columns) {ColumnName columnName = columnContainer.get(column);Field field = propContainer.get(column);String jdbcType = jdbcSupport.decideJDBCType(column, field, columnName);ColumnInfo columnInfo = new ColumnInfo();columnInfo.setType(jdbcType);columnInfo.setIsPrimary(false);columnInfo.setColumnName(column);columnInfos.add(columnInfo);}jdbcSupport.createTable(schemaDefinition.getTableName(), columnInfos);}
}
public interface DBJDBCType {static DBJDBCType matchJDBCType(String databaseType) {return databaseType.toLowerCase().contains("mysql") ? new MySqlJDBCType() : new OtherJDBCType();}String varcharType(int length);String intType();String longType();String booleanType();String dateType();String textType();
}
public class JDBCSupport implements InitializingBean {private final static String SQL_ADD_MYSQL = "alter table %s add %s %s %s comment '%s';";private final static String SQL_ADD_ORACLE = "alter table %s add %s %s %s;";private final static String SQL_COMMENT_ORACLE = "comment on table %s.%s is '%s';";private final static String SQL_MODIFY = "alter table %s modify %s %s;";private final static String SQL_CREATE = "create table %s (%s);";private DataSource dataSource;private String databaseProductName;private boolean isMysql;public JDBCSupport(DataSource dataSource) {this.dataSource = dataSource;}public void createTable(String tableName, List<ColumnInfo> columnInfos) {List<String> createColumns = ListUtils.list2list(columnInfos, ColumnInfo::toCreateColumn);String columnSqlItems = StrUtils.join(createColumns);String createSql = String.format(SQL_CREATE, tableName, columnSqlItems);executeSql(createSql);}public void addColumn(String tableName, String columnName, String type, Object defaultValue, String comment) {String sql;// 默认值String defaultValueSegment = "";if (StrUtils.isNotNull(defaultValue)) {if (defaultValue instanceof String) {defaultValueSegment = "default '" + defaultValue + "'";} else {if (!"-999".equals(String.valueOf(defaultValue))) {defaultValueSegment = "default " + defaultValue;}}}// 注释comment = StrUtils.isNull(comment) ? "" : comment;if (isMysql) {sql = String.format(SQL_ADD_MYSQL, tableName, columnName, type, defaultValueSegment, comment);executeSql(sql);} else {sql = String.format(SQL_ADD_ORACLE, tableName, columnName, type, defaultValueSegment);String commentSql = String.format(SQL_COMMENT_ORACLE, tableName, columnName, comment);executeSql(sql);executeSql(commentSql);}}public void modifyColumn(String tableName, String columnName, String type) {String sql = String.format(SQL_MODIFY, tableName, columnName, type);executeSql(sql);}public String decideJDBCPrimaryType() {DBJDBCType dbjdbcType = DBJDBCType.matchJDBCType(databaseProductName);// 主键用60位字符return dbjdbcType.varcharType(60);}public String decideJDBCType(String columnName, Field field, ColumnName definition) {DBJDBCType dbjdbcType = DBJDBCType.matchJDBCType(databaseProductName);if (null != definition) {return chooseByColumnDefinition(definition, dbjdbcType);} else {return chooseByField(columnName, field, dbjdbcType);}}@Overridepublic void afterPropertiesSet() {try (Connection connection = dataSource.getConnection()) {this.databaseProductName = connection.getMetaData().getDatabaseProductName();this.isMysql = "MySQL".equals(databaseProductName);} catch (Exception e) {e.printStackTrace();}}private String chooseByField(String columnName, Field field, DBJDBCType dbjdbcType) {if (null == field) {return dbjdbcType.varcharType(200);}String name = field.getName();Class<?> fieldType = field.getType();if (String.class.isAssignableFrom(fieldType)) {// 基于经验的一些合理猜测判断if (columnName.endsWith("_id")) {return dbjdbcType.varcharType(60);} else if (columnName.endsWith("_ids")) {return dbjdbcType.varcharType(500);} else if (name.equals("content")) {return dbjdbcType.varcharType(500);} else if (name.equals("createBy")) {return dbjdbcType.varcharType(60);} else if (name.equals("updateBy")) {return dbjdbcType.varcharType(60);} else if (name.equals("areaId")) {return dbjdbcType.varcharType(60);} else {return dbjdbcType.varcharType(200);}}if (Integer.class.isAssignableFrom(fieldType)) {// 基于经验的一些合理猜测判断if (columnName.startsWith("is_") || columnName.startsWith("has_")) {return dbjdbcType.booleanType();} else {return dbjdbcType.intType();}}if (Long.class.isAssignableFrom(fieldType)) {return dbjdbcType.longType();}if (Date.class.isAssignableFrom(fieldType)) {return dbjdbcType.dateType();}return dbjdbcType.varcharType(200);}private String chooseByColumnDefinition(ColumnName definition, DBJDBCType dbjdbcType) {if (definition.varcharColumn()) {return dbjdbcType.varcharType(definition.varcharLength());} else if (definition.booleanColumn()) {return dbjdbcType.booleanType();} else if (definition.intColumn()) {return dbjdbcType.intType();} else if (definition.longColumn()) {return dbjdbcType.longType();} else if (definition.dateColumn()) {return dbjdbcType.dateType();} else if (definition.textColumn()) {return dbjdbcType.textType();} else {return dbjdbcType.varcharType(definition.varcharLength());}}private void executeSql(String sql) {try (Connection connection = dataSource.getConnection();PreparedStatement preparedStatement = connection.prepareStatement(sql)) {preparedStatement.execute();} catch (Exception e) {log.warn("sql[{}]执行异常", sql);}}
}
public class MySqlJDBCType implements DBJDBCType {@Overridepublic String varcharType(int length) {return "varchar(" + length + ")";}@Overridepublic String intType() {return "int";}@Overridepublic String longType() {return "bigint";}@Overridepublic String booleanType() {return "tinyint";}@Overridepublic String dateType() {return "date";}@Overridepublic String textType() {return "text";}
}
public class OtherJDBCType implements DBJDBCType {@Overridepublic String varcharType(int length) {return "varchar2(" + (length * 2) + ")";}@Overridepublic String intType() {return "number(10)";}@Overridepublic String longType() {return "number(19)";}@Overridepublic String booleanType() {return "number(1)";}@Overridepublic String dateType() {return "date";}@Overridepublic String textType() {return "text";}
}
@Configuration
@ConditionalOnProperty(prefix = "com.xxx.framework", name = "enable-auto-alter-table", havingValue = "true")
public class SchemaAnalyserAutoConfiguration {@Beanpublic JDBCSupport jdbcSupport(DataSource dataSource) {return new JDBCSupport(dataSource);}@Beanpublic CreateTableHandler createTableHandler() {return new CreateTableHandler();}@Beanpublic UpdateTableHandler updateTableHandler() {return new UpdateTableHandler();}@Beanpublic SchemaAnalyserExecutor schemaAnalyserRunner() {return new SchemaAnalyserExecutor();}@Bean@DependsOn("frameworkProperties") // 主要是读取系统类型用于一些判断,所以要依赖public SchemaDefinitionLoader schemaDefinitionLoader() {return new SchemaDefinitionLoader();}@Beanpublic SchemaExistedDefinitionLoader schemaExistedDefinitionLoader() {return new SchemaExistedDefinitionLoader();}
}
public class SchemaAnalyserExecutor implements EasySpringListener {@Autowiredprivate CreateTableHandler createTableHandler;@Autowiredprivate UpdateTableHandler updateTableHandler;@Autowiredprivate SchemaDefinitionLoader schemaDefinitionLoader;@Autowiredprivate SchemaExistedDefinitionLoader schemaExistedDefinitionLoader;@Overridepublic void doBusiness(ApplicationContext applicationContext) {List<SchemaDefinition> projectSchemaDefinition = schemaDefinitionLoader.getProjectSchemaDefinition();Map<String, TableInfo> tableContainer = schemaExistedDefinitionLoader.findExistedTableInfo();generateDelete(tableContainer);// 对比已存在的表和字段,更新字段或新建表for (SchemaDefinition schemaDefinition : projectSchemaDefinition) {// 看表里存不存在该表的定义信息TableInfo tableInfo = tableContainer.get(schemaDefinition.getTableName());if (null != tableInfo) {try {updateTableHandler.updateTable(schemaDefinition, tableInfo);} catch (Exception e) {e.printStackTrace();}} else {try {createTableHandler.createTable(schemaDefinition);} catch (Exception e) {e.printStackTrace();}}}}public void generateDelete(Map<String, TableInfo> tableContainer) {if (FrameworkProperties.enableGenerateDeleteScript) {Set<String> strings = tableContainer.keySet();List<String> tableNames = ZYListUtils.set2list(strings);tableNames.sort(Comparator.comparing(a -> a));List<String> deleteSqls = new ArrayList<>();tableNames.forEach(tableName -> {deleteSqls.add("delete from " + tableName + " ;");});FileUtils.writeLines(deleteSqls, "D://clear_" + ZYDateUtils.formart(new Date(), "yyyy-MM-dd-HH-mm-ss") + ".sql", "utf-8", true);}}@Overridepublic int getOrder() {return 0;}
}
public class SchemaDefinition {private String tableName;private String tableComment;// 表格信息private TableName tableNameAnnotation;// 主键信息private TableId tableId;private List<String> columns = new ArrayList<>();// 字段属性定义private Map<String, Field> propContainer = new HashMap<>();// 字段描述信息private Map<String, ColumnName> columnContainer = new HashMap<>();// 字段定义信息private Map<String, TableField> fieldContainer = new HashMap<>();public void joinSchemaDefinition(SchemaDefinition schemaDefinition) {List<String> targetColumns = schemaDefinition.getColumns();Map<String, ColumnName> targetColumnContainer = schemaDefinition.getColumnContainer();Map<String, TableField> targetFieldContainer = schemaDefinition.getFieldContainer();for (String targetColumn : targetColumns) {if (!columns.contains(targetColumn)) {ColumnName columnName = targetColumnContainer.get(targetColumn);TableField tableField = targetFieldContainer.get(targetColumn);if (null != columnName && null != tableField) {columns.add(targetColumn);columnContainer.put(targetColumn, columnName);fieldContainer.put(targetColumn, tableField);}}}}public SchemaDefinition(Class<?> aClass) {this.tableNameAnnotation = aClass.getAnnotation(TableName.class);// 表名this.tableName = tableNameAnnotation.value().toLowerCase();// 收集字段定义信息Field[] fields = ZYReflectUtils.getFields(aClass);for (Field field : fields) {field.setAccessible(true);// 主键字段TableId tableId = field.getAnnotation(TableId.class);if (null != tableId) {this.tableId = tableId;continue;}// 普通字段TableField tableField = field.getAnnotation(TableField.class);if (null == tableField) {continue;}if (!tableField.exist()) {continue;}String column = tableField.value().toLowerCase();// 字段集合columns.add(column);// 表格定义fieldContainer.put(column, tableField);// 字段反射属性propContainer.put(column, field);// 建表描述ColumnName columnName = field.getAnnotation(ColumnName.class);if (null != columnName) {columnContainer.put(column, columnName);}}}
}
public class SchemaDefinitionLoader implements InterestedClassAware {private List<SchemaDefinition> schemaDefinitions = new ArrayList<>();public List<SchemaDefinition> getProjectSchemaDefinition() {return schemaDefinitions;}@Overridepublic boolean match(AnnotationMetadata annotationMetadata) {return annotationMetadata.hasAnnotation(TableName.class.getName());}@Overridepublic void setClasses(Set<Class<?>> classes) {List<SchemaDefinition> definitions = new ArrayList<>();Map<String, TableCondition> tableConditionCache = new HashMap<>();for (Class<?> aClass : classes) {TableExplain tableExplain = aClass.getAnnotation(TableExplain.class);if (isNecessary(tableConditionCache, tableExplain, aClass)) {SchemaDefinition schemaDefinition = new SchemaDefinition(aClass);if (null != tableExplain) {// 表的注释schemaDefinition.setTableComment(tableExplain.value());}definitions.add(schemaDefinition);}}Map<String, List<SchemaDefinition>> schemaContainer = ZYListUtils.groupList(definitions, SchemaDefinition::getTableName);schemaContainer.forEach((schemaName, schemas) -> {if (schemas.size() == 1) {schemaDefinitions.add(schemas.get(GlobalConstant.FIRST));} else if (schemas.size() > 1) {SchemaDefinition schemaDefinition = schemas.get(GlobalConstant.FIRST);// 合并集合for (int i = 1; i < schemas.size(); i++) {schemaDefinition.joinSchemaDefinition(schemas.get(i));}schemaDefinitions.add(schemaDefinition);}});}private boolean isNecessary(Map<String, TableCondition> tableConditionCache, TableExplain tableExplain, Class<?> aClass) {if (null == tableExplain) {return true;}if (tableExplain.exclude()) {return false;}Class<? extends TableCondition> condition = tableExplain.condition();String name = condition.getName();TableCondition tableCondition = tableConditionCache.get(name);if (null == tableCondition) {tableCondition = ReflectUtils.newInstance(condition);tableConditionCache.put(name, tableCondition);}return tableCondition.isNecessary(aClass);}
}
public class SchemaExistedDefinitionLoader {@Autowiredprivate DataSourceProperties dataSourceProperties;@Autowiredprivate DataSource dataSource;@SneakyThrowspublic Map<String, TableInfo> findExistedTableInfo() {DataSourceConfig dataSourceConfig = new DataSourceConfig();dataSourceConfig.setDriverName(dataSourceProperties.getDriverClassName());dataSourceConfig.setPassword(dataSourceProperties.getPassword());dataSourceConfig.setUsername(dataSourceProperties.getUsername());String url = dataSourceProperties.getUrl();dataSourceConfig.setUrl(url);this.connection = dataSourceConfig.getConn();dataSourceConfig.setSchemaName(this.connection.getSchema());this.dataSourceConfig = dataSourceConfig;if (url.contains("kingbase8")) {this.dbQuery = new OracleQuery();} else {this.dbQuery = dataSourceConfig.getDbQuery();}this.strategyConfig = new StrategyConfig();this.globalConfig = new GlobalConfig();List<TableInfo> tablesInfo = getTablesInfo();return ZYListUtils.groupModel(tablesInfo, TableInfo::getName);// 表名全改成小写}private IDbQuery dbQuery;private DataSourceConfig dataSourceConfig;private Connection connection;private GlobalConfig globalConfig;private StrategyConfig strategyConfig;private List<TableInfo> getTablesInfo() {//所有的表信息List<TableInfo> tableList = new ArrayList<>();//不存在的表名PreparedStatement preparedStatement = null;try {String tablesSql = dbQuery.tablesSql();if (DbType.POSTGRE_SQL == dbQuery.dbType()) {String schema = dataSourceConfig.getSchemaName();if (schema == null) {//pg默认schema=publicschema = "public";dataSourceConfig.setSchemaName(schema);}tablesSql = String.format(tablesSql, schema);}//oracle数据库表太多,出现最大游标错误else if (DbType.ORACLE == dbQuery.dbType()) {String schema = dataSourceConfig.getSchemaName();//oracle默认用户的schema=usernameif (schema == null) {schema = dataSourceConfig.getUsername().toUpperCase();dataSourceConfig.setSchemaName(schema);}tablesSql = String.format(tablesSql, schema);}preparedStatement = connection.prepareStatement(tablesSql);ResultSet results = preparedStatement.executeQuery();TableInfo tableInfo;while (results.next()) {String tableName = results.getString(dbQuery.tableName());if (StringUtils.isNotEmpty(tableName)) {String tableComment = results.getString(dbQuery.tableComment());if ("VIEW".equalsIgnoreCase(tableComment)) {// 跳过视图continue;}tableInfo = new TableInfo();tableInfo.setName(tableName.toLowerCase());tableInfo.setComment(tableComment);tableList.add(tableInfo);} else {System.err.println("当前数据库为空!!!");}}tableList.forEach(ti -> convertTableFields(ti, strategyConfig.getColumnNaming()));} catch (SQLException e) {e.printStackTrace();} finally {// 释放资源try {if (preparedStatement != null) {preparedStatement.close();}if (connection != null) {connection.close();}} catch (SQLException e) {e.printStackTrace();}}return tableList;}private TableInfo convertTableFields(TableInfo tableInfo, NamingStrategy strategy) {boolean haveId = false;List<TableField> fieldList = new ArrayList<>();try {String tableFieldsSql = dbQuery.tableFieldsSql();if (DbType.POSTGRE_SQL == dbQuery.dbType()) {tableFieldsSql = String.format(tableFieldsSql, dataSourceConfig.getSchemaName(), tableInfo.getName());} else if (DbType.ORACLE == dbQuery.dbType()) {tableFieldsSql = String.format(tableFieldsSql.replace("#schema", dataSourceConfig.getSchemaName()), tableInfo.getName());} else {tableFieldsSql = String.format(tableFieldsSql, tableInfo.getName());}PreparedStatement preparedStatement = connection.prepareStatement(tableFieldsSql);ResultSet results = preparedStatement.executeQuery();while (results.next()) {TableField field = new TableField();field.setName(results.getString(dbQuery.fieldName().toLowerCase()));field.setType(results.getString(dbQuery.fieldType()));field.setColumnType(dataSourceConfig.getTypeConvert().processTypeConvert(globalConfig, field.getType()));field.setComment(results.getString(dbQuery.fieldComment()));fieldList.add(field);}} catch (SQLException e) {System.err.println("SQL Exception:" + e.getMessage());}tableInfo.setFields(fieldList);return tableInfo;}
}
public class UpdateTableHandler {@Autowiredprivate JDBCSupport jdbcSupport;public void updateTable(SchemaDefinition schemaDefinition, TableInfo tableInfo) {if (!FrameworkProperties.enableAutoAlterTableAddColumn && !FrameworkProperties.enableAutoAlterTableModifyColumn) {return;}List<String> columns = schemaDefinition.getColumns();List<TableField> commonFields = tableInfo.getFields();Map<String, TableField> existsColumnContainer = ZYListUtils.groupModel(commonFields, TableField::getName);// 列的定义Map<String, ColumnName> columnContainer = schemaDefinition.getColumnContainer();// 列的字段描述Map<String, Field> propContainer = schemaDefinition.getPropContainer();String tableName = tableInfo.getName();for (String column : columns) {// 列的定义注解ColumnName columnName = columnContainer.get(column);// 列属性的反射类型Field field = propContainer.get(column);// 决定jdbc现有的类型String jdbcType = jdbcSupport.decideJDBCType(column, field, columnName);if (!existsColumnContainer.containsKey(column)) {// 添加字段if (FrameworkProperties.enableAutoAlterTableAddColumn) {Object defaultValue = null;String comment = "";if (null != columnName && null != field) {boolean isVarchar = String.class.isAssignableFrom(field.getType());defaultValue = isVarchar ? columnName.varcharDefaultValue() : columnName.intDefaultValue();comment = columnName.value();}jdbcSupport.addColumn(tableName, column, jdbcType, defaultValue, comment);}} else {// 更新字段TableField existsTableField = existsColumnContainer.get(column);if (compareAndNecessaryModify(column, columnName, field, existsTableField)) {if (FrameworkProperties.enableAutoAlterTableModifyColumn) {jdbcSupport.modifyColumn(tableName, column, jdbcType);}}}}}// 比对下新旧数据库,看字段是否需要modifyprivate boolean compareAndNecessaryModify(String column, ColumnName columnName, Field field, TableField existsTableField) {// 主要是字段类型跟长度String type = existsTableField.getType();String jdbcType = jdbcSupport.decideJDBCType(column, field, columnName);return !type.equals(jdbcType);}
}

相关文章:

基于Spring包扫描工具和MybatisPlus逆向工程组件的数据表自动同步机制

公司产品产出的项目较多。同步数据库表结构工作很麻烦。一个alter语句要跑到N个客户机上执行脚本。超级费时麻烦。介于此&#xff0c;原有方案是把增量脚本放到一resource包下&#xff0c;项目启动时执行逐行执行一次。但由于模块开发人员较多&#xff0c;总有那么一两个机灵鬼…...

leetcode 面试题 0106.字符串压缩

⭐️ 题目描述 &#x1f31f; leetcode链接&#xff1a;面试题 0106.字符串压缩 思路&#xff1a; 开辟一个新的空间&#xff08;空间要大一点&#xff0c;因为可能压缩后的字符串比原字符串大&#xff09;&#xff0c;然后遍历原字符串统计当前字符的个数&#xff0c;再写入到…...

三、Spring源码-实例化

Spring源码-Bean的实例化 接下来我们看看Bean的实例化处理 一、BeanDefinition 首先我们来看看BeanDefinition的存放位置。因为Bean对象的实例化肯定是BeanFactory基于对应的BeanDefinition的定义来实现的&#xff0c;所以在这个过程中BeanDefinition是非常重要的&#xff0c;…...

算法的法律框架:预测未来的关键趋势

随着科技的飞速发展&#xff0c;算法和人工智能&#xff08;AI&#xff09;已成为我们社会生活的重要组成部分。然而&#xff0c;它们也带来了许多新的法律和道德挑战&#xff0c;这使得算法的法律框架变得日益重要。在这个背景下&#xff0c;预测未来算法法律框架的关键趋势成…...

Ubuntu Server版 之 共享文件 samba和NFS 两种方法

NFS 和 Samba NFS &#xff1a; linux之间资源共享 Samba&#xff1a; 是windows系统与Linux系统之间资源共享的 samba 安装samba 工具 sudo apt install samba 创建共享目录 sudo mkdir /home/shared sudo chmod 777 /home/shared 配置sambd sudo vim /etc/samba/smb.con…...

实时协作:团队效率倍增的关键

实时协作是指团队在当前时刻共同完成项目的能力。无论是否使用技术&#xff0c;都能实现这一点。然而&#xff0c;随着远程工作的盛行&#xff0c;安全的协作工具被用来让团队成员在项目和一般业务之间保持联系和同步。 传统协作与实时协作的区别 两种类型的协作最明显的区别…...

电脑选睡眠、休眠还是关机?

关机 这是大家最熟悉的。关机时&#xff0c;系统首先关闭所有运行中的程序&#xff0c;然后关闭系统后台服务。随后&#xff0c;系统向主板请求关机&#xff0c;主板断开电源的供电使能&#xff0c;让电源切断对绝大多数设备的供电&#xff08;只剩一些内部零件仍会维持电源供应…...

算法通关村第三关——不简单的数组增删改查

线性表基础 线性表概念 线性表就是具有相同特征数据元素的一个有限序列&#xff0c;其中包含元素的个数称为线性表的长度 线性表类型 从不同的角度看&#xff0c;线性表有不同的分类 语言实现角度 顺序表有两种实现方式 一体式 分离式 一体式结构 一体式&#xff1a;存储信息…...

【Linux】动静态库

目录 写在前面的话 如何编写静态库库 编写静态库 ar命令 Makefile自动化形成静态库 如何使用编写的静态库 1.拷贝到系统路径中 2.指定路径搜索 如何编写动态库 编写动态库 完善Makefile 如何使用编写的动态库 指定路径搜索(不可行及原因) 环境变量LD_LIBRARY_PAT…...

《kubernetes权威指南》-第一章学习笔记

1.什么是kubernetes&#xff1f; kubernetes是一个全新的基于容器技术的分布式架构领先方案。 2.为什么要用kubernetes&#xff1f; 使用kubernetes提供的解决方案能够减少30%的开发成本&#xff0c;并且能够将开发人员的精力更加集中于业务本身&#xff0c;同时可以降低系统…...

ubuntu 18.04 磁盘太满无法进入系统

安装了一个压缩包&#xff0c;装了一半提示磁盘空间少导致安装失败。我也没在意&#xff0c;退出虚拟机打算扩展硬盘。等我在虚拟机设置中完成扩展操作&#xff0c;准备进入虚拟机内部进行操作时&#xff0c;发现登录不进去了 shift 登入GUN GRUB设置项的问题 网上都是在开机…...

基于LNMP配置WordPress建站时出现的问题汇总

目录 wordpress上传文件报错问题描述原因分析&#xff1a;解决方案&#xff1a; wordpress裁剪图片报错问题描述原因分析&#xff1a;解决方案&#xff1a; 配置固定链接和伪链接 wordpress上传文件报错 WP内部错误&#xff0c;在上传文件时发生了错误&#xff0c;显示权限不足…...

【Spring Cloud】Gateway的配置与使用

文章目录 前言第一步&#xff0c;创建一个springboot工程第二步&#xff0c;添加依赖第三步&#xff0c;编写yml文件第四步&#xff0c;启动主启动类总结 前言 Gateway其实是springcloud 原生的东西&#xff0c;但是我还是想放在这里讲&#xff0c;因为我们使用nacos时&#x…...

概念、框架简介--ruoyi学习(一)

开始进行ruoyi框架的学习&#xff0c;比起其他的前后端不分离的&#xff0c;这个起码看的清晰一些吧。 这一节主要是看了ruoyi的官方文档后&#xff0c;记录了以下不懂的概念&#xff0c;并且整理了ruoyi框架中的相关内容。 一些概念 前端 store store是状态管理库&#x…...

IDEA的基础使用——【初识IDEA】

IDEA的基础使用——【初识IDEA】 文章目录 IDEA简介前言官网 IDEA的下载与安装选择下载路径勾选自己需要的其余按默认选项进行即可 目录简介安装目录简介 运行Hello WorldIDEA快捷键常用模板模板一&#xff1a;psvm&#xff08;main&#xff09;模板二&#xff1a;模板三&#…...

LeetCode刷题总结-动态规划篇

LeetCode刷题总结-动态规划篇 本文总结LeetCode上有动态规划的算法题&#xff0c;推荐刷题总数为54道。具体考点分析如下图&#xff1a; 1.中心扩展法 题号&#xff1a;132. 分割回文串 II&#xff0c;难度困难 2.背包问题 题号&#xff1a;140. 单词拆分 II&#xff0c;难…...

el-table使用xlsx实现导入文件编辑功能

需求&#xff1a;列表根据xlsx文件导入后&#xff0c;和列表进行对比&#xff0c;之后实现编辑功能 1.下载xlsx 我下的是之前的版本&#xff0c;新版不知道兼不兼容&#xff0c;这个包900多k npm install xlsx0.14.5 2.在需要使用表格导入的页面引入 import XLSX from &quo…...

Android9、11 有线网络开关设置

Android9、11 有线网络开关设置 Android9、11 有线网络开关设置_android 以太网开关_峥嵘life的博客-CSDN博客...

【MySQL】mysql问题 | [ERROR] unknown variable ‘column-statistics=0‘

一、说明 1、用到一个开源项目&#xff0c;dbBkTool[asurplus] 2、这个项目用于MySQL定时备份的 3、然后有个执行的时候&#xff0c;发下报错 [ERROR] unknown variable column-statistics0 二、解决 1、把MySQL客户端升级到8.0.19之后&#xff0c;就不报错了 2、column-stat…...

ElasticSearch 7.x

前言 elastic表示可伸缩&#xff0c;search表示查询。所以es的核心即为查询。通常情况下&#xff0c;我们的数据可以分为三类&#xff1a;结构化数据、非结构化数据、半结构化数据。 结构化数据&#xff1a;一般会用特定的结构来组织和管理数据&#xff0c;表现为二维表结构。…...

MVC乱码问题

RequestMapping(value "insert",produces {"text/html;charsetutf-8"}) //前端响应回去加响应头&#xff0c;解决乱码问题,这个还跟JSP响应头还不一样&#xff0c;这是响应的字符串&#xff0c;纯文本&#xff0c;那个前端的是out.Writer()对象&#xff…...

1004. 最大连续1的个数 III

题目描述&#xff1a; 主要思路&#xff1a; 刚看到这个问题首先想到的是二分答案&#xff0c;二分长度&#xff0c;然后利用滑动窗口判断是否可以达成。 class Solution { public:bool find(int x,vector<int> nums, int k){int now0;for(int i0,j0;i<nums.size();…...

【机器学习】西瓜书学习心得及课后习题参考答案—第3章线性模型

过了一遍第三章&#xff0c;大致理解了内容&#xff0c;认识了线性回归模型&#xff0c;对数几率回归模型&#xff0c;线性判别分析方法&#xff0c;以及多分类学习&#xff0c;其中有很多数学推理过程以参考他人现有思想为主&#xff0c;没有亲手去推。 术语学习 线性模型 l…...

面试官问我:一个 TCP 连接可以发多少个 HTTP 请求?我竟然回答不上来...

一道经典的面试题是从 URL 在浏览器被被输入到页面展现的过程中发生了什么&#xff0c;大多数回答都是说请求响应之后 DOM 怎么被构建&#xff0c;被绘制出来。但是你有没有想过&#xff0c;收到的 HTML 如果包含几十个图片标签&#xff0c;这些图片是以什么方式、什么顺序、建…...

树莓派Pico|RP2040|官方文档|在MS Windows上构建“Hello World”及环境配置

9.2. 在MS Windows上构建 在Microsoft Windows 10或Windows 11上安装工具链与其他平台有些不同。然而安装后&#xff0c;RP2040的构建代码基本类似。  警告 官方不支持在Windows 7或8上使用Raspberry Pi Pico&#xff0c;但在Windows 7或8上可以使其工作。 9.2.1. 安装工具…...

全球公链进展| 2023/7/31

一周速览 过去一周&#xff0c;明星项目动态如下&#xff1a; 第114次以太坊核心开发者共识会议&#xff1a;Devnet #8 最早下周推出 Layer2网络Shibarium跨链桥已上线公开测试 Optimism 推出「Law of Chains」v0.1 版本 Sui 通过 SIP#6 &#xff0c;允许开发人员构建流动…...

Spring源码(三)Spring Bean生命周期

Bean的生命周期就是指&#xff1a;在Spring中&#xff0c;一个Bean是如何生成的&#xff0c;如何销毁的 Bean生命周期流程图 1、生成BeanDefinition Spring启动的时候会进行扫描&#xff0c;会先调用org.springframework.context.annotation.ClassPathScanningCandidateCompo…...

【iOS】Cydia Impactor 错误:file http.hpp; line:37; what: _assert(code == 200)

Cydia Impactor 报错&#xff0c;信息如下 file http.hpp; line:37; what: _assert(code 200)解决方案&#xff1a;Cydia Impactor 已被弃用&#xff0c;切换到sideloadly 即可&#xff0c;亲测成功&#xff0c;并且支持双重验证登录 csdn备份地址 HERE...

3ds MAX绘制茶壶

综合一下之前的内容画个茶壶 长方形&#xff0c;然后转化为可编辑多边形&#xff0c;添加节点并设置圆角&#xff0c;如下图 车削生成一个圆环&#xff0c;其实这一步也可以用一个圆柱体和两个圆角圆柱体解决 效果如下&#xff1a; 茶壶的底座绘制好了 接下来是茶壶的上半边 …...

【element-plus】 table表格每行圆角解决方案 element也通用

系列文章目录 【Vue3ViteTselement-plus】使用tsx实现左侧栏菜单无限层级封装 前言 我们在使用element-plus或element 的table时是否有时UI给到的UI效果是如下面这样的&#xff0c;但是我们翻遍了组件库的文档 调整了很多次样式 发现在 左右侧栏固定的时候 普通的方法是完全…...