基于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);}
}

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

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

相关文章

高电压放大器ATA-2021B技术指标

随着ATA-2021H高压放大器的升级改版&#xff0c;新品ATA-2021B高电压放大器走进了更多工程师、研究人员的视野。相比于升级之前&#xff0c;ATA-2021B高压放大器拥有了更多更好地优势&#xff0c;可以更好地的帮助研究人员高效完成测试项目。今天Aigtek小编就带大家了解一下关于…

windos 服务器设置指定ip访问指定端口,其他ip不能访问

需求&#xff1a;设置指定ip访问指定端口&#xff0c;其他ip不能访问 一&#xff0c;禁止所有ip访问 需要打开IP安全策略 或者winR 输入secpol.msc 1.先创建一个ip安全策略 2.点击添加&#xff0c;不使用添加向导&#xff0c;建一个安全策略 继续点添加 二&#xff0c;放开需…

苍穹外卖 Spring Task 来单提醒 催单Apache ECharts day10~11

苍穹外卖-day10 课程内容 Spring Task订单状态定时处理WebSocket来单提醒客户催单 功能实现&#xff1a;订单状态定时处理、来单提醒和客户催单 订单状态定时处理&#xff1a; 来单提醒&#xff1a; 客户催单&#xff1a; 1. Spring Task 1.1 介绍 Spring Task 是Spring框架提供…

【chatGpt】关于websocket连接中对未授权的捕捉问题

目录 问题 有效提问 有效的细节提问 问题 一路上&#xff0c;通过简单的error进行判断弹出授权&#xff0c;会有很多乱弹的现象&#xff1a; &#xff08;1&#xff09;链路正常切换会断 &#xff08;2&#xff09;服务器没有启动会连接不上 &#xff08;3&#xff09;没…

Python pygame(GUI编程)模块最完整教程(7)

上一篇文章&#xff1a; Python pygame(GUI编程)模块最完整教程&#xff08;6&#xff09;_Python-ZZY的博客-CSDN博客 总目录&#xff1a; README.md Python-ZZY/Python-Pygame最完整教程 - Gitee.com 21 OpenGL与Pygame 不会OpenGL的读者可以跳过本章节。 21.1 OpenGL简…

CRM系统化整合从N-1做减法实践 | 京东物流技术团队

1 背景 京销易系统已经接入大网、KA以及云仓三个条线商机&#xff0c;每个条线商机规则差异比较大&#xff0c;当前现状是独立实现三套系统分别做支撑。 2 目标 2022年下半年CRM目标是完成9个新条线业务接入&#xff0c;完成销售过程线上化&#xff0c;实现销售规则统一。 …

js版计算比亚迪行驶里程连续12个月计算不超3万公里改进版带echar

