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

java:Druid工具类解析sql获取表名

java:Druid工具类解析sql获取表名

1 前言

alibaba的druid连接池除了sql执行的功能外,还有sql语法解析的工具提供,参考依赖如下:

<dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.15</version>
</dependency>

2 使用

参考druid的工具类:com.alibaba.druid.sql.parser.SQLParserUtils#getTables(String sql, DbType dbType)方法,可以用于获取sql的表名:

比如针对mysql的select语句:

package com.xiaoxu.parser;import com.alibaba.druid.DbType;
import com.alibaba.druid.sql.parser.SQLParserUtils;import java.util.List;/*** @author xiaoxu* @date 2024-03-11* java_demo2:com.xiaoxu.parser.SQLParserTest*/
public class SQLParserTest {public static void main(String[] args) {List<String> tables = SQLParserUtils.getTables("select * from my_fuitrs_99 where id = ?", DbType.mysql);System.out.println(tables);String nihao_99 = "select * from my_fuitrs_99 where id = ?".replace(tables.get(0), "my_fuitrs");System.out.println(nihao_99);}}

执行结果如下:
在这里插入图片描述

该方法的本质是根据自定义的hash值,对于每个标识符,能唯一生成一个hashCode,将标识符与存入map中的标识符来比对。比如当lexer.token获取的是com.alibaba.druid.sql.parser.Token类中的FROM(“FROM”)时,先执行lexer.nextToken()方法调用,获取到用户自定义的from后续的标识符(from关键字后续跟着的空格等字符会跳过),判断在没有获取到,则将其置为:Token.IDENTIFIER,表示用户自定义的identifier标识符。如下 keywords.getKeyword(hashLCase) 如果获取的标识符不存在,则将判断为用户自定义的标识符。

参考com.alibaba.druid.sql.parser.Lexer的scanIdentifier()方法:

public void scanIdentifier() {this.hashLCase = 0;this.hash = 0;final char first = ch;if (ch == '`') {mark = pos;bufPos = 1;char ch;int startPos = pos + 1;int quoteIndex = text.indexOf('`', startPos);if (quoteIndex == -1) {throw new ParserException("illegal identifier. " + info());}hashLCase = 0xcbf29ce484222325L;hash = 0xcbf29ce484222325L;for (int i = startPos; i < quoteIndex; ++i) {ch = text.charAt(i);hashLCase ^= ((ch >= 'A' && ch <= 'Z') ? (ch + 32) : ch);hashLCase *= 0x100000001b3L;hash ^= ch;hash *= 0x100000001b3L;}stringVal = MySqlLexer.quoteTable.addSymbol(text, pos, quoteIndex + 1 - pos, hash);//stringVal = text.substring(mark, pos);pos = quoteIndex + 1;this.ch = charAt(pos);token = Token.IDENTIFIER;return;}final boolean firstFlag = isFirstIdentifierChar(first);if (!firstFlag) {throw new ParserException("illegal identifier. " + info());}hashLCase = 0xcbf29ce484222325L;hash = 0xcbf29ce484222325L;hashLCase ^= ((ch >= 'A' && ch <= 'Z') ? (ch + 32) : ch);hashLCase *= 0x100000001b3L;hash ^= ch;hash *= 0x100000001b3L;mark = pos;bufPos = 1;char ch = 0;for (; ; ) {char c0 = ch;ch = charAt(++pos);if (!isIdentifierChar(ch)) {if ((ch == '(' || ch == ')') && c0 > 256) {bufPos++;continue;}break;}hashLCase ^= ((ch >= 'A' && ch <= 'Z') ? (ch + 32) : ch);hashLCase *= 0x100000001b3L;hash ^= ch;hash *= 0x100000001b3L;bufPos++;continue;}this.ch = charAt(pos);if (bufPos == 1) {switch (first) {case '(':token = Token.LPAREN;return;case ')':token = Token.RPAREN;return;default:break;}token = Token.IDENTIFIER;stringVal = CharTypes.valueOf(first);if (stringVal == null) {stringVal = Character.toString(first);}return;}Token tok = keywords.getKeyword(hashLCase);if (tok != null) {token = tok;if (token == Token.IDENTIFIER) {stringVal = SymbolTable.global.addSymbol(text, mark, bufPos, hash);} else {stringVal = null;}} else {token = Token.IDENTIFIER;stringVal = SymbolTable.global.addSymbol(text, mark, bufPos, hash);}
}

获取表名的工具类方法:

public static List<String> getTables(String sql, DbType dbType) {Set<String> tables = new LinkedHashSet<>();boolean set = false;Lexer lexer = createLexer(sql, dbType);lexer.nextToken();SQLExprParser exprParser;switch (dbType) {case odps:exprParser = new OdpsExprParser(lexer);break;case mysql:exprParser = new MySqlExprParser(lexer);break;default:exprParser = new SQLExprParser(lexer);break;}for_:for (; lexer.token != Token.EOF; ) {switch (lexer.token) {case CREATE:case DROP:case ALTER:set = false;lexer.nextToken();if (lexer.token == Token.TABLE) {lexer.nextToken();if (lexer.token == Token.IF) {lexer.nextToken();if (lexer.token == Token.NOT) {lexer.nextToken();}if (lexer.token == Token.EXISTS) {lexer.nextToken();}}SQLName name = exprParser.name();tables.add(name.toString());if (lexer.token == Token.AS) {lexer.nextToken();}}continue for_;case FROM:case JOIN:lexer.nextToken();if (lexer.token != Token.LPAREN&& lexer.token != Token.VALUES) {SQLName name = exprParser.name();tables.add(name.toString());}continue for_;case SEMI:set = false;break;case SET:set = true;break;case EQ:if (set && dbType == DbType.odps) {lexer.nextTokenForSet();continue for_;}break;default:break;}lexer.nextToken();}return new ArrayList<>(tables);
}

比如上述的from关键字,当执行完lexer.nextToken()方法后,lexer.stringVal()方法即可以通过字符串的头尾下标切割字符串并返回该标识符,比如表名,也就是我们自定义的标识符。同时关注源码逻辑可知,数据库的表名,druid工具类会将大写字符、小写字符(大写字母A-Z的ASCII码值范围是65-90,而小写字母的ASCII码值范围是97-122,在大写字母的ASCII码值上+32即可转换成小写字母)判定为标识符,同时druid在处理时,除了大小写字母外,下划线(_)、美元符号($)、数字(0-9,ASCII码值范围是48-57)等等,均可判定为标识符。

修改表名含有大写字母,如下获取sql的表名:

List<String> tables = SQLParserUtils.getTables("select * from my_fuitrs_99 where id = ?", DbType.mysql);
System.out.println(tables);
String nihao_99 = "select * from my_fuitrs_99 where id = ?".replace(tables.get(0), "my_fuitrs");
System.out.println(nihao_99);
List<String> tables2 = SQLParserUtils.getTables("select * from MY_fuitrs_99 where id = ?", DbType.mysql);
System.out.println(tables2);

重新执行执行结果如下:

[my_fuitrs_99]
select * from my_fuitrs where id = ?
[MY_fuitrs_99]

3 举一反三

那么我们可以根据上面的工具,简单自定义实现一个替换sql表名的工具,工具类如下:

SQLParserUtil:

package com.xiaoxu.parser;import com.alibaba.druid.DbType;
import com.alibaba.druid.sql.ast.SQLName;
import com.alibaba.druid.sql.dialect.mysql.parser.MySqlExprParser;
import com.alibaba.druid.sql.dialect.mysql.parser.MySqlLexer;
import com.alibaba.druid.sql.parser.Lexer;
import com.alibaba.druid.sql.parser.SQLExprParser;
import com.alibaba.druid.sql.parser.SQLParserFeature;
import com.alibaba.druid.sql.parser.Token;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;/*** @author xiaoxu* @date 2024-03-12* java_demo2:com.xiaoxu.parser.SQLParserUtil*/
@SuppressWarnings("all")
public class SQLParserUtil {public static String getSqlFromReplaceNameIfNeccessary(String sql, DbType dbType, @Nullable String replaceTableName) {String table = null;String newSql = sql;Lexer lexer = createLexer(sql, dbType);lexer.nextToken();SQLExprParser exprParser;switch (dbType) {case mysql:exprParser = new MySqlExprParser(lexer);break;default:exprParser = new SQLExprParser(lexer);break;}for_:for (; lexer.token() != Token.EOF; ) {switch (lexer.token()) {case FROM:case INTO:lexer.nextToken();if (lexer.token() != Token.LPAREN && lexer.token() != Token.VALUES) {if (StringUtils.hasText(replaceTableName)) {int mark = ((MLexer) lexer).getMark();int bufPoss = ((MLexer) lexer).getBufPos();StringBuilder sbd = new StringBuilder();sbd.append(sql.substring(0, mark));sbd.append(replaceTableName);sbd.append(sql.substring(mark + bufPoss));newSql = sbd.toString();}SQLName name = exprParser.name();table = name.toString();}break for_;default:break;}lexer.nextToken();}System.out.println("原本的表名:" + table);System.out.println("替换表名为:" + replaceTableName + "后的sql:" + newSql);return newSql;}public static Lexer createLexer(String sql, DbType dbType) {return createLexer(sql, dbType, new SQLParserFeature[0]);}public static Lexer createLexer(String sql, DbType dbType, SQLParserFeature... features) {if (dbType == null) {dbType = DbType.mysql;}switch (dbType) {case mysql:return new MLexer(sql);default:return new Lexer(sql, null, dbType);}}private static class MLexer extends MySqlLexer {public MLexer(char[] input, int inputLength, boolean skipComment) {super(input, inputLength, skipComment);}public MLexer(String input) {super(input);}public MLexer(String input, SQLParserFeature... features) {super(input, features);}public MLexer(String input, boolean skipComment, boolean keepComments) {super(input, skipComment, keepComments);}public int getBufPos() {return this.bufPos;}public int getMark() {return this.mark;}}}

测试下我们自定义的SQLParserUtil工具类:

package com.xiaoxu.parser;import com.alibaba.druid.DbType;/*** @author xiaoxu* @date 2024-03-12* java_demo2:com.xiaoxu.parser.SQLParserTest2*/
public class SQLParserTest2 {public static void main(String[] args) {String sql = SQLParserUtil.getSqlFromReplaceNameIfNeccessary("select * from my_fruits_99 where id = ?",DbType.mysql, "xiaoxu_88");System.out.println(sql);System.out.println("\n");String sql2 = SQLParserUtil.getSqlFromReplaceNameIfNeccessary("insert into apple_$66 values()",DbType.mysql, "Pear$_88");System.out.println(sql2);}}

执行结果如下:

在这里插入图片描述

可以看到,上面工具针对扫描到标识符为FROM(比如select * from语句)或者INTO(比如insert into语句)时,可以实现替换sql的表名功能,其余类似功能参考druid的工具类自行实现即可。

再来举个栗子,新增方法getSqlInHoldCountIfNeccessary如下:

package com.xiaoxu.parser;import com.alibaba.druid.DbType;
import com.alibaba.druid.sql.ast.SQLName;
import com.alibaba.druid.sql.dialect.mysql.parser.MySqlExprParser;
import com.alibaba.druid.sql.dialect.mysql.parser.MySqlLexer;
import com.alibaba.druid.sql.parser.Lexer;
import com.alibaba.druid.sql.parser.SQLExprParser;
import com.alibaba.druid.sql.parser.SQLParserFeature;
import com.alibaba.druid.sql.parser.Token;
import com.google.common.collect.Lists;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;import java.util.List;/*** @author xiaoxu* @date 2024-03-12* java_demo2:com.xiaoxu.parser.SQLParserUtil*/
@SuppressWarnings("all")
public class SQLParserUtil {public static String getSqlInHoldCountIfNeccessary(String sql, DbType dbType, @Nullable Integer[] count) {StringBuilder tempSql = new StringBuilder();Lexer lexer = createLexer(sql, dbType);lexer.nextToken();int posInc = 0;int startPos = 0;int endPos = 0;int subStartPos = 0;for_:for (; lexer.token() != Token.EOF; ) {switch (lexer.token()) {case IN:lexer.nextToken();startPos = endPos;endPos = ((MLexer) lexer).getPos();if (lexer.token() == Token.LPAREN) {tempSql.append(sql, startPos, endPos);subStartPos = endPos;do {endPos = ((MLexer) lexer).getPos();lexer.nextToken();} while (lexer.token() != Token.RPAREN);String replaceMent = sql.substring(subStartPos, endPos);if (count != null && count.length > posInc && count[posInc] != null && count[posInc] > 0) {List<String> incStrs = Lists.newArrayList();for (int i = 0; i < count[posInc]; i++) {incStrs.add("?");}tempSql.append(String.join(",", incStrs));} else {tempSql.append(replaceMent);}posInc++;}continue for_;default:break;}lexer.nextToken();}tempSql.append(sql.substring(endPos));return tempSql.toString();}public static String getSqlFromReplaceNameIfNeccessary(String sql, DbType dbType, @Nullable String replaceTableName) {String table = null;String newSql = sql;Lexer lexer = createLexer(sql, dbType);lexer.nextToken();SQLExprParser exprParser;switch (dbType) {case mysql:exprParser = new MySqlExprParser(lexer);break;default:exprParser = new SQLExprParser(lexer);break;}for_:for (; lexer.token() != Token.EOF; ) {switch (lexer.token()) {case FROM:case INTO:lexer.nextToken();if (lexer.token() != Token.LPAREN && lexer.token() != Token.VALUES) {if (StringUtils.hasText(replaceTableName)) {int mark = ((MLexer) lexer).getMark();int bufPoss = ((MLexer) lexer).getBufPos();StringBuilder sbd = new StringBuilder();sbd.append(sql.substring(0, mark));sbd.append(replaceTableName);sbd.append(sql.substring(mark + bufPoss));newSql = sbd.toString();}SQLName name = exprParser.name();table = name.toString();}break for_;default:break;}lexer.nextToken();}System.out.println("原本的表名:" + table);System.out.println("替换表名为:" + replaceTableName + "后的sql:" + newSql);return newSql;}public static Lexer createLexer(String sql, DbType dbType) {return createLexer(sql, dbType, new SQLParserFeature[0]);}public static Lexer createLexer(String sql, DbType dbType, SQLParserFeature... features) {if (dbType == null) {dbType = DbType.mysql;}switch (dbType) {case mysql:return new MLexer(sql);default:return new Lexer(sql, null, dbType);}}private static class MLexer extends MySqlLexer {public MLexer(char[] input, int inputLength, boolean skipComment) {super(input, inputLength, skipComment);}public MLexer(String input) {super(input);}public MLexer(String input, SQLParserFeature... features) {super(input, features);}public MLexer(String input, boolean skipComment, boolean keepComments) {super(input, skipComment, keepComments);}public int getBufPos() {return this.bufPos;}public int getMark() {return this.mark;}public int getPos() {return this.pos;}}}

getSqlInHoldCountIfNeccessary方法的效果是,我们知道在mysql的子查询IN中,假设一个sql有多处具有子查询IN,假定为:in (?)。但是我们需要自定义IN子查询后续的参数个数,意即类似更新sql的子查询参数数目为in (?,?,?),同时不改变原有sql的语句,那么通过我们自定义的该方法可以达到效果,测试如下:

package com.xiaoxu.parser;import com.alibaba.druid.DbType;/*** @author xiaoxu* @date 2024-03-12* java_demo2:com.xiaoxu.parser.SQLParserTest2*/
public class SQLParserTest2 {public static void main(String[] args) {String sql = SQLParserUtil.getSqlInHoldCountIfNeccessary("select * from my where id in( ?,?) and status is not null and name in (?) and ot ='N' and pr in ()",DbType.mysql, new Integer[]{null, 4});System.out.println("最终结果是:");System.out.println(sql);String sql2 = SQLParserUtil.getSqlInHoldCountIfNeccessary("select * from my where id in (?)",DbType.mysql, new Integer[]{3});System.out.println(sql2);String sql3 = SQLParserUtil.getSqlInHoldCountIfNeccessary("select * from my where id = ?",DbType.mysql, new Integer[]{3});System.out.println(sql3);}}

执行结果如下:

在这里插入图片描述
其中参数new Integer[]{null, 4}的效果是,第一个IN子查询不变,第二个子查询更新为in (?,?,?,?)。该逻辑是按照顺序更新IN的后续参数数目,同时不改变原有的sql。

相关文章:

java:Druid工具类解析sql获取表名

java&#xff1a;Druid工具类解析sql获取表名 1 前言 alibaba的druid连接池除了sql执行的功能外&#xff0c;还有sql语法解析的工具提供&#xff0c;参考依赖如下&#xff1a; <dependency><groupId>com.alibaba</groupId><artifactId>druid</ar…...

MySQL--深入理解MVCC机制原理

什么是MVCC&#xff1f; MVCC全称 Multi-Version Concurrency Control&#xff0c;即多版本并发控制&#xff0c;维持一个数据的多个版本&#xff0c;主要是为了提升数据库的并发访问性能&#xff0c;用更高性能的方式去处理数据库读写冲突问题&#xff0c;实现无锁并发。 什…...

数据挖掘简介与应用领域概述

数据挖掘&#xff0c;作为信息技术领域中的重要分支之一&#xff0c;旨在从大量数据中发现潜在的模式、关联和趋势&#xff0c;以提取有用的信息和知识。在信息爆炸时代&#xff0c;大量数据的积累成为了常态&#xff0c;数据挖掘技术的出现填补了人们处理这些数据的空白&#…...

瑞熙贝通打造智慧校园实验室安全综合管理平台

一、建设思路 瑞熙贝通实验室安全综合管理平台是基于以实验室安全&#xff0c;用现代化管理思想与人工智能、大数据、互联网技术、物联网技术、云计算技术、人体感应技术、语音技术、生物识别技术、手机APP、自动化仪器分析技术有机结合&#xff0c;通过建立以实验室为中心的管…...

openstack调整虚拟机CPU 内存 磁盘 --来自gpt

在OpenStack中调整虚拟机&#xff08;即实例&#xff09;的CPU、内存&#xff08;RAM&#xff09;和磁盘大小通常涉及到以下几个步骤&#xff1a;首先&#xff0c;确定你要修改的实例名称或ID&#xff1b;其次&#xff0c;根据需要调整的资源类型&#xff0c;使用相应的命令进行…...

【IC设计】Verilog线性序列机点灯案例(三)(小梅哥课程)

声明&#xff1a;案例和代码来自小梅哥课程&#xff0c;本人仅对知识点做做笔记&#xff0c;如有学习需要请支持官方正版。 文章目录 该系列目录设计目标设计思路RTL及Testbench代码RTL代码Testbench代码 仿真结果上板视频 该系列目录 Verilog线性序列机点灯案例(一)&#xff…...

【打工日常】使用Docker部署团队协作文档工具

一、ShowDoc介绍 ​ShowDoc是一个适合IT团队共同协作API文档、技术文档的工具。通过showdoc&#xff0c;可以方便地使用markdown语法来书写出API文档、数据字典文档、技术文档、在线excel文档等等。 响应式网页设计&#xff1a;可将项目文档分享到电脑或移动设备查看。同时也可…...

(一)Neo4j下载安装以及初次使用

&#xff08;一&#xff09;下载 官网地址&#xff1a;Neo4j Graph Database & AnamConnect data as its stored with Neo4j. Perform powerful, complex queries at scale and speed with our graph data platform.https://neo4j.com/ &#xff08;二&#xff09;安装并配…...

QT for Mcu的学习建议

QT for MCU&#xff08;微控制器单元&#xff09;是一个相对较新的领域&#xff0c;它允许在资源受限的微控制器上运行Qt框架&#xff0c;从而为嵌入式设备带来丰富的用户界面和跨平台的开发体验。以下是一些建议&#xff0c;可以帮助你开始学习Qt for MCU&#xff1a; 理解Qt…...

【C语言初阶(五)】数组

❣博主主页: 33的博客❣ ▶文章专栏分类: C语言从入门到精通◀ &#x1f69a;我的代码仓库: 33的代码仓库&#x1f69a; 目录 1. 前言2.一维数组的概念3.一维数组的创建和初始化3.1数组的创建3.2数组的初始化3.3数组的类型 4.一维数组的使用4.1数组下标4.2数组元素打印4.4数组元…...

词令微信小程序怎么添加到我的小程序?

微信小程序怎么添加到我的小程序&#xff1f; 1、找到并打开要添加的小程序&#xff1b; 2、打开小程序后&#xff0c;点击右上角的「…」 3、点击后底部弹窗更多选项&#xff0c;请找到并点击「添加到我的小程序」&#xff1b; 4、添加成功后&#xff0c;就可以在首页下拉我的…...

【PyTorch】基础学习:在Pycharm等IDE中打印或查看Pytorch版本信息

【PyTorch】基础学习&#xff1a;在Pycharm等IDE中打印或查看Pytorch版本信息 &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程&#x1…...

SDN网络简单认识(2)——南向接口

目录 一、概述 二、南向接口与南向协议 2.1 南向接口&#xff08;Southbound Interfaces&#xff09; 2.2 南向协议&#xff08;Southbound Protocols&#xff09; 2.3 区别与联系 三、常见南向协议 2.1 OpenFlow 2.2 OVSDB&#xff08;Open vSwitch Database Manageme…...

如何保存缓存和MySQL的双写一致呢?

如何保存缓存和MySQL的双写一致呢&#xff1f; 所谓的双写一致指的是&#xff0c;在同时使用缓存(如Redis)和数据库(如MySQL)的场景下,确保数据在缓存和数据库中的更新操作保持一致。当对数据进行修改的时候&#xff0c;无论是先修改缓存还是先修改数据库&#xff0c;最终都要保…...

第十三篇:复习Java面向对象

文章目录 一、面向对象的概念二、类和对象1. 如何定义/使用类2. 定义类的补充注意事项 三、面向对象三大特征1. 封装2. 继承2.1 例子2.2 继承类型2.3 继承的特性2.4 继承中的关键字2.4.1 extend2.4.2 implements2.4.3 super/this2.4.4 final 3. 多态4. 抽象类4.1 抽象类4.2 抽象…...

PyTorch学习笔记之基础函数篇(四)

文章目录 2.8 torch.logspace函数讲解2.9 torch.ones函数2.10 torch.rand函数2.11 torch.randn函数2.12 torch.zeros函数 2.8 torch.logspace函数讲解 torch.logspace 函数在 PyTorch 中用于生成一个在对数尺度上均匀分布的张量&#xff08;tensor&#xff09;。这意味着张量中…...

C++/CLI学习笔记3(快速打通c++与c#相互调用的桥梁)

c/cli变量和操作符 3.1:什么是变里 变量是存储数据以便应用程序临时使用的内存位置&#xff0c;具有名称、类型和值。变量值在应用程序执行期间可能改变&#xff0c;变量名也是。变量使用前必须声明&#xff0c;即指定类型和提供名称。变量的类型决定了值的范围以及能执行的操…...

unity

Unity官方下载_Unity最新版_从Unity Hub下载安装 | Unity中国官网 Unity Remote - Unity 手册 登陆账号&#xff0c;找到一个3d 免费资源 3D Animations & Models | Unity Asset Store unity 里面window->package Manager 里面可以看到自己的asset &#xff0c;下载后…...

考研复习C语言初阶(3)

目录 一.函数是什么? 二.C语言中函数的分类 2.1库函数 2.2自定义函数 三.函数的参数 3.1实际参数&#xff08;实参&#xff09; 3.2 形式参数&#xff08;形参&#xff09; 四.函数的调用 4.1 传值调用 4.2 传址调用 五. 函数的嵌套调用和链式访问 5.1 嵌套调用 5…...

CCF 202009-3 点亮数字人生(拓扑排序)

题目背景 土豪大学的计算机系开了一门数字逻辑电路课&#xff0c;第一个实验叫做“点亮数字人生”&#xff0c;要用最基础的逻辑元件组装出实际可用的电路。时间已经是深夜了&#xff0c;尽管实验箱上密密麻麻的连线已经拆装了好几遍&#xff0c;小君同学却依旧没能让她的电路正…...

MongoDB学习和应用(高效的非关系型数据库)

一丶 MongoDB简介 对于社交类软件的功能&#xff0c;我们需要对它的功能特点进行分析&#xff1a; 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具&#xff1a; mysql&#xff1a;关系型数据库&am…...

23-Oracle 23 ai 区块链表(Blockchain Table)

小伙伴有没有在金融强合规的领域中遇见&#xff0c;必须要保持数据不可变&#xff0c;管理员都无法修改和留痕的要求。比如医疗的电子病历中&#xff0c;影像检查检验结果不可篡改行的&#xff0c;药品追溯过程中数据只可插入无法删除的特性需求&#xff1b;登录日志、修改日志…...

1688商品列表API与其他数据源的对接思路

将1688商品列表API与其他数据源对接时&#xff0c;需结合业务场景设计数据流转链路&#xff0c;重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点&#xff1a; 一、核心对接场景与目标 商品数据同步 场景&#xff1a;将1688商品信息…...

2.Vue编写一个app

1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...

Cinnamon修改面板小工具图标

Cinnamon开始菜单-CSDN博客 设置模块都是做好的&#xff0c;比GNOME简单得多&#xff01; 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...

【论文笔记】若干矿井粉尘检测算法概述

总的来说&#xff0c;传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度&#xff0c;通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...

【JavaSE】绘图与事件入门学习笔记

-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角&#xff0c;以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向&#xff0c;距离坐标原点x个像素;第二个是y坐标&#xff0c;表示当前位置为垂直方向&#xff0c;距离坐标原点y个像素。 坐标体系-像素 …...

【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具

第2章 虚拟机性能监控&#xff0c;故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令&#xff1a;jps [options] [hostid] 功能&#xff1a;本地虚拟机进程显示进程ID&#xff08;与ps相同&#xff09;&#xff0c;可同时显示主类&#x…...

pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)

目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关&#xff0…...

tree 树组件大数据卡顿问题优化

问题背景 项目中有用到树组件用来做文件目录&#xff0c;但是由于这个树组件的节点越来越多&#xff0c;导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多&#xff0c;导致的浏览器卡顿&#xff0c;这里很明显就需要用到虚拟列表的技术&…...