<!DOCTYPE html> <html lang"zh-CN" style"height: 100%"> <head> <meta charset"utf-8" /> <title>连续12个月不超3万公里计算LIGUANGHUA</title> <style> .clocks { …

计算机组成原理问答5

CPU CPU两大核心部件&#xff1a;运算器控制器 运算器的核心&#xff1a;ALU&#xff08;算数逻辑单元&#xff09;&#xff0c;功能&#xff1a;对数据进行加工。基本结构&#xff1a;ALU、暂存寄存器、通用寄存器组、累加寄存器ACC、程序状态寄存器PSW、移位器、计数器。 …

第一次编程测试(分频器)

一&#xff0c;分频器 定义 分频器&#xff08;Divider&#xff09;是一种电子电路或设备&#xff0c;用于将输入信号的频率降低到较低的频率。它常用于数字系统、通信系统和计时应用中。原理 整数分频器使用计数器来实现频率的降低。计数器根据输入信号的边沿触发进行计数&am…

Mybatis学习笔记教程

Mybatis-9.28 环境&#xff1a; JDK1.8Mysql 5.7maven 3.6.1IDEA 回顾&#xff1a; JDBCMysqlJava基础MavenJunit SSM框架&#xff1a;配置文件的。 最好的方式&#xff1a;看官网文档&#xff1b; 1、简介 1.1、什么是Mybatis MyBatis 是一款优秀的持久层框架它支持定制…

基于Vue+Element Plus实现表格组件

目录 前言分析实现例子效果图前言 表格对于管理类项目是很重要的,可以只管的展示和比比较数据。使用Element Plus能解决一部分问题,但是还存在一些缺点和不足。 分析 浏览器上表格数据展示空间不足。列显示太多不够直观。完全依赖官方表格组件代码过于臃肿不利于管理和优化…

通过VIOOVI,了解联合作业分析的意义和目标!

现如今企业的主流生产模式就是流水线生产&#xff0c;一道工序结束后&#xff0c;紧接着开展下一项工序&#xff0c;这种作业模式可以以一种比较高效的方式缩减生产时间。尽管流水作业的效率已经够高的了&#xff0c;但是各个工序之间如果衔接不到位的话&#xff0c;会造成生产…

K8S初级入门系列之九-共享存储

一、前言 Pod里面的容器都有自己独立的文件系统&#xff0c;来自容器镜像&#xff0c;用于保存容器运行的数据&#xff0c;但容器的文件存储有两个弊端&#xff0c;一个是无法持久化&#xff0c;其生命周期与容器一致&#xff0c;一旦容器销毁&#xff0c;相关的数据也就随之一…

Hive内部表和外部表

表类型详解 表分类 在Hive中,表类型主要分为两种 第一种&#xff1a;内部表 也叫管理表表目录会创建在集群上的{hive.metastore.warehouse.dir}下的相应的库对应的目录中。默认创建的表就是内部表 第二种&#xff1a;外部表 外部表需要使用关键字"external"&#xff…

SpringBoot第8讲:SpringBoot添加Logback日志

SpringBoot第8讲&#xff1a;SpringBoot添加Logback日志 本文是SpringBoot第8讲&#xff0c;对SpringBoot添加Logback日志。SpringBoot开发中如何选用日志框架呢&#xff1f; 出于性能等原因&#xff0c;Logback 目前是springboot应用日志的标配&#xff1b; 当然有时候在生产环…

264. 丑数 II

题目描述&#xff1a; 主要思路&#xff1a; 利用动态规划的思想&#xff0c;记录2 3 5分别乘到了哪里&#xff0c;然后取最小作为新的数字。 class Solution { public:int nthUglyNumber(int n) {int dp[n1];dp[1]1;int p21,p31,p51;for(int i2;i<n;i){int num2 dp[p2]*…

说一说java中的自定义注解之设计及实现

一、需求背景 比如我们需要对系统的部分接口进行token验证&#xff0c;防止对外的接口裸奔。所以&#xff0c;在调用这类接口前&#xff0c;先校验token的合法性&#xff0c;进而得到登录用户的userId/role/authority/tenantId等信息&#xff1b;再进一步对比当前用户是否有权…

【ESP32】Espressif-IDE及ESP-IDF安装

一、下载Espressif-IDE 2.10.0 with ESP-IDF v5.0.2 1.打开ESP-IDF 编程指南 2.点击快速入门–>安装–>手动安装–>Windows Installer–>Windows Installer Download 3.点击下载Espressif-IDE 2.10.0 with ESP-IDF v5.0.2 二、安装Espressif-IDE 2.10.0 wit…

分布式I/O,IT和OT融合少不了它

长期以来信息技术IT和操作运营技术OT是相互隔离的&#xff0c;随着大数据分析和边缘计算业务的对现场级实时数据的采集需求&#xff0c;IT和OT有了逐渐融合的趋势。IT与OT融合&#xff0c;它赋予工厂的管理者监控运行和过程的能力大为增强&#xff0c;甚至可以预测到可能发生的…

TypeScript -- 类

文章目录 TypeScript -- 类TS -- 类的概念创建一个简单的ts类继承 public / private / protected-- 公共/私有/受保护的public -- 公共private -- 私有的protected -- 受保护的 其他特性readonly -- 只读属性静态属性 -- static修饰ts的getter /setter抽象类abstract TypeScrip…